Reputation: 3139
I'm quite new on Django Rest Framework, and I've been trying to write a serializer for one of my models. For my project I intend to output the json result according to the JSON API Standards, and, for doing so, I am using the SerializerMethodField in which I call the method get_data()
as follows:
class Level(MPTTModel):
name = models.CharField(max_length=100)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
class MPTTMeta:
order_insertion_by = ['name']
class Group(models.Model):
name = models.CharField(max_length=100)
level_set = models.ManyToManyField(Level, blank=True)
class LevelSerializer(serializers.ModelSerializer):
data = serializers.SerializerMethodField()
class Meta:
model = Level
fields = ('data',)
def get_data(self, instance):
return {
"type": "Level",
"uuid": instance.id_key(),
"attributes": {
"name": instance.name,
},
"relationships": {
"children": self.get_children_recursive(),
},
}
def get_children_recursive(self, child=None):
"""
Generates a tree of levels structured according to JSON API standards.
"""
if not child:
children = self.instance.get_children()
level = self.instance
else:
children = child.get_children()
level = child
tree = {
'data': {
'type': 'Level',
'uuid': level.id_key(),
'attributes': {
'name': level.name,
},
'relationships': {
'children': [],
'parents': [],
}
}
}
for child in children:
tree['data']['relationships']['children'].append(self.get_children_recursive(child))
return tree
class GroupSerializer(serializers.ModelSerializer):
root_level_set = LevelSerializer(many=True)
class Meta:
model = Group
fields = ('id_key', 'name', 'root_level_set')
The weird thing is that if I go to the shell and try to serialize an instance of Level it works fine, but trying to serialize a Group gives me an error at get_children_recursive()
at line with the if not child statement
saying that 'NoneType' object has no attribute 'get_children'
. The outputs are these:
Run:
from core.serializers import LevelSerializer
from core.models import Level
lvl = Level.objects.all()[0]
serializer = LevelSerializer(lvl)
print(serializer.data)
Outputs the nested level and it's sublevels according to the JSON structure I designed following the JSON API Standards.
Although if I run:
from core.serializers import GroupSerializer
from core.models import Group
grp = Group.objects.all()[0]
serializer = GroupSerializer(grp)
print(serializer.data)
Outputs:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/maumau/.virtualenvs/olist/lib/python3.5/site-packages/rest_framework/serializers.py", line 503, in data
ret = super(Serializer, self).data
File "/home/maumau/.virtualenvs/olist/lib/python3.5/site-packages/rest_framework/serializers.py", line 239, in data
self._data = self.to_representation(self.instance)
File "/home/maumau/.virtualenvs/olist/lib/python3.5/site-packages/rest_framework/serializers.py", line 472, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "/home/maumau/.virtualenvs/olist/lib/python3.5/site-packages/rest_framework/serializers.py", line 614, in to_representation
self.child.to_representation(item) for item in iterable
File "/home/maumau/.virtualenvs/olist/lib/python3.5/site-packages/rest_framework/serializers.py", line 614, in <listcomp>
self.child.to_representation(item) for item in iterable
File "/home/maumau/.virtualenvs/olist/lib/python3.5/site-packages/rest_framework/serializers.py", line 472, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "/home/maumau/.virtualenvs/olist/lib/python3.5/site-packages/rest_framework/fields.py", line 1653, in to_representation
return method(value)
File "/mnt/SHARED-DRIVE/Workspace/interview-tests/work-at-olist/core/serializers.py", line 20, in get_data
"children": self.get_children_recursive(),
File "/mnt/SHARED-DRIVE/Workspace/interview-tests/work-at-olist/core/serializers.py", line 29, in get_children_recursive
children = self.instance.get_children()
AttributeError: 'NoneType' object has no attribute 'get_children'
It doesn't seem to make sense for me that the serialization of Level it self possesses the attribute instance set while the serialization of Group, which calls the serialization of Level as well, does not. Any clue?
Upvotes: 3
Views: 1385
Reputation: 3752
why designing everything yourself? let djrf do some magic :)
class LevelSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
attributes = serializers.SerializerMethodField()
relationships = serializers.SerializerMethodField()
def get_type(self, instance):
return "Level"
def get_attributes(self, instance):
return {
"name": instance.name
}
def get_relationships(self, instance):
return {
"children": self.__class__(instance.get_children(), many=True).data,
"parents": []
}
class Meta:
model = Level
fields = ('type', 'attributes', 'relationships')
class GroupSerializer(serializers.ModelSerializer):
level_set = LevelSerializer(many=True)
class Meta:
model = Group
fields = ('name', 'level_set')
why your code does not work? because of wrong code:
LevelSerializer
in those 2 cases - one parameter - many=True
; when many=True
- serializer does not have instance
propertyget_children_recursive
will duplicate first element as first childModelSerializer
at all, because all is done manually - in such a case, simple Serializer
will be betterdata
field - you are making your code more complex, why not use data directly to avoid dictionaries with one data
keymy example code is missing id_key
field - you had not documented it here, but adding it should be a breze
using extra module rest_framework_recursive
, code can be simplified:
from rest_framework_recursive.fields import RecursiveField
class LevelSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
children = serializers.ListField(child=RecursiveField(), source='get_children')
def get_type(self, instance):
return "Level"
class Meta:
model = Level
fields = ('type', 'name', 'children')
Upvotes: 1
Reputation: 2249
Attribute name in serialzier: root_level_set
. In model: level_set
.
DRF tries to find attribute root_level_set
in group, does not find it and replace it by None
. Thats why you get such error.
Fix:
root_level_set
-> level_set
in serializer.source
to field: root_level_set = LevelSerializer(source='level_set', many=True)
Upvotes: 0