djangoのmodel.save()がfull_clean()を呼び出さないのはなぜですか?


150

モデルフォームの一部として保存されていない限り、djangoのormがモデルで 'full_clean'を呼び出さないのに十分な理由があるかどうかを誰かが知っているのであれば、私は興味があります。

モデルのsave()メソッドを呼び出しても、full_clean()は自動的には呼び出されないことに注意してください。手動で作成した独自のモデルのワンステップモデル検証を実行する場合は、手動で呼び出す必要があります。 djangoの完全なクリーンドキュメント

(注:Django 1.6用に更新された引用...以前のdjangoのドキュメントにもModelFormsに関する警告がありました。)

人々がこの振る舞いを望まない理由はありますか?時間をかけてモデルに検証を追加した場合、モデルが保存されるたびに検証を実行したいと思うでしょう。

すべてを適切に機能させる方法を知っています。ただ説明を求めています。


11
この質問を本当にありがとう、それは私がずっと壁に頭をぶつけないようにしました。他の人を助けるかもしれないミックスインを作成しました。要点を確認してください:gist.github.com/glarrain/5448253
glarrain

そして最後に、シグナルを使用してpre_saveフックをキャッチし、キャッチさfull_cleanれたすべてのモデルで行います。
Alfred Huang

回答:


59

私の知る限り、これは後方互換性のためです。除外されたフィールドのあるModelForms、デフォルト値のあるモデル、pre_save()シグナルなどにも問題があります。

あなたが興味を持っているかもしれないソース:


3
2番目のリファレンスからの最も有用な抜粋(IMHO):「実際に有用であるほど単純であり、すべてのエッジケースを処理するのに十分に堅牢である「自動」検証オプションの開発は-それが可能でさえあれば-はるかに1.2の時間枠で達成できます。したがって、今のところ、Djangoにはそのようなものはなく、1.2にはありません。1.3で機能させることができると考えられる場合、最善の策は、少なくともいくつかのサンプルコードと、シンプルかつ堅牢に保つ方法の説明を含む提案。」
Josh

30

互換性を考慮して、djangoカーネルでは保存時の自動クリーンアップが有効になっていません。

新しいプロジェクトを開始していて、saveModel のデフォルトメソッドを自動的にクリーンアップしたい場合は、次の信号を使用して、すべてのモデルが保存される前にクリーンアップを実行できます。

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

@receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
    instance.full_clean()

2
これは、一部のBaseModel(他のすべての継承元)のsaveメソッドをオーバーライドして、最初にfull_cleanを呼び出してからsuper()を呼び出すよりも良い(または悪い)のはなぜですか。
J__

7
このアプローチには2つの問題があります。1)ModelFormのfull_clean()が2回呼び出される場合:フォームとシグナルによる2)フォームが一部のフィールドを除外する場合でも、シグナルによって検証されます。
メフメット2017

1
@mehmetでは、これらif send == somemodel, then exclude some fieldsを追加できるかもしれませんpre_save_handler
Simin Jie

4
このアプローチを使用している、または使用を検討している場合:このアプローチはDjangoによって正式にサポートされておらず、近い将来サポートされないことに注意してください(Djangoバグトラッカーのこのコメントを参照:code.djangoproject.com/ticket/ 29655#comment:3)、すべてのモデルの検証を有効にすると、認証が機能しなくなる(code.djangoproject.com/ticket/29655)など、いくつかの欠陥に遭遇する可能性があります。あなたは自分でそのような問題に対処しなければならないでしょう。ただし、これ以上のアプローチ方法はありません。
Evgeny A.

2
Django 2.2.3以降では、これにより基本認証システムで問題が発生します。を取得しValidationError: Session with this Session key already existsます。これを回避sender in list_of_model_classesするには、信号がDjangoのデフォルトの認証モデルを上書きしないようにするためのifステートメントを追加する必要があります。list_of_model_classes選択した方法で定義
Addison Klinke

15

full_cleanメソッドを呼び出す最も簡単な方法は、単にsaveあなたの中でメソッドをオーバーライドすることmodelです:

def save(self, *args, **kwargs):
    self.full_clean()
    return super(YourModel, self).save(*args, **kwargs)

これが信号を使用するよりも良い(または悪い)のはなぜですか?
J__

6
このアプローチには2つの問題があります。1)ModelFormのfull_clean()が2回呼び出される場合:フォームと保存によって2)フォームがいくつかのフィールドを除外する場合、それらは保存によって検証されます。
メフメット2017

3

レシーバーを宣言するコードを挿入する代わりに、アプリをINSTALLED_APPSセクションとして使用できますsettings.py

INSTALLED_APPS = [
    # ...
    'django_fullclean',
    # your apps here,
]

その前に、django-fullcleanPyPIを使用してインストールする必要がある場合があります。

pip install django-fullclean

13
自分でこれらの行を記述するのではなく、pip install4行のコードを含むアプリ(ソースコードを確認する)があるのはなぜですか?
David D.17年

私が試していない別のライブラリ:github.com/danielgatis/django-smart-save
Flimm

2

確認したいモデルが少なくとも1つのFK関係を持っていてnull=False、デフォルトのFK(ガベージデータになる)を設定する必要があるために使用したくない場合、私が思いついた最良の方法はカスタム.clean().save()メソッドを追加します。.clean()検証エラーを発生させ、.save()clean を呼び出します。このようにして、フォームと他の呼び出しコード、コマンドライン、およびテストの両方から整合性が適用されます。これがないと、(AFAICT)モデルが特定の選択された(デフォルトではない)他のモデルとのFK関係を持っていることを確認するテストを作成する方法がありません。

class Payer(models.Model):

    name = models.CharField(blank=True, max_length=100)
    # Nullable, but will enforce FK in clean/save:
    payer_group = models.ForeignKey(PayerGroup, null=True, blank=True,)

    def clean(self):
        # Ensure every Payer is in a PayerGroup (but only via forms)
        if not self.payer_group:
            raise ValidationError(
                {'payer_group': 'Each Payer must belong to a PayerGroup.'})

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)

    def __str__(self):
        return self.name

1

@Alfred Huangの回答にコメントし、コメントします。現在のモジュール(models.py)でクラスのリストを定義し、それをpre_saveフックでチェックすることで、pre_saveフックをアプリにロックできます。

CUSTOM_CLASSES = [obj for name, obj in
        inspect.getmembers(sys.modules[__name__])
        if inspect.isclass(obj)]

@receiver(pre_save)
def pre_save_handler(sender, instance, **kwargs):
    if type(instance) in CUSTOM_CLASSES:
        instance.full_clean()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.