Jak należy zidentyfikować nowy obiekt w niestandardowej metodzie save () modelu django?

172

Chcę wyzwolić specjalną akcję w metodzie save () obiektu modelu Django, kiedy zapisuję nowy rekord (nie aktualizuję istniejącego rekordu).

Czy sprawdzenie (self.id! = None) jest konieczne i wystarczające, aby zagwarantować, że zapis własny jest nowy i nie jest aktualizowany? Czy są jakieś szczególne przypadki, które może przeoczyć?

MikeN
źródło
Wybierz stackoverflow.com/a/35647389/8893667 jako poprawną odpowiedź. Odpowiedź nie działa w wielu przypadkach, takich jakUUIDField pk
Kotlinboy,

Odpowiedzi:

204

Zaktualizowano: Z wyjaśnieniem, że self._statenie jest to zmienna prywatnej instancji, ale nazwana w ten sposób, aby uniknąć konfliktów, sprawdzanie self._state.addingjest teraz preferowanym sposobem sprawdzania.


self.pk is None:

zwraca True w nowym obiekcie Model, chyba że obiekt ma UUIDFieldjako swój primary_key.

Problem narożny, o który możesz się martwić, dotyczy tego, czy istnieją ograniczenia unikalności w polach innych niż id (np. Drugorzędne unikalne indeksy w innych polach). W takim przypadku nadal możesz mieć w ręku nowy rekord, ale nie możesz go zapisać.

Dave W. Smith
źródło
20
Powinieneś is notraczej używać niż !=podczas sprawdzania tożsamości z Noneobiektem
Ben James
3
Nie wszystkie modele mają atrybut id, tj. Model rozszerzający inny poprzez rozszerzenie models.OneToOneField(OtherModel, primary_key=True). Myślę, że musisz użyćself.pk
AJP
4
W niektórych przypadkach MOŻE NIE DZIAŁAĆ. Sprawdź tę odpowiedź: stackoverflow.com/a/940928/145349
fjsj
5
To nie jest poprawna odpowiedź. Jeśli używasz UUIDFieldjako klucza podstawowego, self.pknigdy nie jest None.
Daniel van Flymen
1
Uwaga dodatkowa: ta odpowiedź była przed datą UUIDField.
Dave W. Smith
190

Alternatywnym sposobem sprawdzenia self.pkjest sprawdzenie self._statemodelu

self._state.adding is True tworzenie

self._state.adding is False aktualizacja

Mam to z tej strony

