[Django]-Filter queryset for nested serializer in django rest framework

4👍

You might want to filter the queryset to ensure that only results relevant to the currently authenticated user making the request are returned.

You can do so by filtering based on the value of request.user.

For example:

from myapp.models import Purchase
from myapp.serializers import PurchaseSerializer
from rest_framework import generics

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        This view should return a list of all the purchases
        for the currently authenticated user.
        """
        user = self.request.user
        return Purchase.objects.filter(purchaser=user)

EDIT

You can subclass the ListSerializer and overwrite the to_representation method.

By default the to_representation method calls data.all() on the nested queryset. So you effectively need to make data = data.filter(**your_filters) before the method is called. Then you need to add your subclassed ListSerializer as the list_serializer_class on the meta of the nested serializer.

1- subclass ListSerializer, overwriting to_representation and then calling super

2- Add subclassed ListSerializer as the meta list_serializer_class on the nested Serializer.

Code relevant to yours:

class FilteredListSerializer(serializers.ListSerializer):

def to_representation(self, data):
    data = data.filter(user=self.request.user, edition__hide=False)
    return super(FilteredListSerializer, self).to_representation(data)


class OrganizationSerializer(serializers.ModelSerializer):
class Meta:
    list_serializer_class = FilteredListSerializer
    model = GroupProfile
    fields = ('title','slug',)



class DepartmentSerializer(serializers.ModelSerializer):
    organizations = OrganizationSerializer(many=True, read_only=True)
    class Meta:
        model = DepartmentModel
        fields = ('title', 'organizations',)


class SectorSerializer(serializers.ModelSerializer):
    # title = serializers.CharField()
    departments = DepartmentSerializer(many=True, read_only=True)

    class Meta:
    model = SectorModel
    fields = ('title','departments',)

0👍

Thanks to @ans2human for the inspiration behind this answer.

Here’s a new approach that is working great for me. I have several Models with is_active = BooleanField(...) that I need to filter out in nested relationships.

NOTE: this solution does not filter out results on non-list fields. for that, you should look to the primary queryset on your View

The core of the work is done by overloading the to_representation() function on a custom ListSerializer, and the many_init on an accompanying custom ModelSerializer:

class FilteredListSerializer(serializers.ListSerializer):
    filter_params:dict
    def __init__(self, *args, filter_params:dict={"is_active":True}, **kwargs):
        super().__init__(*args, **kwargs)
        self.filter_params = filter_params

    def set_filter(self, **kwargs):
        self.filter_params = kwargs

    def to_representation(self, data):
        data = data.filter(**self.filter_params)
        return super().to_representation(data)

class FilteredModelSerializer(serializers.ModelSerializer):

    LIST_SERIALIZER_KWARGS = serializers.LIST_SERIALIZER_KWARGS + ("filter_params",)
    LIST_ONLY_KWARGS = ('allow_empty', 'filter_params')

    @classmethod
    def many_init(cls, *args, **kwargs):
        list_kwargs = dict()
        for arg in cls.LIST_ONLY_KWARGS:
            value = kwargs.pop(arg, None)
            if value is not None:
                list_kwargs[arg] = value
        child_serializer = cls(*args, **kwargs, **({"read_only":True} if "read_only" not in kwargs else dict()))
        list_kwargs['child'] = child_serializer
        list_kwargs.update({
            key: value for key, value in kwargs.items()
            if key in cls.LIST_SERIALIZER_KWARGS
        })
        meta = getattr(cls, 'Meta', None)
        list_serializer_class = getattr(meta, 'list_serializer_class', FilteredListSerializer)
        return list_serializer_class(*args, **list_kwargs)

Then, your custom ModelSerializer for whatever view would instead just extend FilteredModelSerializer instead.

class ChildSerializer(FilteredModelSerializer):
    is_active = BooleanField() # not strictly necessary, just for visibilty
    ... # the rest of your serializer

class ParentSerializer(serializers.ModelSerializer):
    children = ChildSerializer(many=True)
    ...# the rest of your parent serializer

Now, the children field on the ParentSerializer will filter for is_active = True.

If you have a custom query that you wanted to apply, you can do so by providing a dict of filter params in the standard queryset format:

class ParentSerializer(serializers.ModelSerializer):
    children = ChildSerializer(many=True, filter_params={"my_field":my_value, "my_datetime__gte": timezone.now()})
    ...# the rest of your parent serializer

Alternatively, one could also utilize the set_filter(...) method on the FilteredListSerializer after instantiating the field, like so. This will yield a more familiar format to the original QuerySet.filter(...) style:

class ParentSerializer(serializers.ModelSerializer):
    children = ChildSerializer(many=True)
    children.set_filter(my_field=my_value, my_datetime__gte=timezone.now())
    ...# the rest of your parent serializer

Leave a comment