Django模型字段默认值基于同一模型中的另一个字段

137 投票
4 回答
66810 浏览
提问于 2025-04-16 08:11

我有一个模型,想要包含一个科目的名字和它的首字母(这些数据有点匿名,主要通过首字母来追踪)。

现在,我写了

class Subject(models.Model):

    name = models.CharField("Name", max_length=30)
    def subject_initials(self):
        return ''.join(map(lambda x: '' if len(x)==0 else x[0],
                           self.name.split(' ')))
    # Next line is what I want to do (or something equivalent), but doesn't work with
    # NameError: name 'self' is not defined
    subject_init = models.CharField("Subject Initials", max_length=5, default=self.subject_initials)

正如最后一行所说,我希望能够把首字母作为一个独立的字段存储在数据库里,而这个字段的默认值是根据名字字段来初始化的。不过,我遇到了一些问题,因为Django模型似乎没有“self”这个概念。

如果我把这一行改成subject_init = models.CharField("科目首字母", max_length=2, default=subject_initials),我可以进行数据库同步,但无法创建新的科目。

在Django中,是否可以通过一个可调用的函数,根据另一个字段的值来给某个字段设置默认值呢?

(顺便说一下,我想单独存储首字母的原因是,有些情况下,奇怪的姓氏可能和我追踪的首字母不同。例如,有人决定把名为“John O'Mallory”的科目1的首字母定为“JM”,而不是“JO”,并希望作为管理员进行修正。)

4 个回答

12

使用Django信号,我们可以很早就完成这个操作,方法是接收来自模型的 post_init信号

from django.db import models
import django.dispatch

class LoremIpsum(models.Model):
    name = models.CharField(
        "Name",
        max_length=30,
    )
    subject_initials = models.CharField(
        "Subject Initials",
        max_length=5,
    )

@django.dispatch.receiver(models.signals.post_init, sender=LoremIpsum)
def set_default_loremipsum_initials(sender, instance, *args, **kwargs):
    """
    Set the default value for `subject_initials` on the `instance`.

    :param sender: The `LoremIpsum` class that sent the signal.
    :param instance: The `LoremIpsum` instance that is being
        initialised.
    :return: None.
    """
    if not instance.subject_initials:
        instance.subject_initials = "".join(map(
                (lambda x: x[0] if x else ""),
                instance.name.split(" ")))

post_init信号是在类完成实例的初始化后发送的。这样,实例在检查它的非空字段是否被设置之前,就能获得name的值。

19

我不知道有没有更好的方法,但你可以使用一个叫做信号处理器的东西,来处理在保存之前的信号(pre_save

from django.db.models.signals import pre_save

def default_subject(sender, instance, using):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

pre_save.connect(default_subject, sender=Subject)
128

模型确实有一个“自我”!只是你试图把模型类的一个属性定义成依赖于模型实例,这样是不行的,因为在你定义类和属性之前,实例是不存在(也不能存在)的。

要实现你想要的效果,可以重写模型类的save()方法。先对实例进行你想要的任何修改,然后再调用父类的方法来完成实际的保存。下面是一个简单的例子。

def save(self, *args, **kwargs):
    if not self.subject_init:
        self.subject_init = self.subject_initials()
    super(Subject, self).save(*args, **kwargs)

这个内容在文档的重写模型方法部分有详细介绍。

撰写回答