Mauricio
Mauricio

Reputation: 3139

Django Rest Framework serialization of many generates NoneType instances of serialized relation

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:

models.py

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)

serializers.py

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

Answers (2)

Jerzyk
Jerzyk

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:

  1. look what is a difference in calling LevelSerializer in those 2 cases - one parameter - many=True; when many=True - serializer does not have instance property
  2. beside (1), first run of get_children_recursive will duplicate first element as first child
  3. using your approach you do not need ModelSerializer at all, because all is done manually - in such a case, simple Serializer will be better
  4. by creating virtual data field - you are making your code more complex, why not use data directly to avoid dictionaries with one data key

my 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

zymud
zymud

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:

  1. Rename root_level_set -> level_set in serializer.
  2. Or add source to field: root_level_set = LevelSerializer(source='level_set', many=True)

Upvotes: 0

Related Questions