Django - Перевизначення методу Model.create ()?


88

Документи Django містять лише приклади заміщення save()та delete(). Однак я хотів би визначити додаткову обробку для своїх моделей лише тоді, коли вони створені . Для всіх, хто знайомий з Rails, це було б еквівалентно створенню :before_createфільтра. Чи можливо це?

Відповіді:


161

Перевизначення __init__()призведе до виконання коду щоразу, коли буде представлено екземпляр представлення об'єкта python. Я не знаю напрямків, але :before_createdфільтр для мене звучить так, ніби це код, який повинен бути виконаний при створенні об'єкта в базі даних. Якщо ви хочете виконати код, коли в базі даних створюється новий об'єкт, слід замінити save(), перевіривши, чи має об'єкт pkатрибут чи ні. Код буде виглядати приблизно так:

def save(self, *args, **kwargs):
    if not self.pk:
        # This code only happens if the objects is
        # not in the database yet. Otherwise it would
        # have pk
    super(MyModel, self).save(*args, **kwargs)

7
Я насправді знайшов рішення за допомогою сигналів: docs.djangoproject.com/en/dev/topics/signals (зокрема, сигнал попереднього збереження). Однак, схоже, це набагато більш прагматичне рішення. Спасибі купу.
— ground5hark

4
Припускаю, ви маєте на увазі перевизначення методу менеджера create? Це цікаве рішення, але воно не буде працювати в тих випадках, коли об’єкт створюється за допомогою Object(**kwargs).save()або будь-яких інших варіацій щодо цього.
— Зак

3
Я не думаю, що це хакерство. Це одне з офіційних рішень.
— les

6
Чи не повинно бути super(MyModel, self).save(*args, **kwargs)?
— Mark Chackerian

1
Можливо, перевірка на self.pkне найкращий варіант перевірити, чи об’єкт нещодавно створюється чи просто оновлюється. Іноді ви вводите ідентифікатор об’єкта під час створення (спеціальне значення, яке не генерується з бази даних, наприклад KSUID), і це призведе до того, що цей пункт ніколи не буде виконуватися ... Існує self._state.addingзначення, щоб переконатися, що він зберігається вперше або просто оновлюється, що допомагає в тих випадках.
— Шахінізм

22

приклад того, як створити сигнал post_save (з http://djangosnippets.org/snippets/500/ )

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

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    """Create a matching profile whenever a user object is created."""
    if created: 
        profile, new = UserProfile.objects.get_or_create(user=instance)

ось вдумливе обговорення того, чи найкраще використовувати сигнали чи власні методи збереження https://web.archive.org/web/20120815022107/http://www.martin-geber.com/thought/2007/10/29/ django-signal-vs-custom-save-method /

На мою думку, використання сигналів для цього завдання є більш надійним, легшим для читання, але довшим.


Це кращий спосіб замість того, щоб возитись із внутрішніми об’єктами, однак, якщо ви вносите зміни до розглянутої моделі, а не просто створюєте іншу у наведеному вище прикладі, не забудьте зателефонуватиinstance.save() . Отже, у цьому випадку також передбачено покарання за продуктивність, оскільки в базі даних буде один запит INSERT та один UPDATE.
— Mike Shultz

Посилання на сигнали та власні методи збереження порушено.
— Сандер Ванден Хаут,

21

Це старий, має прийняту відповідь, яка працює (Зака), і більш ідіоматичну також (Майкл Билстра), але оскільки це все ще перший результат у Google, який бачить більшість людей, я думаю, що нам потрібен більше найкращих практик modern-django відповідь стилю тут :

from django.db.models.signals import post_save

class MyModel(models.Model):
    # ...
    @classmethod
    def post_create(cls, sender, instance, created, *args, **kwargs):
        if not created:
            return
        # ...what needs to happen on create

post_save.connect(MyModel.post_create, sender=MyModel)

Справа в наступному:

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

Ще чистіше було б, якби основний Django мав реальний post_createсигнал. (Імхо, якщо вам потрібно передати логічний аргумент, щоб змінити поведінку методу, це має бути 2 методи.)


15

Щоб відповісти на запитання буквально, createметод у менеджері моделі - це стандартний спосіб створення нових об’єктів у Django. Щоб замінити, зробіть щось на зразок

from django.db import models

class MyModelManager(models.Manager):
    def create(self, **obj_data):
        # Do some extra stuff here on the submitted data before saving...
        # For example...
        obj_data['my_field'] = my_computed_value(obj_data['my_other_field'])

        # Now call the super method which does the actual creation
        return super().create(**obj_data) # Python 3 syntax!!

class MyModel(models.model):
    # An example model
    my_field = models.CharField(max_length=250)
    my_other_field = models.CharField(max_length=250)

    objects = MyModelManager()

У цьому прикладі я переоцінюю метод createметоду Manager, щоб зробити додаткову обробку до того, як екземпляр буде фактично створений.

ПРИМІТКА: Код подібний

my_new_instance = MyModel.objects.create(my_field='my_field value')

буде виконувати цей модифікований createметод, але як код

my_new_unsaved_instance = MyModel(my_field='my_field value')

не буде.


3

Перевизначення __init__()дозволить вам виконати код, коли модель буде інстанційована. Не забудьте зателефонувати батькові __init__().


Ах так, це була відповідь. Не знаю, як я це пропустив. Дякую Ігнасіо.
— ground5hark


1

Бажана відповідь є правильною, але тест на те, чи створюється об’єкт, не працює, якщо ваша модель походить від UUIDModel. Поле pk вже матиме значення.

У цьому випадку ви можете зробити це:

already_created = MyModel.objects.filter(pk=self.pk).exists()

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