Perché il modello.save () di django non chiama full_clean ()?


150

Sono solo curioso di sapere se qualcuno sa se c'è una buona ragione per cui orm di django non chiama 'full_clean' su un modello a meno che non venga salvato come parte di un modulo modello.

Si noti che full_clean () non verrà chiamato automaticamente quando si chiama il metodo save () del modello. Dovrai chiamarlo manualmente quando desideri eseguire la convalida del modello in un passaggio per i tuoi modelli creati manualmente. django è un documento completamente pulito

(NOTA: la citazione aggiornata per Django 1.6 ... i precedenti documenti di django avevano anche un avvertimento su ModelForms.)

Ci sono buoni motivi per cui le persone non vogliono questo comportamento? Penserei che se ti prendessi il tempo per aggiungere la convalida a un modello, vorresti che quella convalida venga eseguita ogni volta che il modello viene salvato.

So come far funzionare tutto correttamente, sto solo cercando una spiegazione.


11
Grazie mille per questa domanda, mi ha impedito di sbattere la testa contro il muro molto più tempo. Ho creato un mixin che potrebbe aiutare gli altri. Dai
un'occhiata

E finalmente uso il segnale per catturare il pre_savegancio e fare full_cleansu tutti i modelli catturati.
Alfred Huang,

Risposte:


59

AFAIK, questo è dovuto alla compatibilità con le versioni precedenti. Ci sono anche problemi con ModelForms con campi esclusi, modelli con valori predefiniti, segnali pre_save (), ecc.

Fonti in cui potresti essere interessato:


3
L'estratto più utile (IMHO) dal secondo riferimento: "Sviluppare un'opzione di convalida" automatica "che sia abbastanza semplice da essere effettivamente utile e abbastanza robusto da gestire tutti i casi limite è - se possibile - molto più di può essere realizzato nel tempo 1.2. Quindi, per ora, Django non ha nulla del genere, e non lo avrà in 1.2. Se pensi di poter farlo funzionare per 1.3, la tua scommessa migliore è quella di elaborare un proposta, incluso almeno un po 'di codice di esempio, insieme a una spiegazione di come la manterrai semplice e robusta. "
Josh,

30

A causa della compatibilità considerata, la pulizia automatica al salvataggio non è abilitata nel kernel di django.

Se stiamo avviando un nuovo progetto e desideriamo che il savemetodo predefinito su Modello possa essere pulito automaticamente, possiamo utilizzare il seguente segnale per eseguire la pulizia prima di salvare ogni modello.

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
Perché è meglio (o peggio) che sovrascrivere il metodo save su alcuni BaseModel (che tutti gli altri erediteranno) per chiamare prima full_clean, quindi chiamare super ()?
J__

7
Vedo due problemi con questo approccio 1) nel caso in cui full_clean () di ModelForm venga chiamato due volte: dal modulo e dal segnale 2) Se il modulo esclude alcuni campi, verrebbero comunque convalidati dal segnale.
Mehmet,

1
@mehmet Quindi potresti aggiungerli if send == somemodel, then exclude some fieldsinpre_save_handler
Simin Jie,

4
Per coloro che utilizzano o stanno prendendo in considerazione l'utilizzo di questo approccio: tenere presente che questo approccio non è ufficialmente supportato da Django e non sarà supportato nel prossimo futuro (vedere questo commento nel tracker di bug di Django: code.djangoproject.com/ticket/ 29655 # comment: 3 ), quindi è probabile che si verifichino alcune imperfezioni come l'autenticazione che smette di funzionare ( code.djangoproject.com/ticket/29655 ) se si abilita la convalida per tutti i modelli. Dovrai affrontare questi problemi da solo. Tuttavia, non esiste un approccio migliore atm.
Evgeny A.

2
A partire da Django 2.2.3, ciò causa un problema con il sistema di autenticazione di base. Otterrai un ValidationError: Session with this Session key already exists. Per evitare ciò, è necessario aggiungere un'istruzione if per sender in list_of_model_classesimpedire al segnale di sovrascrivere i modelli di autorizzazione predefiniti di Django. Definisci come list_of_model_classespreferisci
Addison Klinke,

15

Il modo più semplice per chiamare il full_cleanmetodo è solo quello di sovrascrivere il savemetodo in model:

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

Perché è meglio (o peggio) dell'uso di un segnale?
J__

6
Vedo due problemi con questo approccio 1) in caso di full_clean () di ModelForm verrebbe chiamato due volte: dal modulo e dal salvataggio 2) Se il modulo esclude alcuni campi, verrebbero comunque convalidati dal salvataggio.
Mehmet,

3

Invece di inserire un pezzo di codice che dichiara un destinatario, possiamo usare un'app come INSTALLED_APPSsezione insettings.py

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

Prima di ciò, potrebbe essere necessario installare django-fullcleanutilizzando PyPI:

pip install django-fullclean

13
Perché dovresti avere pip installqualche app con 4 righe di codice (controlla il codice sorgente ) invece di scrivere queste righe tu stesso?
David D.

Un'altra libreria che non ho provato io stesso: github.com/danielgatis/django-smart-save
Flimm

2

Se si dispone di un modello che si desidera garantire abbia almeno una relazione FK e non si desidera utilizzare null=Falseperché ciò richiede l'impostazione di un FK predefinito (che sarebbe dati inutili), il modo migliore che ho trovato è per aggiungere personalizzazioni .clean()e .save()metodi. .clean()genera l'errore di convalida e .save()chiama clean. In questo modo l'integrità viene applicata sia dai moduli sia da altro codice chiamante, dalla riga di comando e dai test. Senza questo, non esiste (AFAICT) un modo per scrivere un test che assicuri che un modello abbia una relazione FK con un altro modello scelto (non predefinito).

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

Commentando la risposta di @Alfred Huang e la commenta. Si potrebbe bloccare l'hook pre_save in basso su un'app definendo un elenco di classi nel modulo corrente (models.py) e verificando contro di esso nell'hook 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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.