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

Posted

技术标签:

【中文标题】如何在 Django Rest Framework 中过滤嵌套的序列化程序?【英文标题】:How do you filter a nested serializer in Django Rest Framework? 【发布时间】:2015-03-25 15:49:03 【问题描述】:

在 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

【问题讨论】:

get_querysetModelViewSet 上的一个类,而不是序列化器上的一个类,这就是它没有被调用的原因 【参考方案1】:

您可以继承 ListSerializer 并覆盖 to_representation 方法。

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

    子类ListSerializer,覆盖to_representation然后调用super 将子类 ListSerializer 添加为嵌套序列化器上的元 list_serializer_class

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

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

【讨论】:

成功了!虽然最后我认为我的序列化程序变得过于复杂,所以我重构了它们,迫使客户端运行更多的 api 调用,但大大简化了我的应用程序。 试图以此作为解决类似问题的基础;不确定它是否真的值得提出自己的问题。我如何将 var 从 QuestionnaireSerializer 传递到 ListSerializer?为了近似,我需要按版本 ID 和问卷 ID 进行过滤。 这应该在 DRF 文档中。超级有用,谢谢! 在我的实现中,我得到了 'FilteredListSerializer' object has no attribute 'request' 其他人得到相同的结果吗? 要回答@Dominooch,您需要使用 self.context['request'] 而不是 self.request【参考方案2】:

虽然上述所有答案都有效,但我发现使用 Django 的 Prefetch 对象是最简单的方法。

假设一个Restaurant obj 有很多MenuItems,其中一些是is_removed == 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 过滤数据。此解决方案过滤查询中的数据并在批量请求中获取它。在大多数情况下会更好。 完美解决方案!为我工作。 这成功了!除了我必须使用 Prefetch 相关名称的第一个参数。我完全删除了 to_attr【参考方案3】:

测试了来自 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",你就会明白这一点。

【讨论】:

是的!此注释利用源的能力成为您在模型上定义的函数,然后您可以利用其中的过滤!酷! 可以给类下的函数传字符串吗? 我只需要订购许多相关领域。还尝试了许多不同的解决方案(双关语)。但这是唯一对我有用的解决方案!谢谢! 在 django 的代码哲学方面,这似乎是比公认的答案更正确的解决方案。 Django 提出了 ActiveModel(“胖模型”)方法,所以过滤应该在模型级别(或视图集级别)进行,序列化应该对业务逻辑一无所知。 我喜欢这种方法【参考方案4】:

我发现在要过滤的序列化器字段上使用SerializerMethodField 更简单、更直接。

所以你会做这样的事情。

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 的覆盖。

它还有一个额外的好处是可以准确地看到过滤器在序列化器中的作用,而不是深入到子类定义中。

当然,缺点是如果您有一个包含许多嵌套对象的序列化程序,这些对象都需要以某种方式过滤。它可能导致序列化程序代码大大增加。由您决定如何过滤。

希望这会有所帮助!

【讨论】:

【参考方案5】:

当一个序列化器被实例化并且 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)

【讨论】:

以上是关于如何在 Django Rest Framework 中过滤嵌套的序列化程序?的主要内容,如果未能解决你的问题,请参考以下文章

Django.rest_framework:如何序列化一对多?

如何使用 TemplateHTMLRenderer 在 Django-REST-Framework 中创建/放置?

django-rest-framework:如何序列化已经包含 JSON 的字段?

如何在 Django Rest Framework 中散列 Django 用户密码?

如何仅使用 django 作为后端并使用 django-rest-framework 发布

如何在 React 中显示来自 django-rest-framework 的错误消息