-
Notifications
You must be signed in to change notification settings - Fork 13
chore: Fix save behavior for Django 3 and drop Django 2 support #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
afeced8
5b6febc
33ddf83
d39caba
8b912d3
2759c94
f3d637d
4f9f49b
df7abd6
3ccfcd7
1cca5a9
1d2e44b
8be864d
997c41a
e3247e3
280f207
565f46f
25cb3f5
d24e1ce
f8d5275
63605b3
b8a02d6
6cdb446
0d8c1e2
4e4a9cb
8219921
a7948c4
af2798c
b9ebcb6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,8 @@ | |
| from django.conf import settings | ||
| from django.http import HttpResponse | ||
|
|
||
| from dirtyfields.dirtyfields import reset_state | ||
|
|
||
| from django_declarative_apis.machinery.filtering import apply_filters_to_object | ||
| from django_declarative_apis.models import BaseConsumer | ||
| from django_declarative_apis.resources.utils import HttpStatusCode | ||
|
|
@@ -36,7 +38,7 @@ | |
| ResourceField, | ||
| ) | ||
|
|
||
| # these imports are unusued in this file but may be used in other projects | ||
| # these imports are unused in this file but may be used in other projects | ||
| # that use `machinery` as an interface | ||
| from .attributes import TypedEndpointAttributeMixin, RequestFieldGroup # noqa | ||
| from .utils import locate_object, rate_limit_exceeded | ||
|
|
@@ -84,7 +86,7 @@ def resource(self): | |
| return Todo.objects.get(id=self.resource_id) | ||
| """ | ||
|
|
||
| def __init__(self, type, filter=None, returns_list=False, **kwargs): | ||
| def __init__(self, type=None, filter=None, returns_list=False, **kwargs): | ||
| super().__init__(**kwargs) | ||
| self.type = type | ||
| self.filter = filter | ||
|
|
@@ -100,10 +102,12 @@ def get_instance_value(self, owner_instance, owner_class): | |
| return self | ||
| try: | ||
| value = self.func(owner_instance) | ||
| except django.core.exceptions.ObjectDoesNotExist: | ||
| raise errors.ClientErrorNotFound( | ||
| "{0} instance not found".format(self.type.__name__) | ||
| ) | ||
| except django.core.exceptions.ObjectDoesNotExist as e: # noqa: F841 | ||
| try: | ||
| message = f"{self.type.__name__} instance not found" | ||
| except AttributeError as e: # noqa: F841 | ||
| message = "Resource instance not found" | ||
| raise errors.ClientErrorNotFound(message) | ||
|
|
||
| if value.__class__ == dict: | ||
| return value | ||
|
|
@@ -173,6 +177,35 @@ def __init__(cls, class_name, bases=None, dict=None): | |
| pass | ||
|
|
||
|
|
||
| def current_dirty_dict(resource): | ||
| """Get the `current` (in-memory) values for fields that have not yet been written to the database.""" | ||
| new_data = resource.get_dirty_fields(check_relationship=True, verbose=True) | ||
| field_name_to_att_name = {f.name: f.attname for f in resource._meta.concrete_fields} | ||
| return { | ||
| field_name_to_att_name[key]: values["current"] | ||
| for key, values in new_data.items() | ||
| } | ||
|
|
||
|
|
||
| def update_dirty(resource): | ||
| """Write dirty fields to the database.""" | ||
| dirty_dict = current_dirty_dict(resource) | ||
| resource_next, created = type(resource).objects.update_or_create( | ||
| pk=resource.pk, defaults=dirty_dict | ||
| ) | ||
|
|
||
| # update fields in memory that changed on save to the database | ||
| field_name_to_att_name = {f.name: f.attname for f in resource._meta.concrete_fields} | ||
| for k, v in resource_next._as_dict(check_relationship=True).items(): | ||
| att_key = field_name_to_att_name[k] | ||
| if getattr(resource, att_key, None) != v: | ||
| setattr(resource, att_key, v) | ||
| resource._state.adding = False | ||
| resource._state.db = resource_next._state.db | ||
| resource._state.fields_cache = {} | ||
| reset_state(type(resource), resource) | ||
|
|
||
|
|
||
| class EndpointBinder: | ||
| class BoundEndpointManager: | ||
| def __init__(self, manager, bound_endpoint): | ||
|
|
@@ -197,7 +230,7 @@ def get_response(self): # noqa: C901 | |
|
|
||
| if hasattr(resource, "is_dirty"): | ||
| if resource and resource.is_dirty(check_relationship=True): | ||
| resource.save() | ||
| update_dirty(resource) | ||
|
|
||
| endpoint_tasks = sorted( | ||
| self.manager.endpoint_tasks, key=lambda t: t.priority | ||
|
|
@@ -213,13 +246,18 @@ def get_response(self): # noqa: C901 | |
| immediate_task.run(self.bound_endpoint) | ||
|
|
||
| except errors.ClientError as ce: | ||
| if ce.save_changes and resource and resource.is_dirty(): | ||
| resource.save() | ||
| if ( | ||
| ce.save_changes | ||
| and resource | ||
| and hasattr(resource, "is_dirty") | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a missing
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (unrelated to Django 3) |
||
| and resource.is_dirty() | ||
| ): | ||
| update_dirty(resource) | ||
| raise | ||
|
|
||
| if hasattr(resource, "is_dirty"): | ||
| if resource and resource.is_dirty(check_relationship=True): | ||
| resource.save() | ||
| update_dirty(resource) | ||
|
|
||
| for deferred_task in deferred_tasks: | ||
| deferred_task.run(self.bound_endpoint) | ||
|
|
@@ -904,10 +942,10 @@ class ResourceEndpointDefinition(EndpointDefinition): | |
| """ | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| super().__init__() | ||
| super().__init__(*args, **kwargs) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a fix noticed in passing (unrelated to Django 3) |
||
| self._cached_resource = None | ||
|
|
||
| @property | ||
| @EndpointResourceAttribute() | ||
| def resource(self): | ||
| """Queries the object manager of `self.resource_model` for the given id | ||
| (`self.resource_id`). | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| Django >=2.2, <4 | ||
| Django >=3.2, <4 | ||
| celery>=4.0.2,!=4.1.0 | ||
| cryptography>=2.0,<=3.4.8 | ||
| decorator==4.0.11 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,3 +51,14 @@ class ParentModel(models.Model): | |
| class RootNode(models.Model): | ||
| id = models.IntegerField(primary_key=True) | ||
| parent_field = models.ForeignKey(ParentModel, on_delete=models.CASCADE) | ||
|
|
||
|
|
||
| try: | ||
| import dirtyfields | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's this about?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was told that DDA supports models that use |
||
|
|
||
| class DirtyFieldsModel(dirtyfields.DirtyFieldsMixin, models.Model): | ||
| field = models.CharField(max_length=100) | ||
| fk_field = models.ForeignKey(TestModel, null=False, on_delete=models.CASCADE) | ||
|
|
||
| except Exception: | ||
| pass | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have the check? I guess it's a performance toss-up: Is it worth writing every field every time, which would likely be less performant in cases where only one field changes vs when many fields are updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - this is @dshafer's code, but that's my guess.