diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 9b83b2b7b..92d64b799 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -256,11 +256,23 @@ When passing data to a serializer instance, the unmodified data will be made ava ## 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` -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 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 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. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index e80b81dc6..d42000260 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -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 #### DEFAULT_VERSION diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 223d93f6e..4cba31761 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -997,7 +997,8 @@ class ModelSerializer(Serializer): # relationships as being a special case. During updates we already # have an instance pk for the relationships to be associated with. 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(): if attr in info.relations and info.relations[attr].to_many: m2m_fields.append((attr, value)) @@ -1005,7 +1006,7 @@ class ModelSerializer(Serializer): setattr(instance, attr, value) 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) else: instance.save() @@ -1632,6 +1633,29 @@ class ModelSerializer(Serializer): 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): """ diff --git a/rest_framework/settings.py b/rest_framework/settings.py index e6e2ce2a1..9eb4c5653 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -50,10 +50,6 @@ DEFAULTS = { # Generic view behavior 'DEFAULT_PAGINATION_CLASS': None, 'DEFAULT_FILTER_BACKENDS': [], - - # Model serializer behavior - 'STRICT_PARTIAL_UPDATE': False, - 'PARTIAL_UPDATE_EXTRA_FIELDS': [], # Schema 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',