PUT et PATCH font tous deux partie du même mixin (Le UpdateModelMixin).

Donc si je le prolonge comme ça:

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

PUT et PATCH sont tous deux autorisés. Je ne veux pas du tout autoriser PUT pour mon application (puisque PATCH fait déjà le travail, et je veux limiter la création d'objets en utilisant uniquement POST). Une façon consiste à créer une autorisation:

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

Et pour donner cette autorisation à tous mes ViewSets qui autorisent PATCH. Est-ce la meilleure façon de le faire? Y a-t-il un moyen plus préféré?

Edit: Après avoir regardé la réponse fournie par @wim, ce sera une bonne solution (tout est resté le même sauf le mappage pour put a été supprimé):

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={}
        ),
    ]

Ou aurais-je besoin de redéfinir d'autres méthodes dans SimpleRoute (par exemple __init()__, get_routes(), _get_dynamic_routes(), get_method_map() etc.) pour que cela fonctionne correctement?

4
user2719875 20 avril 2017 à 23:43

3 réponses

Meilleure réponse

Au lieu d'utiliser mixins.UpdateModelMixin, définissez simplement votre propre mixin qui n'effectuerait que le patch:

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()
3
Linovia 21 avril 2017 à 05:45

Similaire à réponse de @ linovia mais en utilisant le mixin standard:

from rest_framework.exceptions import MethodNotAllowed

class UpdateModelMixin(mixins.UpdateModelMixin, viewsets.GenericViewSet):
    """
    update:
        Update Model
    """

    def update(self, *args, **kwargs):
        raise MethodNotAllowed("POST", detail="Use PATCH")

    def partial_update(self, request, *args, **kwargs):
        # Override Partial Update Code if desired
        return super().update(*args, **kwargs, partial=True)
0
gtalarico 26 mars 2020 à 22:35

Je pense qu'une solution supérieure serait d'utiliser un routeur personnalisé et désactiver la route pour PUT. Utilisez ensuite votre routeur personnalisé pour les ensembles de vues.

class SimpleRouter(BaseRouter):
    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': 'update',
                '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={}
        ),
    ]

^ L'implémentation du routeur ressemble à quelque chose comme ça. Il vous suffit donc d'hériter de SimpleRouter, ou peut-être de DefaultRouter, et de définir l'attribut de classe routes comme vous le souhaitez. Vous pouvez supprimer complètement le mappage pour 'put' dans le Route(mapping={...}), ou vous pouvez définir votre propre action pour le gérer et renvoyer la réponse appropriée de 400 quelque chose.

1
wim 20 avril 2017 à 21:04