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.