Warum ruft djangos model.save () nicht full_clean () auf?


150

Ich bin nur neugierig, ob jemand weiß, ob es einen guten Grund gibt, warum Djangos Orm für ein Modell nicht 'full_clean' aufruft, es sei denn, es wird als Teil eines Modellformulars gespeichert.

Beachten Sie, dass full_clean () nicht automatisch aufgerufen wird, wenn Sie die save () -Methode Ihres Modells aufrufen. Sie müssen es manuell aufrufen, wenn Sie eine einstufige Modellvalidierung für Ihre eigenen manuell erstellten Modelle ausführen möchten. Djangos voll sauberes Dokument

(HINWEIS: Das Zitat wurde für Django 1.6 aktualisiert. Frühere Django-Dokumente hatten ebenfalls eine Einschränkung in Bezug auf ModelForms.)

Gibt es gute Gründe, warum Menschen dieses Verhalten nicht wollen würden? Ich würde denken, wenn Sie sich die Zeit genommen hätten, einem Modell eine Validierung hinzuzufügen, möchten Sie, dass diese Validierung jedes Mal ausgeführt wird, wenn das Modell gespeichert wird.

Ich weiß, wie man alles richtig zum Laufen bringt, ich suche nur nach einer Erklärung.


11
Vielen Dank für diese Frage, sie hat mich davon abgehalten, meinen Kopf viel länger gegen die Wand zu schlagen. Ich habe ein Mixin erstellt, das anderen helfen könnte. Überprüfen Sie das Wesentliche: gist.github.com/glarrain/5448253
glarrain

Und schließlich benutze ich das Signal, um den pre_saveHaken zu fangen und full_cleanbei allen gefangenen Modellen zu tun .
Alfred Huang

Antworten:


59

AFAIK, das liegt an der Abwärtskompatibilität. Es gibt auch Probleme mit ModelForms mit ausgeschlossenen Feldern, Modellen mit Standardwerten, pre_save () -Signalen usw.

Quellen, an denen Sie interessiert sein könnten:


3
Der hilfreichste Auszug (IMHO) aus der zweiten Referenz: "Die Entwicklung einer" automatischen "Validierungsoption, die sowohl einfach genug ist, um tatsächlich nützlich zu sein, als auch robust genug, um alle Randfälle zu behandeln, ist - wenn es überhaupt möglich ist - weit mehr als kann im 1.2-Zeitrahmen erreicht werden. Daher hat Django im Moment keine solche und wird sie in 1.2 nicht haben. Wenn Sie glauben, dass Sie es für 1.3 zum Laufen bringen können, ist es am besten, a zu erarbeiten Vorschlag, der mindestens einen Beispielcode enthält, sowie eine Erklärung, wie Sie ihn sowohl einfach als auch robust halten können. "
Josh

30

Aus Gründen der Kompatibilität ist die automatische Bereinigung beim Speichern im Django-Kernel nicht aktiviert.

Wenn wir ein neues Projekt starten und möchten, dass die Standardmethode savefür Modell automatisch bereinigt wird, können wir das folgende Signal verwenden, um eine Bereinigung durchzuführen, bevor jedes Modell gespeichert wurde.

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
Warum ist dies besser (oder schlechter) als das Überschreiben der Speichermethode auf einem BaseModel (von dem alle anderen erben), um zuerst full_clean und dann super () aufzurufen?
J__

7
Ich sehe zwei Probleme bei diesem Ansatz: 1) Wenn ModelForms full_clean () zweimal aufgerufen wird: durch das Formular und durch das Signal. 2) Wenn das Formular einige Felder ausschließt, werden sie dennoch durch das Signal validiert.
Mehmet

1
@mehmet Vielleicht können Sie diese hinzufügen if send == somemodel, then exclude some fieldsinpre_save_handler
Simin Jie

4
Für diejenigen, die diesen Ansatz verwenden oder in Betracht ziehen: Beachten Sie, dass dieser Ansatz von Django nicht offiziell unterstützt wird und auf absehbare Zeit nicht unterstützt wird (siehe diesen Kommentar in Django Bug Tracker: code.djangoproject.com/ticket/). 29655 # comment: 3 ), sodass Sie wahrscheinlich auf einige Mängel stoßen, z. B. wenn die Authentifizierung nicht mehr funktioniert ( code.djangoproject.com/ticket/29655 ), wenn Sie die Validierung für alle Modelle aktivieren. Sie müssen sich selbst mit solchen Problemen befassen. Es gibt jedoch keinen besseren Ansatz atm.
Evgeny A.

2
Ab Django 2.2.3 verursacht dies ein Problem mit dem Basisauthentifizierungssystem. Du wirst eine bekommen ValidationError: Session with this Session key already exists. Um dies zu vermeiden, müssen Sie eine if-Anweisung für hinzufügen, um sender in list_of_model_classeszu verhindern, dass das Signal die Standardauthentifizierungsmodelle von Django überschreibt. Definieren list_of_model_classesSie, wie Sie möchten
Addison Klinke

15

Der einfachste Weg, die full_cleanMethode aufzurufen , besteht darin, die saveMethode in Ihrem Verzeichnis zu überschreiben model:

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

Warum ist das besser (oder schlechter) als ein Signal zu verwenden?
J__

6
Ich sehe zwei Probleme mit diesem Ansatz: 1) Wenn ModelForms full_clean () zweimal aufgerufen wird: durch das Formular und durch das Speichern. 2) Wenn das Formular einige Felder ausschließt, werden sie dennoch durch das Speichern validiert.
Mehmet

3

Anstatt einen Code einzufügen, der einen Empfänger deklariert, können wir eine App als INSTALLED_APPSAbschnitt in verwendensettings.py

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

Zuvor müssen Sie möglicherweise django-fullcleanPyPI installieren :

pip install django-fullclean

13
Warum sollten Sie pip installeine App mit 4 Codezeilen verwenden (überprüfen Sie den Quellcode ), anstatt diese Zeilen selbst zu schreiben?
David D.

Eine andere Bibliothek, die ich selbst nicht ausprobiert habe: github.com/danielgatis/django-smart-save
Flimm

2

Wenn Sie ein Modell haben, für das Sie sicherstellen möchten, dass es mindestens eine FK-Beziehung hat, und das Sie nicht verwenden möchten, null=Falseda hierfür eine Standard-FK festgelegt werden muss (bei der es sich um Mülldaten handelt), ist der beste Weg, den ich mir ausgedacht habe benutzerdefinierte .clean()und .save()Methoden hinzufügen . .clean()Löst den Validierungsfehler aus und .save()ruft die Bereinigung auf. Auf diese Weise wird die Integrität sowohl von Formularen als auch von anderen aufrufenden Codes, der Befehlszeile und Tests erzwungen. Ohne dies gibt es (AFAICT) keine Möglichkeit, einen Test zu schreiben, der sicherstellt, dass ein Modell eine FK-Beziehung zu einem speziell ausgewählten (nicht standardmäßigen) anderen Modell hat.

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

Kommentar zu @Alfred Huangs Antwort und Kommentaren dazu. Sie können den pre_save-Hook für eine App sperren, indem Sie eine Liste von Klassen im aktuellen Modul (models.py) definieren und im pre_save-Hook dagegen prüfen:

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()
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.