6

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

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

4

4 回答 4

3

将父对象序列化器字段设为只读。它不是可选的,但也不是来自请求数据。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
于 2013-08-04T10:48:17.280 回答
1

这是我为解决它所做的,尽管如果有更通用的方法来解决它会很好,因为它是一种常见的 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

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

于 2013-08-04T08:02:24.857 回答
0

如果您正在使用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 实现此方法Meta并为您在类中指定的模型创建/更新实例。如果给定instance的是None,则意味着仍然必须创建对象,因此您只需parent_id在参数上添加属性attrs并调用super().

所以这样你就不必指定一个只读字段,或者有一个自定义的视图/序列化器。

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

于 2014-04-28T09:08:01.120 回答
0

也许有点晚了,但我想这个 drf 嵌套路由器库可能有助于该操作。

于 2016-03-19T23:58:11.723 回答