首页 > 解决方案 > Django Admin中的外键和其他字段聚合

问题描述

我在 Django 中工作,并且在我的管理站点中正确显示某些内容时遇到问题。这些是模型

class IndexSetSize(models.Model):
    """ A time series of sizes for each index set """
    index_set = models.ForeignKey(IndexSet, on_delete=models.CASCADE)
    byte_size = models.BigIntegerField()
    timestamp = models.DateTimeField()

class IndexSet(models.Model):
    title = models.CharField(max_length=4096)
    # ...  some other stuff that isn't really important
    def __str__(self):
        return f"{self.title}"

它显示了我需要的所有适当数据,但是,我想显示 IndexSetSize 的总和,按 index_set 键分组,还按时间戳分组(对于给定的时间戳,IndexSet 可以多次出现,所以我想将所有字节大小相加)。目前只显示每条记录。此外,我希望 total_size 字段是可排序的

当前的 Admin 模型如下所示:

class IndexSetSizeAdmin(admin.ModelAdmin):
    """ View-only admin for index set sizes """
    fields = ["index_set", "total_size", "byte_size", "timestamp"]
    list_display = ["index_set", "total_size", "timestamp"]
    search_fields = ["index_set"]
    list_filter = ["index_set__title"]

    def total_size(self, obj):
        """ Returns human readable size """
        if obj.total_size:
            return humanize.naturalsize(obj.total_size)
        return "-"
    total_size.admin_order_field = 'total_size'

    def get_queryset(self, request):
        queryset = super().get_queryset(request).select_related()
        queryset = queryset.annotate(
            total_size=Sum('byte_size', filter=Q(index_set__in_graylog=True)))
        return queryset

似乎在 Django 中进行分组的正确方法是使用 .values(),尽管如果我在 get_queryset 中使用它,则会抛出错误说Cannot call select_related() after .values() or .values_list(). 如果有一种“正确”的值/注释/聚合方法可以与 get_queryset 一起正常工作,我无法在文档中找到。这是一个非常简单的 sum/group by query 我正在尝试做的事情,但我不确定“Django 方式”是什么来完成它。

谢谢

标签: pythondjangodjango-admin

解决方案


我认为您无法返回完整的查询集并分组index_setget_queryset因为您无法选择所有列,而是按 sql 中的单个列分组

SELECT *, SUM(index_size) FROM indexsetsize GROUP BY index_set   // doesn't work

您可以在方法中执行额外的查询total_size来获取聚合值。但是,这将对返回的每一行执行查询并减慢页面加载速度。

    def total_size(self, obj):
        """ Returns human readable size """
        return humanize.naturalsize(sum(IndexSetSize.objects.filter(
                                        index_set=obj.index_set).values_list(
                                       'byte_size', flat=True)))
    total_size.admin_order_field = 'total_size'

最好在 中执行此注释,IndexSetAdmin因为它index_set已经通过反向外键分组。这意味着您可以在get_queryset. 我还会将related_name外键设置为 on,IndexSetSize以便您可以使用该名称访问实际IndexSetSize对象。IndexSet

class IndexSetSize(models.Model):
    index_set = models.ForeignKey(IndexSet, on_delete=models.CASCADE, related_name='index_set_sizes')
    ...

class IndexSetAdmin(admin.ModelAdmin):
    ...

    def total_size(self, obj):
        """ Returns human readable size """
        if obj.total_size:
            return humanize.naturalsize(obj.total_size)
        return "-"

    def get_queryset(self, request):
        queryset = super().get_queryset(request).prefetch_related('index_set_sizes').annotate(
            total_size=Sum('index_set_sizes__byte_size')).order_by('total_size')
        return queryset

推荐阅读