web-dev-qa-db-ja.com

Django同じモデル内の別のフィールドに基づいたモデルフィールドのデフォルト

サブジェクト名とそのイニシャルを含めたいモデルがあります。 (データは多少匿名化され、イニシャルによって追跡されます。)

今、私は書いた

_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」という名前のイニシャルを「JO」ではなく「JM」で、管理者として編集を修正したい。)

75
dr jimbob

モデルには確かに「自己」があります!モデルクラスの属性をモデルインスタンスに依存していると定義しようとしているだけです。クラスとその属性を定義する前にインスタンスが存在しない(そして存在できない)ため、それは不可能です。

目的の効果を得るには、モデルクラスのsave()メソッドをオーバーライドします。インスタンスに必要な変更を加えてから、スーパークラスのメソッドを呼び出して実際の保存を行います。以下に簡単な例を示します。

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

これは、ドキュメントの モデルメソッドのオーバーライド で説明されています。

74
Elf Sternberg

これを行うより良い方法があるかどうかはわかりませんが、 シグナルハンドラを使用する for the pre_save signal

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)
14
Gabi Purcaru

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_シグナルは、インスタンスで初期化が完了するとクラスによって送信されます。このようにして、インスタンスは、null不可フィールドが設定されているかどうかをテストする前に、nameの値を取得します。

6
bignose

Gabi Purcar の答えの代替実装として、pre_savereceiverデコレーターを使用したシグナル

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 に従って取得する必要があるワイルドカードキーワード引数。

1
Kurt Peek