How can i use nested serializer inside the same serializer? - django

class UserViewSerializer(DynamicFieldsMixin,serializers.ModelSerializer):
followers_set = UserViewSerializer(required=False,many=True)
class Meta:
model = User
fields = ('id','email','username','password','followers_set')
depth = 2
is there anyway i can use this function without getting this error?
followers_set = UserViewSerializer(source='follows',required=False,many=True)
NameError: name 'UserViewSerializer' is not defined
i tried SerializerMethodField but then i can't use depth option there
following_set = serializers.SerializerMethodField()
def get_following_set(self, user):
return UserViewSerializer(User.objects.filter(follows__author=user), many=True).data
using SerializerMethodField gives me error of:
RecursionError at /api/users/
maximum recursion depth exceeded
Can somebody help me please?

The simplest way to handle this is to make three serializers: one without a followers_set, one with a followers_set that uses the previous one, and one that users the second model, so:
# no followers_set
class UserViewSerializer0(DynamicFieldsMixin,serializers.ModelSerializer):
class Meta:
model = User
fields = ('id','email','username')
# followers_set, but only depth 1
class UserViewSerializer1(DynamicFieldsMixin,serializers.ModelSerializer):
followers_set = UserViewSerializer0(source='follows',required=False,many=True)
class Meta:
model = User
fields = ('id','email','username')
# followers_set, with depth 2
class UserViewSerializer(DynamicFieldsMixin,serializers.ModelSerializer):
followers_set = UserViewSerializer1(source='follows',required=False,many=True)
class Meta:
model = User
fields = ('id','email','username')
This is more safe as well, since you can not define recursive loops, unless you indeed work with SerializerMethodField, which is not a good idea if you add extra serializers.
It might however be better no to go to depth two, but stick to depth one. It will generate large responses already, make creating objects more cumbersome, and it will result in a lot of extra queries.

Related

Prevent need for the same select_related clause on multiple views in DRF

Given the following models...
class Player(models.Model):
user = models.ForeignKey(User)
class Activity(models.Model):
player = models.ForeignKey(Player)
and these serializers...
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = ['user']
class ActivitySerializer(serializers.ModelSerializer):
player = PlayerSerializer()
class Meta:
model = Activity
fields = ['player']
if I want to use django-rest-framework to list all the activities, I need to do something like this...
class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Activity.objects.select_related("player__user") <--- this is needed because the activity serializer is going to serialize the player which has a user
serializer_class = ActivitySerializer
That's all fine. But then, every time I write a new view which, at some level, uses PlayerSerializer, I'm going to have to remember to do the proper select_related clause in order to keep the number of DB lookups low.
I'm going to end up writing a lot of views which have the same select_related clauses (mine are actually a LOT more complicated than this example) and, if the PlayerSerializer ever changes, I'm going to need to remember to change all the view lookups.
This doesn't seem very DRY to me and I feel like there must be a better way to do it. Have I missed something obvious?
Maybe having a base parent class like:
class BaseViewset(viewsets.ReadOnlyModelViewSet):
SELECT_RELATED_FIELD = None # for providing select related field in your queryset or maybe have a default value for this one (based on your use case)
def get_queryset(self):
if not self.SELECT_RELATED_FIELD or not isinstance(self.SELECT_RELATED_FIELD, str):
raise NotImplementedError("Ensure 'SELECT_RELATED_FIELD' is not empty/ Is instance of string")
return super().get_queryset().select_related(self.SELECT_RELATED_FIELD)
And then use this parent class in all of your views instead of ReadOnlyModelViewSet:
class ActivityViewSet(BaseViewset):
SELECT_RELATED_FIELD = "player__user"
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
would be a good start (in all views that you have select_related part).

How to set depth in Django serializer to relations depth?

