In un metodo save () personalizzato del modello django, come dovresti identificare un nuovo oggetto?


172

Voglio innescare un'azione speciale nel metodo save () di un oggetto modello Django quando sto salvando un nuovo record (non aggiornando un record esistente).

Il controllo per (self.id! = Nessuno) è necessario e sufficiente per garantire che l'autoregistrazione sia nuova e non venga aggiornata? Qualche caso speciale che potrebbe essere trascurato?


Seleziona stackoverflow.com/a/35647389/8893667 come risposta corretta. La risposta non funziona in molti casi come unUUIDField pk
Kotlinboy del

Risposte:


204

Aggiornato: con il chiarimento che self._statenon è una variabile di istanza privata, ma denominata in questo modo per evitare conflitti, il controllo self._state.addingè ora il modo preferibile per controllare.


self.pk is None:

restituisce True in un nuovo oggetto Model, a meno che l'oggetto non abbia un UUIDFieldas come suo primary_key.

Il caso angolare di cui potresti preoccuparti è se ci sono vincoli di unicità su campi diversi dall'id (ad esempio, indici univoci secondari su altri campi). In tal caso, potresti avere ancora un nuovo record in mano, ma non essere in grado di salvarlo.


20
Dovresti usarlo is notpiuttosto che !=quando controlli l'identità con l' Noneoggetto
Ben James,

3
Non tutti i modelli hanno un attributo id, ovvero un modello che ne estende un altro tramite a models.OneToOneField(OtherModel, primary_key=True). Penso che devi usareself.pk
AJP

4
Questo potrebbe NON funzionare in alcuni casi. Si prega di verificare questa risposta: stackoverflow.com/a/940928/145349
fjsj

5
Questa non è la risposta corretta Se si utilizza un UUIDFieldcome chiave primaria, self.pknon lo è mai None.
Daniel van Flymen,

1
Nota a margine: questa risposta è precedente a UUIDField.
Dave W. Smith,

190

Modo alternativo al controllo self.pkpossiamo verificare self._stateil modello

self._state.adding is True la creazione di

self._state.adding is False in aggiornamento

L'ho preso da questa pagina


12
Questo è l'unico modo corretto quando si utilizza un campo chiave primaria personalizzato.
webtweakers,

9
Non sono sicuro di tutti i dettagli di come self._state.addingfunziona, ma un avvertimento equo che sembra sempre uguale Falsese lo controlli dopo aver chiamato super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ ...
Agilgur5,

1
Questo è il modo corretto e dovrebbe essere votato / impostato come risposta corretta.
flungo,

7
@guival: _statenon è privato; come _meta, è preceduto da un carattere di sottolineatura per evitare confusione con i nomi dei campi. (Notate come viene usato nella documentazione collegata.)
Ry-

2
Questo è il modo migliore Ho usato is_new = self._state.adding, allora super(MyModel, self).save(*args, **kwargs)e poiif is_new: my_custom_logic()
kotrfa il

45

Il controllo self.idpresuppone che idsia la chiave primaria per il modello. Un modo più generico sarebbe usare il collegamento pk .

is_new = self.pk is None


15
Suggerimento Pro: metti questo PRIMA del super(...).save().
sbdchd,

39

Il controllo per nonself.pk == None è sufficiente per determinare se l'oggetto verrà inserito o aggiornato nel database.

Il Django O / RM presenta un hack particolarmente brutto che sostanzialmente controlla se c'è qualcosa nella posizione PK e in tal caso esegui un AGGIORNAMENTO, altrimenti esegui un INSERT (questo viene ottimizzato su un INSERT se il PK è None).

Il motivo per cui deve farlo è perché ti è permesso impostare il PK quando viene creato un oggetto. Sebbene non sia comune se si dispone di una colonna di sequenza per la chiave primaria, ciò non vale per altri tipi di campo chiave primaria.

Se vuoi davvero sapere che devi fare ciò che fa O / RM e cercare nel database.

Naturalmente avete un caso specifico nel codice e per questo è molto probabile che self.pk == Nonetutto quello che c'è da sapere dice, ma è non è una soluzione generale.


