I am currently trying to familiarize myself with DRF and while going through a tutorial these serializers were used
class EmbeddedAnswerSerializer(serializers.ModelSerializer):
votes = serializers.IntegerField(read_only=True)
class Meta:
model = Answer
fields = ('id', 'text', 'votes',)
class QuestionSerializer(serializers.ModelSerializer):
answers = EmbeddedAnswerSerializer(many=True,source='answer_set')
class Meta:
model = Question
fields = ('id', 'answers', 'created_at', 'text', 'user_id',)
These are the models
class Question(models.Model):
user_id = models.CharField(max_length=36)
text = models.CharField(max_length=140)
created_at = models.DateTimeField(auto_now_add=True)
class Answer(models.Model):
question = models.ForeignKey(Question,on_delete=models.PROTECT)
text = models.CharField(max_length=25)
votes = models.IntegerField(default=0)
My question is in the statement in the Question serializer
answers = EmbeddedAnswerSerializer(many=True,source='answer_set')
what is the purpose of many = True
and source='answer_set' ?
I read from the documentation the following regarding many=True
You can also still use the many=True argument to serializer classes.
It's worth noting that many=True argument transparently creates a
ListSerializer instance, allowing the validation logic for list and
non-list data to be cleanly separated in the REST framework codebase.
I am confused by what that means ? If I remove many=True
from the code I get the error
AttributeError at /api/quest/1/2/
Got AttributeError when attempting to get a value for field `text` on serializer `EmbeddedAnswerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `RelatedManager` instance.
Original exception text was: 'RelatedManager' object has no attribute 'text'.
Can anyone explain what many=True
does and what source
field does ?
Adding to the answer above by @neverwalkaloner
Many = True
many=True
signals that there is more than one object (an iterable) being passed to the serializer. Passing this field in turn triggers the many_init
within BaseSerializer
to automagically create a ListSerializer
instance.
Source Code Snippet:
def __new__(cls, *args, **kwargs):
# We override this method in order to automagically create
# `ListSerializer` classes instead when `many=True` is set.
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
return super(BaseSerializer, cls).__new__(cls, *args, **kwargs)
Source = "xyz"
This tells DRF which object attribute supplies the value for the field. The default assumption is that the field name declared on the serializer is the same as the field on the object instance that supplies the value. In cases where this is not true, source
allows you to explicitly supply the object instance where the serializer will look for the value. Here's a peek into the def bind(self, field_name, parent)
inside serializers.fields
where this happens
Source Code Snippet:
# self.source should default to being the same as the field name.
if self.source is None:
self.source = field_name
# self.source_attrs is a list of attributes that need to be looked up
# when serializing the instance, or populating the validated data.
if self.source == '*':
self.source_attrs = []
else:
self.source_attrs = self.source.split('.')
Finally the value is gotten as follows using the source
and source_attrs
declared in bind
:
def get_attribute(self, instance):
"""
Given the *outgoing* object instance, return the primitive value
that should be used for this field.
"""
try:
return get_attribute(instance, self.source_attrs)
except (KeyError, AttributeError) as exc:
Assuming a Question
can have multiple Answers
, your approach is correct.
The problem appears to be that the source you supplied is the RelatedManager
instance itself, and not the queryset of Answer
objects. I assumed DRF resolves this accurately, can you try changing it to source='answer_set.all'.
answer_set
is the default RelatedManager
name given by Django. It might be wise to name your backward relationship using related_name in the Django model. This can be achieved by changing:
question = models.ForeignKey(Question,on_delete=models.PROTECT, related_name='answers')
Probably not the best explanation and someone can add more details but briefly many=True
tells to serializer that it will take list of objects for serilizing proccess. In other words it's just a trigger that allows you to specify will you serialize, well, many objects at once, or just single object.
source
on the other side specify which attribute of objects should be serializing with current serializer's field.
In practice this line
answers = EmbeddedAnswerSerializer(many=True, source='answer_set')
means that you want to serialize answer_set
attribute of Question
object with EmbeddedAnswerSerializer
. Since answer_set
is list of object you should add many=True
as argument to make serializer aware that it will work with list of objects instead of single object.