Wie sollten Sie in einer benutzerdefinierten save () -Methode eines Django-Modells ein neues Objekt identifizieren?

171

Ich möchte eine spezielle Aktion in der save () -Methode eines Django-Modellobjekts auslösen, wenn ich einen neuen Datensatz speichere (ohne einen vorhandenen Datensatz zu aktualisieren).

Ist die Überprüfung auf (self.id! = None) notwendig und ausreichend, um sicherzustellen, dass der Self Record neu ist und nicht aktualisiert wird? Gibt es Sonderfälle, die dies übersehen könnte?

MikeN
quelle
Bitte wählen Sie stackoverflow.com/a/35647389/8893667 als richtige Antwort. Die Antwort funktioniert in vielen Fällen nicht wie einUUIDField pk
Kotlinboy

Antworten:

203

Aktualisiert: Mit der Klarstellung, dass self._statees sich nicht um eine private Instanzvariable handelt, sondern so benannt wurde, um Konflikte zu vermeiden, self._state.addingist die Überprüfung jetzt die bevorzugte Methode zur Überprüfung.


self.pk is None:

Gibt True innerhalb eines neuen Modellobjekts zurück, es sei denn, das Objekt hat ein UUIDFieldas als sein primary_key.

Der Eckfall, über den Sie sich möglicherweise Sorgen machen müssen, ist, ob es Eindeutigkeitsbeschränkungen für andere Felder als die ID gibt (z. B. sekundäre eindeutige Indizes für andere Felder). In diesem Fall haben Sie möglicherweise noch einen neuen Datensatz in der Hand, können ihn jedoch nicht speichern.

Dave W. Smith
quelle
20
Sie sollten is noteher verwenden als !=bei der Überprüfung der Identität mit dem NoneObjekt
Ben James
3
Nicht alle Modelle haben ein ID-Attribut, dh ein Modell, das ein anderes durch a erweitert models.OneToOneField(OtherModel, primary_key=True). Ich denke, Sie müssen verwendenself.pk
AJP
4
Dies kann in einigen Fällen nicht funktionieren. Bitte überprüfen Sie diese Antwort: stackoverflow.com/a/940928/145349
fjsj
5
Dies ist nicht die richtige Antwort. Wenn Sie a UUIDFieldals Primärschlüssel verwenden, self.pkist dies niemals der Fall None.
Daniel van Flymen
1
Randnotiz: Diese Antwort datiert vor UUIDField.
Dave W. Smith
190

Alternativ zur Überprüfung können self.pkwir self._statedas Modell überprüfen

self._state.adding is True Erstellen

self._state.adding is False Aktualisierung

Ich habe es von dieser Seite bekommen

SaintTail
quelle
12
Dies ist der einzig richtige Weg, wenn Sie ein benutzerdefiniertes Primärschlüsselfeld verwenden.
Webtweakers
9
Ich bin mir nicht sicher, wie es self._state.addingfunktioniert, aber eine faire Warnung, dass es immer gleich zu sein scheint, Falsewenn Sie es nach dem Anruf überprüfen super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ …
agilgur5
1
Dies ist der richtige Weg und sollte als die richtige Antwort bewertet werden.
Flungo
7
@guival: _stateist nicht privat; wie _meta, ist es mit einem Unterstrich zu vermeiden Verwechslungen mit Feldnamen vorangestellt. (Beachten Sie, wie es in der verknüpften Dokumentation verwendet wird.)
Ry-
2
Dies ist der beste Weg. Ich benutzte is_new = self._state.addingdann super(MyModel, self).save(*args, **kwargs)und dannif is_new: my_custom_logic()
Kotrfa
45

Bei der Überprüfung wird self.iddavon ausgegangen , dass dies idder Primärschlüssel für das Modell ist. Ein allgemeinerer Weg wäre die Verwendung der pk-Verknüpfung .

is_new = self.pk is None

Gerry
quelle
15
Pro-Tipp: Setzen Sie dies VOR dem super(...).save().
sbdchd
39

Die Prüfung auf self.pk == Noneist nicht ausreichend , um zu bestimmen , ob das Objekt wird in der Datenbank eingefügt oder aktualisiert werden.

Das Django O / RM verfügt über einen besonders bösen Hack, bei dem im Grunde genommen überprüft wird, ob sich etwas an der PK-Position befindet, und wenn ja, ein UPDATE durchgeführt wird, andernfalls ein INSERT (dies wird zu einem INSERT optimiert, wenn die PK None ist).

Der Grund dafür ist, dass Sie die PK festlegen dürfen, wenn ein Objekt erstellt wird. Obwohl dies nicht üblich ist, wenn Sie eine Sequenzspalte für den Primärschlüssel haben, gilt dies nicht für andere Arten von Primärschlüsselfeldern.

Wenn Sie wirklich wissen wollen, müssen Sie das tun, was der O / RM tut, und in der Datenbank suchen.

