如何将计算字段添加到 Django 模型

Posted

技术标签:

【中文标题】如何将计算字段添加到 Django 模型【英文标题】:How to add a calculated field to a Django model 【发布时间】:2013-07-14 23:45:00 【问题描述】:

我有一个简单的Employee 模型,其中包括firstnamelastnamemiddlename 字段。

在管理方面,可能在其他地方,我想将其显示为:

lastname, firstname middlename

对我来说,这样做的合乎逻辑的地方是在模型中创建一个计算字段:

from django.db import models
from django.contrib import admin

class Employee(models.Model):
    lastname = models.CharField("Last", max_length=64)
    firstname = models.CharField("First", max_length=64)
    middlename = models.CharField("Middle", max_length=64)
    clocknumber = models.CharField(max_length=16)
    name = ''.join(
        [lastname.value_to_string(),
        ',',
         firstname.value_to_string(),
        ' ',
         middlename.value_to_string()])

    class Meta:
        ordering = ['lastname','firstname', 'middlename']

class EmployeeAdmin(admin.ModelAdmin):
    list_display = ('clocknumber','name')
    fieldsets = [("Name", "fields":(("lastname", "firstname", "middlename"), "clocknumber")),
        ]

admin.site.register(Employee, EmployeeAdmin)

我认为我最终需要的是获取名称字段的值作为字符串。我得到的错误是value_to_string() takes exactly 2 arguments (1 given)。字符串的值需要self, obj。我不确定obj 是什么意思。

一定有一个简单的方法可以做到这一点,我相信我不是第一个想要这样做的人。

编辑:下面是我修改为丹尼尔答案的代码。我得到的错误是:

django.core.exceptions.ImproperlyConfigured: 
    EmployeeAdmin.list_display[1], 'name' is not a callable or an 
    attribute of 'EmployeeAdmin' of found in the model 'Employee'.
from django.db import models
from django.contrib import admin

class Employee(models.Model):
    lastname = models.CharField("Last", max_length=64)
    firstname = models.CharField("First", max_length=64)
    middlename = models.CharField("Middle", max_length=64)
    clocknumber = models.CharField(max_length=16)

    @property
    def name(self):
        return ''.join(
            [self.lastname,' ,', self.firstname, ' ', self.middlename])

    class Meta:
        ordering = ['lastname','firstname', 'middlename']

class EmployeeAdmin(admin.ModelAdmin):
    list_display = ('clocknumber','name')
    fieldsets = [("Name", "fields":(("lastname", "firstname", "middlename"), "clocknumber")),
]

admin.site.register(Employee, EmployeeAdmin)

【问题讨论】:

Django Model: field value is calculation of other fileds 的可能重复项 【参考方案1】:

这不是你作为一个领域所做的事情。即使该语法有效,它也只会在定义类时给出值,而不是在您访问它时给出值。你应该把它作为一个方法来做,你可以使用@property装饰器让它看起来像一个普通的属性。

@property
def name(self):
    return ''.join(
        [self.lastname,' ,', self.firstname, ' ', self.middlename])

self.lastname 等只是作为它们的值出现,因此无需调用任何其他方法来转换它们。

【讨论】:

我认为这是放置在 Employee 模型中......这给了我一个错误。该方法应该进入 EmployeeAdmin 类吗? 我编辑了问题以显示我使用的代码和我收到的错误消息。 BTW Django 1.5.1 和 python 2.7 如果重要的话。 这个@property 可以在复合主键的外键中使用吗? @CpILL:不,因为它只在 Python 端处理,而外键也在数据库中处理。 这不允许您 filter() 一个 QuerySet,因为要这样做,它必须是一个字段而不是一个属性。对我有用的是注释一个表达式(使用 QuerySet.annotate() )告诉数据库如何派生值(例如 models.F(field1) - models.F(field2)),然后过滤那个注释.【参考方案2】:

Daniel Roseman 的解决方案使计算字段成为Model 的属性,但它是does not make it accessible via QuerySet methods(例如.all().values())。这是因为QuerySet方法call the database directly,绕过了django的Model

由于 QuerySet 直接访问数据库,解决方案是通过附加计算字段来覆盖 Manager.get_queryset() 方法。计算字段是使用.annotate() 创建的。最后,将Model 中的objects Manager 设置为新的Manager

以下是一些演示代码:

models.py

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

class InvoiceManager(models.Manager):
    """QuerySet manager for Invoice class to add non-database fields.

    A @property in the model cannot be used because QuerySets (eg. return
    value from .all()) are directly tied to the database Fields -
    this does not include @property attributes."""

    def get_queryset(self):
        """Overrides the models.Manager method"""
        qs = super(InvoiceManager, self).get_queryset().annotate(link=Concat(Value("<a href='#'>"), 'id', Value('</a>')))
        return qs

