I have a many-to-many relationship between my Student and Macro models, using an intermediary model
class Person(models.Model):
# cf/NIN optional by design
cf = models.CharField(_('NIN'), unique=True, blank=True, null=True, max_length=16)
first_name = models.CharField(_('first name'), blank=False, max_length=40)
last_name = models.CharField(_('last name'), blank=False, max_length=40)
date_of_birth = models.DateField(_('date of birth'), blank=False)
class Meta:
ordering = ['last_name', 'first_name']
abstract = True
def __str__(self):
return self.first_name + ' ' + self.last_name
class Macro(models.Model):
name = models.CharField(_('name'), unique=True, blank=False, max_length=100)
description = models.TextField(_('description'), blank=True, null=True)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
class Student(Person):
enrollment_date = models.DateField(_('enrollment date'), blank=True, null=True)
description = models.TextField(_('description'), blank=True, null=True)
macro = models.ManyToManyField(Macro, through='MacroAssignement')
class MacroAssignement(models.Model):
student = models.ForeignKey(Student, related_name='macros', on_delete=models.CASCADE)
macro = models.ForeignKey(Macro, related_name='students', on_delete=models.CASCADE)
def __str__(self):
return str(self.student)
I configure serializers in order to exploit the nested serialization when I serialize students
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ('id',
'cf',
'first_name',
'last_name',
'date_of_birth')
abstract = True
class StudentSerializer(PersonSerializer):
macro = serializers.StringRelatedField(many=True, read_only=True)
class Meta(PersonSerializer.Meta):
model = Student
fields = PersonSerializer.Meta.fields + ('enrollment_date',
'description',
'macro')
extra_kwargs = {'enrollment_date': {'default': date.today()},
'description': {'required': False}}
class MacroSerializer(serializers.ModelSerializer):
students = StudentSerializer(many=True, read_only=True)
class Meta:
model = Macro
fields = ('id',
'name',
'description',
'students')
Untill here no problem, when I request student data, the macro related information comes along with it. Here's an example
{
"id": 18,
"cf": "ciaciacia",
"first_name": "Paolo",
"last_name": "Bianchi",
"date_of_birth": "2020-05-01",
"enrollment_date": null,
"description": null,
"macro": [
"macro1"
]
},
Now, on the contrary, when I request for a macro, I would like to view also the related students list. I've tried to implement nested serialization also in the MacroSerializer
class MacroSerializer(serializers.ModelSerializer):
students = StudentSerializer(many=True, read_only=True)
This doesn't work, as I get the following error
AttributeError: Got AttributeError when attempting to get a value for field `first_name` on serializer `StudentSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `MacroAssignement` instance.
Original exception text was: 'MacroAssignement' object has no attribute 'first_name'.
[NOTE: first_name
is a field of Student model inherited from Person model]
Of course I could implement a function to query the database and get the name of students assigned to a given macro, but I'm wondering if there's a buil-in django way of doing it. Kind of like 2-way nested serialization
As stated in previous helpful comments, the fix is about using related_name. My code has been modified as following
serializers.py
class StudentSerializer(PersonSerializer):
macro = serializers.StringRelatedField(many=True, read_only=True)
class Meta(PersonSerializer.Meta):
model = Student
fields = PersonSerializer.Meta.fields + ('enrollment_date',
'description',
'macro')
extra_kwargs = {'enrollment_date': {'default': date.today()},
'description': {'required': False}}
class MacroSerializer(serializers.ModelSerializer):
students = StudentSerializer(many=True, read_only=True)
class Meta:
model = Macro
fields = ('id',
'name',
'description',
'students')
models.py
class Student(Person):
enrollment_date = models.DateField(_('enrollment date'), blank=True, null=True)
description = models.TextField(_('description'), blank=True, null=True)
macro = models.ManyToManyField(Macro, through='MacroAssignement', related_name='students')
Note that what I wrote in my question is actually a poor design decision, because it would lead to a circular dependency. Indeed, StudentSerializer
would need MacroSerializer
and viceversa. I highly suggest to read this question about dependencies in serializers
************ EDITED ************
Quick fix: set depth = 1
in the related model serializer (in this case, StudentSerializer) Thanks to @neverwalkaloner answer on this question