Buon punto! Posso evitarlo nella mia applicazione (controllando la chiave primaria None) perché non ho mai impostato il pk per nuovi oggetti. Ma questo non sarebbe sicuramente un buon controllo per un plug-in riutilizzabile o parte del framework.
MikeN,

1
Ciò è particolarmente vero quando si assegna la chiave primaria da soli e tramite il database. In tal caso, la cosa più sicura da fare è fare un viaggio nel db.
Costantino M,

1
Anche se il codice dell'applicazione non specifica esplicitamente pks, le apparecchiature per i casi di test potrebbero. Tuttavia, poiché vengono comunemente caricati prima dei test, potrebbe non essere un problema.
Risadinha,

1
Ciò è particolarmente vero nel caso di utilizzo di una UUIDFieldchiave primaria: la chiave non viene popolata a livello di DB, quindi lo self.pkè sempre True.
Daniel van Flymen,

10

Potresti semplicemente connetterti al segnale post_save che invia un kwargs "creato", se vero, il tuo oggetto è stato inserito.

http://docs.djangoproject.com/en/stable/ref/signals/#post-save


8
Ciò può potenzialmente causare condizioni di gara se c'è molto carico. Questo perché il segnale post_save viene inviato al momento del salvataggio, ma prima del commit della transazione. Questo può essere problematico e può rendere molto difficile il debug delle cose.
Abel Mohler,

Non sono sicuro che le cose siano cambiate (dalle versioni precedenti), ma i miei gestori di segnale vengono chiamati all'interno della stessa transazione, quindi un errore ovunque ripristina l'intera transazione. Sto usando ATOMIC_REQUESTS, quindi non sono davvero sicuro del valore predefinito.
Tim Tisdall,

7

Controlla self.ide la force_insertbandiera.

if not self.pk or kwargs.get('force_insert', False):
    self.created = True

# call save method.
super(self.__class__, self).save(*args, **kwargs)

#Do all your post save actions in the if block.
if getattr(self, 'created', False):
    # So something
    # Do something else

Questo è utile perché l'oggetto appena creato (sé) ha il suo pkvalore


5

Sono in ritardo a questa conversazione, ma ho riscontrato un problema con il popolamento self.pk quando è associato un valore predefinito.

Il modo in cui l'ho aggirato è l'aggiunta di un campo date_created al modello

date_created = models.DateTimeField(auto_now_add=True)

Da qui puoi andare

created = self.date_created is None


4

Per una soluzione che funziona anche quando hai una UUIDFieldcome chiave primaria (che come altri hanno notato non Noneè solo se esegui l' override save), puoi collegarti al segnale post_save di Django . Aggiungi questo al tuo model.py :

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

@receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
    if created:
        # do extra work on your instance, e.g.
        # instance.generate_avatar()
        # instance.send_email_notification()
        pass

Questo callback bloccherà il savemetodo, quindi puoi fare cose come innescare le notifiche o aggiornare ulteriormente il modello prima che la tua risposta sia rispedita via cavo, sia che tu stia usando i moduli o il framework Django REST per le chiamate AJAX. Naturalmente, usa responsabilmente e scarica compiti pesanti in una coda di lavoro invece di far aspettare i tuoi utenti :)



1

È il modo comune per farlo.

l'id verrà dato mentre salvato la prima volta nel db


0

Funzionerebbe con tutti questi scenari?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...

questo provocherebbe un ulteriore colpo al database.
David Schumann,

0
> def save_model(self, request, obj, form, change):
>         if form.instance._state.adding:
>             form.instance.author = request.user
>             super().save_model(request, obj, form, change)
>         else:
>             obj.updated_by = request.user.username
> 
>             super().save_model(request, obj, form, change)

Usando clean_data.get (), sono stato in grado di determinare se avevo un'istanza, avevo anche un CharField dove nullo e vuoto dove vero. Questo verrà aggiornato ad ogni aggiornamento in base all'utente che ha effettuato l'accesso
Swelan Auguste

-3

Per sapere se stai aggiornando o inserendo l'oggetto (dati), usa self.instance.fieldnamenel tuo modulo. Definisci una funzione pulita nel tuo modulo e controlla se la voce del valore corrente è la stessa della precedente, in caso contrario la stai aggiornando.

self.instancee self.instance.fieldnameconfronta con il nuovo valore

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.