2026-09-07 19:43:48 +03:00

199 lines
8.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Django сигналы для автоматической генерации уведомлений в модуле Requests.
Обрабатывает события:
- Новое заявление (для ответственных/руководителей)
- Одобрение/отклонение заявления
- Комментарий к заявлению
- Изменение статуса заявления
"""
import logging
from django.db.models.signals import m2m_changed, post_save, pre_save
from django.dispatch import receiver
from ..models import Request
from .handlers import notify_new_request, notify_status_change
logger = logging.getLogger(__name__)
# Флаг для отслеживания новых заявлений, ожидающих уведомления
_pending_new_requests = set()
def dispatch_pending_new_request_notifications(instance: Request) -> None:
"""Отправляет отложенные уведомления о новой заявке ровно один раз.
Используется, когда recipients/cc_users выставляются пачкой в serializer
и m2m-сигналы нужно временно подавить, чтобы не отправлять уведомление
до установки полного списка адресатов.
"""
if instance.id in _pending_new_requests:
_pending_new_requests.discard(instance.id)
notify_new_request(instance)
@receiver(post_save, sender=Request)
def create_request_notifications(sender, instance, created, **kwargs):
"""
Создает уведомления при создании или изменении заявления.
Обрабатывает:
1. Новое заявление - помечаем для отправки уведомлений
после установки recipients
2. Изменение статуса - уведомление автору
"""
try:
request_obj = instance
logger.debug(
"Request notification signal received",
extra={
"event": "request.notification_signal.received",
"business_request_id": request_obj.id,
"object_created": created,
"request_status": request_obj.status,
},
)
if created and request_obj.status != "draft":
# Помечаем заявление как ожидающее отправки уведомлений
# Уведомления будут отправлены после установки recipients через
# m2m_changed
logger.debug(
"Request marked for deferred notification",
extra={
"event": "request.notification.deferred",
"business_request_id": request_obj.id,
},
)
_pending_new_requests.add(request_obj.id)
if not created:
# Проверяем изменение статуса через сохраненный атрибут _old_status
if hasattr(request_obj, "_old_status"):
old_status = request_obj._old_status
new_status = request_obj.status
if old_status != new_status:
logger.info(
"Request status change detected",
extra={
"event": "request.status.changed",
"business_request_id": request_obj.id,
"old_status": old_status,
"new_status": new_status,
},
)
# Отправляем уведомления напрямую.
# channels.py автоматически
# сделает асинхронную отправку через Celery
notify_status_change(request_obj, old_status, new_status)
except Exception as error:
logger.exception(
"Request notification signal failed",
extra={
"event": "request.notification_signal.failed",
"business_request_id": getattr(instance, "id", None),
"signal": "request_saved",
"error_type": type(error).__name__,
},
)
@receiver(pre_save, sender=Request)
def track_status_change(sender, instance, **kwargs):
"""
Сохраняем старый статус перед обновлением для отслеживания изменений.
"""
if instance.pk:
try:
old_instance = Request.objects.get(pk=instance.pk)
instance._old_status = old_instance.status
except Request.DoesNotExist:
instance._old_status = None
@receiver(m2m_changed, sender=Request.recipients.through)
def notify_on_recipients_changed(sender, instance, action, **kwargs):
"""
Отправляет уведомления о новом заявлении ПОСЛЕ установки recipients.
Срабатывает когда recipients устанавливаются через .set(), .add() и т.д.
"""
try:
if getattr(instance, "_suppress_new_request_notifications", False):
return
# Проверяем что это завершение операции установки recipients
if action == "post_add" and instance.id in _pending_new_requests:
logger.info(
"Request recipients are ready for notification",
extra={
"event": "request.notification.recipients_ready",
"business_request_id": instance.id,
"recipient_type": "primary",
},
)
_pending_new_requests.discard(instance.id)
# Отправляем уведомления напрямую - channels.py автоматически
# сделает асинхронную отправку через Celery
notify_new_request(instance)
except Exception as error:
logger.exception(
"Request recipient notification signal failed",
extra={
"event": "request.notification_signal.failed",
"business_request_id": getattr(instance, "id", None),
"signal": "recipients_changed",
"error_type": type(error).__name__,
},
)
@receiver(m2m_changed, sender=Request.cc_users.through)
def notify_on_cc_users_changed(sender, instance, action, **kwargs):
"""
Дополнительная проверка для cc_users.
Если recipients не были установлены, но установлены cc_users,
также отправляем уведомления.
"""
try:
if getattr(instance, "_suppress_new_request_notifications", False):
return
# Если это завершение добавления cc_users И заявление все еще ожидает
# уведомлений
if action == "post_add" and instance.id in _pending_new_requests:
# Проверяем что recipients пусты (значит уведомления еще не
# отправлены)
if instance.recipients.count() == 0:
logger.info(
"Request recipients are ready for notification",
extra={
"event": "request.notification.recipients_ready",
"business_request_id": instance.id,
"recipient_type": "cc",
},
)
_pending_new_requests.discard(instance.id)
# Отправляем уведомления напрямую - channels.py автоматически
# сделает асинхронную отправку через Celery
notify_new_request(instance)
except Exception as error:
logger.exception(
"Request CC notification signal failed",
extra={
"event": "request.notification_signal.failed",
"business_request_id": getattr(instance, "id", None),
"signal": "cc_users_changed",
"error_type": type(error).__name__,
},
)