[Django]-How do you filter a nested serializer in Django Rest Framework?

119πŸ‘

βœ…

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

Here is the relevant code for your sample.

class FilteredListSerializer(serializers.ListSerializer):

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


class EditionSerializer(serializers.ModelSerializer):

    class Meta:
        list_serializer_class = FilteredListSerializer
        model = Edition


class QuestionnaireSerializer(serializers.ModelSerializer):
    edition = EditionSerializer(read_only=True)
    company = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Questionnaire
πŸ‘€inperspective

55πŸ‘

While all the above answers work, I find the use of Django’s Prefetch object the easiest way of all.

Say a Restaurant obj has a lot of MenuItems, some of which are is_removed == True, and you only want those that are not removed.

In RestaurantViewSet, do something like

from django.db.models import Prefetch

queryset = Restaurant.objects.prefetch_related(
    Prefetch('menu_items', queryset=MenuItem.objects.filter(is_removed=False), to_attr='filtered_menu_items')
)

In RestaurantSerializer, do something like

class RestaurantSerializer(serializers.ModelSerializer):
    menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)

πŸ‘€Dennis Lau

33πŸ‘

Tested many solutions from SO and other places.

Found only one working solution for Django 2.0 + DRF 3.7.7.

Define a method in model which has nested class. Craft a filter that will fit your needs.

class Channel(models.Model):
    name = models.CharField(max_length=40)
    number = models.IntegerField(unique=True)
    active = models.BooleanField(default=True)

    def current_epg(self):
        return Epg.objects.filter(channel=self, end__gt=datetime.now()).order_by("end")[:6]


class Epg(models.Model):
    start = models.DateTimeField()
    end = models.DateTimeField(db_index=True)
    title = models.CharField(max_length=300)
    description = models.CharField(max_length=800)
    channel = models.ForeignKey(Channel, related_name='onair', on_delete=models.CASCADE)

.

class EpgSerializer(serializers.ModelSerializer):
    class Meta:
        model = Epg
        fields = ('channel', 'start', 'end', 'title', 'description',)


class ChannelSerializer(serializers.ModelSerializer):
    onair = EpgSerializer(many=True, read_only=True, source="current_epg")

    class Meta:
        model = Channel
        fields = ('number', 'name', 'onair',)

Pay attention to source="current_epg" and you’ll get the point.

πŸ‘€duddits

16πŸ‘

I find it easier, and more straight forward, to use a SerializerMethodField on the serializer field you want to filter.

So you would do something like this.

class CarTypesSerializer(serializers.ModelSerializer):

    class Meta:
        model = CarType
        fields = '__all__'


class CarSerializer(serializers.ModelSerializer):

    car_types = serializers.SerializerMethodField()

    class Meta:
        model = Car
        fields = '__all__'

    def get_car_types(self, instance):
        # Filter using the Car model instance and the CarType's related_name
        # (which in this case defaults to car_types_set)
        car_types_instances = instance.car_types_set.filter(brand="Toyota")
        return CarTypesSerializer(car_types_instances, many=True).data

This saves you from having to create many overrides of the serializers.ListSerializer if you need different filtering criteria for different serializers.

It also has the extra benefit of seeing exactly what the filter does within the serializer instead of diving into a subclass definition.

Of course the downside is if you have a serializer with many nested objects that all need to be filtered in some way. It could cause the serializer code to greatly increase. It’s up to you how you would like to filter.

Hope this helps!

πŸ‘€Rob Barber

8πŸ‘

When a serializer is instantiated and many=True is passed, a
ListSerializer instance will be created. The serializer class then
becomes a child of the parent ListSerializer

This method takes the target of the field as the value argument, and
should return the representation that should be used to serialize the
target. The value argument will typically be a model instance.

Below is the example of the nested serializer

class UserSerializer(serializers.ModelSerializer):
    """ Here many=True is passed, So a ListSerializer instance will be 
     created"""
    system = SystemSerializer(many=True, read_only=True)

    class Meta:
        model = UserProfile
        fields = ('system', 'name')

class FilteredListSerializer(serializers.ListSerializer):
    
    """Serializer to filter the active system, which is a boolen field in 
       System Model. The value argument to to_representation() method is 
      the model instance"""
    
    def to_representation(self, data):
        data = data.filter(system_active=True)
        return super(FilteredListSerializer, self).to_representation(data)

class SystemSerializer(serializers.ModelSerializer):
    mac_id = serializers.CharField(source='id')
    system_name = serializers.CharField(source='name')
    serial_number = serializers.CharField(source='serial')

    class Meta:
        model = System
        list_serializer_class = FilteredListSerializer
        fields = (
            'mac_id', 'serial_number', 'system_name', 'system_active', 
        )

In view:

class SystemView(viewsets.GenericViewSet, viewsets.ViewSet):
    def retrieve(self, request, email=None):
        data = get_object_or_404(UserProfile.objects.all(), email=email)
        serializer = UserSerializer(data)
        return Response(serializer.data)
πŸ‘€Vinay Kumar

1πŸ‘

The following worked for me, from self.context[β€˜view’], You can get the filter params inside the serializer and use it however you want.

class ShipmentDocumentSerializer(serializers.ModelSerializer):

    class Meta:
       model = Document
       fields = ['id', 'created_date', 'consignment', 'document', 'org', 'title' ]

class ShipmentDocumentTypeSerializer(serializers.ModelSerializer):
   documents = serializers.SerializerMethodField()

   class Meta:
       model = DocumentType
       fields = ['id', 'type', 'documents']

    def get_documents(self, instance):
        consignment_id=self.context['view'].kwargs['consignment_id']
        queryset = Document.objects.filter(consignment__id=consignment_id)
        return ShipmentDocumentSerializer(queryset, many=True).data

Leave a comment