首页 > 解决方案 > Django admin annotation - Sum 相关模型的字段

问题描述

我有一个 django-admin 仪表板/列表,它尝试使用我认为无法正常工作的带注释的查询集来跟踪和总结“工作”。作为表格的一部分,我显示了来自相关对象的数据量的计算字段(由带注释的查询集提供)。

实际上,它是跟踪计算机设备及其各自存储设备的工作的作业列表,因此作业可以有多个设备,这些设备可以有多个存储设备。设备和存储都可以包含数据量(就像手机可以具有内部存储器和可移动存储卡一样。)对于某些工作,设备可能是裸硬盘驱动器或具有 5 个 HDD 的 PC 塔,所以我试图容纳这些场景...

模型.py:

class Job(models.Model):
    ...
    job_number = models.CharField(max_length=50)
    job_name = models.CharField(max_length=100)
    ...

class Device(models.Model):
    ...
    device_number = models.CharField(max_length=50)
    job = models.ForeignKey(Job, null=True, on_delete=models.CASCADE)
    capacity = models.FloatField(null=True)
    ...

class Storage(models.Model):
    ...
    storage_number = models.CharField(max_length=50)
    device = models.ForeignKey(Device, null=True, on_delete=models.CASCADE)
    capacity = models.FloatField(null=True)
    ...

对于 Job 的管理模型,我尝试了以下方法,这似乎可行,但是当我过滤(例如基于对作业名称的文本搜索)时,数据量的总数会猛增

管理员.py

class JobAdmin(admin.ModelAdmin)

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        queryset = queryset.annotate(
            _device_count=Count("device", distinct=True),
            _device_volume=Sum("device__capacity", distinct=True)+Sum("device__storage__capacity", distinct=True)
           )
        return queryset

    def data_label(self,obj):
        return obj._device_volume

我注意到,如果我搜索/过滤一个职位名称并获得多次点击,它似乎将数据总和乘以结果数,但如果我过滤其他字段(使用 list_filter 字段)它不会发生.

谁能看到我哪里出错了?我很感激任何建议。

标签: djangodjango-admin

解决方案


尽量不要将多个聚合annotate()结合起来,因为这会产生错误的结果(因为使用连接而不是子查询

那么该怎么做呢?使用子查询,类似的东西应该可以完成这项工作:

from django.db.models import OuterRef, Subquery, Sum, Count


def get_queryset(self, request):
    queryset = super().get_queryset(request)
    queryset = queryset.annotate(
        _device_count=Subquery(
            Device.objects.filter(job=OuterRef("pk"))
            .values("job")
            .annotate(cnt=Count("id"))
            .values("cnt")
        ),
        _device_capacity=Subquery(
            Device.objects.filter(job=OuterRef("pk"))
            .values("job")
            .annotate(vol=Sum("capacity"))
            .values("vol")
        ),
        _storage_capacity=Subquery(
            Storage.objects.filter(device__job=OuterRef("pk"))
            .values("device__job")
            .annotate(vol=Sum("capacity"))
            .values("vol")
        ),
    )
    return queryset

您可以尝试再做一个注释以获得 and 的总和_device_capacity_storage_capacity但我想在 python 中对其求和很容易,所以也许不需要打扰数据库。


推荐阅读