Django:用ForeignKey注释对象的计数/总和 - 输出错误

时间:2016-04-05 13:52:23

标签: django postgresql django-models

我有设置外键的模型:

STATUS = Choices('active', 'inactive', 'deleted')

class Project(models.Model):
    status = models.CharField(
        choices=STATUS,
        default=STATUS.active,
        max_length=20
    )
    objects = ProjectManager()

    def delete(self):
        self.status = STATUS.deleted
        self.save()

class Observation(models.Model):
    status = models.CharField(
        choices=STATUS,
        default=STATUS.active,
        max_length=20
    )
    location = models.ForeignKey(
        Location, related_name='locations'
    )
    project = models.ForeignKey(
        Project, related_name='observations'
    )
    history = HistoricalRecords()
    objects = ObservationManager()

    def delete(self):
        self.status = STATUS.deleted
        self.save()

class Comment(models.Model):
    status = models.CharField(
        choices=STATUS,
        default=STATUS.active,
        max_length=20
    )
    commentto = models.ForeignKey(
        Observation, related_name='comments'
    )
    objects = CommentManager()

    def delete(self):
        self.status = STATUS.deleted
        self.save()

如您所见,可以更改对象的状态。此外,还有一个名为“已删除”的状态。因此,我只是将状态设置为“已删除”,而不是删除实际对象,然后管理员负责其余部分(除了已删除的项目之外还返回所需内容)。

在视图中,我想返回所有项目,但总共有一些观察和评论(每个项目)。此外,由于有数百个项目,我希望尽可能减少查询。

所以我最终得到了这个解决方案:

Project.objects.all().annotate(
    observations_count=Sum(
        Case(
            When(
                ~Q(observations__status='deleted') &
                Q(observations__isnull=False),
                then=1
            ),
            default=0,
            output_field=IntegerField()
        ),
        distinct=True
    ),
    comments_count=Sum(
        Case(
            When(
                ~Q(observations__status='deleted') &
                ~Q(observations__comments___status='deleted') &
                Q(observations__comments__isnull=False),
                then=1
            ),
            default=0,
            output_field=IntegerField()
        ),
        distinct=True
    )
)

基本上,我想要计算所有与“已删除”不同且实际存在的观察结果,然后我对评论也这样做。

然而,这给了我错误的结果......

例如,如果我创建一个观察和两个评论,那么我删除一个评论,它将计为:2个观察,1个评论(这是完全错误的)。

或者如果我添加两个观察,一个有两个评论,但删除一个评论,另一个评论但删除该观察,它会给我:2个观察,1个评论。

现在,如果我这样做:

projects = Project.objects.all()

for project in projects:
    observations = project.observations.all()
    project.observations_count = len(observations)
    project.comments_count = Comment.objects.filter(commentto=observations).count()

它给了我正确的结果。但是,很明显,这使得许多SQL查询变得非常麻烦,并且对我没有好处。

有人可以就如何尝试解决这个问题给我任何建议吗?

我选择的解决方案......

所以我无法以任何方式解决这个问题......并最终解决这个问题:

我在观察模型中添加了新字段num_comments = models.IntegerField(默认值= 0),每次添加/删除评论时都会更新。然后我设法像这样正确计算一切:

Project.objects.all().annotate(
    observations_count=Sum(
        Case(
            When(
                ~Q(observations__status='deleted') &
                Q(observations__isnull=False),
                then=1
            ),
            default=0,
            output_field=IntegerField()
        ),
        distinct=True
    ),
    comments_count=Sum(
        Case(
            When(
                ~Q(observations__status='deleted') &
                Q(observations__isnull=False),
                then='observations__num_comments'
            ),
            default=0,
            output_field=IntegerField()
        ),
        distinct=True
    )
)

这不是我第一次去的地方,但是做的工作......无论如何,如果有人有更好的解决方案,请不要犹豫,在下面留言。

1 个答案:

答案 0 :(得分:0)

这非常棘手。你能在下面试试吗,

projects = Project.objects.\
    exclude(observations__status='deleted', observations__comments___status='deleted').\
    filter(observations__status__isnull=False, observations__comments___status__isnull=False).\
    annotate(count_observations=Count('observations__id'), count_comments=Count('observations__comments__id'))

你应该得到计数,

for p in projects:
    print p.count_observations
    print p.count_comments

<强>更新

实际上,上述查询不符合要求。所以我想这必须分两步完成。请在下面试试,

projects = Project.objects.\
    exclude(observations__status='deleted').\
    filter(observations__status__isnull=False).\
    annotate(count_observations=Count('observations__id'))

projects = projects.\
    exclude(observations__comments___status='deleted').\
    filter(observations__comments___status__isnull=False).\
    annotate(count_comments=Count('observations__comments__id'))

for p in projects:
    print p.count_observations
    print p.count_comments