Примените фильтр к вложенным отношениям обратного внешнего ключа, используя набор запросов - PullRequest
1 голос
/ 11 апреля 2020

Контекст

Я пытаюсь отфильтровать список объектов на основе значения обратного атрибута внешнего ключа. Я смог решить ее на уровне представления, но другие попытки решить проблему с использованием функции 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

1 Ответ

2 голосов
/ 11 апреля 2020

Вы можете использовать объект 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()

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...