Vasiliy Rusin
Vasiliy Rusin

Reputation: 1122

Django rest framework serialize filtered Foreign keys

Models.py

class ModelA(models.Model):
    views = models.PositiveBigIntegerField()


class ModelB(models.Model):
    parent = models.ForeignKey(ModelA, on_delete=models.CASCADE, related_name='modelB', blank=True, null=True)
    string = models.CharField()

Views.py

class ModelAListView(generics.ListAPIView):
    serializer_class = ModelASerialezer
    queryset = ModelA.objects.all().prefetch_related('modelb')

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset.filter(modelb__string__icontains=request.GET['string']), many=True)
        return Response(serializer.data)

Serializers.py

class ModelASerializer(serializers.ModelSerializer):
    id = serializers.ReadOnlyField()
    modelB = ModelBSerializer(source='modelB', many=True, read_only=False)

    class Meta:
        model = ModelA
        exclude = ('views',)

class ModelBSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(required=False)

    class Meta:
        model = ModelB
        fields = '__all__'

If I need to search by "string" field I can write

modelA.objects.filter(modelB__string__icontains=request.GET['string']).values('modelB__string')

Which return ModelB instances only with necessary string values:

<QuerySet [{'modelB__string': 'Test1'}]>

When I filter by modelb_string I expect to get only filtered FK value:

{
    "id": 1,
    "views": 0,
    "modelb": [
        {
            "id": 46,
            "string": "Test1",
            "item": 1
        }
    ]
}

but I get all FK values:

{
    "id": 1,
    "views": 0,
    "modelb": [
        {
            "id": 46,
            "string": "Test1",
            "item": 1
        },
        {
            "id": 47,
            "string": "Test85",
            "item": 1
        },
        {
            "id": 48,
            "string": "Test64",
            "item": 1
        }
    ]
}

Upvotes: 2

Views: 1699

Answers (2)

alper
alper

Reputation: 81

The best practice would be on your view class

from django.db.models import Prefetch


modelA.objects.all().prefetch_related(Prefetch(modelB, queryset=modelB.objects.filter(string__icontains=request.GET['string'])))

This returns modelA objects with filtered modelB FK related instances

Upvotes: 1

Umair Mohammad
Umair Mohammad

Reputation: 4635

Few points worth mentioning :

  1. You don't need to implement the list function and redo what's already being done in the mixin

  2. If we want to customise the queryset then we should override the get_queryset, details

Something like this :

class ModelAListView(generics.ListAPIView):
    serializer_class = ModelASerialezer

    def get_queryset(self, *args, **kwargs):
        queryset = modelA.objects.all()

        given_string = self.request.query_params.get('string', None)

        if given_string is not None:
            queryset = queryset.filter(modelB__string__icontains=given_string)

        return queryset
  1. Maybe you should consider renaming the field string

References :

Upvotes: 0

Related Questions