Контекст
Я пытаюсь отфильтровать список объектов на основе значения обратного атрибута внешнего ключа. Я смог решить ее на уровне представления, но другие попытки решить проблему с использованием функции ORM приводят к дополнительным запросам.
В результате я хочу получить набор запросов со всеми объектами, но связанные объекты fkey фильтруются внутри каждого из них. object.
Образцы моделей
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 },
]
...
Подзапрос
У меня нет отличных оценок asp о том, как работают подзапросы, но на некоторых примерах я попробовал:
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. Это работает, но я бы предпочел, чтобы это работало с наборами запросов
# 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