refactored the use of update_fields as a meta attribute

This commit is contained in:
iamr0b0x 2022-05-21 18:29:46 +01:00
parent 5f7348102f
commit 572ed7c3d4
4 changed files with 39 additions and 26 deletions

View File

@ -256,11 +256,23 @@ When passing data to a serializer instance, the unmodified data will be made ava
## Partial updates ## Partial updates
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the `partial` argument in order to allow partial updates. Note that `STRICT_PARTIAL_UPDATE` must be set to `True` to enable field specific updates using `.save(update_fields=[...])` for `ModelSerializer` By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the `partial` argument in order to allow partial updates. Note that `STRICT_PARTIAL_UPDATE` must be set to `True` to enable field specific updates using `.save(update_fields=[...])` for `ModelSerializer`
with `partial=True`. `PARTIAL_UPDATE_EXTRA_FIELDS` lets you specify the fields you will like to update everytime like `mod_date` or `modified_at`. with `partial=True`.
# Update `comment` with partial data # Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True) serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)
The current implementation of `ModelSerializer` performs `instance.save()` which [saves all fields](https://docs.djangoproject.com/en/4.0/ref/models/instances/#specifying-which-fields-to-save) in a model which may lead to a race condition given a high frequency of partial
update requests on a single resource. Specifying `partial_update_extra_fields = []` in a `ModelSerializer`'s `Meta` will save only the fields specified in a partial update by enforcing the use of
[update_fields](https://docs.djangoproject.com/en/4.0/ref/models/instances/#specifying-which-fields-to-save) when saving an instance during a `ModelSerializer` update with `partial=True`.
# define the ModelSerializer with partial_update_extra_fields
class EventSerializer(serializers.ModelSerializer):
class Meta:
fields = "__all__"
model = Event
partial_update_extra_fields = []
For cases where there are fields like `modified_at` that need to be updated during a partial update even when not provided during a partial update, those field names should be specified in `partial_update_extra_fields` like so: `partial_update_extra_fields = ['modified_at']` or `partial_update_extra_fields = ['mod_date']`
## Dealing with nested objects ## Dealing with nested objects
The previous examples are fine for dealing with objects that only have simple datatypes, but sometimes we also need to be able to represent more complex objects, where some of the attributes of an object might not be simple datatypes such as strings, dates or integers. The previous examples are fine for dealing with objects that only have simple datatypes, but sometimes we also need to be able to represent more complex objects, where some of the attributes of an object might not be simple datatypes such as strings, dates or integers.

View File

@ -143,25 +143,6 @@ Default: `ordering`
--- ---
## Model serializer settings
*The following settings control the behavior of the model serializer.*
#### STRICT_PARTIAL_UPDATE
Enforce the use of [update_fields](https://docs.djangoproject.com/en/4.0/ref/models/instances/#specifying-which-fields-to-save)
when saving an instance during a model serializer update with `partial=True`.
Default: `False`
#### PARTIAL_UPDATE_EXTRA_FIELDS
Lets you specify the fields you will like to update everytime like `mod_date` or `modified_at`.
Default: `[]`
---
## Versioning settings ## Versioning settings
#### DEFAULT_VERSION #### DEFAULT_VERSION

View File

@ -997,7 +997,8 @@ class ModelSerializer(Serializer):
# relationships as being a special case. During updates we already # relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with. # have an instance pk for the relationships to be associated with.
m2m_fields = [] m2m_fields = []
update_fields = [*api_settings.PARTIAL_UPDATE_EXTRA_FIELDS] partial_update_extra_fields = self.get_partial_update_extra_fields()
update_fields = [*(partial_update_extra_fields or [])]
for attr, value in validated_data.items(): for attr, value in validated_data.items():
if attr in info.relations and info.relations[attr].to_many: if attr in info.relations and info.relations[attr].to_many:
m2m_fields.append((attr, value)) m2m_fields.append((attr, value))
@ -1005,7 +1006,7 @@ class ModelSerializer(Serializer):
setattr(instance, attr, value) setattr(instance, attr, value)
update_fields.append(attr) update_fields.append(attr)
if self.partial and api_settings.STRICT_PARTIAL_UPDATE: if self.partial and partial_update_extra_fields is not None:
instance.save(update_fields=update_fields) instance.save(update_fields=update_fields)
else: else:
instance.save() instance.save()
@ -1632,6 +1633,29 @@ class ModelSerializer(Serializer):
return validators return validators
def get_partial_update_extra_fields(self):
partial_update_extra_fields = getattr(self.Meta, 'partial_update_extra_fields', None)
if partial_update_extra_fields is not None and not isinstance(partial_update_extra_fields, (list, tuple)):
raise TypeError(
'The `partial_update_extra_fields` option must be a NoneType or list or tuple. Got %s.' %
type(partial_update_extra_fields).__name__
)
fields = self.get_fields()
if partial_update_extra_fields is not None:
for field_name in partial_update_extra_fields:
assert field_name in fields, (
"The field '{field_name}' was included on serializer "
"{serializer_class} in the 'partial_update_extra_fields' option, but does "
"not match any model field.".format(
field_name=field_name,
serializer_class=self.__class__.__name__
)
)
return partial_update_extra_fields
class HyperlinkedModelSerializer(ModelSerializer): class HyperlinkedModelSerializer(ModelSerializer):
""" """

View File

@ -51,10 +51,6 @@ DEFAULTS = {
'DEFAULT_PAGINATION_CLASS': None, 'DEFAULT_PAGINATION_CLASS': None,
'DEFAULT_FILTER_BACKENDS': [], 'DEFAULT_FILTER_BACKENDS': [],
# Model serializer behavior
'STRICT_PARTIAL_UPDATE': False,
'PARTIAL_UPDATE_EXTRA_FIELDS': [],
# Schema # Schema
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema', 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',