У моделі джанго користувацький метод save (), як слід визначити новий об'єкт?


172

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

Чи потрібний і достатній для перевірки (self.id! = None), щоб гарантувати самозапис, чи не оновлюється? Якісь особливі випадки, на які це може не помітити?


Будь ласка , виберіть stackoverflow.com/a/35647389/8893667 як правильну відповідь. Відповідь не працює у багатьох випадках, як-отUUIDField pk
Котлінбой

Відповіді:


204

Оновлено: З уточненням, що self._stateце не змінна приватна інстанція, але названа таким чином, щоб уникнути конфліктів, перевірка self._state.addingзараз є кращим способом перевірки.


self.pk is None:

повертає True у новому об'єкті Model, якщо тільки об'єкт не має UUIDFieldяк його primary_key.

Кутовий випадок, про який, можливо, вам доведеться потурбуватись, чи є обмеження унікальності для інших полів, ніж ідентифікатор (наприклад, вторинні унікальні індекси в інших полях). У такому випадку ви все ще можете мати новий запис у руці, але не зможете зберегти його.


20
Вам слід скористатися, is notа не !=перевіряти особистість Noneоб'єкта
Бен Джеймс

3
Не всі моделі мають атрибут id, тобто модель, що розширює іншу через 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 оновлення

Я отримав це з цієї сторінки


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()
kotrfa

45

Перевірка self.idпередбачає, що idце основний ключ для моделі. Більш загальним способом було б використання ярлика pk .

is_new = self.pk is None


15
Pro Tip: помістіть це ПЕРЕДsuper(...).save() .
sbdchd

39

Перевірка на self.pk == Noneце НЕ досить , щоб визначити , якщо об'єкт буде вставлений або оновлені в базі даних.

Django O / RM має особливо неприємний злом, який в основному полягає у тому, щоб перевірити, чи є щось на позиції PK, і якщо це зробити UPDATE, в іншому випадку зробіть INSERT (це оптимізується до ВСТАВКИ, якщо PK немає).

Причина, чому він повинен це робити, полягає в тому, що вам дозволяється встановлювати ПК при створенні об'єкта. Хоча це не часто, коли у вас є стовпець послідовності для основного ключа, це не застосовується для інших типів поля первинного ключа.

Якщо ви дійсно хочете знати, ви повинні робити те, що робить O / RM, і шукати в базі даних.

Звичайно, у вашому коді є конкретний випадок, і це, ймовірно, self.pk == Noneговорить про все, що вам потрібно знати, але це не загальне рішення.


Гарна думка! Я можу піти з цим у своїй програмі (перевірка на відсутність первинного ключа None), оскільки я ніколи не встановлював pk для нових об’єктів. Але це, безумовно, не буде гарною перевіркою на плагін для багаторазового використання або частину фреймворку.
MikeN

1
Це особливо справедливо, коли ви присвоюєте первинний ключ самостійно та через базу даних. У такому випадку найбезпечніше, що потрібно зробити, це здійснити поїздку в небі.
Костянтин М

1
Навіть якщо ваш код програми не вказує чітко pks, то можуть бути кріплення для ваших тестових випадків. Хоча, оскільки вони зазвичай завантажуються перед тестами, це може не бути проблемою.
Risadinha

1
Особливо це стосується використання UUIDFieldв якості первинного ключа: ключ не заповнюється на рівні БД, так self.pkце завжди True.
Даніель ван Флаймен

10

Ви можете просто підключитися до сигналу post_save, який надсилає "створені" кварги, якщо це правда, ваш об'єкт було вставлено.

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

Це зручно, тому що ваш новостворений об’єкт («я») має його pkзначення


5

Я дуже запізнююся на цю розмову, але я зіткнувся з проблемою із заповненням self.pk, коли з ним пов’язане значення за замовчуванням.

Те, як я обійшов це, додає в модель поле «дата_створене»

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. Звичайно, відповідально використовуйте та завантажуйте важкі завдання до черги, а не для того, щоб ваші користувачі чекали :)



1

Це загальний спосіб зробити це.

ідентифікатор буде наданий під час першого збереження в db


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)

Використовуючи clean_data.get (), я зміг визначити, чи є у мене екземпляр, у мене також було CharField, де null та blank, де true. Це оновлення буде оновлено під час кожного оновлення відповідно до зареєстрованого користувача
Swelan Auguste

-3

Щоб знати, чи ви оновлюєте чи вставляєте об’єкт (дані), використовуйте self.instance.fieldnameу своїй формі. Визначте чисту функцію у вашій формі та перевірте, чи є поточне введення значення таким, як попереднє, якщо ні, то оновите його.

self.instanceі self.instance.fieldnameпорівняти з новим значенням

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.