Natürlich haben Sie einen bestimmten Fall in Ihrem Code und dafür ist es sehr wahrscheinlich, self.pk == Nonedass er Ihnen alles sagt, was Sie wissen müssen, aber es ist keine allgemeine Lösung.

KayEss
quelle
Guter Punkt! Ich kann damit in meiner Anwendung durchkommen (nach Keine Primärschlüssel suchen), weil ich das pk nie für neue Objekte festgelegt habe. Dies wäre jedoch definitiv keine gute Überprüfung für ein wiederverwendbares Plugin oder einen Teil des Frameworks.
MikeN
1
Dies gilt insbesondere dann, wenn Sie den Primärschlüssel selbst und über die Datenbank zuweisen. In diesem Fall ist es am sichersten, einen Ausflug in die Datenbank zu machen.
Constantine M
1
Selbst wenn Ihr Anwendungscode pks nicht explizit angibt, können die Fixtures für Ihre Testfälle. Da sie üblicherweise vor den Tests geladen werden, ist dies möglicherweise kein Problem.
Risadinha
1
Dies gilt insbesondere für die Verwendung von a UUIDFieldals Primärschlüssel: Der Schlüssel wird nicht auf DB-Ebene ausgefüllt, sondern self.pkimmer True.
Daniel van Flymen
10

Sie können sich einfach mit dem Signal post_save verbinden, das ein "erstelltes" kwargs sendet. Wenn true, wurde Ihr Objekt eingefügt.

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

JF Simon
quelle
8
Dies kann möglicherweise zu Rennbedingungen führen, wenn viel Last vorhanden ist. Dies liegt daran, dass das Signal post_save beim Speichern gesendet wird, jedoch bevor die Transaktion festgeschrieben wurde. Dies kann problematisch sein und das Debuggen sehr erschweren.
Abel Mohler
Ich bin nicht sicher, ob sich etwas geändert hat (gegenüber älteren Versionen), aber meine Signalhandler werden innerhalb derselben Transaktion aufgerufen, sodass ein Fehler die gesamte Transaktion rückgängig macht. Ich verwende ATOMIC_REQUESTS, daher bin ich mir über die Standardeinstellung nicht sicher.
Tim Tisdall
7

Überprüfen Sie für self.idund die force_insertFlagge.

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

Dies ist praktisch, da Ihr neu erstelltes Objekt (Selbst) seinen pkWert hat

Kwaw Annor
quelle
5

Ich bin sehr spät zu dieser Konversation gekommen, aber ich bin auf ein Problem mit der Datei self.pk gestoßen, die ausgefüllt wird, wenn ihr ein Standardwert zugeordnet ist.

Um dies zu umgehen, füge ich dem Modell ein Feld date_created hinzu

date_created = models.DateTimeField(auto_now_add=True)

Von hier aus können Sie gehen

created = self.date_created is None

Jordanien
quelle
4

Für eine Lösung, die auch dann funktioniert, wenn Sie UUIDFieldeinen Primärschlüssel haben (was, wie andere angemerkt haben, nicht der Fall ist, Nonewenn Sie nur überschreiben save), können Sie das post_save- Signal von Django anschließen . Fügen Sie dies Ihrer models.py hinzu :

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

Dieser Rückruf blockiert die saveMethode, sodass Sie beispielsweise Benachrichtigungen auslösen oder das Modell weiter aktualisieren können, bevor Ihre Antwort über das Netzwerk zurückgesendet wird, unabhängig davon, ob Sie Formulare oder das Django REST-Framework für AJAX-Aufrufe verwenden. Verwenden Sie verantwortungsbewusst und verlagern Sie schwere Aufgaben in eine Jobwarteschlange, anstatt Ihre Benutzer warten zu lassen :)

Metakermit
quelle
3

Verwenden Sie lieber pk anstelle von id :

if not self.pk:
  do_something()
yedpodtrzitko
quelle
1

Dies ist der übliche Weg.

Die ID wird beim ersten Speichern in der Datenbank angegeben

vikingosegundo
quelle
0

Würde dies für alle oben genannten Szenarien funktionieren?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
Sachin
quelle
Dies würde einen zusätzlichen Datenbanktreffer verursachen.
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)
Swelan Auguste
quelle
Mit clean_data.get () konnte ich feststellen, ob ich eine Instanz hatte, und ich hatte auch ein CharField, wo null und leer, wo true. Dies wird bei jedem Update entsprechend dem angemeldeten Benutzer
aktualisiert
-3

Verwenden Sie self.instance.fieldnamein Ihrem Formular , um festzustellen, ob Sie das Objekt (Daten) aktualisieren oder einfügen . Definieren Sie eine Bereinigungsfunktion in Ihrem Formular und prüfen Sie, ob der aktuelle Werteintrag mit dem vorherigen übereinstimmt. Wenn nicht, aktualisieren Sie ihn.

self.instanceund self.instance.fieldnamemit dem neuen Wert vergleichen

ha22109
quelle