可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have model that looks like this:
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)
I managed to get flat json representation of all categories with serializer:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
subcategories = serializers.ManyRelatedField()
class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')
Now what I want to do is for subcategories list to have inline json representation of subcategories instead of their ids. How would I do that with django-rest-framework? I tried to find it in documentation, but it seems incomplete.
回答1:
Instead of using ManyRelatedField, use a nested serializer as your field:
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')
If you want to deal with arbitrarily nested fields you should take a look at the customising the default fields part of the docs. You can't currently directly declare a serializer as a field on itself, but you can use these methods to override what fields are used by default.
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()
Actually, as you've noted the above isn't quite right.
This is a bit of a hack, but you might try adding the field in after the serializer is already declared.
class CategorySerializer(serializers.ModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')
CategorySerializer.base_fields['subcategories'] = CategorySerializer()
A mechanism of declaring recursive relationships is something that needs to be added.
Edit: Note that there is now a third-party package available that specifically deals with this kind of use-case. See djangorestframework-recursive.
回答2:
@wjin's solution was working great for me until I upgraded to Django REST framework 3.0.0, which deprecates to_native. Here's my DRF 3.0 solution, which is a slight modification.
Say you have a model with a self-referential field, for example threaded comments in a property called "replies". You have a tree representation of this comment thread, and you want to serialize the tree
First, define your reusable RecursiveField class
class RecursiveField(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data
Then, for your serializer, use the the RecursiveField to serialize the value of "replies"
class CommentSerializer(serializers.Serializer):
replies = RecursiveField(many=True)
class Meta:
model = Comment
fields = ('replies, ....)
Easy peasy, and you only need 4 lines of code for a re-usable solution.
NOTE: If your data structure is more complicated than a tree, like say a directed acyclic graph (FANCY!) then you could try @wjin's package -- see his solution. But I haven't had any problems with this solution for MPTTModel based trees.
回答3:
Late to the game here, but here's my solution. Let's say I'm serializing a Blah, with multiple children also of type Blah.
class RecursiveField(serializers.Serializer):
def to_native(self, value):
return self.parent.to_native(value)
Using this field I can serialize my recursively-defined objects that have many child-objects
class BlahSerializer(serializers.Serializer):
name = serializers.Field()
child_blahs = RecursiveField(many=True)
I wrote a recursive field for DRF3.0 and packaged it for pip
https://pypi.python.org/pypi/djangorestframework-recursive/
回答4:
Another option that works with 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
回答5:
I was able to achieve this result using a serializers.SerializerMethodField
. I'm not sure if this is the best way, but worked for me:
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
回答6:
Another option would be to recurse in the view that serializes your model. Here's an example:
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)
回答7:
I recently had the same problem and came up with a solution that seems to work so far, even for arbitrary depth.
The solution is a small modification of the one from Tom Christie:
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()
I'm not sure it can reliably work in any situation, though...
回答8:
This is an adaptation from the caipirginka solution that works on drf 3.0.5 and django 2.7.4:
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')
Note that the CategorySerializer in 6th line is called with the object and the many=True attribute.
回答9:
I thought I'd join in on the fun!
Via wjin and Mark Chackerian I created a more general solution, which works for direct tree-like models and tree structures which have a through model. I'm not sure if this belongs in it's own answer but I thought I might as well put it somewhere. I included a max_depth option which will prevent infinite recursion, at the deepest level children are represented as URLS (that's the final else clause if you'd rather it wasn't a url).
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'])
回答10:
With Django REST framework 3.3.1, I needed the following code to get subcategories added to categories:
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')