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

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

在 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

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

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

  1. 子类ListSerializer,覆盖to_representation然后调用super
  2. 在嵌套的 Serializer
  3. 上添加子类化的 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

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

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

在嵌套的模型中定义一个方法class。制作满足您需求的过滤器。

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",你就会明白。

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

在视图中:

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 的 Prefetch 对象是最简单的方法。

假设一个Restaurant对象有很多MenuItem,其中一些是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)

我发现在要过滤的序列化程序字段上使用 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 的覆盖。

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

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

希望对您有所帮助!

以下对我有用,来自 self.context['view'],您可以在序列化程序中获取过滤器参数并根据需要使用它.

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