Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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')
like image 842
o_c Avatar asked Feb 18 '16 23:02

o_c


People also ask

How do you pass extra context data to Serializers in Django REST Framework?

In function based views we can pass extra context to serializer with "context" parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with "self. context". From example, to get "exclude_email_list" we just used code 'exclude_email_list = self.

Do we need Serializers in Django REST Framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.

What is nested serializer in Django REST Framework?

DRF provides a Serializer class that gives you a powerful, generic way to control the output of your responses, as well as a ModelSerializer class that provides a useful shortcut for creating serializers that deal with model instances and querysets.


1 Answers

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

like image 100
o_c Avatar answered Sep 30 '22 18:09

o_c