同じモデルの別のフィールドに基づく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」という名前のイニシャルは「JO」ではなく「JM」を修正し、管理者として修正したいと考えています。)

回答:


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引数なしで)呼び出すことができることに注意してください。
カートピーク

1
残念ながら、デフォルトのモデル属性値を別のものに基づいて定義することはできません。保存時に処理するため、管理側(たとえば、自動インクリメントされたフィールド)で特に役立ちます。それとも私はそれを誤解していますか?
Vadorequest 2018

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
Ali Rasim Kocal 2015年

1
@arkocal:頭を上げてくれてありがとう。あなたはそのような場合に自分で編集を提案できます、それはこのようなものを修正するのに役立ちます:)
Gabi Purcaru

1
この実装には、docs.djangoproject.com / en / 2.0 / topics / signals /…**kwargsに従ってすべてのレシーバー関数が持つべき引数が欠けているようです。
カートピーク2018年

ドキュメントでは、制御するコードパスのシグナルに対して推奨しています。overridingの受け入れられた回答はsave()、おそらくoverridingに変更されましたが__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それは、インスタンス上で初期化を行っているいったん信号はクラスによって送信されます。このようにして、インスタンスは、namenull不可フィールドが設定されているかどうかをテストする前にの値を取得します。


ドキュメントでは、制御するコードパスのシグナルに対して推奨しています。overridingの受け入れられた回答はsave()、おそらくoverridingに変更されましたが__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に従って取得する必要があるワイルドカードキーワード引数も使用します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.