Corp/backend/notifications/channels.py
lizerk d5b4ae9f43 refactor: Полный переход на новую архитектуру уведомлений v2.0
BREAKING CHANGES:
- Удалена старая система (6 моделей → 2 модели)
- models_new.py → models.py (Notification, UserChannelPreferences)
- signals_new.py → signals.py (notify.send API)
- admin_new.py → admin.py

Новая архитектура:
- Все отправки через Celery (полностью асинхронно)
- Модульная структура tasks/ (base, email, push, websocket)
- Каждый канал - отдельный класс задачи наследующий BaseNotificationTask
- Rate limiting, retry, приоритеты из коробки

Обратная совместимость:
- Создан services.py facade для старого API
- NotificationService.create_notification() работает через notify.send()
- Старый код продолжает работать без изменений

Удалено:
- Старые models.py, signals.py, admin.py, tasks.py (→ .old)
- telegram_bot.py, telegram_models.py (Telegram отключен)
- test_views.py, views.py, urls.py (устаревшие)

Создано:
- tasks/ модуль с классовой архитектурой (как senders/)
- tasks/base.py - BaseNotificationTask
- tasks/email.py - EmailNotificationTask + DigestEmailTask
- tasks/push.py - PushNotificationTask
- tasks/websocket.py - WebSocketNotificationTask

Обновлено:
- apps.py - загружает signals, channels, tasks
- channels.py - все отправки через Celery задачи
- Все импорты models_new → models
2026-03-09 16:36:14 +03:00

128 lines
4.5 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 - с автоматическим удалением неактивных устройств
"""
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)
Args:
notification: Объект Notification
DEPRECATED: Используйте PushNotificationSender().send()
"""
sender = PushNotificationSender()
return sender.send(notification)
# === 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)