107

I'm trying to implement partial_update with Django Rest Framework but I need some clarification because I'm stuck.

  1. Why do we need to specify partial=True?
    In my understanding, we could easily update Demo object inside of partial_update method. What is the purpose of this?

  2. What is inside of serialized variable?
    What is inside of serialized variable in partial_update method? Is that a Demo object? What function is called behind the scenes?

  3. How would one finish the implementation here?

Viewset

class DemoViewSet(viewsets.ModelViewSet):
    serializer_class = DemoSerializer

    def partial_update(self, request, pk=None):
        serialized = DemoSerializer(request.user, data=request.data, partial=True)
        return Response(status=status.HTTP_202_ACCEPTED)

Serializer

class DemoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Demo
        fields = '__all__'

    def update(self, instance, validated_data):
        print 'this - here'
        demo = Demo.objects.get(pk=instance.id)
        Demo.objects.filter(pk=instance.id)\
                           .update(**validated_data)
        return demo
Kushan Gunasekera
  • 7,268
  • 6
  • 44
  • 58
intelis
  • 7,829
  • 14
  • 58
  • 102
  • here you can find more information about the methods inside views, serializers etc. http://www.cdrf.co/3.9/rest_framework.generics/UpdateAPIView.html – khashashin Aug 18 '19 at 12:14

7 Answers7

181

I when digging into the source code of rest_framework and got the following findings:

For question 1. Why do we need to specify partial=True?

This question is related to HTTP verbs.

PUT: The PUT method replaces all current representations of the target resource with the request payload.

PATCH: The PATCH method is used to apply partial modifications to a resource.

Generally speaking, partial is used to check whether the fields in the model is needed to do field validation when client submitting data to the view.

For example, we have a Book model like this, pls note both of the name and author_name fields are mandatory (not null & not blank).

class Book(models.Model):
    name = models.CharField('name of the book', max_length=100)
    author_name = models.CharField('the name of the author', max_length=50)

# Create a new instance for testing
Book.objects.create(name='Python in a nut shell', author_name='Alex Martelli')

For some scenarios, we may only need to update part of the fields in the model, e.g., we only need to update name field in the Book. So for this case, client will only submit the name field with new value to the view. The data submit from the client may look like this:

{"pk": 1, name: "PYTHON IN A NUT SHELL"}

But you may have notice that our model definition does not allow author_name to be blank. So we have to use partial_update instead of update. So the rest framework will not perform field validation check for the fields which is missing in the request data.

For testing purpose, you can create two views for both update and partial_update, and you will get more understanding what I just said.

Example:

views.py

from rest_framework.generics import GenericAPIView
from rest_framework.mixins import UpdateModelMixin
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book


class BookUpdateView(GenericAPIView, UpdateModelMixin):
    '''
    Book update API, need to submit both `name` and `author_name` fields
    At the same time, or django will prevent to do update for field missing
    '''
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

class BookPartialUpdateView(GenericAPIView, UpdateModelMixin):
    '''
    You just need to provide the field which is to be modified.
    '''
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def put(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

urls.py

urlpatterns = patterns('',
    url(r'^book/update/(?P<pk>\d+)/$', BookUpdateView.as_view(), name='book_update'),
    url(r'^book/update-partial/(?P<pk>\d+)/$', BookPartialUpdateView.as_view(), name='book_partial_update'),
)

Data to submit

{"pk": 1, name: "PYTHON IN A NUT SHELL"}

When you submit the above json to the /book/update/1/, you will got the following error with HTTP_STATUS_CODE=400:

{
  "author_name": [
    "This field is required."
  ]
}

But when you submit the above json to /book/update-partial/1/, you will got HTTP_STATUS_CODE=200 with following response,

{
  "id": 1,
  "name": "PYTHON IN A NUT SHELL",
  "author_name": "Alex Martelli"
}

For question 2. What is inside of serialized variable?

serialized is a object wrapping the model instance as a serialisable object. and you can use this serialized to generate a plain JSON string with serialized.data .

For question 3. How would one finish the implementation here?

I think you can answer yourself when you have read the answer above, and you should have known when to use update and when to used partial_update.

If you still have any question, feel free to ask. I just read part of the source code of the rest framework, and may have not understand very deeply for some terms, and please point it out when it is wrong...

starball
  • 20,030
  • 7
  • 43
  • 238
Enix
  • 4,415
  • 1
  • 24
  • 37
  • When I try the code for the partial_update, it gives me an error saying "update() method not implemented"... – dpstart Oct 01 '17 at 09:39
  • Hey I want to get the data in the fields at the time of updation which I insert at the time of creation in rest browser which and also I am using your way of partial but I am still getting fields required error, I don't know if it is coming because in browser there is no data showing in the fields at the time of updation in nested serializer.... Actually It is showing me User's data but not profile data at updation – Nikhil Bhardwaj Nov 26 '19 at 12:18
  • Why in if I do a PATCH, on pre_save instance is populated with this instance database data? This should not be done on save? – mrroot5 May 28 '20 at 17:22
  • 1
    how about the `update_fields` parameter? it should be only include the fields to be updated. – Enix May 29 '20 at 00:53
  • The answer to question 3 here is completely false. DRF doesn't separate the two functions for the two purposes, so you can't just rely on selecting one for PATCH. You have to handle both through the update() function, and the update() function takes a kwarg `partial`, which, when true, means that the request coming in was a PATCH verb. I do something like, if it's a partial, just call `partial_update` instead. Nobody can figure this out unless they delve deep into the calls to see the behavior, because this isn't really documented much anywhere. – kevr Dec 29 '22 at 23:19
50

For partial update - PATCH http method

For full update - PUT http method

When doing an update with DRF, you are supposed to send request data that includes values for all (required) fields. This is at least the case when the request is via the PUT http method. From what I understand, you want to update one or at least not all model instance fields. In this case make a request with the PATCH http method. Django rest framework (DRF) will take care of it out of the box.

Example (with token auth):

curl -i -X PATCH -d '{"name":"my favorite banana"}' -H "Content-Type: application/json" -H 'Authorization: Token <some token>'  http://localhost:8000/bananas/
Alison R.
  • 4,204
  • 28
  • 33
Gooshan
  • 2,361
  • 1
  • 20
  • 15
16

So simple, just override init method of your serializer like that:

def __init__(self, *args, **kwargs):
    kwargs['partial'] = True
    super(DemoSerializer, self).__init__(*args, **kwargs)
Alihaydar Gubatov
  • 988
  • 1
  • 12
  • 27
  • 3
    i wonder why this answer isn't receiving more upvotes? it worked like a charm for me – lingxiao Sep 03 '20 at 07:19
  • 2
    in update function partial=False is replaced. Simply use PATCH instead of PUT. – Ebrahim Abdollahian Sep 03 '22 at 08:31
  • @lingxiao The reason it's not receiving more upvotes is because this answer is hacking both incoming PUT and PATCH requests to be seen by the serializer as PATCH requests. This is not exactly explaining how the partial update process works through the rest framework, or through his particular base view class. May have worked great for you, but for anybody needing to support both, this just ruins everything. – kevr Dec 29 '22 at 23:17
  • 1
    @kevr In the Django Rest Framework, partial updates are enabled by default for PATCH requests, but not for PUT requests. Therefore, if you want to allow partial updates to be performed using PUT requests as well, you can use the partial argument in the serializer's constructor, as I did in my solution. It is not accurate to say that this solution "ruins everything". In fact, it provides a simple and straightforward way to enable partial updates. – Alihaydar Gubatov Dec 31 '22 at 14:59
5

Just a quick note as it seems that nobody has already pointed this out:

serialized = DemoSerializer(request.user, data=request.data, partial=True)

The first argument of DemoSerializer should be a Demo instance, not a user (at least if you use DRF 3.6.2 like me).

I don't know what you are trying to do, but this is a working example:

def partial_update(self, request, *args, **kwargs):
    response_with_updated_instance = super(DemoViewSet, self).partial_update(request, *args, **kwargs)
    Demo.objects.my_func(request.user, self.get_object())
    return response_with_updated_instance

I do the partial update and then I do other things calling my_func and passing the current user and the demo instance already updated.

Hope this helps.

Sergio Morstabilini
  • 2,035
  • 21
  • 28
  • I want to show the data into the fields at the time of updation do you know how do we do that in nested serializing...... because otherwise I am getting null or blank fields, so if I edit one field it shows me an error, that these fields can't be blank or null.... please help.... Thank You – Nikhil Bhardwaj Nov 28 '19 at 02:21
0

I had an issue where my multi-attribute/field validation in a rest_framework serializer was working with a POST /resources/ request but failing with a PATCH /resources/ request. It failed in the PATCH case because it was only looking for values in the supplied attrs dict and not falling back to values in self.instance. Adding a method get_attr_or_default to do that fallback seems to have worked:

class EmailSerializer(serializers.ModelSerializer):

    def get_attr_or_default(self, attr, attrs, default=''):
        """Return the value of key ``attr`` in the dict ``attrs``; if that is
        not present, return the value of the attribute ``attr`` in
        ``self.instance``; otherwise return ``default``.
        """
        return attrs.get(attr, getattr(self.instance, attr, ''))

    def validate(self, attrs):
        """Ensure that either a) there is a body or b) there is a valid template
        reference and template context.
        """
        existing_body = self.get_attr_or_default('body', attrs).strip()
        if existing_body:
            return attrs
        template = self.get_attr_or_default('template', attrs)
        templatecontext = self.get_attr_or_default('templatecontext', attrs)
        if template and templatecontext:
            try:
                render_template(template.data, templatecontext)
                return attrs
            except TemplateRendererException as err:
                raise serializers.ValidationError(str(err))
        raise serializers.ValidationError(NO_BODY_OR_TEMPLATE_ERROR_MSG)
jrwdunham
  • 43
  • 1
  • 4
0

I don't know why, but for me, the only way to solve it was to override the validate method in the Serializer class.

Maybe it's related to the fact that I'm using MongoDB with Djongo

class DemoSerializer(serializers.ModelSerializer):
   def validate(self, attrs):
       self._kwargs["partial"] = True
       return super().validate(attrs)
Yinon_90
  • 1,315
  • 12
  • 17
-8

You forgot serializer.save()

You can finish it the following way . . .

class DemoViewSet(viewsets.ModelViewSet):
    serializer_class = DemoSerializer

    def partial_update(self, request, pk=None):
        serializer = DemoSerializer(request.user, data=request.data, partial=True)
        serializer.save()
        serializer.is_valid(raise_exception=True)
        return Response(serializer.data)

Also, you shouldn't need to override the update method in the serializer.

Clay Risser
  • 3,272
  • 1
  • 25
  • 28
  • 15
    I think first you have to validate your serializer then save it. So serializer.is_valid() will come first then serializer.save() – HibernatedGuy Sep 13 '17 at 05:42