class Invoice(models.Model):
    # fields

    # Overridden objects manager
    objects = InvoiceManager()

现在,您将能够调用.values().all() 并访问Manager 中声明的新计算的link 属性。

也可以在.annotate() 中使用other functions,例如F()

我相信该属性在object._meta.get_fields() 中仍然不可用。我相信您可以在此处添加它,但我尚未探索如何 - 任何编辑/cmets 都会有所帮助。

【讨论】:

就我自己的理解而言,为什么 def str__(self): return self.whatever 不能充分解决这个问题?这在 OP 引用的部分(Django 1.10 版本)docs.djangoproject.com/en/1.10/topics/db/models/#model-methods 下方的文档中提到。这是为了让您可以有不止一种调用或命名对象的方法吗?或者,您可以执行 def__str2 return self.othername 之类的操作吗? @MalikA.Rumi self.whatever 将仅适用于模型的单个实例,但不适用于 QuerySet(即 .all().filter() 返回的所有实例)。 self.whatever 指的是单个实例,对于 QuerySet 不存在。 谢谢亚历克斯,这对我来说适用于 Django 1.11。我还需要在 Admin 中使用这些带注释的字段,但这是不可能的,但解决方法 here 允许在 Admin 界面中也使用它。 这是最复杂但最有效的解决方案。如果属性需要进行数据库查询,使用 Python 的原生属性装饰器会大大降低性能。【参考方案3】:

好的... Daniel Roseman 的回答似乎应该奏效。与往常一样,在发布问题后,您会找到您要查找的内容。

在Django 1.5 docs 中,我发现这个示例开箱即用。感谢大家的帮助。

这是有效的代码:

from django.db import models
from django.contrib import admin

class Employee(models.Model):
    lastname = models.CharField("Last", max_length=64)
    firstname = models.CharField("First", max_length=64)
    middlename = models.CharField("Middle", max_length=64)
    clocknumber = models.CharField(max_length=16)

    def _get_full_name(self):
        "Returns the person's full name."
        return '%s, %s %s' % (self.lastname, self.firstname, self.middlename)
    full_name = property(_get_full_name)


    class Meta:
        ordering = ['lastname','firstname', 'middlename']

class EmployeeAdmin(admin.ModelAdmin):
    list_display = ('clocknumber','full_name')
    fieldsets = [("Name", "fields":(("lastname", "firstname", "middlename"), "clocknumber")),
]

admin.site.register(Employee, EmployeeAdmin)

【讨论】:

这不正是@property 装饰器的作用吗? 这是一个完整的例子。在每个部分添加一些 cmets 对于像我这样的新手会更有帮助。谢谢 较新版本的文档使用@property: docs.djangoproject.com/en/3.1/topics/db/models/#model-methods【参考方案4】:

我最近开发了一个库,可以很容易地解决您遇到的问题。

https://github.com/brechin/django-computed-property

安装它,添加到 INSTALLED_APPS 然后

class Employee(models.Model):
    ...
    name = computed_property.ComputedCharField(max_length=3 * 64, compute_from='full_name')

    @property
    def full_name(self):
        return 'LAST, FIRST MIDDLE'.format(LAST=self.lastname, FIRST=self.firstname, MIDDLE=self.middlename')

【讨论】:

我试过这个应用程序。我首先虽然它避免在数据库中创建一个字段,但它没有。此外,在实施之后,迁移和循环模型(cfr doc note)以更新计算字段 => 计算结果在数据库中不可见!即使它正在工作,与在模型中使用 save() 方法相比,使用这个应用程序有什么优势?!【参考方案5】:

在这种情况下,如果您只打算在管理站点中使用该字段表示以及此类问题,您最好考虑覆盖 str() 或 unicode( ) django 文档here 中提到的类的方法:

class Employee(models.Model):
    # fields definitions
    def __str__(self):
        return self.lastname + ' ,' + self.firstname + ' ' + self.middlename

【讨论】:

以上是关于如何将计算字段添加到 Django 模型的主要内容,如果未能解决你的问题,请参考以下文章

如何计算Django模型中某些字段的平均值并将其发送到rest API?

如何将类、id、占位符属性添加到 django 模型表单中的字段

Django:如何根据来自行的数据和来自另一个模型的数据将聚合字段添加到查询集中?

如何在 django 中计算某些 if 条件的字段平均值?

如何在 Django 中向模型添加临时字段?

Django 将括号添加到 Django 模型字段