如何在Django DRF中过滤嵌套的序列化器的字段

时间:2019-11-28 07:53:06

标签: python django django-rest-framework

我有两个名为“学校”和“学生”的模型。我已经为每个学校创建了序列化器和嵌套的序列化器,并将学生序列化器作为嵌套字段用于School。

这里我想使用'django-filters'在序列化器的字段上应用过滤器,并且几乎可以正常工作,但是...问题是当我过滤嵌套字段(即'student's field')时,它没有没有告诉我所需的结果。 我的模特是:

class School(models.Model):
    name = models.CharField(max_length=256)
    principal = models.CharField(max_length=256)
    location = models.CharField(max_length=256)
    is_government = models.BooleanField(default=True)

    def __str__(self):
        return self.name


class Student(models.Model):
    name = models.CharField(max_length=256)
    age = models.PositiveIntegerField()
    school = models.ForeignKey(School,related_name='students',on_delete = models.CASCADE)
    is_adult  = models.BooleanField(default=True)

    def __str__(self):
        return self.name

和我的序列化器是:

class SchoolSerializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
    # Don't pass the 'fields' arg up to the superclass
        # Instantiate the superclass normally
        super(SchoolSerializer, self).__init__(*args, **kwargs)
        allow_students = self.context.get("allow_students",None)
        if allow_students:
            self.fields['students'] = StudentSerializer(many=True, context=kwargs['context'], fields=['name','age','is_adult']) 


    class Meta():
        model = School
        fields = '__all__'


class StudentSerializer(DynamicFieldsModelSerializer):
    class Meta():
        model = Student
        fields = '__all__'

这些是我在视图中使用的过滤器:

from django_filters.rest_framework import DjangoFilterBackend
from django_filters import FilterSet
from django_filters import rest_framework as filters


class SchoolStudentAPIView(generics.ListAPIView, mixins.CreateModelMixin):
    queryset              = School.objects.all()
    serializer_class      = SchoolSerializer

    filter_backends       = (DjangoFilterBackend,)
    filter_fields         = ('is_government','students__is_adult')

在这里,问题是当我搜索“ students__is_adult”(这是一个嵌套字段)时,它会过滤掉成年学生列表以及未成年学生。

有人可以添加一些其他东西或提供其他解决方案吗?谢谢

1 个答案:

答案 0 :(得分:0)

问题

首先,Django Rest Framework没有执行您期望的查询。让我们看看如何检查。

调试实际查询的一种方法是向list()类添加自定义SchoolStudentAPIView方法,如下所示:

    def list(self, request, *args, **kwargs):
        resp = super().list(request, *args, **kwargs)
        from django.db import connection
        print(connection.queries) # or set a breakpoint here
        return resp

此方法无非就是将所有已执行的查询转储到控制台。

connection.queries的最后一个元素是我们应该关注的重点。它将是dict(),其"sql"键如下所示:

SELECT `school`.`id`, `school`.`name`, `school`.`location`, `school`.`is_government`
FROM `school` INNER JOIN `student` ON (`school`.`id` = `student`.`school_id`)
WHERE `student`.`is_adult` = 1

此查询意味着SchoolSerializer将通过具有至少一个成年学生的所有学校

顺便说一句,同一所学校可以出现多次,因为上述查询会每名成年学生产生一行

最后,SchoolSerializer显示Student中的所有School,而与任何过滤选项无关:这是此行实现的功能。

        if allow_students:
            self.fields['students'] = StudentSerializer(many=True, ...) 

建议的解决方案

使用序列化器找不到简单的解决方案。也许更直接的方法是在list()类中编写自定义SchoolStudentAPIView方法。

该方法将:

  • 查找查询字符串参数student__is_adult:如果存在,该方法将在查询集中的每个School(我将其命名为filtered_students)上创建一个自定义字段,字段指向正确的Student查询集。
  • 将上下文参数传递给SchoolSerializer,以告知学生已被过滤

SchoolSerializer类将根据上下文参数的存在或不存在,以两种不同的方式填充其students字段。具体来说,如果在传递的StudentSerializer中存在source键,则students__is_adult字段将具有context kwarg。

在代码中:

class SchoolStudentAPIView(generics.ListAPIView, mixins.CreateModelMixin):
    # ...
    def list(self, request, *args, **kwargs):
        schools = self.get_queryset()
        ctx = {}
        if 'students__is_adult' in request.query_params:
            filter_by_adult = bool(request.query_params['students__is_adult'])
            ctx = {
                'students__is_adult': filter_by_adult,
                'allow_students': True,
            }
            for s in schools:
                s.filtered_students = s.students.filter(is_adult=filter_by_adult)

        ser = SchoolSerializer(data=schools, many=True, context=ctx)
        ser.is_valid()
        return Response(ser.data)
class SchoolSerializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
        super(SchoolSerializer, self).__init__(*args, **kwargs)
        allow_students = self.context.get("allow_students", None)
        if allow_students:
            # Change 'source' to custom field if students are filtered
            filter_active = self.context.get("posts__is_active", None)
            if filter_active is not None:
                stud = StudentSerializer(
                    source='filtered_students', many=True,
                    context=kwargs['context'],
                    fields=['name', 'age', 'is_adult'])
            else:
                stud = StudentSerializer(
                    many=True, context=kwargs['context'],
                    fields=['name', 'age', 'is_adult'])

            self.fields['students'] = stud