Got Key Error on Django Serializer when combining 2 models

Viewed 210

Good day SO. I want to combine two models in one django serializer. I just want to first get them inside one serializer and I can do the rest. Based on other SO question, I need to first declare a serializer and add it to my 2nd serializer. But it is giving me an error. I thought I just need to use the word major and declare it to my minor fields.

class PrefectureMajorSerializer(serializers.ModelSerializer):

    id = serializers.IntegerField()
    text = serializers.CharField(source='prefecture_name')

    class Meta:
        model = PrefectureMajor
        fields = ["id", "text"]

class PrefectureSerializer(serializers.ModelSerializer):

    id = serializers.IntegerField()
    text = serializers.CharField(source='prefecture_name')
    major = PrefectureMajorSerializer()

    class Meta:
        model = PrefectureMinor
        fields = ['id', 'text', 'prefecture_major_id', 'major']

Models:

class PrefectureMajor(models.Model):
    prefecture_name = models.CharField(max_length=100, default='', null=False)
    is_active = models.BooleanField(default=True, null=False)
    
    def __str__(self):
        return self.prefecture_name

class PrefectureMinor(models.Model):
    prefecture_major = models.ForeignKey("PrefectureMajor", related_name="PrefectureMinor.prefecture_name +", on_delete=models.CASCADE)
    prefecture_name = models.CharField(max_length=100, default='', null=False)
    is_active = models.BooleanField(default=True, null=False)
    
    def __str__(self):
        return self.prefecture_name

Traceback Error Message:

C:\Users\SOMEPATH\.virtualenvs\DjangoProjects-k8989gUR\lib\site-packages\rest_framework\fields.py, line 457, in get_attribute
            return get_attribute(instance, self.source_attrs) …
▼ Local vars
Variable    Value
instance    
{'id': 1, 'is_active': True, 'prefecture_major_id': 2, 'prefecture_name': '北海道'}
msg 
('Got KeyError when attempting to get a value for field `major` on serializer '
 '`PrefectureSerializer`.\n'
 'The serializer field might be named incorrectly and not match any attribute '
 'or key on the `dict` instance.\n'
 "Original exception text was: 'perfecture_major'.")
self    
PrefectureMajorSerializer(source='perfecture_major'):
    id = IntegerField()
    text = CharField(source='prefecture_name')
C:\Users\SOMEPATH\.virtualenvs\DjangoProjects-k8989gUR\lib\site-packages\rest_framework\fields.py, line 95, in get_attribute
                instance = instance[attr] …
▼ Local vars
Variable    Value
attr    
'perfecture_major'
attrs   
['perfecture_major']
instance    
{'id': 1, 'is_active': True, 'prefecture_major_id': 2, 'prefecture_name': '北海道'}

View:

def get_all_prefectures(request):
    prefecture_list = PrefectureMinor.objects.filter(is_active=True).order_by("id").values()
    prefecture_serializer = PrefectureSerializer(prefecture_list, many=True)
    data = prefecture_serializer.data
    
    return JsonResponse(data, safe=False)
2 Answers

The ForeignKey is prefecture_major, so you should use that as source=… for your serializer:

class PrefectureSerializer(serializers.ModelSerializer):

    id = serializers.IntegerField()
    text = serializers.CharField(source='prefecture_name')
    major = PrefectureMajorSerializer(source='perfecture_major')

    class Meta:
        model = PrefectureMinor
        fields = ['id', 'text']

You should also not use .values(…) since then you "erode" the model layer, and you thus can no longer follow the links to the related object.

Your view thus should look like:

def get_all_prefectures(request):
    prefecture_list = PrefectureMinor.objects.filter(
        is_active=True
    ).select_related('perfecture_major').order_by('id')
    prefecture_serializer = PrefectureSerializer(
        prefecture_list, many=True
    )
    data = prefecture_serializer.data
    
    return JsonResponse({'data': data})

The .select_related(…) is not necessary, but will boost efficiency, since now we load both the PerfectureMinor, and its related PerfectureMajor in the same database query.


Note: In 2008, Phil Haack discovered a way to obtain data from the outer JSON array. While most browser have implemented countermeasures, it is still better to use a JSON object as "envelope" of the data. That is why a JsonResponse [Django-doc] by default does not allow something else than a dict as outer object. It might thus better to use a dictionary and keep safe=True on True.

I spent 30 min solving it and I couldn't, but I came with a different solution which works the same, here is my code:

class PrefectureSerializer(serializers.ModelSerializer):

    id = serializers.IntegerField()
    text = serializers.CharField(source='prefecture_name')
    # major = PrefectureMajorSerializer(many=True)

    class Meta:
        model = PrefectureMinor
        fields = ['id', 'text', 'prefecture_major_id']
    
    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['major'] = PrefectureMajorSerializer(instance).data
        return data

I have used to_representation function to add additional data to the serializer and it woks, you can try it by yourself.

Related