Tristan
Tristan

Reputation: 3232

Django REST Framework: Filter related data based on parent object

I have the following models:

class Section(models.Model):
    name = models.CharField(max_length=255)

class Dataset(models.Model):
    name = models.CharField(max_length=255)
    sections = models.ManyToManyField(Section)

class File(models.Model):
    dataset = models.ForeignKey(Dataset)
    section = models.ForeignKey(Section, related_name='files')
    key = models.CharField(max_length=255)

Serializers:

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields = ('id', 'key')

class SectionSerializer(serializers.ModelSerializer):
    files = FileSerializer(many=True)

    class Meta:
        model = Section
        fields = ('name', 'files')

class DatasetSerializer(serializers.ModelSerializer):
    sections = SectionSerializer(many=True)

    class Meta:
        model = Dataset
        fields = ('id', 'name', 'sections')

And viewset:

class DatasetsViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = DatasetSerializer

    queryset = Dataset.objects.prefetch_related(
        'sections', 'sections__metric', 'sections__feature', 'sections__files')

I am trying to load the datasets (/api/datasets endpoint) with the list of their sections and for each section the list of files associated to get something like:

[
    {
        "id": 1,
        "name": "Q4 2015",
        "sections": [
            {
                "id": 1,
                "name": "Overall Scores"
                "files": [
                    {
                        "id": 1,
                        "key": "this/is/a/path"
                    }
                ]
            }
         ]
    }
]

The tricky part is that the list of files for a given section should be filtered by the parent dataset. Right now the sections contains all the files regardless of their dataset. What would be the best way to do this?

Thanks!

Upvotes: 2

Views: 1837

Answers (2)

Tristan
Tristan

Reputation: 3232

Ok so I found a solution I don't know if that's the best way but that worked for me: I modified the serializers to pass the parent object down to the children.

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields = ('id', 'key')

class SectionSerializer(serializers.ModelSerializer):
    files = serializers.SerializerMethodField()
    def get_files(self, obj):
        dataset_id = self.context.get('dataset_id')

        if dataset_id:
            return FileSerializer(many=True).to_representation(
                [f for f in obj.files.all() if f.dataset_id == dataset_id]
                # Using
                #   obj.files.filter(dataset_id=dataset_id)
                # would hit the database for every section making the
                # prefetching useless
            )
        return FileSerializer(many=True).to_representation(obj.files.all())

    class Meta:
        model = Section
        fields = ('name', 'files')

class DatasetSerializer(serializers.ModelSerializer):
    sections = serializers.SerializerMethodField()
    def get_sections(self, obj):
        context = self.context
        context.update({'dataset_id': obj.id})
        return SectionSerializer(many=True, context=context).to_representation(
            obj.sections
        )

    class Meta:
        model = Dataset
        fields = ('id', 'name', 'sections')

Upvotes: 1

Aaron Lelevier
Aaron Lelevier

Reputation: 20760

One way to do this would be to use a query param.

You can override get_queryset to look for this query param in the ViewSet like so:

def get_queryset(self):
    qs = super(DatasetsViewSet, self).get_queryset()
    dataset = self.request.query_params.pop('dataset', None)
    if dataset:
        qs = qs.filter(dataset=dataset)
    return qs

An example URL with a query param (assuming your API base url is /api/files/) would be:

'/api/files/?dataset=1'

Upvotes: 0

Related Questions