如何使Django模型字段在运行时计算?
我有一个模型:
class Person (models.Model):
name = models.CharField ()
birthday = models.DateField ()
age = models.IntegerField ()
我想让 age
字段像一个属性一样工作:
def get_age (self):
return (datetime.datetime.now() - self.birthday).days // 365
age = property (get_age)
但同时我需要 age
还是一个真正的字段,这样我才能在 Person._meta.fields
中找到它,并给它设置一些属性,比如 age.help_text = "这个人的年龄"
等等。
显然,我不能仅仅重写 Person.save()
方法来计算并把 age
存储到数据库中,因为这样做以后数据肯定会出错(实际上,age
根本不应该存储在数据库里)。
其实,我现在不需要设置器,但一个好的解决方案应该有设置功能。
在 Django 中,这可能实现吗?或者说有没有更符合 Python 和 Django 风格的方法来解决我的问题?
4 个回答
还有一种可能更简单的方法,就是在管理模型中使用自定义表单。你可以这样做:
class Person (models.Model):
birthday = models.DateField()
class PersonForm(forms.ModelForm):
age = forms.IntegerField() # This will not be in the database
class Meta:
model = Person
def __init__(self, *args, **kwargs):
# verify instance was passed
instance = kwargs.get('instance')
if instance:
self.base_fields['age'].initial = (datetime.datetime.now() - self.birthday).days
super(PersonForm, self).__init__(*args, **kwargs)
class FooAdmin(admin.ModelAdmin):
form = PersonForm
# Save the age in the birthday field
def save_model(self, request, obj, form, change):
obj.birthday = datetime.datetime.now() + form.cleaned_data['age']
obj.save()
你可以通过使用 django-computed-property 来实现这个功能。
首先,你需要用下面的命令安装 django-computed-property:
pip install django-computed-property
然后,你要在 settings.py 文件中的 INSTALLED_APPS
列表里添加 computed_property
:
INSTALLED_APPS = [
...
'computed_property'
]
现在,你可以在你的模型中导入并使用里面的字段类,像这样:
from django.db import models
from computed_property import ComputedTextField
import random
class Person(models.Model):
age = ComputedTextField(compute_from='calculation')
@property
def calculation(self):
return str(random.randint(1,101))
在 Person 模型中的年龄字段每次被调用时都会返回一个随机数,这样可以演示它是在运行时计算的。
你可以像平常一样读取年龄字段的值,但你不能直接设置这个字段的值。当访问这个字段或者保存模型实例时,它会使用提供的可调用对象(函数/匿名函数)、属性
age
或者属性age
来计算字段的值。
你这样做有点奇怪。其实你只需要存储生日(你已经在这么做了)。
如果你需要根据年龄来查询,可以先输入年龄,然后再查找符合条件的生日。
from datetime import datetime
# get people over 50
age = 50 # in years
age_ago = datetime.now() - age # use timedelta i don't know the syntax off the top of my head
Person.objects.filter(birthday__lte=age_ago) # people whose birthday is before fifty years ago
你自己也说过“[年龄]根本不应该存储在数据库里”。
没错,确实没有必要有年龄这个字段……只存生日就可以了。