Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Not allow the PUT method at all but allow PATCH in a DRF ViewSet?

PUT and PATCH are both part of the same mixin (The UpdateModelMixin).

So if I extend it like so:

class UserViewSet(mixins.UpdateModelMixin, GenericViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

Both PUT and PATCH are allowed. I want to not allow PUT at all for my app (since PATCH already does the work, and I want to limit object creation using just POST). One way is to create a permission:

class NoPut(permissions.BasePermission):
    """
    PUT not allowed.
    """
    message = 'You do not have permission to complete the action you are trying to perform.'

    def has_object_permission(self, request, view, obj):
        if view.action == "update":
            return False
        return True

And to give this permission to all my ViewSets which allow PATCH. Is this the best way to do it? Is there a more preferred way?

Edit: After looking at the answer provided by @wim, will this be a fine solution (everything kept the same except the mapping for put was removed):

from rest_framework.routers import SimpleRouter
class NoPutRouter(SimpleRouter):

    routes = [
        # List route.
        Route(
            url=r'^{prefix}{trailing_slash}$',
            mapping={
                'get': 'list',
                'post': 'create'
            },
            name='{basename}-list',
            initkwargs={'suffix': 'List'}
        ),
        # Dynamically generated list routes.
        # Generated using @list_route decorator
        # on methods of the viewset.
        DynamicListRoute(
            url=r'^{prefix}/{methodname}{trailing_slash}$',
            name='{basename}-{methodnamehyphen}',
            initkwargs={}
        ),
        # Detail route.
        Route(
            url=r'^{prefix}/{lookup}{trailing_slash}$',
            mapping={
                'get': 'retrieve',
                 # put removed
                'patch': 'partial_update',
                'delete': 'destroy'
            },
            name='{basename}-detail',
            initkwargs={'suffix': 'Instance'}
        ),
        # Dynamically generated detail routes.
        # Generated using @detail_route decorator on methods of the viewset.
        DynamicDetailRoute(
            url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$',
            name='{basename}-{methodnamehyphen}',
            initkwargs={}
        ),
    ]

or would I need to redefine other methods in SimpleRoute (e.g. __init()__, get_routes(), _get_dynamic_routes(), get_method_map() etc.) in order for it to work correctly?

like image 264
SilentDev Avatar asked Apr 20 '17 20:04

SilentDev


3 Answers

If you want to use builtin mixins.UpdateModelMixin, limit to PATCH and disable swagger from showing PUT you can use http_method_names

class UserViewSet(mixins.UpdateModelMixin, GenericViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    http_method_names = ["patch"]
like image 171
Mike Br Avatar answered Oct 22 '22 12:10

Mike Br


A simple and straight forward approach:

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    http_method_names = ['get', 'post', 'patch'] # <---------

Like this the PUT method will not be allowed.

like image 42
Elias Prado Avatar answered Oct 22 '22 11:10

Elias Prado


Instead of using mixins.UpdateModelMixin just define your own mixin that would perform patch only:

class UpdateModelMixin(object):
    """
    Update a model instance.
    """
    def partial_update(self, request, *args, **kwargs):
        partial = True
        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()
like image 30
Linovia Avatar answered Oct 22 '22 10:10

Linovia