如何在 DRF ViewSet 中完全不允许 PUT 方法但允许 PATCH?
How to Not allow the PUT method at all but allow PATCH in a DRF ViewSet?
PUT
和 PATCH
都是同一个 mixin 的一部分(UpdateModelMixin)。
所以如果我这样扩展它:
class UserViewSet(mixins.UpdateModelMixin, GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
PUT
和 PATCH
都可以。我不想让 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
的 ViewSet。这是最好的方法吗?有没有更好的方式?
编辑:在查看@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()
等)以便它能正常工作吗?
我认为更好的解决方案是使用自定义 router 并禁用 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={}
),
]
^ 路由器实现看起来像那样。因此,您只需要继承 SimpleRouter
,或者 DefaultRouter
,并根据需要定义 routes
class 属性。您可以完全删除 Route(mapping={...})
中 'put' 的映射,或者您可以定义自己的操作来处理它和 return 适当的 400 多岁的反应。
而不是使用 mixins.UpdateModelMixin
只需定义您自己的仅执行补丁的 mixin:
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()
类似于但使用标准混合:
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)
如果您想使用内置 mixins.UpdateModelMixin
,限制为 PATCH
并禁止 swagger 显示 PUT,您可以使用 http_method_names
class UserViewSet(mixins.UpdateModelMixin, GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
http_method_names = ["patch"]
这是我正在使用的解决方案:
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 UI 中禁用它。
类似于@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)
一个简单直接的方法:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
http_method_names = ['get', 'post', 'patch'] # <---------
像这样 PUT
方法将不被允许。
PUT
和 PATCH
都是同一个 mixin 的一部分(UpdateModelMixin)。
所以如果我这样扩展它:
class UserViewSet(mixins.UpdateModelMixin, GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
PUT
和 PATCH
都可以。我不想让 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
的 ViewSet。这是最好的方法吗?有没有更好的方式?
编辑:在查看@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()
等)以便它能正常工作吗?
我认为更好的解决方案是使用自定义 router 并禁用 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={}
),
]
^ 路由器实现看起来像那样。因此,您只需要继承 SimpleRouter
,或者 DefaultRouter
,并根据需要定义 routes
class 属性。您可以完全删除 Route(mapping={...})
中 'put' 的映射,或者您可以定义自己的操作来处理它和 return 适当的 400 多岁的反应。
而不是使用 mixins.UpdateModelMixin
只需定义您自己的仅执行补丁的 mixin:
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()
类似于
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)
如果您想使用内置 mixins.UpdateModelMixin
,限制为 PATCH
并禁止 swagger 显示 PUT,您可以使用 http_method_names
class UserViewSet(mixins.UpdateModelMixin, GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
http_method_names = ["patch"]
这是我正在使用的解决方案:
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 UI 中禁用它。
类似于@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)
一个简单直接的方法:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
http_method_names = ['get', 'post', 'patch'] # <---------
像这样 PUT
方法将不被允许。