我已经有很多年没有使用 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
。
因此,为了实现在单个查询中返回每个项目的未完成任务数量的目标,我们需要调整您的方法以有效地使用 Django 的 ORM 功能。这是您的代码的修订版本,我认为应该可以工作。
具体方法如下:
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")
注意:请注意,
target_completion_count
应该是项目模型中的一个字段才能正常工作。如果不是,您需要相应地调整查询以提供每个项目的目标完成计数。