如何在Django Admin中为计算的模型属性实现排序,而无需编写两次逻辑?

OBu*_*OBu 5 python django django-admin

在我的Django中model,我定义了一个@property效果很好的属性,该属性可以在管理中显示,list_display没有任何问题。

我不仅需要在管理员中使用此属性,还需要在其他地方的代码逻辑中使用此属性,因此将其作为模型的属性是有意义的。

现在,我想使此属性的列可排序,并在When对象的Django文档的帮助下,此F()计算的StackOverflow问题以及此排序的链接,我设法构建了如下所示的工作解决方案。

在这里提出问题的原因是:实际上,我实现了两次逻辑,一次在python中实现,一次以表达式形式实现,这违反了只实现一次逻辑的设计规则。所以我想问我是否错过了一个更好的解决方案。任何想法表示赞赏。

这是模型(标识已修改):

class De(models.Model):

    fr = models.BooleanField("[...]")
    de = models.SmallIntegerField("[...]")
    gd = models.SmallIntegerField("[...]")
    na = models.SmallIntegerField("[...]")
    # [several_attributes, Meta, __str__() removed for readability]

    @property
    def s_d(self):
        if self.fr:
            return self.de
        else:
            return self.gd + self.na
Run Code Online (Sandbox Code Playgroud)

这是Model Admin

class DeAdmin(admin.ModelAdmin):
    list_display = ("[...]", "s_d", "gd", "na", "de", "fr" )

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        queryset = queryset.annotate(
            _s_d=Case(
                When(fr=True, then='s_d'),
                When(fr=False, then=F('gd') + F('na')),
                default=Value(0),
                output_field=IntegerField(),
            )
        )
        return queryset

    def s_d(self, obj):
        return obj._s_d
    s_d.admin_order_field = '_s_d'
Run Code Online (Sandbox Code Playgroud)

如果没有其他方法,我也希望确认这一事实作为答案。

Joh*_*fis 5

TL/DR:是的,您的解决方案似乎遵循唯一有意义的方法。


好吧,您在这里撰写的内容似乎是您在问题中列出的来源的推荐方式,并且有充分的理由。

什么是好的理由?
我还没有在代码库中找到明确的答案,但我想这与@property装饰器在 Python 中的工作方式有关。

当我们用装饰器设置一个属性时,我们不能向它添加属性,因为它admin_order_field是一个属性,所以我们不能在那里拥有它。该声明似乎从Django Admin 的list_display文档中得到了加强,其中存在以下段落:

的元素list_display也可以是属性。但是请注意,由于属性在 Python 中的工作方式,只有在使用函数时才可以设置short_description属性property()而不能与@property装饰器一起使用。

与此 QA 相结合的引用:AttributeError: 'property' object has no attribute 'admin_order_field'似乎解释了为什么不可能将模型属性的可订购项直接放入管理面板。


这解释了(可能?)是时候进行一些心理体操了!!

前面提到的文档部分中,我们还可以看到admin_order_field自 2.1 版以来可以接受查询表达式:

可以在 admin_order_field 中使用查询表达式。例如:

from django.db.models import Value
from django.db.models.functions import Concat

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def full_name(self):
        return self.first_name + ' ' + self.last_name
    full_name.admin_order_field = Concat('first_name', Value(' '), 'last_name')
Run Code Online (Sandbox Code Playgroud)

结合上一部分关于property()方法的内容,我们可以重构您的代码并将annotation部分移动到模型中:

class De(models.Model):
    ...
    def calculate_s_d(self):
        if self.fr:
            return self.de
        else:
            return self.gd + self.na

    calculate_s_d.admin_order_field = Case(
        When(fr=True, then='s_d'),
        When(fr=False, then=F('gd') + F('na')),
        default=Value(0),
        output_field=IntegerField(),
    )

    s_d = property(calculate_s_d)
Run Code Online (Sandbox Code Playgroud)

最后,admin.py我们只需要:

class DeAdmin(admin.ModelAdmin):
    list_display = ("[...]", "s_d")
Run Code Online (Sandbox Code Playgroud)