Corp/backend/notifications/channels.py
lizerk 72b67d5c6a fix: Исправлены синтаксические ошибки и добавлены миграции
Исправлено:
- channels.py: удалены осиротевшие строки после рефакторинга
- api/v1/notifications/urls.py: исправлен file для реэкспорта
- notifications/urls.py: создан для обратной совместимости

Миграции:
- 0010_userchannelpreferences_and_more.py: переход на v2 модели
  * Notification с GenericForeignKey (actor/action_object/target)
  * UserChannelPreferences вместо UserNotificationSettings
  * Удалены старые модели (NotificationCategory, NotificationType и тд.)
  * Переименована таблица в notifications_notification_v2
2026-03-09 17:15:08 +03:00

126 lines
4.6 KiB
Python
Raw 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.

"""
Обработчики каналов доставки уведомлений
Слушают post_save сигнал от Notification и асинхронно отправляют через Celery:
- WebSocket (realtime) - мгновенно
- Email - с retry и rate limiting
- Web Push - с автоматическим удалением неактивных устройств
TODO: Добавить integration тесты:
- Проверка вызова Celery tasks при создании Notification
- Режим DND (только WebSocket silent)
- Отключенные каналы (email_enabled=False)
- Отключенные verb'ы (is_verb_enabled)
"""
from django.db.models.signals import post_save
from django.dispatch import receiver
import logging
logger = logging.getLogger(__name__)
@receiver(post_save, sender='notifications.Notification')
def route_notification_to_channels(sender, instance, created, **kwargs):
"""
Главный роутер уведомлений по каналам.
Вызывается при создании нового уведомления.
Асинхронно отправляет в Celery очередь для каждого канала.
"""
if not created:
return # Обрабатываем только новые уведомления
notification = instance
user = notification.recipient
# Получаем настройки пользователя
try:
prefs = user.channel_preferences
except Exception:
# Если настроек нет - создаем с дефолтными значениями
from .models import UserChannelPreferences
prefs = UserChannelPreferences.objects.create(user=user)
# Проверяем, не отключен ли этот тип уведомлений
if not prefs.is_verb_enabled(notification.verb):
logger.debug(f'Notification verb "{notification.verb}" disabled for user {user.id}')
return
# Импортируем задачи
from .tasks import (
send_websocket_notification,
send_email_notification,
send_push_notification,
)
# Проверяем режим "Не беспокоить"
is_dnd = prefs.is_in_dnd_period()
if is_dnd:
logger.debug(f'User {user.id} is in DND period, only web notifications (silent)')
# В DND режиме только WebSocket без звука
if prefs.web_enabled:
send_websocket_notification.delay(notification.id, silent=True)
return
# Отправляем по каналам асинхронно через Celery
if prefs.web_enabled:
send_websocket_notification.delay(notification.id, silent=False)
if prefs.email_enabled and prefs.email_frequency == 'instant':
send_email_notification.delay(notification.id)
if prefs.push_enabled:
send_push_notification.delay(notification.id)
# === Digest Email (для email_frequency = daily/weekly) ===
def send_email_digest(user, frequency='daily'):
"""
Отправка дайджеста уведомлений
Args:
user: User объект
frequency: 'daily' или 'weekly'
"""
try:
from datetime import timedelta
from django.utils import timezone
from .models import Notification
# Определяем период
if frequency == 'daily':
since = timezone.now() - timedelta(days=1)
elif frequency == 'weekly':
since = timezone.now() - timedelta(weeks=1)
else:
raise ValueError(f'Invalid frequency: {frequency}')
# Получаем неотправленные уведомления
notifications = Notification.objects.filter(
recipient=user,
timestamp__gte=since,
emailed=False,
deleted=False,
).order_by('-timestamp')
if not notifications.exists():
logger.debug(f'No notifications to digest for user {user.id}')
return
# Используем EmailNotificationSender для отправки дайджеста
sender = EmailNotificationSender()
success = sender.send_digest(user, notifications, frequency)
if success:
logger.info(
f'Email digest ({frequency}) sent to {user.email} '
f'with {notifications.count()} notifications'
)
except Exception as e:
logger.error(f'Failed to send email digest: {e}', exc_info=True)