我有一个带有分层资源的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传递给序列化程序?我使用HyperlinkedModelSerializer
和ModelViewSet
作为我各自的基类。有一些推荐的方法吗?到目前为止,我唯一的想法是完全重新实现ViewSets并创建一个自定义的Serializer类,其get_default_fields()来自一个从ViewSet传入的字典,由其kwargs填充。这似乎涉及一些我认为完全是普通的东西,所以我不禁想到我错过了一些东西。我见过的每个REST API都有可写端点这种基于URL的参数推断,所以django-rest-framework似乎根本无法做到这一点似乎很奇怪。
答案 0 :(得分:3)
将父对象序列化程序字段设为read_only。它不是可选的,但也不是来自请求数据。而是从pre_save()
...
# 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
类中指定的模型创建/更新实例。如果给定的instance
为None
,则表示仍然需要创建对象,因此您只需在parent_id
参数上添加attrs
属性并调用super()
因此,您不必指定只读字段,也不必拥有自定义视图/序列化程序。
更多信息: http://www.django-rest-framework.org/api-guide/serializers#declaring-serializers
答案 3 :(得分:0)
可能有点迟了,但我猜this drf nested routers library可能对此操作有所帮助。