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