Django REST Framework:使用URL参数创建分层对象

时间:2013-08-04 01:44:05

标签: python django rest django-rest-framework

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

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

4 个答案:

答案 0 :(得分:3)

将父对象序列化程序字段设为read_only。它不是可选的,但也不是来自请求数据。而是从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

答案 1 :(得分: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

(我知道我实际上不需要在pk上做.get来将它关联到模型上,但在我的情况下,我暴露了slug而不是公共API中的主键)

答案 2 :(得分: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 implements this method并为您在Meta类中指定的模型创建/更新实例。如果给定的instanceNone,则表示仍然需要创建对象,因此您只需在parent_id参数上添加attrs属性并调用super()

因此,您不必指定只读字段,也不必拥有自定义视图/序列化程序。

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

答案 3 :(得分:0)

可能有点迟了,但我猜this drf nested routers library可能对此操作有所帮助。