Enable Partial Updates in Django REST

serhatteker

Serhat Teker

Posted on November 23, 2020

Enable Partial Updates in Django REST

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?

# views.py
from rest_framework import permissions, viewsets


class TodoViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
Enter fullscreen mode Exit fullscreen mode

After some lookup for the doc and core of DRF I found that ModelViewSet inherits some Mixins parent classes and you have tooverride 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
Enter fullscreen mode Exit fullscreen mode

You can see it below on line 9.

# 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)
Enter fullscreen mode Exit fullscreen mode

Solution

So you simply override update function like below:

# 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)
Enter fullscreen mode Exit fullscreen mode

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:

# 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)
Enter fullscreen mode Exit fullscreen mode

Use it like below:

# views.py
from rest_framework import permissions, viewsets
from .utils import EnablePartialUpdateMixin


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

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

# 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}"
Enter fullscreen mode Exit fullscreen mode
# 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",
        ]
Enter fullscreen mode Exit fullscreen mode

All done!


Changelog

  • 2020-10-26 : Added Background subsection
💖 💪 🙅 🚩
serhatteker
Serhat Teker

Posted on November 23, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related