【问题标题】:Django REST Framework: creating hierarchical objects using URL argumentsDjango REST Framework:使用 URL 参数创建分层对象
【发布时间】:2013-08-05 00:42:03
【问题描述】:

我有一个带有分层资源的 django-rest-framework REST API。我希望能够通过 POST 到 /v1/objects/<pk>/subobjects/ 来创建子对象,并让它自动将新子对象上的外键设置为来自 URL 的 pk kwarg,而无需将其放入有效负载中。目前,序列化程序正在导致 400 错误,因为它希望 object 外键位于有效负载中,但也不应将其视为可选的。子对象的 URL 是 /v1/subobjects/<pk>/(因为父对象的键不是识别它所必需的),所以如果我想 PUT 现有资源,仍然需要它。

我应该让您使用有效负载中的父级 POST 到 /v1/subobjects/ 以添加子对象,还是有一种干净的方法将 pk kwarg 从 URL 传递到序列化程序?我使用HyperlinkedModelSerializerModelViewSet 作为我各自的基类。有一些推荐的方法吗?到目前为止,我唯一的想法是完全重新实现 ViewSets 并制作一个自定义 Serializer 类,其 get_default_fields() 来自从 ViewSet 传入的字典,由其 kwargs 填充。这似乎与我认为完全普通的事情有关,所以我不禁认为我错过了一些东西。我见过的每个具有可写端点的 REST API 都有这种基于 URL 的参数推断,因此 django-rest-framework 似乎根本无法做到这一点的事实似乎很奇怪。

【问题讨论】:

    标签: python django rest django-rest-framework


    【解决方案1】:

    将父对象序列化器字段设为只读。它不是可选的,但也不是来自请求数据。相反,您从pre_save()...中的 URL 中提取 pk/slug...

    # Assuming list and detail URLs like:
    #   /v1/objects/<parent_pk>/subobjects/
    #   /v1/objects/<parent_pk>/subobjects/<pk>/
    def pre_save(self, obj):
        parent = models.MainObject.objects.get(pk=self.kwargs['parent_pk'])
        obj.parent = parent
    

    【讨论】:

    • pre_save 直到反序列化后才会被调用,此时它已经抛出了验证错误。
    • 这是一种选择,适用于 cmets 之类的东西,但我拥有的一些对象应该能够重新设置父对象,因此在 PUT 中显式设置父对象也会很好。另一方面,我可以这样做,然后有一个明确的'reparent' POST 操作或其他东西,但这似乎不太 RESTful。我会接受这个答案,因为这是个好主意,并且适用于大多数情况。
    • 随着 django rest 3.x pre_save 不再可用。我在视图中成功使用了perform_create()。见django-rest-framework.org/api-guide/generic-views/#methods
    【解决方案2】:

    这就是我为解决它所做的工作,但如果有更通用的方法来解决它会很好,因为它是一种常见的 URL 模式。首先,我为我的 ViewSets 创建了一个 mixin,它重新定义了 create 方法:

    class CreatePartialModelMixin(object):
        def initial_instance(self, request):
            return None
    
        def create(self, request, *args, **kwargs):
            instance = self.initial_instance(request)
            serializer = self.get_serializer(
                instance=instance, data=request.DATA, files=request.FILES,
                partial=True)
    
            if serializer.is_valid():
                self.pre_save(serializer.object)
                self.object = serializer.save(force_insert=True)
                self.post_save(self.object, created=True)
                headers = self.get_success_headers(serializer.data)
                return Response(
                    serializer.data, status=status.HTTP_201_CREATED,
                    headers=headers)
    
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    

    它大部分是从CreateModelMixin 复制和粘贴的,但它定义了一个initial_instance 方法,我们可以在子类中重写该方法,以为序列化程序提供一个起点,该序列化程序设置为进行部分反序列化。然后我可以做,例如,

    class SubObjectViewSet(CreatePartialModelMixin, viewsets.ModelViewSet):
        # ....
    
        def initial_instance(self, request):
            instance = models.SubObject(owner=request.user)
            if 'pk' in self.kwargs:
                parent = models.MainObject.objects.get(pk=self.kwargs['pk'])
                instance.parent = parent
            return instance
    

    (我意识到我实际上不需要在 pk 上执行 .get 以将其与模型关联,但在我的情况下,我公开的是 slug 而不是公共 API 中的主键)

    【讨论】:

      【解决方案3】:

      如果您使用的是ModelSerializer(由HyperlinkedModelSerializer 实现),它就像实现restore_object() 方法一样简单:

      class MySerializer(serializers.ModelSerializer):
      
          def restore_object(self, attrs, instance=None):
      
              if instance is None:
                  # If `instance` is `None`, it means we're creating
                  # a new object, so we set the `parent_id` field.
                  attrs['parent_id'] = self.context['view'].kwargs['parent_pk']
      
              return super(MySerializer, self).restore_object(attrs, instance)
      
          # ...
      

      restore_object() 用于将属性字典反序列化为对象实例。 ModelSerializer implements this method 并为您在 Meta 类中指定的模型创建/更新实例。如果给定的instanceNone,则意味着仍然需要创建对象,因此您只需在attrs 参数上添加parent_id 属性并调用super()

      因此,您不必指定只读字段,也不必使用自定义视图/序列化器。

      更多信息: http://www.django-rest-framework.org/api-guide/serializers#declaring-serializers

      【讨论】:

        【解决方案4】:

        可能有点晚了,但我想this drf nested routers library 可能对那个操作有帮助。

        【讨论】:

          猜你喜欢
          • 2017-12-29
          • 1970-01-01
          • 2018-12-05
          • 1970-01-01
          • 2013-09-19
          • 2020-06-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多