Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRF nested serializers - Filtering data on child serializers

I am trying to use nested serializer. How do I use the root serializer to filter data on the grandchild serializer?

School and Program have a many to many relationship So that any school can subscribe to any program. Each school has classes and those classes are part of a program, that's why PClass has foreign keys to both School and program.

When I call my api .../api/school/1 I want to get all the programs that school subscribes to and which classes are available in each program (in that school)

class School(TimeStampedModel, SoftDeletableModel):
    name = models.CharField(max_length=40)
    slug = models.SlugField(max_length=40, default='', blank=True)

class Program(TimeStampedModel, SoftDeletableModel):
    name = models.CharField(max_length=50, unique=True)
    slug = models.SlugField(max_length=50,default='',blank=True, unique=True)
    description = models.CharField(max_length=100, blank=True)
    school = models.ForeignKey(School, blank=True, null=True, related_name="programs")

class PClass(TimeStampedModel, SoftDeletableModel):
    name = models.CharField(max_length=50)
    slug = models.SlugField(max_length=50,default='',blank=True)
    description = models.CharField(max_length=100)
    program = models.ForeignKey(Program, related_name="classes")
    school = models.ForeignKey(School, related_name="classes")

and the following serializers:

class SchoolSerializer( serializers.ModelSerializer):
    programs = ProgramSerializer(source='get_programas',many=True,read_only=True)
    class Meta:
        model = School
        fields = '__all__'
        lookup_field = 'slug'
        extra_kwargs = {
            'url': {'lookup_field': 'slug'}
        }

class PClassSerializer(serializers.ModelSerializer):
    class Meta:
        model = Class
        fields = ('name','slug')

class ProgramSerializer(serializers.ModelSerializer):
    school = serializers.SlugRelatedField(queryset=School.objects.all(),
                                            slug_field='name',
                                            required=False)
    classes = PClassSerializer(many=True,read_only=True)
    class Meta:
        model = Program
        exclude = ('id',)
        lookup_field = 'slug'
        extra_kwargs = {
            'url': {'lookup_field': 'slug'}
        }

is this possible? or is it a problem with the way I set up my models?

like image 771
JLugao Avatar asked Jul 21 '17 20:07

JLugao


1 Answers

There's 2 ways I know how to do this. The first is you're pretty close already EDIT: Noticed you're using related names. I've updated the answer for that

class SchoolSerializer( serializers.ModelSerializer):
    programas = ProgramSerializer(source='programs',many=True,read_only=True)

For more complex filtering the best way is to use a SerializerMethodField Field. Here's an example.

You'll probably want to also do some pre-fetches in your view to get the queryset to minimize the # of queries.

class SchoolSerializer(serializers.ModelSerializer):
    programas = SerializerMethodField(source='get_programas',many=True,read_only=True)
    class Meta:
        model = Unidade
        fields = '__all__'
        lookup_field = 'slug'
        extra_kwargs = {
            'url': {'lookup_field': 'slug'}
        }

    def get_programas(self, obj):
        # You can do more complex filtering stuff here.
        return ProgramaSerializer(obj.programs.all(), many=True, read_only=True).data

To get the PClasses you'll just need to filter your queryset with

program.classes.filter(school=program.school)

Full example for ProgramSerializer is

class ProgramSerializer(serializers.ModelSerializer):
    classes = SerializerMethodField(source='get_classes', many=True, read_only=True)
    class Meta:
        model = Program

    def get_classes(self, obj):
        return PClassSerializer(obj.classes.filter(school=obj.school), many=True, read_only=True).data

EDIT 10 or so:

Since you have changed the program -> School from foreignkey to ManyToMany, this changes everything.

For the schoolserializer, you need to use a SerializerMethodField. This way you can pass in extra context to your nested serializer.

class SchoolSerializer(serializers.ModelSerializer):
    classes = SerializerMethodField(source='get_programs')
    class Meta:
        model = School

    def get_programs(self, obj):
        return ProgramSerializer(obj.program_set.all(), many=True, read_only=True, context={ "school": obj }).data

class ProgramSerializer(serializers.ModelSerializer):
    classes = SerializerMethodField(source='get_classes', many=True, read_only=True)
    class Meta:
        model = Program

    def get_classes(self, obj):
        return PClassSerializer(obj.classes.filter(school=self.context["school"]), many=True, read_only=True).data
like image 146
jonzlin95 Avatar answered Oct 30 '22 19:10

jonzlin95