Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
PUT-as-create mixin class for Django REST framework.
class AllowPUTAsCreateMixin(object):
"""
The following mixin class may be used in order to support PUT-as-create
behavior for incoming requests.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object_or_none()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
if instance is None:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup_value = self.kwargs[lookup_url_kwarg]
extra_kwargs = {self.lookup_field: lookup_value}
serializer.save(**extra_kwargs)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.save()
return Response(serializer.data)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def get_object_or_none(self):
try:
return self.get_object()
except Http404:
if self.request.method == 'PUT':
# For PUT-as-create operation, we need to ensure that we have
# relevant permissions, as if this was a POST request. This
# will either raise a PermissionDenied exception, or simply
# return None.
self.check_permissions(clone_request(self.request, 'POST'))
else:
# PATCH requests where the object does not exist should still
# return a 404 response.
raise
@cancan101
Copy link

cancan101 commented Feb 13, 2015

PATCH is also allowed to create. From http://tools.ietf.org/html/rfc5789:

If the Request-URI does not
point to an existing resource, the server MAY create a new resource,
depending on the patch document type (whether it can logically modify
a null resource) and permissions, etc.

@Natim
Copy link

Natim commented Mar 28, 2018

from django.http import Http404

from rest_framework.response import Response
from rest_framework.request import clone_request

@feliesp
Copy link

feliesp commented Apr 23, 2018

from rest_framework import status

@mightyroser
Copy link

mightyroser commented May 14, 2018

If you want to support overridable hooks perform_create and/or perform_update similar to what are available in CreateModelMixin and UpdateModelMixin ( documented in http://www.django-rest-framework.org/api-guide/generic-views/ ) you could modify the update function above as follows:

def update(self, request, *args, **kwargs):
    partial = kwargs.pop('partial', False)
    instance = self.get_object_or_none()
    serializer = self.get_serializer(instance, data=request.data, partial=partial)
    serializer.is_valid(raise_exception=True)

    if instance is None:
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
        lookup_value = self.kwargs[lookup_url_kwarg]
        extra_kwargs = {self.lookup_field: lookup_value}
        self.perform_create(serializer, **extra_kwargs)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    self.perform_udpate(serializer)
    return Response(serializer.data)

def perform_create(self, serializer, **kwargs):
    serializer.save(**kwargs)
    
def perform_udpate(self, serializer):
    serializer.save()

@manuel14
Copy link

manuel14 commented May 23, 2018

The mixin works fine, but how can i make optional the pk field of my object? is it possible?

@PetrDlouhy
Copy link

PetrDlouhy commented Mar 25, 2020

I have problem with this fixture - I am getting duplicate errors if the request is called more than once quickly. Does anyone had the same problem? I tried @transaction.atomic, but without success.

@aryaniyaps
Copy link

aryaniyaps commented Mar 29, 2021

with support for perform_create and perform_update hooks (without changing signature)
and multiple-field mixins.

class PutAsCreateMixin(object):
    """
    The following mixin class may be used in order to support
    PUT-as-create behavior for incoming requests.
    """

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object_or_none()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)

        if instance is None:
            self.perform_create(serializer)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        self.perform_update(serializer)
        return Response(serializer.data)

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

    def perform_create(self, serializer):
        if not hasattr(self, 'lookup_fields'):
            lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
            lookup_value = self.kwargs[lookup_url_kwarg]
            extra_kwargs = {self.lookup_field: lookup_value}
        else:
            # set kwargs for additional fields
            extra_kwargs = {
                field: self.kwargs[field]
                for field in self.lookup_fields if self.kwargs[field]
            }
        serializer.save(**extra_kwargs)
    
    def perform_update(self, serializer):
        serializer.save()

    def get_object_or_none(self):
        try:
            return self.get_object()
        except Http404:
            if self.request.method == 'PUT':
                # For PUT-as-create operation, we need to ensure that we have
                # relevant permissions, as if this was a POST request. This
                # will either raise a PermissionDenied exception, or simply
                # return None.
                self.check_permissions(clone_request(self.request, 'POST'))
            else:
                # PATCH requests where the object does not exist should still
                # return a 404 response.
                raise

this way, additional attributes can also be added during creation.

@libcthorne
Copy link

libcthorne commented Dec 16, 2021

Are there any plans to have something like this included in DRF by default?

@tomchristie
Copy link
Author

tomchristie commented Dec 16, 2021

Nope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment