Как определить новый объект в пользовательском методе save () модели django?

172

Я хочу вызвать специальное действие в методе save () объекта модели Django, когда я сохраняю новую запись (не обновляя существующую запись).

Является ли проверка (self.id! = None) необходимой и достаточной, чтобы гарантировать, что собственная запись является новой и не обновляется? Какие-нибудь особые случаи это может упускать из виду?

MikeN
источник
Пожалуйста, выберите stackoverflow.com/a/35647389/8893667 в качестве правильного ответа. Ответ не работает во многих случаях, какUUIDField pk
Kotlinboy

Ответы:

204

Обновлено: с пояснением, self._stateкоторое не является частной переменной экземпляра, но названо таким образом, чтобы избежать конфликтов, проверка self._state.addingтеперь является предпочтительным способом проверки.


self.pk is None:

возвращает True в новом объекте Model, если только объект не имеет UUIDFieldего primary_key.

Угловой случай, о котором вам, возможно, придется беспокоиться, это наличие ограничений уникальности для полей, отличных от идентификатора (например, вторичных уникальных индексов для других полей). В этом случае у вас все еще может быть новая запись, но вы не сможете ее сохранить.

Дейв В. Смит
источник
20
Вы должны использовать, is notа не !=при проверке идентичности с Noneобъектом
Бен Джеймс
3
Не все модели имеют атрибут id, то есть модель, расширяющую другую через a models.OneToOneField(OtherModel, primary_key=True). Я думаю, что вам нужно использоватьself.pk
AJP
4
Это может не работать в некоторых случаях. Пожалуйста, проверьте этот ответ: stackoverflow.com/a/940928/145349
fjsj
5
Это не правильный ответ. Если использовать UUIDFieldв качестве первичного ключа, self.pkникогда None.
Даниэль ван Флаймен
1
Примечание: этот ответ предшествовал UUIDField.
Дейв У. Смит
190

Альтернативный способ проверки self.pkмы можем проверить self._stateмодель

self._state.adding is True создание

self._state.adding is False обновление

Я получил это с этой страницы

SaintTail
источник
12
Это единственный правильный способ использования настраиваемого поля первичного ключа.
webtweakers
9
Не уверен во всех деталях того, как self._state.addingработает, но справедливое предупреждение, что оно кажется всегда равным, Falseесли вы проверяете его после вызова super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ …
agilgur5
1
Это правильный путь, и он должен быть поставлен как правильный ответ.
flungo
7
@guival: _stateне частный; например _meta, с префиксом подчеркивания, чтобы избежать путаницы с именами полей. (Обратите внимание на то, как это используется в связанной документации.)
Ry
2
Это лучший способ. Я использовал is_new = self._state.addingтогда super(MyModel, self).save(*args, **kwargs)и потомif is_new: my_custom_logic()
котрфа
45

Проверка self.idпредполагает, что idэто первичный ключ для модели. Более общий способ - использовать ярлык pk .

is_new = self.pk is None

Gerry
источник
15
Pro Tip: поместите это ПЕРЕДsuper(...).save() .
sbdchd
39

Проверка на self.pk == Noneэто не достаточно , чтобы определить , если объект будет вставлен или обновлены в базе данных.

Django O / RM имеет особенно неприятный хак, который в основном должен проверить, есть ли что-то в позиции PK и, если это так, сделать UPDATE, в противном случае сделать INSERT (это оптимизируется до INSERT, если PK - None).

Причина, по которой он должен это делать, заключается в том, что вам разрешено устанавливать PK при создании объекта. Хотя это редко встречается, когда у вас есть столбец последовательности для первичного ключа, это не относится к другим типам полей первичного ключа.

Если вы действительно хотите знать, вы должны делать то, что делает O / RM, и искать в базе данных.

Конечно, в вашем коде есть конкретный случай, и вполне вероятно, что он self.pk == Noneговорит вам все, что вам нужно знать, но это не общее решение.

KayEss
источник
Хорошая точка зрения! Я могу избежать неприятностей с этим в моем приложении (проверяя первичный ключ None), потому что я никогда не устанавливал pk для новых объектов. Но это определенно не будет хорошей проверкой для повторно используемого плагина или части фреймворка.
MikeN
1
Это особенно верно, когда вы назначаете первичный ключ самостоятельно и через базу данных. В этом случае самое верное, что нужно сделать, это совершить поездку в БД.
Константин М
1
Даже если код вашего приложения не указывает явно pks, приспособления для ваших тестовых случаев могут. Хотя, поскольку они обычно загружаются перед тестами, это не может быть проблемой.
Risadinha
1
Это особенно верно в случае использования UUIDFieldв качестве первичного ключа: ключ не заполняется на уровне БД, поэтому self.pkвсегда True.
Даниэль ван Флаймен
10

Вы можете просто подключиться к сигналу post_save, который посылает «созданные» kwargs, если true, ваш объект был вставлен.

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

Дж. Ф. Саймон
источник
8
Это может потенциально вызвать условия гонки, если есть большая нагрузка. Это потому, что сигнал post_save отправляется при сохранении, но до того, как транзакция была зафиксирована. Это может быть проблематично и может затруднить отладку.
Абель Молер
Я не уверен, что что-то изменилось (из более старых версий), но мои обработчики сигналов вызываются в одной и той же транзакции, поэтому сбой в любом месте откатывает всю транзакцию. Я использую ATOMIC_REQUESTS, так что я не совсем уверен по умолчанию.
Тим
7

Проверьте self.idи force_insertфлаг.

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

Это удобно, потому что ваш вновь созданный объект (self) имеет pkзначение

Квав Аннор
источник
5

Я очень опаздываю на этот разговор, но столкнулся с проблемой заполнения self.pk, когда с ним связано значение по умолчанию.

Как я обошёл это, добавив поле date_created к модели

date_created = models.DateTimeField(auto_now_add=True)

Отсюда вы можете пойти

created = self.date_created is None

Иордания
источник
4

Для решения, которое также работает, даже если у вас есть UUIDFieldпервичный ключ (что, как отметили другие, Noneесли вы просто переопределите save), вы можете подключиться к сигналу Django post_save . Добавьте это к своим 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

Этот обратный вызов заблокирует saveметод, так что вы можете делать такие вещи, как триггерные уведомления или обновлять модель, прежде чем ваш ответ будет отправлен обратно по проводной связи, используете ли вы формы или инфраструктуру Django REST для вызовов AJAX. Конечно, используйте ответственно и переносите тяжелые задачи в очередь на работу вместо того, чтобы заставлять пользователей ждать :)

metakermit
источник
3

лучше использовать pk вместо id :

if not self.pk:
  do_something()
yedpodtrzitko
источник
1

Это обычный способ сделать это.

Идентификатор будет дан при первом сохранении в БД

vikingosegundo
источник
0

Будет ли это работать для всех вышеупомянутых сценариев?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
Сэчин
источник
это может вызвать дополнительный удар по базе данных.
Дэвид Шуман
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)
Свелан Огюст
источник
Используя cleaned_data.get (), я смог определить, есть ли у меня экземпляр, у меня также был CharField, где null и пусто, где true. Это будет обновляться при каждом обновлении в соответствии с зарегистрированным пользователем
Swelan Auguste
-3

Чтобы узнать, обновляете ли вы или вставляете объект (данные), используйте self.instance.fieldnameв своей форме. Определите чистую функцию в своей форме и проверьте, совпадает ли текущая запись значения с предыдущей, если нет, то вы обновляете ее.

self.instanceи self.instance.fieldnameсравнить с новым значением

ha22109
источник