Background

There are 2 HTTP Request Methods to update the data:

PUT

The PUT method requests that the enclosed entity be stored under the supplied URI. If the URI refers to an already existing resource, it is modified; if the URI does not point to an existing resource, then the server can create the resource with that URI.

PATCH

The PATCH method applies partial modifications to a resource.

As we can see PUT updates the every resource of entire data, whereas PATCH updates the partial of data.

In other words: We can say PUT replace where PATCH modify.

So in this article we are going to look for PATCH method.

If you want more information about HTTP you can look at: Hypertext Transfer Protocol - WIKI.

Entrée

Let’s assume you have a simple ModelViewSet for your Django Rest Framework, and you want to allow users to update partial data field(s) of this model. How can you achieve that?

1
2
3
4
5
6
7
# views.py
from rest_framework import permissions, viewsets


class TodoViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

After some lookup for the doc and core of DRF I found that ModelViewSet inherits some Mixins parent classes and you have to override default partial kwargs in UpdateModelMixin.

# viewsets.py
# https://github.com/encode/django-rest-framework/blob/91916a4db1/rest_framework/viewsets.py

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

You can see it below on line 9.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# https://github.com/encode/django-rest-framework/blob/91916a4db14cd6a06aca13fb9a46fc667f6c0682/rest_framework/mixins.py#L64


class UpdateModelMixin:
    """
    Update a model instance.
    """
    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)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

Solution

So you simply override update function like below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# views.py
from rest_framework import permissions, viewsets


class TodoViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

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

Clean Solution

In order to be clean side of programming it would be better to separate it and use it as Mixin for maintenance and testing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# utils.py


class EnablePartialUpdateMixin:
    """Enable partial updates

    Override partial kwargs in UpdateModelMixin class
    https://github.com/encode/django-rest-framework/blob/91916a4db14cd6a06aca13fb9a46fc667f6c0682/rest_framework/mixins.py#L64
    """
    def update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return super().update(request, *args, **kwargs)

Use it like below:

1
2
3
4
5
6
7
8
# views.py
from rest_framework import permissions, viewsets
from .utils import EnablePartialUpdateMixin


class TodoViewSet(EnablePartialUpdateMixin, viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

Appendix

You can find model and serializer class of the TodoViewSet below.

Also you can look up for full code detail in this repo: YADRTA

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# models.py


class Task(BaseModelMixin):
    STATES = (
        ("todo", "Todo"),
        ("wip", "Work in Progress"),
        ("suspended", "Suspended"),
        ("waiting", "Waiting"),
        ("done", "Done"),
    )

    title = models.CharField(max_length=255, blank=False, unique=True)
    description = models.TextField()
    status = models.CharField(max_length=15, choices=STATES, default="todo")
    tag = models.ForeignKey(to=Tag, on_delete=models.DO_NOTHING)
    category = models.ForeignKey(to=Category, on_delete=models.DO_NOTHING)

    class Meta:
        ordering = ("title",)

    def __str__(self):
        return f"{self.created_by}:{self.title}"
# serializers.py
from rest_framework import serializers


class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = base_model_mixin_fields + [
            "title",
            "description",
            "status",
            "tag",
            "category",
        ]

All done!


Changelog

  • 2020-10-26 : Added Background subsection