o_c
o_c

Reputation: 4213

Extend django rest framework to allow inheriting context in nested serializers

I'm using Django 1.6 (very soon upgrading to 1.8), Python 2.7, and DRF 3.2.5 (very soon upgrading to latest).

I've got a set of deeply nested serializers (~10 levels deep, with a total of 20-30 models that are serialized). I'm trying to add a boolean flag to the context, which will determine whether the serialized output hierarchy will be detailed (include all models' fields) or basic (part of the fields only).

I wrote the following code (partial snippet):

from rest_framework import serializers
from app.models import Institute, Department, Member

class MemberSerializer(serializers.ModelSerializer):
    def get_fields(self):
        fields = super(MemberSerializer, self).get_fields()
        if self.context['basic_view']:
            for field in ['height', 'weight']:
                del fields[field]
        return fields

    class Meta:
        model = Member
        fields = ('id', 'birth_date', 'height', 'weight')


class DepartmentSerializer(serializers.ModelSerializer):
    members = MemberSerializer(many=True, read_only=True)

    def get_fields(self):
        fields = super(DepartmentSerializer, self).get_fields()
        if self.context['basic_view']:
            for field in ['type', 'manager']:
                del fields[field]
        return fields

    class Meta:
        model = Department
        fields = ('id', 'name', 'type', 'manager', 'members')


class InstituteSerializer(serializers.ModelSerializer):
    departments = DepartmentSerializer(many=True, read_only=True)

    def get_fields(self):
        fields = super(InstituteSerializer, self).get_fields()
        if self.context['basic_view']:
            for field in ['name', 'type']:
                del fields[field]
        return fields

    class Meta:
        model = Institute
        fields = ('id', 'name', 'type', 'departments')

def get_entities(is_basic_view):
    institutes_list = Institute.objects.all()

    serializer = InstituteSerializer(institutes_list, many=True, read_only=True, context={'basic_view': is_basic_view})

    return serializer.data

But then found out that the 'context' that is passed from 'get_entities' to 'InstituteSerializer' is not passed-on to the nested serializers. Meaning that in the example above - InstituteSerializer has 'basic_view' in the 'context', but MemberSerializer & DepartmentSerializer don't.

I found a working solution in context in nested serializers django rest framework : to use SerializerMethodField per nested field (e.g. 'departments'), and in the 'get_' method to manually pass-on the context. My problem with that solution is that it requires embedding this code 20-30 times in my code, eventually doubling the number of source lines.

My request - if someone has (or can help implement) an extension for serializers.ModelSerializer, which will get an additional parameter upon construction, e.g. 'inherit_context'. Then the only thing I'll need to change in my classes, for example in 'InstituteSerializer', is the addition of that parameter:

class InstituteSerializer(serializers.ModelSerializer):
    departments = DepartmentSerializer(many=True, read_only=True, inherit_context=True)

    def get_fields(self):
        fields = super(InstituteSerializer, self).get_fields()
        if self.context['basic_view']:
            for field in ['name', 'type']:
                del fields[field]
        return fields

    class Meta:
        model = Institute
        fields = ('id', 'name', 'type', 'departments')

Upvotes: 11

Views: 3943

Answers (1)

o_c
o_c

Reputation: 4213

Apparently I missed something... The 'context' is already inherited down to the nested serializers...

However, the reason it didn't work for me, is because as part of my nesting, some of the child serializers were defined via serializers.SerializerMethodField(). And in such as case (only!) the context is not automatically inherited.

The solution is to simply pass-on the 'context', within the 'get_...' method related to each SerializerMethodField:

class ParentSerializer(serializers.ModelSerializer):
    child = serializers.SerializerMethodField()

    def get_child(self, obj):
        child = ....
        serializer = ChildSerializer(instance=child, context=self.context)
        return serializer.data

P.S - a DRF github issue similar to mine was created a while ago: https://github.com/tomchristie/django-rest-framework/issues/2555

Upvotes: 12

Related Questions