Сигналы Django вместо хуков WordPress

Статья из цикла "взгляд на Python и Django глазами PHP, WordPress разработчика".

Задача: В момент сохранения поста (по джанговски - модели) через админку Django необходимо выполнить некий код. Например, отправить запрос к API маркетплейса.

Как это решается в WordPress. Если коротко, то хуками или фильтрами фильтрами (второе кратно реже). Находим в документации необходимый хук или фильтр, который срабатывает в момент сохранения нужно нам поста, при помощи add_action или add_filter регистрируем функцию, которая будет срабатывать в этот момент, а в самой функции реализуем процесс отправки запроса по API.

Как это решается в Django. Хоть в Джанго и нет привычного любому WP-шнику механизма хуков, но за то есть Сигналы, которые во многом на этот самый механизм хуков похожи. Дословно в документации сказано следующее:

Django включает в себя «диспетчер сигналов», который помогает разделенным приложениям получать уведомления о действиях, происходящих в других частях фреймворка. Иными словами, сигналы позволяют определенным отправителям уведомлять набор получателей о том, что произошло какое-то действие.

Или, говоря привычным ВПшнику языком - в коде основных функций Django существуют хуки, сигналы, которые срабатывают во время тех или иных событий. Например:

django.db.models.signals.pre_save & django.db.models.signals.post_save - Отправляется до или после вызова save() метода модели.

django.db.models.signals.pre_delete & django.db.models.signals.post_delete - Отправляется до или после delete() вызова delete() метода модели или метода набора запросов .

django.db.models.signals.m2m_changed - Отправляется при изменении ManyToManyFieldмодели.

django.core.signals.request_started & django.core.signals.request_finished - Отправляется, когда Django запускает или завершает HTTP-запрос.

Более полный список существующих сигналов можно посмотреть тут.

Выполнение кода при сохранении поста Django

Разберём пример. В файле models.py у нас зарегистрирована модель ProductCategory, а ниже, начиная с from django.db.models.signals import pre_save - обработчик сигнала, который срабатывает всякий раз, когда мы сохраняем модель ProductCategory. В примере используем сигнал pre_save.

class ProductCategory(MPTTModel):
    title = models.CharField(max_length=50, unique=True, verbose_name='Название')
    slug = models.SlugField(max_length=150, verbose_name='slug', null=False, unique=True)
    parent = TreeForeignKey('self', on_delete=models.PROTECT, null=True, blank=True, related_name='children',
                            db_index=True, verbose_name='Родительская категория')

    class Meta:
        ordering = ['pk']
        verbose_name = 'Категория'
        verbose_name_plural = 'Категории'

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

@receiver(pre_save, sender=ProductCategory)
def my_callback(sender, **kwargs):
    print('Сработал сигнал сохранения категории')

Как получить ID сохраняемого поста (записи) в Django

На примере сигнала post_save код сигнала Джаного для получения сохраняемого поста будет выглядеть так:

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

@receiver(post_save, sender=ProductCategory)
def my_callback(sender, instance, raw, using, update_fields, created, **kwargs):
    print('Сработал сигнал сохранения категории. ID категории смотри ниже:')
    print(instance.id)  # собственно выводим ID сохраняемой категории

Конечно, с сигналами не так удобном работать в том плане, что в отличии от привычных вордпресовцу хуков и фильтров вмести с ними не передаются дополнительные параметры, точнее передаются, но далеко не все и не так, как это реализовано в в WordPress. Но, согласитесь, что это всё равно удобно и довольно просто.

Как получить значение произвольного поля при сохранении

Как вы уже поняли, основная "вкусняшка" находится в instance. Например, если в нашей модели Django определено поле ozon_id, то внутри нашей функции по обработке сигнала мы можем получить значение следующим образом:

class ProductCategory(MPTTModel):
    ozon_id = models.PositiveIntegerField(verbose_name='OZON ID', null=True, blank=True)
...
@receiver(post_save, sender=ProductCategory)
def my_callback(sender, instance, raw, using, update_fields, created, **kwargs):
    print(instance.ozon_id)  # выводим значение поля ozon_id сохраняемой категории

Если вы обратили внимание, то в примерах у коллбэк функции я использовал разное количество аргументов. По факту, если смотреть на последний пример, то для работоспособности нам можно записать всё так:

def my_callback(instance, **kwargs):
    print(instance.ozon_id)

т.е в обязательном порядке использовать только **kwargs и непосредственно тот аргумент с которым работаем (в нашем случае это instance).

Как видите, сигналы Django очень полезная и важная штука, разобраться с которой стоит обязательно.

Рейтинг: 5

2023-10-24 / / 0 комментариев / Про кодинг и сервер / , ,