如何在DRF ViewSet中完全禁止使用PUT方法但允许使用PATCH方法?

15

PUTPATCH都是同一个mixin的一部分(即UpdateModelMixin)。

所以,如果我像这样扩展它:

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

允许使用PUTPATCH。我不希望我的应用程序完全允许使用PUT(因为PATCH已经可以实现相关功能,而我希望只使用POST来限制对象的创建)。一种方法是创建一个权限:

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

我希望给予所有允许使用 PATCH 的视图集这个权限。这是最佳实践吗?还有更好的方法吗?

编辑:查看 @wim 给出的答案后,这是否是一个好的解决方案(除了删除了 put 的映射之外其他都保持不变):

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

或者我需要重新定义SimpleRoute中的其他方法(例如__init()__get_routes()_get_dynamic_routes()get_method_map()等)才能使它正常工作吗?

7个回答

14

如果您想要使用内置的 mixins.UpdateModelMixin,并且仅限于使用 PATCH,同时禁用 swagger 显示 PUT,则可以使用 http_method_names

class UserViewSet(mixins.UpdateModelMixin, GenericViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    http_method_names = ["patch"]

http_method_names = ["patch"] 这个可行,谢谢! - PolarBear10

6
一个简单而直接的方法:
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    http_method_names = ['get', 'post', 'patch'] # <---------

像这样,PUT方法将不被允许。


1
这应该是被接受的答案。它非常直截了当。 - Dean Christian Armada

6

不要使用mixins.UpdateModelMixin,而是定义自己的mixin,仅执行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()

1

Here's the solution I'm using:

class SomeViewSet(
    mixins.UpdateModelMixin,
    ...
):
    @swagger_auto_schema(auto_schema=None)
    def update(self, request, *args, **kwargs):
        """Disabled full update functionality"""
        partial = kwargs.get('partial', False)  # This must be .get() not .pop()
        if not partial:
            raise exceptions.MethodNotAllowed(request.method)

        return super(SomeViewSet, self).update(request, *args, **kwargs)

这也会在 drf-yasg 的用户界面中禁用它。

1

@linovia的回答类似,但使用标准Mixin:

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)

这个可行,但如何禁用Swagger显示PUT端点? - TNT
1
对于drf-spectacular,您可以使用@extend_schema_view(update=extend_schema(exclude=True)) - phoenix

1
类似于 @EbramShehata 的解决方案,但适用于 drf-spectacular (OpenAPI 3)。这将禁止完全更新(PUT),并将其从生成的 OpenAPI 3 模式中排除。
class SomeViewSet(
    mixins.UpdateModelMixin,
    ...
):
    @extend_schema(exclude=True)
    def update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
        """Disallow full update (PUT) and allow partial update (PATCH)."""
        if kwargs.get("partial", False):  # Use .get() instead of .pop()
            return super().update(request, args, kwargs)

        raise MethodNotAllowed(request.method)

1
我认为一个更好的解决方案是使用自定义路由器并禁用PUT路由,然后在视图集中使用您的自定义路由器。
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={}
        ),
    ]

^ 路由器实现大致如此。 因此,您只需要继承SimpleRouterDefaultRouter,并定义routes类属性的方式即可。 您可以完全删除Route(mapping = {...})中'put'的映射,或者您可以定义自己的操作来处理它并返回适当的400多个响应。


好的。我已经编辑了我的答案,包括了新的自定义路由器。您能确认这个自定义路由器是否正确吗?或者我需要重新定义 SimpleRouter 中的其他方法以使其正常工作? - SilentDev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接