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


91

我有一个模型,希望包含一个科目名称及其首字母(他的数据有些匿名,并且通过首字母进行跟踪)。

现在,我写道

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模型似乎没有“自我”,我遇到了问题。

如果将行更改为subject_init = models.CharField("Subject initials", max_length=2, default=subject_initials),则可以执行syncdb,但不能创建新主题。

在Django中,有一个可调用函数可以根据另一个字段的值为某个字段提供默认值,这是否可能?

(出于好奇,我想分开存储首字母缩写的原因是在极少数情况下,怪异的姓氏可能与我跟踪的名字不同。例如,其他人认为名为“ John O'Mallory”的姓名的首字母1是“ JM”而不是“ JO”,并希望以管理员身份对其进行修改。)

Answers:


88

模特当然有一个“自我”!只是您要尝试将模型类的属性定义为依赖于模型实例。这是不可能的,因为在定义类及其属性之前,实例就不存在(也不能存在)。

要获得所需的效果,请覆盖模型类的save()方法。对实例进行必要的任何更改,然后调用超类的方法进行实际保存。这是一个简单的例子。

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

文档中的“ 覆盖模型方法”中对此进行了介绍。


4
请注意,在Python 3中,您可以简单地调用super().save(*args, **kwargs)(不带Subject, self参数),如参考文档中的示例所示。
Kurt Peek

1
遗憾的是,这不允许定义基于另一个的默认模型属性值,这在管理端(例如,对于自动递增的字段)特别有用,因为它在保存时会对其进行处理。还是我误会了?
Vadorequest '18

18

我不知道是否有这样做的更好的办法,但你可以使用一个信号处理器对于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)

1
您导入的是post_save而不是pre_save
阿里·拉西姆·科卡尔

1
@arkocal:感谢您的注意,只需将其修复即可。在这种情况下,您可以建议自己进行编辑,这有助于解决如下问题:)
Gabi Purcaru 2015年

1
似乎此实现缺少**kwargs根据docs.djangoproject.com/en/2.0/topics/signals/…所有接收器功能应具有的参数?
Kurt Peek

该文档建议不要针对您控制的代码路径使用信号。覆盖的公认答案(save()可能已更改为覆盖__init__)是更好的,因为它更明确。
亚当·约翰逊

7

使用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在测试是否设置了其非空字段之前会获取一个值。


该文档建议不要针对您控制的代码路径使用信号。覆盖的公认答案(save()可能已更改为覆盖__init__)是更好的,因为它更明确。
亚当·约翰逊

2

作为Gabi Purcaru答案的替代实现,您还可以pre_save使用receiver装饰器连接到信号:

from django.db.models.signals import pre_save
from django.dispatch import receiver


@receiver(pre_save, sender=Subject)
def default_subject(sender, instance, **kwargs):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

此接收器函数还采用**kwargs所有信号处理程序必须根据https://docs.djangoproject.com/en/2.0/topics/signals/#receiver-functions必须使用的通配符关键字参数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.