I need to show all content of every related field on the way. If I try to set depth = sys.maxint it receives an error:
AssertionError: 'depth' may not be greater than 10.
Serializer exapmle:
class AppleSerializer(serializers.ModelSerializer):
class Meta:
model = Apple
fields = '__all__'
depth = sys.maxint
So no values greater then 10. I have relation threads of more than 10 models so need some bigger depth. What is the way to set it bigger or just tell serializer to substitute content in all related fields trough all thread?
EDITED
The best practice I riched is to just make special serializers with expanding related fields in each of them so that'd look like this:
class OneSerializer(serializers.ModelSerializer):
class Meta:
model = One
fields = '__all__'
class TwoSerializer(serializers.ModelSerializer):
one = OneSerializer()
class Meta:
model = Two
fields = '__all__'
class ThreeSerializer(serializers.ModelSerializer):
two = TwoSerializer()
class Meta:
model = Three
fields = '__all__'
But it requires to do all this middleware serializers so if I need only one the last in this serializers chain which has like 20 related models one by one I'd be required to create 20 not necessary serializers.

Create nested serializer of same type with depth option support

Assume there is a child and parent relation in models, such as:
class Foo(models.Model):
parent = models.ForeignKey('Foo', related_name='children')
Now I want to have a serializer to show children of a Foo object, something like this:
class FooSerializer(serializers.ModelSerializer):
children = FooSerializer(many=True)
class Meta:
model = Foo
fields = '__all__'
But this gives me error that it does not recognize FooSerializer when creating that class which is correct regarding the way python parses the class. How could I implement such relation and have a serializer to get its children.
I must mention that I want to able to use depth option of nested serializer.
I am using django 2.2.7 and rest framework 3.10.1.
Edit
There may be some numbers of nested levels which it must be stopped using depth option, after some levels it must be flatten, so I wanted to able to use depth option along nested serializer.
depth attribute is for ForeignKey relationships, In your case, it's reverse-FK relation, So it won't work
You can achieve the depth like feature by using multiple serializer in Nested configuration.
Example 1: result similar to depth=1
class FooBaseSerializerLevel1(serializers.ModelSerializer):
class Meta:
model = Foo
fields = '__all__'
class FooBaseSerializerLevel0(serializers.ModelSerializer):
children = FooBaseSerializerLevel1(many=True)
class Meta:
model = Foo
fields = '__all__'
Example 2: result similar to depth=2
class FooBaseSerializerLevel2(serializers.ModelSerializer):
class Meta:
model = Foo
fields = '__all__'
class FooBaseSerializerLevel1(serializers.ModelSerializer):
children = FooBaseSerializerLevel2(many=True)
class Meta:
model = Foo
fields = '__all__'
class FooBaseSerializerLevel0(serializers.ModelSerializer):
children = FooBaseSerializerLevel1(many=True)
class Meta:
model = Foo
fields = '__all__'
The key point is that, do not define the children where you want to stop the nested effect
Based on another answer, I wrote a version which supports depth option:
class FooSerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
def get_children(self, obj):
if obj.children:
depth = getattr(self.Meta, 'depth', None)
if not depth:
depth = self.context.get('depth', 0)
if depth:
return SessionSerializer(obj.children.all(),
many=True,
context={'depth': depth - 1}).data
else:
return [child.id for child in obj.children.all()]
else:
return []
It supports depth option too.

N duplicated queries nested model

I've got an Area model allowing sub areas (you might think of it as categories with subcategories). I reached this by nesting one field to self as foreign key.
class Area(models.Model):
area = models.CharField(max_length=120)
parent = models.ForeignKey('self', models.CASCADE, blank=True, null=True, related_name='subarea')
def __str__(self):
return self.area
With the django rest framwork I've manages to get the correct output. The problem is that when I analyze the request with django-toolbar multiple duplicated requests are made (N*Area(parent=None)). I've solved similar issues by using prefetch_related or select_related. But never done it with a nested model. Is there any way to solve this? Or is this design of the model bad?
I manage to serialize the correct output with the following view and
class ListArea(generics.ListCreateAPIView):
serializer_class = AreaSerializer
queryset = Area.objects.prefetch_related('parent').filter(parent=None)
and serializers
class SubAreaSerializer(serializers.ModelSerializer):
class Meta:
model = Area
fields = ('area','id')
class AreaSerializer(serializers.ModelSerializer):
subarea=SubAreaSerializer(many=True)
class Meta:
model = Area
fields = ('area','id','subarea')
Or might those extra calls be due to the browsable API?
Solution
I solved this with help of the following thread Django: Does prefetch_related() follow reverse relationship lookup?
Instead of
queryset = Area.objects.prefetch_related('parent').filter(parent=None)
I should use
queryset = Area.objects.prefetch_related('parent').prefetch_related('subarea')

