使用查询集将过滤器应用于嵌套反向外键关系

问题描述 投票:1回答:1

上下文

我正在尝试根据反向外键属性的值来过滤对象列表。我能够在视图级别解决它,但是其他尝试使用ORM功能解决的尝试也会导致其他查询。

我想要的结果是包含所有对象的queryset,但是相关的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

可能在请求中提供或进行了硬编码。如果可能的话,我们可以将其放在此问题的范围之外,并假定过滤参数固定为mathhistory

{
    "students": [
        {
          "name": "beth",
          "grades": [
              {"subject": "math", "grade": 100 },
              {"subject": "history", "grade": 100 },
              // Exclude one or more grades - eg.
              // science grade not included
          ]
        },
        ...
    ]
}


尝试的解决方案

简单过滤器

仅过滤。我猜这会过滤掉所有students,这些学生的成绩都在列表中,这就是全部。

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
python django django-orm
1个回答
1
投票

您可以使用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()现在仅具有数学和历史成绩。

[可选地,您可以将to_attr='math_history_grades'参数提供给Prefetch,因此您将通过以下方式访问成绩:qs[0].math_history_grades.all()

© www.soinside.com 2019 - 2024. All rights reserved.