![](https://habrastorage.org/getpro/habr/upload_files/f17/e3f/d0c/f17e3fd0c6aec545a21065814ccce726.jpg)
В Django сигналы используются для отправки и получения важной информации при сохранении, изменении или даже удалении модели данных и это относится к определенным прошлым или настоящим событиям в реальном времени. Сигналы помогают нам связывать события с действиями. Меня зовут Ясин, я младший разработчик Python в Kokoc Group, работаю чуть больше года. Изучаю и использую в работе фреймворки Django и FastAPI. Сегодня покажу пример, как можно эффективно использовать сигналы, но ожидаю, что вы имеете базовые представления о Python 3, виртуальной среде и настройке проекта Django версии 3 или выше. Поехали!
Введение в сигналы Django
Зачем они нужны?
Сигнал в Django — это метод обработки задач до или после того, как зарегистрированные события готовы к обновлению. В зависимости от времени выполнения сигнала, события могут быть обработаны либо до, либо после их завершения. Сигналы в Django позволяют обработчикам узнавать об определенных событиях, которые происходят в различных компонентах приложения. Это помогает разработчикам настраивать автоматизацию для различных событий. Например, процесс отправки электронного письма после успешной регистрации пользователя может быть отделен от работы основного приложения.
![](https://habrastorage.org/getpro/habr/upload_files/6b9/137/71c/6b913771cc0820b99ea3df488b0ecc25.jpg)
Сигналы могут использоваться для различных целей, таких как:
-
Отправка уведомлений пользователям, менеджерам при изменении данных.
-
Логирование изменений в базе данных.
-
Обновление связанных моделей.
-
Интеграция с внешними системами.
Обратимся к официальной документации Django, а именно к разделу сигналов, чтобы принимать сигнал, необходимо зарегистрировать функцию-получатель (ресивер) с помощью метода-диспетчера Signal.connect(). Функция-получатель вызывается, когда сигнал отправляется. Все функции-получатели сигнала вызываются по одной за раз, в порядке их регистрации.
![](https://habrastorage.org/getpro/habr/upload_files/c73/353/59e/c7335359ef47c518908ffc73e2840a09.png)
Метод Signal.connect имеет следующий синтаксис:
-
receiver: Функция-ресивер, которая будет подключена к этому сигналу.
-
sender: Указывает конкретного отправителя, от которого будут приниматься сигналы.
-
weak: Django по умолчанию сохраняет обработчики сигналов как слабые ссылки. Таким образом, если ваш ресивер является локальной функцией, он может быть собран сборщиком мусора. Чтобы предотвратить это, установите weak=False при вызове метода connect().
-
dispatch_uid: Уникальный идентификатор для функции-получателя в случаях, когда могут быть отправлены дублирующиеся сигналы.
Диспетчеры сигналов
Диспетчеры — это встроенные методы connect() и disconnect() сигналов Django, которые выполняют подключение или отключение функций-ресиверов с различными параметрами. Они уведомляют, когда определенное действие завершено.
Чтобы зарегистрировать функцию-ресивер, которая вызывается сигналами, используйте метод диспетчера сигналов Django connect(). Например, давайте создадим функцию-ресивер и подключим ее к сигналу, который срабатывает при отправке HTTP-запроса.
![](https://habrastorage.org/getpro/habr/upload_files/cda/680/76f/cda68076fb5ac9f446811610848a6455.png)
Функция-ресивер get_notified() выводит уведомление на экран. Она является ресивером (получателем сигнала), так как ожидает в аргументах sender модель-класс, у которого должен быть вызван сигнал.
Теперь давайте подключим ресивер к диспетчеру. Существует два способа сделать это. Первый способ — импортировать класс request_started из сигналов Django и передать функцию-ресивер get_notified при вызове метода connect() у request_started, как показано на скриншоте ниже.
![](https://habrastorage.org/getpro/habr/upload_files/ff9/e7a/4c0/ff9e7a4c0d1831e47e7b56ec7424a7f3.png)
Регистрация функций-ресиверов с помощью декораторов
Другой способ зарегистрировать функцию-ресивер — это использовать декораторы. Если коротко, декораторы — это функции-обертки, возвращающие другую внутреннюю функцию, которая абстрагирована от использования вне контекста декоратора. Это означает, что функция-ресивер будет передана во внутренний метод, где через известный нам метод connect() будет зарегистрирована в системе сигналов Django. Вот вариант реализации через декоратор @reciever:
![](https://habrastorage.org/getpro/habr/upload_files/c52/dd1/23d/c52dd123d63d8742c71cde8f54652d51.png)
Как работает декоратор @receiver:
-
Декоратор @receiver фактически вызывает метод connect() внутри себя.
-
Метод регистрирует функцию-ресивер в системе сигналов Django.
-
Когда срабатывает сигнал request_started, Django вызывает все зарегистрированные функции-ресиверы, включая get_notified.
Реализация сигналов в Django
Представим, что мы разрабатываем маркетплейс GreenBerries и поступила задача реализовать нотификацию для партнеров об отзывах на их товар. Но как отслеживать создание отзыва или какого-либо другого объекта?
Существует 3 типа сигналов моделей Django:
-
pre_init/post_init : при инициализации экземпляра класса (метод __init__())
-
pre_save/post_save : при изменении данных объекта, и использование метода save()
-
pre_delete/post_delete : при удалении экземпляра модели (метод delete())
Мы воспользуемся сигналом post_save, реализовав механизм, при котором после сохранения отзыва, создается уведомление для продавца. Он сможет отслеживать популярность товарной карточки и поддерживать обратную связь со своими клиентами.
Для этого подготовим 4 модели:
-
User — встроенная модель «Пользователь», куда были добавлены роли «Клиент» (покупатель) и «Партнер» (продавец);
-
Product — модель «Продукт», у которой есть название, описание, текст и продавец;
-
Review — модель «Отзыв», у которой есть связь с Продуктом и покупателем, а также текст, рейтинг и комментарий продавца;
-
Notification — модель «Уведомление», у которой есть получатель, текст и уровень важности.
# marketplace/models.py from django.db.models.signals import post_save from django.dispatch import receiver from django.db import models # User: пользовательская модель с ролями "клиент" и "партнер". class User(AbstractUser): CLIENT = 'client' PARTNER = 'partner' ROLE_CHOICES = [ (CLIENT, 'Client'), (PARTNER, 'Partner'), ] role = models.CharField(max_length=7, choices=ROLE_CHOICES) # Product: модель продукта, связанная с продавцом. class Product(models.Model): name = models.CharField(max_length=255) description = models.TextField() owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='products') def __str__(self): return self.name # Review: модель отзыва, связанная с продуктом, содержащая текст, рейтинг и ответ от партнера. class Review(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='reviews') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reviews') text = models.TextField() rating = models.PositiveSmallIntegerField(choices=[(i, str(i)) for i in range(1, 6)]) partner_response = models.TextField(blank=True, null=True) def __str__(self): return f'Review of {self.product.name} by {self.user.username}' # Notification: модель уведомлений с получателем и текстом. class Notification(models.Model): INFORMATIVE = 'informative' ATTENTION = 'attention' CRITICAL = 'critical' LEVEL_CHOICES = [ (INFORMATIVE, 'Informative'), (ATTENTION, 'Attention'), (CRITICAL, 'Critical'), ] recipient = models.ForeignKey(User, on_delete=models.CASCADE, related_name='notifications') text = models.TextField() level = models.CharField(max_length=12, choices=LEVEL_CHOICES, default=INFORMATIVE) def __str__(self): return f'Notification for {self.recipient.username} ({self.get_level_display()})'
У нас готово все для создания сигнала, и вот поступает задача от бизнеса, которая звучит так: «Необходимо сообщать партнерам о новых отзывах на их товары». Отлично! Мы знакомы с сигналами, а значит решение у нас в кармане, а именно — при сохранении объекта «Отзыв» нам нужно создать информационное «Уведомление» для продавца.
# marketplace/signals.py from django.db.models.signals import post_save from django.dispatch import receiver from .models import Review, Notification, User @receiver(post_save, sender=Review) def create_notification_for_partner(sender, instance, created, **kwargs): if created: product = instance.product owner = product.owner review_notification_text = f'New review for your product "{product.name}" by {instance.user.username}.' Notification.objects.create( recipient=owner, text=review_notification_text, level=Notification.INFORMATIVE )
Давайте разберем построчно весь метод (благо, он короткий):
@receiver(post_save, sender=Review)
Декоратор @receiver подключает функцию create_notification_for_partner к сигналу post_save модели Review. Это означает, что функция будет вызываться каждый раз после того, как экземпляр модели Review будет сохранен.
def create_notification_for_partner(sender, instance, created, **kwargs):
Это определение функции create_notification_for_partner, которая принимает четыре параметра:
-
sender: модель, отправившая сигнал (в данном случае, Review).
-
instance: экземпляр модели Review, который был сохранен.
-
сreated: булевое значение, указывающее, был ли создан новый экземпляр (True), или это было обновление существующего (False).
-
**kwargs: дополнительные аргументы, которые могут быть переданы сигналом.
if created: product = instance.product owner = product.owner
Проверяем, был ли создан новый экземпляр Review. Если created в состоянии True, значит это новый отзыв, и код внутри этого блока будет выполнен. Извлекаем продукт, связанный с данным отзывом (instance.product). И извлекаем владельца продукта (product.owner).
review_notification_text = f'New review for your product "{product.name}" by {instance.user.username}.’ Notification.objects.create(recipient=owner, text=review_notification_text, level=Notification.INFORMATIVE)
Формируем текст уведомления о новом отзыве. И создаем новое уведомление с уровнем INFORMATIVE для владельца продукта, информируя о новом отзыве. Устанавливаем текст уведомления и связываем его с получателем (owner).
Регистрация сигналов при старте приложения
Убедитесь, что сигнал импортируется и регистрируется при запуске приложения. Например, вы можете создать файл signals.py в вашем приложении и импортировать его в методе ready класса конфигурации приложения. Пример ниже:
# marketplace/apps.py from django.apps import AppConfig class MarketplaceConfig(AppConfig): name = 'marketplace' def ready(self): import marketplace.signals
Это обеспечит регистрацию сигнала при старте приложения Django.
Усложнение логики уведомлений
Теперь давайте немного усложним логику и кроме уведомления о новом отзыве в примере выше будем предупреждать продавца о снижении популярности карточки товара. Если у товара есть рейтинг и среднее арифметическое число рейтинга меньше, либо равно 4.5, то создадим уведомление для продавца с уровнем ATTENTION. Вот как выглядит код, после добавления условия.
# marketplace/signals.py from django.db.models.signals import post_save from django.dispatch import receiver from django.db.models import Avg from .models import Review, Notification, User @receiver(post_save, sender=Review) def create_notification_for_partner(sender, instance, created, **kwargs): if created: product = instance.product owner = product.owner # Рассчитываем средний рейтинг продукта и получаем по ключу rating__avg число average_rating = product.reviews.aggregate(Avg('rating')).get('rating__avg', None) # Проверяем, если средний рейтинг меньше 4.5 if average_rating is not None and average_rating <= 4.5: notification_text = f'Attention: The average rating of your product "{product.name}" has fallen below 4.5.' Notification.objects.create( recipient=owner, text=notification_text, level=Notification.ATTENTION ) # Создаем уведомление для нового отзыва review_notification_text = f'New review for your product "{product.name}" by {instance.user.username}.' Notification.objects.create( recipient=owner, text=review_notification_text, level=Notification.INFORMATIVE )
Как видите, ничего сложного.
Заключение
Сигналы в Django позволяют разработчикам создавать мощные и гибкие системы уведомлений и автоматизации, реагируя на различные события в приложении. Они помогают выделить задачи в отдельные модули, что делает код более организованным и легким для сопровождения. Например, отправка уведомлений продавцам о новых отзывах или изменениях в популярности товаров может быть реализована легко и эффективно.
Таким образом, мы можем делать различные проверки в сигнале и не только создавать объекты уведомлений, но имплементировать другую логику. Это открывает широкие возможности для автоматизации и улучшения пользовательского опыта. Можете в комментариях привести другие примеры, как вы используете/использовали сигналы в своей практике.
ссылка на оригинал статьи https://habr.com/ru/articles/826238/
Добавить комментарий