2013-08-04 56 views
6

我有一个带分级资源的django-rest-framework REST API。我希望能够通过发布到/v1/objects/<pk>/subobjects/来创建子对象,并让它自动将新子对象上的外键设置为来自URL的pk kwarg,而不必将其置于有效载荷中。目前,序列化程序正在导致400错误,因为它期望object外键位于有效内容中,但它也不应被视为可选项。子对象的URL是/v1/subobjects/<pk>/(因为父项的密钥不必标识它),所以如果我想PUT现有资源仍然需要。Django REST框架:使用URL参数创建分层对象

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

回答

3

使父对象序列化程序字段为read_only。这不是可选的,但它也不是来自请求数据。相反,你在pre_save()网址拉PK /塞...

# 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 
+0

'pre_save'在反序列化之前不会被调用,此时它已经引发了验证错误。 –

+0

啊,好的。答案已更新。 –

+0

这是一个选项,可以用于评论之类的东西,但我拥有的某些对象应该可以重新设置,因此在PUT中显式设置父项也会很好。另一方面,我可以这样做,然后有一个明确的'重新启动'POST动作或其他东西,但这看起来不那么RESTful。我会接受这个答案,因为这是一个好主意,并且适用于大多数情况。 –

1

下面是我为解决这个问题所做的工作,虽然如果有更通用的方法来解决这个问题,那将会很不错,因为它是一种常见的URL模式。首先,我创建了一个mixin我ViewSets重新定义了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到它的模型相关联,但对我来说,我露出了塞,而不是公共API中的主键)

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()用于将属性字典反序列化为对象实例。 ModelSerializerimplements this method并创建/更新您在Meta类中指定的模型的实例。如果给定的instanceNone这意味着该对象仍然需要创建,所以您只需在attrs参数上添加parent_id属性并致电super()

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

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