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

This comment has been minimized.

Copy link

@cancan101 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

This comment has been minimized.

Copy link

@Natim Natim commented Mar 28, 2018

from django.http import Http404

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

This comment has been minimized.

Copy link

@feliesp feliesp commented Apr 23, 2018

from rest_framework import status

@mightyroser

This comment has been minimized.

Copy link

@mightyroser 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

This comment has been minimized.

Copy link

@manuel14 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

This comment has been minimized.

Copy link

@PetrDlouhy 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.

@aryan340

This comment has been minimized.

Copy link

@aryan340 aryan340 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.

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