52

I have a model 'MyModel' with many fields and I would like to update a field 'status' using PATCH method. I'm using class based views. Is there any way to implement PATCH?

pnhegde
  • 695
  • 1
  • 8
  • 19
  • you can just check `request.method == 'PATCH'` in your view code and then do whatever you want. – Ivan Klass Jan 16 '15 at 09:06
  • I would like to know how to handle this in serializer class. – pnhegde Jan 16 '15 at 09:08
  • Serializer class is not the place that has to know something about request method. So I would recommend you to check view classes (as `APIView` ), not serializers – Ivan Klass Jan 16 '15 at 09:24

5 Answers5

54

Serializers allow partial updates by specifying partial=True when initializing the serialzer. This is how PATCH requests are handled by default in the generic views.

serializer = CommentSerializer(comment, data=request.data, partial=True)

This will allow you to update individual fields in a serializer, or all of the fields if you want, without any of the restrictions of a standard PUT request.

Kevin Brown-Silva
  • 40,873
  • 40
  • 203
  • 237
  • 30
    Where exactly does this go? – Scott Smith Dec 15 '15 at 09:43
  • 8
    Good question @ScottSmith. It seems the documentation and other sources only cite this one-liner as the solution to doing partial updates. But it does one no good if he doesn't know where to put this line of code. – JayGee Jul 16 '16 at 03:40
  • 1
    "where does this go?" depends on how your views are written. This answer is assuming you are not using generic views, but instead are rolling your own. – Kevin Brown-Silva Oct 03 '16 at 11:50
  • 5
    Yep you need to override `def update(self, request, *args, **kwargs)` or `def partial_update(self, request, *args, **kwargs)` inside your view like Kevin mentioned. The reason that patch doesn't work out of the box as expected, when you are using ModelSerializer or Serializer, is because inside `BaseSerializer` constructor, there is: `self.partial = kwargs.pop('partial', False)` which means that even if you are overriding !!`partial_update`!! you still need to pass `partial=True` when creating the serializer. Thus the default behavior of generic views linked above is discarded. – stelios Oct 19 '16 at 18:59
  • You could see generics, see [my answer](http://stackoverflow.com/a/42222871/5019818) – Uri Shalit Feb 14 '17 at 09:54
  • 2
    So, I am using a `ModelSerializer` and `RetrieveUpdateDestroyAPIView`. I am confused on where to specify this `partial=True`! – Sreekar Mouli Jul 21 '18 at 06:26
  • Does .save() not need to be called? Do you call .save() or .update() in this case? – aroooo May 25 '20 at 20:36
17

As Kevin Brown stated you could use the partial=True, which chefarov clarified nicely.

I would just like to correct them and say you could use generics freely, depending on the HTTP method you are using:

  • If you are using PATCH HTTP method like asked, you get it out of the box. You can see UpdateModelMixin code for partial_update:

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)
    
  • For any HTTP method different from PATCH, this can be accomplished by just overriding the get_serializer method as follows:

    def get_serializer(self, *args, **kwargs):
        kwargs['partial'] = True
        return super(YOUR_CLASS, self).get_serializer(*args, **kwargs)
    

This will create the serializer as partial, and the rest of the generics will work like a charm without any manual intervention in the update/partial_update mechanism.

Under the hood

I used the generic: generics.UpdateAPIView which uses the UpdateModelMixin which has this code:

def update(self, request, *args, **kwargs):
    partial = kwargs.pop('partial', False)
    instance = self.get_object()
    serializer = self.get_serializer(instance, data=request.data, partial=partial)
    …

So, if you override the get_serializer function, you can actually override the partial argument and force it to be true.

Please note, that if you want it partial only for some of the HTTP methods, this makes this approach more difficult.

I am using djangorestframework==3.5.3

Community
  • 1
  • 1
Uri Shalit
  • 2,198
  • 2
  • 19
  • 29
5

It does seem to be supported out of the box. On your browser API, navigate to a model detail page, at the bottom next to the HTML Form tab click Raw data, delete everything from the JSON string except the ID field and the field you wish to change, and click PATCH. A partial PATCH update is performed.

I'm using djangorestframework==3.2.4, and haven't had to do anything to my ViewSets and Serializers to enable this.

In this exampe we are updating the bool status_field field of the model, and I'm using jquery 2.2.1. Add the following to the <head>:

<script src="{% static 'my_app/jquery.min.js' %}"></script>
<script>
$(document).ready(function(){
    var chk_status_field = $('#status_field');
    chk_status_field.click(function(){
        $.ajax({url: "{% url 'model-detail' your_rendering_context.id %}",
            type: 'PATCH', timeout: 3000, data: { status_field: this.checked }
        })
        .fail(function(){
            alert('Error updating this model instance.');
            chk_status_field.prop('checked', !chk_status_field.prop('checked'));
        });
    });
});
</script>

Then in a <form>:

<input type="checkbox" id="status_field" {% if your_rendering_context.status_field %} 
    checked {% endif %} >

I chose to permit the checkbox to change, then revert it in the case of failure. But you could replace click with mousedown to only update the checkbox value once the AJAX call has succeeded. I think this will lead to people repeatedly clicking the checkbox for slow connections though.

Chris
  • 5,664
  • 6
  • 44
  • 55
  • Worked out of the box! passing the data through ajax call in a dictionary like object is the thing I was missing on, thanks! – Tanmay D Dec 18 '18 at 15:10
5

If anyone is still planning to find a simple solution using ModelSerializer without changing much of your views, you can subclass the ModelSerializer and have all your ModelSerializers inherit from that.

class PatchModelSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        kwargs['partial'] = True
        super(PatchModelSerializer, self).__init__(*args, **kwargs)

class ArticleSerializer(PatchModelSerializer):
    class Meta:
        model = Article
firecast
  • 988
  • 2
  • 10
  • 20
2

I struggled with this one for a while, but it is a very straightforward implementation using generic views or a combination of generic views and mixins.

In the case of using a generic update view (generics.UpdateAPIView), just use the following code, making sure the request type is PATCH:

class UpdateUser(generics.UpdateAPIView):

    queryset = User.objects.all()
    serializer_class = UserSerializer

There's nothing else to it!

If you're using an update mixin (mixins.UpdateModelMixin) in combination with a generic view (generics.GenericAPIView), use the following code, making sure the request type is PATCH:

class ActivateUser(mixins.UpdateModelMixin, generics.GenericAPIView):

    serializer_class = UserSerializer
    model = User
    lookup_field = 'email'

    def get_queryset(self):
        queryset = self.model.objects.filter(active=False)
        return queryset

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

The second example is more complex, showing you how to override the queryset and lookup field, but the code you should pay attention to is the patch function.

Arun
  • 1,933
  • 2
  • 28
  • 46
0xdesdenova
  • 192
  • 1
  • 8
  • This is the only answer that mentions serializer_class, which is the way that our view is set up. I see that in your top example, you are using a class called UpdateUser. Are you saying that for patch, I would need to create a separate view class just for the PATCH? Can I just enable PATCH in the regular view class that includes POST, etc? Here's how my view is set up: class JobViewSet(viewsets.ModelViewSet): serializer_class = JobSerializer ... – JoeMjr2 Feb 20 '19 at 18:57