Как построить единый запрос в 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, чтобы это работало. Если это не так, вам придется соответствующим образом скорректировать запрос, чтобы обеспечить целевое количество завершений для каждого проекта. Удачи

Вернуться на верх