88
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.
75
@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.
- [Django]-Django dump data for a single model?
- [Django]-Is it bad to have my virtualenv directory inside my git repository?
- [Django]-Django, Models & Forms: replace "This field is required" message
75
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
- [Django]-Django Rest Framework remove csrf
- [Django]-Django, Turbo Gears, Web2Py, which is better for what?
- [Django]-Django character set with MySQL weirdness
32
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/
- [Django]-Django – how to create a file and save it to a model's FileField?
- [Django]-Django: "projects" vs "apps"
- [Django]-Do I need Nginx with Gunicorn if I am not serving any static content?
26
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
- [Django]-How to server HTTP/2 Protocol with django
- [Django]-Get object by field other than primary key
- [Django]-How to convert a Django QuerySet to a list?
11
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)
- [Django]-Foreign key from one app into another in Django
- [Django]-Auth.User.groups: (fields.E304) Reverse accessor for 'User.groups' clashes with reverse accessor for 'UserManage.groups'
- [Django]-How to completely dump the data for Django-CMS
9
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…
- [Django]-Can't compare naive and aware datetime.now() <= challenge.datetime_end
- [Django]-Change a form value before validation in Django form
- [Django]-Django migration strategy for renaming a model and relationship fields
8
This solution is almost similar with the other solutions posted here but has a slight difference in terms of child repetition problem at the root level( if you think its as a problem). For an example
class RecursiveSerializer(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data
class CategoryListSerializer(ModelSerializer):
sub_category = RecursiveSerializer(many=True, read_only=True)
class Meta:
model = Category
fields = (
'name',
'slug',
'parent',
'sub_category'
)
and if you have this view
class CategoryListAPIView(ListAPIView):
queryset = Category.objects.all()
serializer_class = CategoryListSerializer
This will produce the following result,
[
{
"name": "parent category",
"slug": "parent-category",
"parent": null,
"sub_category": [
{
"name": "child category",
"slug": "child-category",
"parent": 20,
"sub_category": []
}
]
},
{
"name": "child category",
"slug": "child-category",
"parent": 20,
"sub_category": []
}
]
Here the parent category
has a child category
and the json representation is exactly what we want it to be represent.
but you can see there is a repetition of the child category
at the root level.
As some people are asking in the comment sections of the above posted answers that how can we stop this child repetition at the root level, just filter your queryset with parent=None
, like as the following
class CategoryListAPIView(ListAPIView):
queryset = Category.objects.filter(parent=None)
serializer_class = CategoryListSerializer
it will solve the problem.
NOTE: This answer might not directly related with the question, but the problem is somehow related. Also this approach of using RecursiveSerializer
is expensive. Better if you use other options which is performance prone.
- [Django]-Good open source django project for learning
- [Django]-Django content-type : how do I get an object?
- [Django]-How to use subquery in django?
6
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.
- [Django]-Django template includes slow?
- [Django]-Django admin file upload with current model id
- [Django]-Django: Filter a Queryset made of unions not working
6
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'])
- [Django]-How to completely uninstall a Django app?
- [Django]-Django Model() vs Model.objects.create()
- [Django]-How to combine multiple QuerySets in Django?
4
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')
- [Django]-How do I import the Django DoesNotExist exception?
- [Django]-How do I get the class of a object within a Django template?
- [Django]-Django 1.5b1: executing django-admin.py causes "No module named settings" error
0
class CategoryListSerializer(ModelSerializer):
sub_category = serializers.SerializerMethodField("get_sub_category")
def get_sub_category(self, obj):
if obj.sub_category:
serializer = self.__class__(obj.sub_category)
return serializer.data
else:
return None
class Meta:
model = Category
fields = (
'name',
'slug',
'parent',
'sub_category'
)
- [Django]-Get protocol + host name from URL
- [Django]-Bypass confirmation prompt for pip uninstall
- [Django]-Django override save for model only in some cases?