如何将计算字段添加到 Django 模型
Posted
技术标签:
【中文标题】如何将计算字段添加到 Django 模型【英文标题】:How to add a calculated field to a Django model 【发布时间】:2013-07-14 23:45:00 【问题描述】:我有一个简单的Employee
模型,其中包括firstname
、lastname
和middlename
字段。
在管理方面,可能在其他地方,我想将其显示为:
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.Rumiself.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 模型表单中的字段