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

139 lines
4.9 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.

"""
Базовый класс для Celery задач отправки уведомлений
"""
from abc import ABC, abstractmethod
from celery import shared_task
import logging
logger = logging.getLogger(__name__)
class BaseNotificationTask(ABC):
"""
Абстрактный базовый класс для Celery задач по отправке уведомлений.
Инкапсулирует общую логику:
- Получение notification из БД
- Обработка ошибок
- Логирование
- Retry логика
Наследники реализуют только send_notification() с конкретным sender'ом.
"""
# Настройки Celery для конкретного канала (переопределяются в наследниках)
task_name = None
max_retries = 3
retry_delay = 60 # секунд
rate_limit = None # например '10/m' для email
def __init__(self):
self.logger = logging.getLogger(self.__class__.__name__)
@classmethod
def register_task(cls):
"""
Регистрирует Celery задачу для этого класса.
Должен вызываться при импорте модуля.
"""
instance = cls()
@shared_task(
name=cls.task_name,
bind=True,
max_retries=cls.max_retries,
default_retry_delay=cls.retry_delay,
autoretry_for=(Exception,),
rate_limit=cls.rate_limit,
)
def task_wrapper(self_task, notification_id: int, **kwargs):
"""Wrapper для вызова метода класса из Celery"""
return instance.execute(self_task, notification_id, **kwargs)
return task_wrapper
def execute(self, celery_task, notification_id: int, **kwargs):
"""
Выполняет задачу отправки уведомления.
Args:
celery_task: Объект Celery task (для retry)
notification_id: ID уведомления
**kwargs: Дополнительные параметры для sender
Returns:
True если успешно, False иначе
"""
try:
# Получаем уведомление из БД
notification = self.get_notification(notification_id)
if not notification:
self.logger.error(f"❌ Notification {notification_id} not found")
return False
# Отправляем через конкретный sender
success = self.send_notification(notification, **kwargs)
if success:
self.log_success(notification)
else:
self.log_failure(notification)
return success
except Exception as exc:
self.log_error(notification_id, exc)
raise celery_task.retry(exc=exc)
def get_notification(self, notification_id: int):
"""Получает Notification из БД"""
try:
from notifications.models import Notification
return Notification.objects.get(id=notification_id)
except Exception:
return None
@abstractmethod
def send_notification(self, notification, **kwargs) -> bool:
"""
Отправляет уведомление через конкретный канал.
Должен быть реализован в наследниках.
Args:
notification: Объект Notification
**kwargs: Дополнительные параметры
Returns:
True если успешно, False иначе
"""
pass
def log_success(self, notification):
"""Логирует успешную отправку"""
self.logger.info(
f"✅ Notification sent: "
f"id={notification.id}, "
f"recipient={notification.recipient.id}, "
f"channel={self.__class__.__name__}"
)
def log_failure(self, notification):
"""Логирует неуспешную отправку"""
self.logger.warning(
f"⚠️ Notification failed: "
f"id={notification.id}, "
f"recipient={notification.recipient.id}, "
f"channel={self.__class__.__name__}"
)
def log_error(self, notification_id: int, error: Exception):
"""Логирует ошибку"""
self.logger.error(
f"❌ Task error: "
f"notification_id={notification_id}, "
f"channel={self.__class__.__name__}, "
f"error={type(error).__name__}: {error}",
exc_info=True
)