【问题标题】:Django REST framework: method PUT not allowed in ViewSet with def update()Django REST 框架:ViewSet 中不允许使用 def update() 方法 PUT
【发布时间】:2015-10-31 01:06:06
【问题描述】:

在 DRF 中,我有一个像这样的简单 ViewSet:

class MyViewSet(viewsets.ViewSet):       

    def update(self, request):
        # do things...
        return Response(status=status.HTTP_200_OK)

当我尝试 PUT 请求时,我收到一个错误,例如不允许 PUT 方法。如果我使用 def put(self, request): 一切正常。根据the docs我应该使用def update():而不是def put():,为什么会这样?

【问题讨论】:

    标签: python django-rest-framework


    【解决方案1】:

    这是因为APIView 没有为.put() 方法定义处理程序,因此传入的请求无法映射到视图上的处理程序方法,从而引发异常。

    (注:viewsets.ViewSet 继承自 ViewSetMixinAPIView

    APIView 中的dispatch() 方法检查是否为请求method 定义了方法处理程序。如果dispatch() 方法找到请求方法的处理程序,它会返回适当的响应。否则,它会引发异常MethodNotAllowed

    根据APIView类中dispatch()方法的源码:

    def dispatch(self, request, *args, **kwargs):       
            ...
            ...    
            try:
                self.initial(request, *args, **kwargs)
    
                # Get the appropriate handler method
                if request.method.lower() in self.http_method_names:
                     # here handler is fetched for the request method
                     # `http_method_not_allowed` handler is assigned if no handler was found
                    handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
                else:
                    handler = self.http_method_not_allowed 
    
                response = handler(request, *args, **kwargs) # handler is called here
    
            except Exception as exc:
                response = self.handle_exception(exc)
    
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
    

    由于.put() 方法处理程序未在您的视图中定义,DRF 调用回退处理程序.http_method_not_allowed。这会引发 MethodNotAllowed 异常。

    .http_method_not_allowed()的源码为:

    def http_method_not_allowed(self, request, *args, **kwargs):
        """
        If `request.method` does not correspond to a handler method,
        determine what kind of exception to raise.
        """
        raise exceptions.MethodNotAllowed(request.method) # raise an exception 
    

    为什么在您的视图中定义 .put() 时它会起作用?

    当您在视图中定义def put(self, request): 时,DRF 可以将传入的请求方法映射到视图上的处理程序方法。这导致返回适当的响应而不会引发异常。

    【讨论】:

    • 所以如果我想使用 create 我会映射它?
    • 您在视图中定义update()时是否在您的url中定义了router
    • 我定义了一个路由器来注册viewSet
    • 此方法应始终有效。请检查您是否按照文档中的说明正确定义了viewsetsrouters,因为router 本身会将put 函数设置为指向update 函数。因此,每当PUT 请求到来时,处理程序都会执行.update() 操作的代码。
    • 您调用的 URL 是否包含详细的 'pk' 路径参数?导致调用 PUT 到根资源,例如 /users/ 没有 /users/ 将导致方法不允许。
    【解决方案2】:

    此代码存在类似的“不允许使用 Method PUT”问题,因为请求中缺少“id”:

    class ProfileStep2Serializer(serializers.ModelSerializer):
        class Meta:
            model = Profile
            fields = ('middle_initial', 'mobile_phone', 'address', 'apt_unit_num', 'city', 'state', 'zip')
    
    class Step2ViewSet(viewsets.ModelViewSet):
        serializer_class = ProfileStep2Serializer
    
        def get_queryset(self):
            return Profile.objects.filter(pk=self.request.user.profile.id)
    

    原来我在序列化器字段中遗漏了“id”,因此 PUT 请求无法为记录提供 id。序列化器的固定版本如下:

    class ProfileStep2Serializer(serializers.ModelSerializer):
        class Meta:
            model = Profile
            fields = ('id', 'middle_initial', 'mobile_phone', 'address', 'apt_unit_num', 'city', 'state', 'zip')
    

    【讨论】:

    • 你真是个好人,你为我节省了很多时间!!
    【解决方案3】:

    PUT 默认需要 id 在 URL 中

    有时POST和PUT是有区别的,因为PUT在URL中需要id 这就是您收到错误消息的原因:“PUT is not Allowed”。

    例子:

    • 发布/api/users/
    • 放置/api/users/1/

    希望它能为某人节省很多时间

    【讨论】:

    • 请注意,如果您只定义方法 put(而不是通过 DRF 混合的 update),那么它可以在没有 ID 的情况下工作。
    • 当然,但不是一直;想象一个“单例”资源,将没有 ID。如果您有一个代表当前登录用户的 /user/me 端点,则可能是这种情况的常见用例。
    • 确实节省了很多时间。谢谢
    • 就我而言,这就像一个魅力,因为我使用了一个viewsets.ModelViewSet 和一个与之相关的serializers.ModelSerializer
    • 也可以使用PATCH方法对Django模型进行部分更新
    【解决方案4】:

    这个答案是对的,Django REST framework: method PUT not allowed in ViewSet with def update(),PUT 是不允许的,因为 DRF 期望实例 id 在 URL 中。话虽如此,在你的 ViewSet 中使用这个 mixin 可能是修复它的最好方法(来自https://gist.github.com/tomchristie/a2ace4577eff2c603b1b 复制粘贴在下面)

    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
    

    【讨论】:

      【解决方案5】:
      def update(self, request, pk=None):
          data_in = request.data
          print(data_in)
      
          instance = self.get_object()
          serializer = self.get_serializer(instance, data=request.data, partial=False)
          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()
          data_out = serializer.data
          return Response(serializer.data)
      

      【讨论】:

      • 虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
      【解决方案6】:

      使用 Django 视图集,您可以轻松地在 url 中映射方法,例如

      path('createtoken/', CreateTokenView.as_view({'post': 'create', 'put':'update'}))
      

      然后在您的班级中随意覆盖方法:

      class CreateTokenView(viewsets.ModelViewSet):
          queryset = yourSet.objects.all()
          serializer_class = yourSerializer
      
          def create(self, request, *args, **kwargs):
              #any method you want here
              return Response("response")
      
          def update(self, request, *args, **kwargs):
              # any method you want here
              return Response("response")
      

      【讨论】:

        【解决方案7】:

        我有多个使用 ModelViewSet 的对象,并且都有不同的(唯一)字段用于查找。

        因此,我通过在父类上定义 put 和可用于将有效负载与现有对象关联的新字段 lookup_body_field 来为这个问题提出另一个解决方案:

        class CustomViewSet(viewsets.ModelViewSet):
            lookup_body_field = 'id'
        
            def put(self, pk=None):
                lookup_value = self.request.data.get(self.lookup_body_field)
                if not lookup_value:
                    raise ValidationError({self.lookup_body_field: "This field is mandatory"})
        
                obj = self.get_queryset().filter(**{self.lookup_body_field: lookup_value}).last()
                if not obj:
                    return self.create(request=self.request)
                else:
                    self.kwargs['pk'] = obj.pk
                    return self.update(request=self.request)
        
        
        class MyViewSetA(CustomViewSet)
            model = ModelA
            lookup_body_field = 'field_a'  # Unique field on ModelA
        
        class MyViewSetB(CustomViewSet)
            model = ModelB
            lookup_body_field = 'field_b'  # Unique field on ModelA
        
        
            
        

        【讨论】:

          【解决方案8】:

          假设

          你已经注册了这样的路由(在 urls.py 中)

          router = DefaultRouter()
          router.register(r'users', UserViewSet, basename='user-viewset')
          urlpatterns += router.urls
          

          而你的 restapi 溃败以 /api/ 开头。

          ViewSets 生成跟随路线。

          创建用户(在正文中发送用户对象)
          POST
          /api/users/

          获取用户列表
          GET
          /api/users/

          获取用户
          获取
          /api/users/{id}/

          更新用户(完整对象更新)
          PUT
          /api/users/{id}/

          用户的部分更新
          PATCH
          /api/users/{id}/

          删除用户
          删除
          /api/users/{id}/

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-06-28
            • 2016-03-29
            • 1970-01-01
            • 2019-05-04
            • 2017-09-17
            • 1970-01-01
            相关资源
            最近更新 更多