使用查询集将过滤器应用于嵌套的反向外键关系
Apply filter to nested reverse foreign key relationship using queryset
上下文
我正在尝试根据反向外键属性的值过滤对象列表。
我能够在视图级别解决它,但是其他使用 ORM 功能解决问题的尝试会导致额外的查询。
我想要的结果是包含所有对象的查询集,但是相关的 fkey 对象在每个对象中被过滤。
示例模型
class Student(models.Model):
name = models.CharField(max_length=128)
class Subject(models.Model):
title = models.CharField(max_length=128)
class Grade(models.Model):
student = models.ForeignKey("Student", related_name="grades", on_delete=models.CASCADE)
subject = models.ForeignKey("Subject", related_name="grades", on_delete=models.CASCADE)
value = models.IntegerField()
鉴于赛程
+------+------------------------+
| name | subject | grade_value |
+------+----------+-------------+
| beth | math | 100 |
| beth | history | 100 |
| beth | science | 100 |
| mark | math | 90 |
| mark | history | 90 |
| mark | science | 90 |
| mike | math | 90 |
| mike | history | 80 |
| mike | science | 80 |
+------+----------+-------------+
期望的结果
我想呈现学生列表,但只包括数学和历史成绩.
例如,也许我想要一个学生列表,但只包含他们成绩的一个子集:
GET students/?subjects=math,history
哪些被过滤可能会在请求中提供,或者硬编码。
如果可能的话,我们可以将其留在这个问题的范围之外,并假设过滤参数固定为 math
和 history
.
{
"students": [
{
"name": "beth",
"grades": [
{"subject": "math", "grade": 100 },
{"subject": "history", "grade": 100 },
// Exclude one or more grades - eg.
// science grade not included
]
},
...
]
}
尝试的解决方案
简单过滤器
只是过滤。我想这会过滤掉所有学生,他们的成绩在列表中有科目,这就是全部。
queryset = Students.objects.all()\
.prefetch_related("grades")\
.filter(grades__subject__in=["math", "history"])
)
# all grades for each student eg.
...
"grades": [
{"subject": "math", "grade": 100 },
{"subject": "history", "grade": 100 },
{"subject": "science", "grade": 100 },
]
...
子查询
我不太了解子查询的工作原理,但使用我尝试过的一些示例:
subjects = Subject.objects.filter(
name__in=["math", "history"]
)
queryset = Students.objects.all()\
.prefetch_related("grades")\
.filter(grades__subject__name__in=Subquery(subjects.values("name")))
还有另一个变体:
grades = Grades.objects.filter(
student_id=OuterRef("id"), subject__name__in=["math", "history"]
)
queryset = Students.objects.all()\
.prefetch_related("grades")\
.filter(grades__pk__in=Subquery(grades.values("pk)))
所有成绩均返回学生。
解决方法
此解决方案使用 python 过滤成绩。它有效,但我宁愿让它与 querysets
一起使用
# in view:
serializer = StundentSerializer(queryset, many=True)
response_data = serializer.data
for student in response_data:
student.grades = [g for g in students.grades if g["subject"] in ["math", "history"]]
...
# return response_data
您可以使用 Prefetch
对象:https://docs.djangoproject.com/en/3.0/ref/models/querysets/#django.db.models.Prefetch
例如:
qs = Students.objects.all().prefetch_related(
Prefetch('grades', queryset=Grade.objects.filter(subject__title__in=["math", "history"])
)
qs[0].grades.all()
现在只有数学和历史成绩。
您可以选择向 Prefetch 提供 to_attr='math_history_grades'
参数,这样您就可以通过以下方式访问成绩:qs[0].math_history_grades.all()
上下文
我正在尝试根据反向外键属性的值过滤对象列表。 我能够在视图级别解决它,但是其他使用 ORM 功能解决问题的尝试会导致额外的查询。
我想要的结果是包含所有对象的查询集,但是相关的 fkey 对象在每个对象中被过滤。
示例模型
class Student(models.Model):
name = models.CharField(max_length=128)
class Subject(models.Model):
title = models.CharField(max_length=128)
class Grade(models.Model):
student = models.ForeignKey("Student", related_name="grades", on_delete=models.CASCADE)
subject = models.ForeignKey("Subject", related_name="grades", on_delete=models.CASCADE)
value = models.IntegerField()
鉴于赛程
+------+------------------------+
| name | subject | grade_value |
+------+----------+-------------+
| beth | math | 100 |
| beth | history | 100 |
| beth | science | 100 |
| mark | math | 90 |
| mark | history | 90 |
| mark | science | 90 |
| mike | math | 90 |
| mike | history | 80 |
| mike | science | 80 |
+------+----------+-------------+
期望的结果
我想呈现学生列表,但只包括数学和历史成绩.
例如,也许我想要一个学生列表,但只包含他们成绩的一个子集:
GET students/?subjects=math,history
哪些被过滤可能会在请求中提供,或者硬编码。
如果可能的话,我们可以将其留在这个问题的范围之外,并假设过滤参数固定为 math
和 history
.
{
"students": [
{
"name": "beth",
"grades": [
{"subject": "math", "grade": 100 },
{"subject": "history", "grade": 100 },
// Exclude one or more grades - eg.
// science grade not included
]
},
...
]
}
尝试的解决方案
简单过滤器
只是过滤。我想这会过滤掉所有学生,他们的成绩在列表中有科目,这就是全部。
queryset = Students.objects.all()\
.prefetch_related("grades")\
.filter(grades__subject__in=["math", "history"])
)
# all grades for each student eg.
...
"grades": [
{"subject": "math", "grade": 100 },
{"subject": "history", "grade": 100 },
{"subject": "science", "grade": 100 },
]
...
子查询
我不太了解子查询的工作原理,但使用我尝试过的一些示例:
subjects = Subject.objects.filter(
name__in=["math", "history"]
)
queryset = Students.objects.all()\
.prefetch_related("grades")\
.filter(grades__subject__name__in=Subquery(subjects.values("name")))
还有另一个变体:
grades = Grades.objects.filter(
student_id=OuterRef("id"), subject__name__in=["math", "history"]
)
queryset = Students.objects.all()\
.prefetch_related("grades")\
.filter(grades__pk__in=Subquery(grades.values("pk)))
所有成绩均返回学生。
解决方法
此解决方案使用 python 过滤成绩。它有效,但我宁愿让它与 querysets
一起使用# in view:
serializer = StundentSerializer(queryset, many=True)
response_data = serializer.data
for student in response_data:
student.grades = [g for g in students.grades if g["subject"] in ["math", "history"]]
...
# return response_data
您可以使用 Prefetch
对象:https://docs.djangoproject.com/en/3.0/ref/models/querysets/#django.db.models.Prefetch
例如:
qs = Students.objects.all().prefetch_related(
Prefetch('grades', queryset=Grade.objects.filter(subject__title__in=["math", "history"])
)
qs[0].grades.all()
现在只有数学和历史成绩。
您可以选择向 Prefetch 提供 to_attr='math_history_grades'
参数,这样您就可以通过以下方式访问成绩:qs[0].math_history_grades.all()