Search code examples
djangodjango-rest-frameworkhttp-putdjango-serializerhttp-patch

HTTP PUT not working properly in django rest framework


I am using the following code in django rest framework to achieve put and patch functionality. The PATCH (partial_update) works fine, however, providing fewer fields for the PUT would not replace the whole resource as a PUT request should. For instance, if the data seems like the following:

{
"id": 6,
"name": "crticial MVP",
"description": "getting things done",
"end": "2012-02-18"   
}

And if I attempt a PUT with the following data(on http://localhost:800/api/sprints/6/) :

{
    "name": "critical MVP2", 
    "end": "2012-02-18"
}

This call should set the description to none. But the description retains its value as if I was doing a PATCH. How would I enforce resource replacement in the update functionality?

PS: I have set the partial=False when initializing the serializer for update.

views.py:

class SprintViewSet(viewsets.ModelViewSet):
    queryset = Sprint.objects.order_by('end')
    serializer_class = SprintSerializer 
    def update(self, request, *args, **kwargs):
        sprint=self.get_object()                        
        serializer = SprintSerializer(sprint, data=request.data, partial=False,
 context={'request': request, 'view':self, 'format':None})
        serializer.is_valid(raise_exception=True)       
        serializer.save()
        return Response(serializer.data)
    def partial_update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = SprintSerializer(instance, data=request.data, partial=True,
 context={'request': request, 'view':self, 'format':None})
        serializer.is_valid(raise_exception=True)
        new_instance = serializer.save()
        return Response(serializer.data)

serializers.py:

class SprintSerializer(serializers.ModelSerializer):
    links = serializers.SerializerMethodField()
    class Meta:     
        model = Sprint              
        fields = ('id', 'name', 'description', 'end', 'links')
    def get_links(self, obj):
        request = self.context['request'] 
        return {
                'self': reverse('sprint-detail',
                kwargs={'pk': obj.pk},request=request),
                }

models.py

class Sprint(models.Model): 
    name = models.CharField(max_length=100, blank=True, default='')
    description = models.TextField(blank=True, default='')
    end = models.DateField(unique=True)
    def __str__(self):
        return self.name or _('Sprint ending %s') % self.end

Solution

  • A PUT expects that the full data object is passed in, so any attributes on the model are replaced with the data that is passed in. But this requires that the full data object is passed in, so if you don't pass in a field then it will be ignored depending on how the data is sent.

    If you are sending the data in using form data (AJAX without JSON, or a HTML <form>), then the field will be completely ignored. This mostly has to do with some browsers not sending certain fields (such as checkboxes) if the value isn't set, so DRF manually overrides it in these situations.

    If you are using JSON and the field is not required (which appears to be your case), DRF will only override the value if the key is passed into the request. This is because a PUT is supposed to contain a full data object, and DRF makes a few assumptions based on this.