django-rest-framework icon indicating copy to clipboard operation
django-rest-framework copied to clipboard

Update method ignoring partial flag on serializer (updates all fields)

Open mlaxm opened this issue 2 years ago • 4 comments

Affected Version: 3.14.0

Link to Code: rest_framework/serializers.py#L928

Documentation Link: Partial Updates

Problem: The update method in Django Rest Framework's serializers appears to ignore the partial flag, resulting in full saves instead of partial updates as expected. This behavior deviates from the documented functionality.

Steps to Reproduce: Create a serializer with partial=True. Attempt to update a model instance with only a subset of fields provided. Observe that all fields in the instance are updated instead of just the provided ones.

Proof of Concept (PoC):

from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["bio", "public_username"]

user = User.objects.get(pk=123)

data = {
    "bio": "new bio",
    "public_username": "new public username",
}
serializer = UserSerializer(user, data=data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()

Expected Behavior: With the partial=True flag set, only fields provided in the serializer's data should be updated in the database, as documented.

Actual Behavior: Despite setting partial=True, all fields in the instance are updated upon calling serializer.save(), including those not listed in the serializer's fields, such as password and is_superuser.

Proposed Fix: A potential fix involves modifying the update method to conditionally use update_fields based on the partial flag. Here's a proposed change:

if self.partial:
    instance.save(update_fields=validated_data.keys())
else:
    instance.save()

Additional Notes: This issue impacts applications relying on partial updates, potentially leading to unintended modifications of instance's attributes, which may lead to unexpected bugs, race conditions etc.

Environment:

Django==4.2.9
djangorestframework==3.14.0
Python: 3.11

mlaxm avatar Jan 25 '24 12:01 mlaxm

@mlaxm I believe there might be a misunderstanding regarding the partial function. The partial function specifically avoids validating certain fields. If you provide data without a required field, regular validation will trigger an error. However, when using partial, it won't enforce validation for those specific fields.

for example :

data = {
    "bio": "new bio",
}
serializer = UserSerializer(user, data=data, partial=False)
serializer.is_valid(raise_exception=True)

this will through validation error

with partial it will not through error

data = {
    "bio": "new bio",
}
serializer = UserSerializer(user, data=data, partial=True)
serializer.is_valid(raise_exception=True)

sarathak avatar Feb 04 '24 15:02 sarathak

Thank you, @sarathak, for providing clarification on the functionality of the "partial" argument. I appreciate the insight into its purpose.

I would like to draw further attention to the matter raised in my previous comment regarding the save operation. Given that I am performing a partial update and consequently not encountering any validation errors from the serializer (as I am only intending to update the "bio" field, for instance), it seems that all fields of the user instance are being updated. I am seeking clarification on whether this behavior aligns with the intended workflow? It may not be the expected outcome when employing partial updates

mlaxm avatar Feb 04 '24 16:02 mlaxm

@mlaxm

Partial updates documentation is fairly explicit that partial allows partial updates. It does not change the other behaviours.

You have shown in your example one way to selectively update only some fields.

Are you establishing a "patch" method that will execute the partial update with fields? This may be a semantic side effect of development if you are creating your API to perform partial updates on put method queries instead of using patch for the partial update.

If you want to use other http verbs to perform a partial update you can do so but your code will need to identify the fields being updated and only update those fields.

The save method doesn't know that you only want it to operate on the limited fields unless you tell it. You have opened up an object, built a serializer based on the object you grabbed and the submitted data, then you saved it. So it then saves the entire serializer object.

Regards Alexander

AlexanderNeilson avatar Feb 06 '24 23:02 AlexanderNeilson

@AlexanderNeilson According to the source code, either the .create or .update methods are called (with .update being invoked if the instance exists). This update method in turn calls the .save method on the serializer. My concern is that the .save method on the serializer always updates all of the model's fields, rather than just a few. There doesn't appear to be a way to specify updating only validated fields? Thanks for your assistance

mlaxm avatar Feb 09 '24 11:02 mlaxm