如何使Django模型字段在运行时计算?

6 投票
4 回答
4854 浏览
提问于 2025-04-15 21:09

我有一个模型:

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 个回答

1

还有一种可能更简单的方法,就是在管理模型中使用自定义表单。你可以这样做:

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()
2

你可以通过使用 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 来计算字段的值。

3

你这样做有点奇怪。其实你只需要存储生日(你已经在这么做了)。

如果你需要根据年龄来查询,可以先输入年龄,然后再查找符合条件的生日。

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

你自己也说过“[年龄]根本不应该存储在数据库里”。

没错,确实没有必要有年龄这个字段……只存生日就可以了。

撰写回答