Django rest框架嵌套自引用对象
我有这样的模型:
class Category(models.Model): parentCategory = models.ForeignKey('self', blank=True, null=True, related_name='subcategories') name = models.CharField(max_length=200) description = models.CharField(max_length=500)
我设法使用序列化程序获得所有类别的平面json表示forms:
class CategorySerializer(serializers.HyperlinkedModelSerializer): parentCategory = serializers.PrimaryKeyRelatedField() subcategories = serializers.ManyRelatedField() class Meta: model = Category fields = ('parentCategory', 'name', 'description', 'subcategories')
现在我想要做的是为子类别列表有内联子类别,而不是他们的ID的JSON表示。 我怎么用django-rest-framework做到这一点? 我试图在文档中find它,但似乎不完整。
而不是使用ManyRelatedField,使用嵌套的序列化程序作为您的领域:
class SubCategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ('name', 'description') class CategorySerializer(serializers.ModelSerializer): parentCategory = serializers.PrimaryKeyRelatedField() subcategories = serializers.SubCategorySerializer() class Meta: model = Category fields = ('parentCategory', 'name', 'description', 'subcategories')
如果你想处理任意嵌套的字段,你应该看看自定义默认字段部分的文档。 您目前不能直接声明序列化程序为自己的字段,但可以使用这些方法来覆盖默认使用的字段。
class CategorySerializer(serializers.ModelSerializer): parentCategory = serializers.PrimaryKeyRelatedField() class Meta: model = Category fields = ('parentCategory', 'name', 'description', 'subcategories') def get_related_field(self, model_field): # Handles initializing the `subcategories` field return CategorySerializer()
事实上,正如你所指出的那样,上述不太正确。 这是一个黑客,但你可能试图在序列化程序已经声明后添加字段。
class CategorySerializer(serializers.ModelSerializer): parentCategory = serializers.PrimaryKeyRelatedField() class Meta: model = Category fields = ('parentCategory', 'name', 'description', 'subcategories') CategorySerializer.base_fields['subcategories'] = CategorySerializer()
声明recursion关系的机制是需要添加的东西。
编辑 :请注意,现在有一个专门处理这种用例的第三方软件包。 请参阅djangorestframework-recursive 。
@ wjin的解决scheme对我来说很好,直到我升级到Django REST框架3.0.0,它弃用了to_native 。 这是我的DRF 3.0解决scheme,这是一个小的修改。
假设您有一个带有自引用字段的模型,例如在名为“回复”的属性中进行了注释。 你有这个注释线程的树形表示,并且你想要序列化树
首先,定义可重用的RecursiveField类
class RecursiveField(serializers.Serializer): def to_representation(self, value): serializer = self.parent.parent.__class__(value, context=self.context) return serializer.data
然后,对于序列化程序,使用RecursiveField将“答复”的值序列化
class CommentSerializer(serializers.Serializer): replies = RecursiveField(many=True) class Meta: model = Comment fields = ('replies, ....)
易于操作,而且只需要4行代码即可实现可重复使用的解决scheme。
注意:如果你的数据结构比树更复杂,比如说一个定向的非循环图 (FANCY!),那么你可以试试@ wjin的包 – 看他的解决scheme。 但是我对这个基于MPTTModel树的解决scheme没有任何问题。
在这里比赛迟到,但这是我的解决scheme。 比方说,我正在序列化Blah,还有多个Blahtypes的孩子。
class RecursiveField(serializers.Serializer): def to_native(self, value): return self.parent.to_native(value)
使用这个字段,我可以序列化我的recursion定义的对象,有许多子对象
class BlahSerializer(serializers.Serializer): name = serializers.Field() child_blahs = RecursiveField(many=True)
我为DRF3.0编写了一个recursion字段,并将其打包为pip https://pypi.python.org/pypi/djangorestframework-recursive/
我最近遇到了同样的问题,并提出了一个解决scheme,即使在任意深度的情况下,这个解决scheme似乎仍然有效。 解决办法是从汤姆·克里斯蒂的一个小的修改:
class CategorySerializer(serializers.ModelSerializer): parentCategory = serializers.PrimaryKeyRelatedField() def convert_object(self, obj): #Add any self-referencing fields here (if not already done) if not self.fields.has_key('subcategories'): self.fields['subcategories'] = CategorySerializer() return super(CategorySerializer,self).convert_object(obj) class Meta: model = Category #do NOT include self-referencing fields here #fields = ('parentCategory', 'name', 'description', 'subcategories') fields = ('parentCategory', 'name', 'description') #This is not needed #CategorySerializer.base_fields['subcategories'] = CategorySerializer()
虽然我不确定它能在任何情况下可靠地工作
另一种select是在序列化模型的视图中进行recursion。 这是一个例子:
class DepartmentSerializer(ModelSerializer): class Meta: model = models.Department class DepartmentViewSet(ModelViewSet): model = models.Department serializer_class = DepartmentSerializer def serialize_tree(self, queryset): for obj in queryset: data = self.get_serializer(obj).data data['children'] = self.serialize_tree(obj.children.all()) yield data def list(self, request): queryset = self.get_queryset().filter(level=0) data = self.serialize_tree(queryset) return Response(data) def retrieve(self, request, pk=None): self.object = self.get_object() data = self.serialize_tree([self.object]) return Response(data)
这是一个适用于drf 3.0.5和django 2.7.4的caipirginka解决scheme:
class CategorySerializer(serializers.ModelSerializer): def to_representation(self, obj): #Add any self-referencing fields here (if not already done) if 'branches' not in self.fields: self.fields['subcategories'] = CategorySerializer(obj, many=True) return super(CategorySerializer, self).to_representation(obj) class Meta: model = Category fields = ('id', 'description', 'parentCategory')
请注意,第6行中的CategorySerializer是通过对象和many = True属性来调用的。
另一个与Django REST Framework 3.3.2一起使用的选项:
class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ('id', 'name', 'parentid', 'subcategories') def get_fields(self): fields = super(CategorySerializer, self).get_fields() fields['subcategories'] = CategorySerializer(many=True) return fields
我能够使用serializers.SerializerMethodField
来实现这个结果。 我不确定这是否是最好的方法,但为我工作:
class CategorySerializer(serializers.ModelSerializer): subcategories = serializers.SerializerMethodField( read_only=True, method_name="get_child_categories") class Meta: model = Category fields = [ 'name', 'category_id', 'subcategories', ] def get_child_categories(self, obj): """ self referral field """ serializer = CategorySerializer( instance=obj.subcategories_set.all(), many=True ) return serializer.data
我想我会join的乐趣!
通过wjin和Mark Chackerian,我创build了一个更一般的解决scheme,它适用于具有直通式模型的直接树状模型和树状结构。 我不确定这是属于自己的答案,但我想我可能把它放在某个地方。 我包含了一个max_depth选项,它可以防止无限recursion,在最深层次上,儿童被表示为URLS(如果你不是url,那么这是最后的else子句)。
from rest_framework.reverse import reverse from rest_framework import serializers class RecursiveField(serializers.Serializer): """ Can be used as a field within another serializer, to produce nested-recursive relationships. Works with through models, and limited and/or arbitrarily deep trees. """ def __init__(self, **kwargs): self._recurse_through = kwargs.pop('through_serializer', None) self._recurse_max = kwargs.pop('max_depth', None) self._recurse_view = kwargs.pop('reverse_name', None) self._recurse_attr = kwargs.pop('reverse_attr', None) self._recurse_many = kwargs.pop('many', False) super(RecursiveField, self).__init__(**kwargs) def to_representation(self, value): parent = self.parent if isinstance(parent, serializers.ListSerializer): parent = parent.parent lvl = getattr(parent, '_recurse_lvl', 1) max_lvl = self._recurse_max or getattr(parent, '_recurse_max', None) # Defined within RecursiveField(through_serializer=A) serializer_class = self._recurse_through is_through = has_through = True # Informed by previous serializer (for through m2m) if not serializer_class: is_through = False serializer_class = getattr(parent, '_recurse_next', None) # Introspected for cases without through models. if not serializer_class: has_through = False serializer_class = parent.__class__ if is_through or not max_lvl or lvl <= max_lvl: serializer = serializer_class( value, many=self._recurse_many, context=self.context) # Propagate hereditary attributes. serializer._recurse_lvl = lvl + is_through or not has_through serializer._recurse_max = max_lvl if is_through: # Delay using parent serializer till next lvl. serializer._recurse_next = parent.__class__ return serializer.data else: view = self._recurse_view or self.context['request'].resolver_match.url_name attr = self._recurse_attr or 'id' return reverse(view, args=[getattr(value, attr)], request=self.context['request'])
使用Django REST框架3.3.1,我需要下面的代码来将子类别添加到类别中:
models.py
class Category(models.Model): id = models.AutoField( primary_key=True ) name = models.CharField( max_length=45, blank=False, null=False ) parentid = models.ForeignKey( 'self', related_name='subcategories', blank=True, null=True ) class Meta: db_table = 'Categories'
serializers.py
class SubcategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ('id', 'name', 'parentid') class CategorySerializer(serializers.ModelSerializer): subcategories = SubcategorySerializer(many=True, read_only=True) class Meta: model = Category fields = ('id', 'name', 'parentid', 'subcategories')