我有两个名为“学校”和“学生”的模型。我已经为每个学校创建了序列化器和嵌套的序列化器,并将学生序列化器作为嵌套字段用于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”(这是一个嵌套字段)时,它会过滤掉成年学生列表以及未成年学生。
有人可以添加一些其他东西或提供其他解决方案吗?谢谢
答案 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