如何在Django Rest Framework中过滤嵌套的序列化器?


81

在Django Rest Framework中,当一个序列化器嵌套在另一个序列化器中时,如何过滤它?

我的过滤器被强加在DRF视图集中,但是当您从另一个序列化器内部调用序列化器时,嵌套序列化器的视图集将永远不会被调用,因此嵌套结果看起来是未经过滤的。

我尝试在原始视图集上添加过滤器,但是它似乎无法过滤嵌套结果,因为嵌套结果被称为单独的预提取查询。(您看到嵌套的序列化程序是反向查找。)

是否可以在嵌套序列化器本身中添加get_queryset()覆盖(将其移出视图集),以在其中添加过滤器?我也尝试过,没有运气。

这是我尝试过的方法,但似乎没有被调用:

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

    class Meta:
        model = Questionnaire

    def get_queryset(self):
        query = super(QuestionnaireSerializer, self).get_queryset(instance)
        if not self.request.user.is_staff:
            query = query.filter(user=self.request.user, edition__hide=False)
        return query

6
get_queryset是在ModelViewSet而不是在Serializer上的类,这就是为什么它没有被调用的原因
NotSimon 2015年

Answers:


96

您可以将ListSerializer子类并覆盖该to_representation方法。

默认情况下,该to_representation方法调用data.all()嵌套的查询集。因此,您实际上需要data = data.filter(**your_filters)在调用该方法之前进行制作。然后,您需要将子类化的ListSerializer添加为嵌套序列化器的meta上的list_serializer_class。

  1. 子类ListSerializer,覆盖to_representation,然后调用super
  2. 将子类ListSerializer添加为list_serializer_class嵌套Serializer上的meta

这是您的示例的相关代码。

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

1
做到了!尽管最终我认为我的序列化程序变得太复杂了,所以我将它们全部重构了,迫使客户端运行更多的api调用,但极大地简化了我的应用程序。
约翰

3
试图以此为基础解决类似问题;不知道它是否真的有其自身的问题。如何将var从传递QuestionnaireSerializer到ListSerializer?大概来说,我需要按Edition的ID以及问卷的ID进行过滤。
布伦丹2015年

3
这应该在DRF文档中。超级有用,谢谢!
Daniel van Flymen 2015年

7
在我的实现中,我得到'FilteredListSerializer' object has no attribute 'request'其他任何人都一样的东西吗?
Dominooch

11
要回答@Dominooch,您需要使用self.context ['request']而不是self.request
rojoca 2016年

24

测试了SO和其他地方的许多解决方案。

仅找到一种适用于Django 2.0 + DRF 3.7.7的解决方案。

在模型中定义一个具有嵌套类的方法。制作适合您需求的过滤器。

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',)

注意source="current_epg",您会明白这一点。


是! 此注释利用了源作为功能的能力,您可以在模型上定义该功能,然后可以在其中进行过滤!凉!
possumkeys

是否可以将字符串传递给该类下的函数?
AlexW

我只需要订购多个相关领域。还尝试了许多不同的解决方案(双关语意)。但这是唯一对我有用的解决方案!谢谢!
gabn88

在Django的代码哲学方面,这似乎比接受的答案更正确。Django提出了ActiveModel(“胖模型”)方法,因此过滤应该在模型级别(或视图集级别)进行,而序列化应该对业务逻辑一无所知。
oxfn

10

尽管以上所有答案均有效,但我发现使用Django的 Prefetch对象是最简单的方法。

假设Restaurantobj有很多MenuItems,其中一些是is_remove == True,而您只希望未删除它们。

在中RestaurantViewSet,执行类似

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')
)

在中RestaurantSerializer,执行类似

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


好的解决方案,我同意这是解决它的最佳方法。
约旦

这应该在顶部。从数据库中提取数据后,当前的顶级解决方案使用to_representation过滤数据。此解决方案对查询中的数据进行过滤,并以批量请求的形式获取。在大多数情况下都更好。
亚历克斯

7

实例化序列化程序并传递many = True时,将创建一个ListSerializer实例。然后,序列化程序类成为父级ListSerializer的子级

此方法将字段的目标作为值参数,并应返回用于序列化目标的表示形式。value参数通常是模型实例。

下面是嵌套序列化器的示例

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', 
        )

鉴于:

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)

4

我发现SerializerMethodField在要过滤的序列化器字段上使用a更容易,更直接。

所以你会做这样的事情。

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

serializers.ListSerializer如果您需要为不同的序列化程序使用不同的过滤条件,则不必创建许多替代。

它还有一个额外的好处,就是可以准确地看到过滤器在串行器中的功能,而不用深入研究子类定义。

当然,不利的一面是如果您有一个带有许多嵌套对象的序列化程序,这些对象都需要以某种方式进行过滤。这可能会导致序列化程序代码大大增加。由您决定如何过滤。

希望这可以帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.