Как построить единый запрос в Django со связанными моделями и подсчетом
Я не использовал Django в течение нескольких лет и попытался модифицировать некоторые старые кодовые базы сегодня.
У меня есть модели:
class MetaTemplate(CreatedModifiedBase):
type = models.CharField(max_length=200)
class Project(CreatedModifiedBase):
name = models.CharField(max_length=200, unique=True)
members = models.ManyToManyField(
User,
through=ProjectMembership,
related_name="annotation_projects_as_member",
blank=True,
)
metatemplate = models.ForeignKey(
MetaTemplate, on_delete=models.SET_NULL, related_name="project", null=True
)
class Dataset(CreatedModifiedBase):
name = models.CharField(max_length=200)
project = models.ForeignKey(
Project, related_name="datasets", on_delete=models.CASCADE
)
class Task(CreatedModifiedBase):
dataset = models.ForeignKey(
Dataset, related_name="tasks", on_delete=models.CASCADE, null=True
)
class Completion(CreatedModifiedBase):
task = models.ForeignKey(Task, related_name="completions", on_delete=models.CASCADE)
annotator = models.ForeignKey(
User,
on_delete=models.SET_NULL,
related_name="annotation_completions",
null=True,
)
У меня есть метод, который возвращает количество незавершенных заданий.
def get_incomplete_tasks(self, user, project_id, target_completion_count) -> int:
return (
Task.objects.annotate(counts=Count("completions"))
.filter(
counts__lt=target_completion_count,
dataset__project=project_id,
)
.exclude(completions__annotator=user)
.prefetch_related(
"completions", "completions__annotator", "dataset__project"
)
.count()
)
Теперь я хочу переработать этот метод и вернуть количество незавершенных задач для каждого из проектов в одном запросе без указания project_id и target_completion_count, переданных пользователем. То есть, по сути, та же логика, но в одном запросе для каждого проекта.
Я написал такой код:
t = Task.objects.filter(
dataset__project=OuterRef('id')
)
completion_count_subquery = t.exclude(completions__annotator=user).prefetch_related(
"completions", "completions__annotator").order_by().annotate(
counts=Func(F('id'), function='Count')).values('counts')
objects = Project.objects.all().annotate(task_count=Subquery(completion_count_subquery)).prefetch_related("members", "metatemplate")
Но я не могу включить counts__lt=target_completion_count
часть должным образом.
Также попробовал этот запрос
t = Task.objects.all().annotate(counts=Count("completions")).filter(dataset__project = OuterRef('id'), counts__lt=OuterRef("target_completion_count")).order_by().values("completions")
completion_count_subquery = t.exclude(completions__annotator=user)
objects = Project.objects.all().annotate(task_count=Subquery(completion_count_subquery)).prefetch_related("members", "metatemplate")
Но он завершается с ошибкой django.db.utils.ProgrammingError: more than one row returned by a subquery used as an expression
Пересмотренная версия вашего кода, чтобы избежать programmingError
, который вы имеете из-за более чем одного возвращенного ряда.
Таким образом, чтобы достичь цели - вернуть количество незавершенных задач для каждого проекта в одном запросе, нам придется скорректировать ваш подход, чтобы эффективно использовать возможности ORM в Django. Вот пересмотренная версия вашего кода, которая, как мне кажется, должна работать.
Вот как:
Не стесняйтесь удалять комментарии, которые я написал, чтобы объяснить вам, что я сделал.
from django.db.models import Count, Q, Subquery, OuterRef
# Assuming 'target_completion_count' is a field in the Project model
t = Project.objects.annotate(
incomplete_tasks_count=Subquery(
Task.objects.filter(
dataset__project=OuterRef('pk'),
completions__annotator=user,
completions__count__lt=OuterRef('target_completion_count')
).annotate(
completions_count=Count('completions')
).filter(
completions_count__lt=OuterRef('target_completion_count')
# We use [:1] to ensure that the subquery returns only one row, which is necessary to avoid the ProgrammingError you encountered
).values('completions_count')[:1],
output_field=models.IntegerField()
)
).prefetch_related("members", "metatemplate")
NB: Обратите внимание, что target_completion_count
должно быть полем в модели Project, чтобы это работало. Если это не так, вам придется соответствующим образом скорректировать запрос, чтобы обеспечить целевое количество завершений для каждого проекта. Удачи