Sort DRF serializer output of nested Serializer field by child's field

I have two serializers, in which one refers to the other with a many=True relationship.
class AttributeInParentSerializer(ModelSerializer):
masterdata_type = CharField(max_length=256, source='masterdata_type_id')
class Meta:
model = Attribute
fields = ('uuid', 'masterdata_type')
class ArticleInArticleSetSerializer(ModelSerializer):
attributes = AttributeInParentSerializer(many=True)
class Meta:
model = Article
fields = ('uuid', 'attributes')
The ordering of the attributes in the Article are not always the same, but I want to output them in the same order, so in this case ordering on the field masterdata_type. How can I accomplish this? Note that I do not want to change any client of the serializer if possible, and surely not any model.
Old thread, but because it's still popping up on Google I want to share my answer as well. Try overwriting the Serializer.to_representation method. Now you can basically do whatever you want, including customising the sorting of your response. In your case:
class ArticleInArticleSetSerializer(ModelSerializer):
attributes = AttributeInParentSerializer(many=True)
class Meta:
model = Article
fields = ('uuid', 'attributes')
def to_representation(self, instance):
response = super().to_representation(instance)
response["attributes"] = sorted(response["attributes"], key=lambda x: x["masterdata_type"])
return response
you can set the ordering of attributes on ArticleInArticleSetSerializer by writing viewset for ArticleInArticleSetSerializer.
class ArticleInArticleSetViewSet(viewsets.ModelViewSet):
serializer_class = ArticleInArticleSetSerializer
queryset = Article.objects.all().order_by('-attributes_id')
Or you can write a function for listing.
def list(self, request):
self.queryset = self.get_queryset().order_by('-id')
return super(yourViewSet, self).list(self, request)
This code only for reference
I found an answer I prefer over rewriting the to_representation method or doing an inline call to the sorted method (which loads all the instances in memory):
class ArticleInArticleSetSerializer(ModelSerializer):
attributes = serializers.SerializerMethodField(method_name='get_attributes_sorted')
#staticmethod
def get_attributes_sorted(instance):
attributes = instance.attributes.order_by('masterdata_type')
return AttributeInParentSerializer(attributes, many=True).data
# Other stuff ...
This way you use a pure ORM solution (that's translated to SQL). The best performance and no memory consumption during sorting.
You can't set the ordering on ArticleInArticleSetSerializer but you can set the ordering of attributes on AttributeInParentSerializer. This is because you can only set the ordering when you are consuming a serializer rather than when you're defining one.
You could perhaps set it in the __init__ method when the queryset or data is passed in, but then you're making assumptions on what is being passed in. I'd probably end up specifying it in the consumers of ArticleInArticleSetSerializer to avoid any future problems with passing in a list to the serializer.
You can use order_by inside of your serializer like this:
class AttributeInParentSerializer(ModelSerializer):
masterdata_type = CharField(max_length=256, source='masterdata_type_id')
class Meta:
model = Attribute
fields = ('uuid', 'masterdata_type')
order_by = (('masterdata_type',))
Hope it helps!
UPDATE:
Looks like I am mistaken. Could not find it from the documentation and it does not seem to work. Now, I don't think serializers are the place to do ordering. Best way to do is in the model or in the view.