SaintTail
źródło
12
To jedyny prawidłowy sposób korzystania z niestandardowego pola klucza podstawowego.
webtweakers
9
Nie jestem pewien wszystkich szczegółów, jak self._state.addingdziała, ale uczciwe ostrzeżenie, że wydaje się zawsze równe, Falsejeśli sprawdzasz to po wywołaniu super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ …
agilgur5
1
To jest właściwy sposób i powinien zostać uznany za poprawny / ustawiony jako poprawna odpowiedź.
flungo,
7
@guival: _statenie jest prywatne; na przykład _metajest poprzedzony podkreśleniem, aby uniknąć pomyłki z nazwami pól. (Zwróć uwagę, jak jest używany w
dołączonej
2
To najlepszy sposób. Kiedyś is_new = self._state.adding, wtedy super(MyModel, self).save(*args, **kwargs)i potemif is_new: my_custom_logic()
kotrfa
45

Sprawdzanie self.idzakłada, że idjest to klucz podstawowy dla modelu. Bardziej ogólnym sposobem byłoby użycie skrótu pk .

is_new = self.pk is None

Gerry
źródło
15
Pro Tip: umieścić ten PRZEDsuper(...).save() .
sbdchd
39

Sprawdzenie nieself.pk == None jest wystarczające, aby określić, czy obiekt ma zostać wstawiony lub zaktualizowany w bazie danych.

Django O / RM zawiera szczególnie paskudny hack, który polega po prostu na sprawdzeniu, czy coś jest na pozycji PK, a jeśli tak, wykonaj AKTUALIZACJĘ, w przeciwnym razie wykonaj INSERT (zostanie zoptymalizowany do INSERT, jeśli PK ma wartość None).

Powodem, dla którego musi to zrobić, jest to, że możesz ustawić PK podczas tworzenia obiektu. Chociaż nie jest to powszechne, gdy masz kolumnę sekwencji dla klucza podstawowego, nie dotyczy to innych typów pól klucza podstawowego.

Jeśli naprawdę chcesz wiedzieć, musisz zrobić to, co robi O / RM i zajrzeć do bazy danych.

Oczywiście masz konkretny przypadek w kodzie i za to, że jest całkiem prawdopodobne, że self.pk == Nonepowie Ci wszystko, co musisz wiedzieć, ale to nie ogólne rozwiązanie.

KayEss
źródło
Słuszna uwaga! Mogę sobie z tym poradzić w mojej aplikacji (sprawdzanie klucza podstawowego Brak), ponieważ nigdy nie ustawiłem pakietu PK dla nowych obiektów. Ale to zdecydowanie nie byłby dobry sprawdzian dla wtyczki wielokrotnego użytku lub części frameworka.
MikeN
1
Jest to szczególnie ważne, gdy przypisujesz klucz podstawowy samodzielnie i za pośrednictwem bazy danych. W takim przypadku najpewniejszą rzeczą do zrobienia jest wycieczka do bazy.
Constantine M
1
Nawet jeśli kod aplikacji nie określa jawnie pks, rozwiązania dla przypadków testowych mogą. Chociaż, ponieważ są one zwykle ładowane przed testami, może to nie stanowić problemu.
Risadinha
1
Jest to szczególnie ważne w przypadku używania UUIDFieldklucza podstawowego jako klucza podstawowego: klucz nie jest zapełniany na poziomie bazy danych, więc self.pkjest zawsze True.
Daniel van Flymen
10

Możesz po prostu połączyć się z sygnałem post_save, który wysyła "utworzone" kwargi, jeśli prawda, to twój obiekt został wstawiony.

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

JF Simon
źródło
8
Może to potencjalnie spowodować warunki wyścigu, jeśli jest duże obciążenie. Dzieje się tak, ponieważ sygnał post_save jest wysyłany przy zapisywaniu, ale przed zatwierdzeniem transakcji. Może to być problematyczne i bardzo utrudniać debugowanie.
Abel Mohler
Nie jestem pewien, czy coś się zmieniło (od starszych wersji), ale moje programy obsługi sygnałów są wywoływane w ramach tej samej transakcji, więc awaria w dowolnym miejscu cofa całą transakcję. Używam ATOMIC_REQUESTS, więc nie jestem pewien co do wartości domyślnej.
Tim Tisdall
7

Sprawdź self.idi force_insertflagę.

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

Jest to przydatne, ponieważ nowo utworzony obiekt (jaźń) ma swoją pkwartość

Kwaw Annor
źródło
5

Jestem bardzo spóźniony na tę rozmowę, ale napotkałem problem z wypełnianiem pliku self.pk, gdy ma skojarzoną z nim wartość domyślną.

Sposób obejścia tego polega na dodaniu pola date_created do modelu

date_created = models.DateTimeField(auto_now_add=True)

Stąd możesz iść

created = self.date_created is None

Jordania
źródło
4

Aby uzyskać rozwiązanie, które działa również wtedy, gdy masz UUIDFieldklucz podstawowy (który, jak zauważyli inni, nie jest, Nonejeśli po prostu nadpisujesz save), możesz podłączyć się do sygnału post_save Django . Dodaj to do swojego models.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

To wywołanie zwrotne zablokuje savemetodę, więc możesz wykonywać takie czynności, jak powiadomienia wyzwalające lub aktualizować model dalej, zanim odpowiedź zostanie wysłana z powrotem przez sieć, niezależnie od tego, czy używasz formularzy, czy struktury Django REST do wywołań AJAX. Oczywiście używaj odpowiedzialnie i przeładowuj ciężkie zadania do kolejki zadań, zamiast czekać, aż użytkownicy będą czekać :)

metakermit
źródło
3

raczej użyj pk zamiast id :

if not self.pk:
  do_something()
yedpodtrzitko
źródło
1

Jest to powszechny sposób.

identyfikator zostanie nadany podczas pierwszego zapisania do bazy danych

vikingosegundo
źródło
0

Czy to zadziała we wszystkich powyższych scenariuszach?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
Sachin
źródło
spowodowałoby to dodatkowe trafienie w bazie danych.
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
źródło
Używając cleaner_data.get (), byłem w stanie określić, czy mam wystąpienie, miałem również CharField, gdzie null i puste, gdzie prawda. Będzie to aktualizowane przy każdej aktualizacji zgodnie z zalogowanym użytkownikiem
Swelan Auguste
-3

Aby wiedzieć, czy aktualizujesz, czy wstawiasz obiekt (dane), użyj self.instance.fieldnamew swoim formularzu. Zdefiniuj czystą funkcję w swoim formularzu i sprawdź, czy bieżąca wartość jest taka sama jak poprzednia, jeśli nie, to ją aktualizujesz.

self.instancei self.instance.fieldnameporównaj z nową wartością

ha22109
źródło