Corp/backend/notifications/channels.py
lizerk ed636081dc refactor: complete notifications v2 system with frontend translation
- Remove hard-coded Russian translations from backend API
- Backend now returns raw verb values (language-agnostic)
- Implement frontend translation functions for notification titles and categories
- Add category badge system with filters on notifications page
- Create translation utilities in frontend (verbTranslations.ts)
- Add notification settings page structure
- Update API endpoints to return simplified notification data
- Refactor notification signals across all apps
- Remove deprecated code and tests
- Add comprehensive documentation for v2 migration

Backend changes:
- notifications/api/views.py: Remove translation logic, simplify responses
- All notification_signals.py: Update to use consistent patterns
- Remove deprecated Telegram bot command
- Update Celery tasks for new notification structure

Frontend changes:
- app/notifications/page.tsx: Add translation functions and category badges
- hooks/useNotificationWebSocket.ts: New WebSocket hook for real-time updates
- lib/verbTranslations.ts: Centralized translation mappings
- components/NotificationCenter.tsx: Update to use new API format

This makes the notifications module fully reusable and project-independent.
2026-03-09 21:35:36 +03:00

142 lines
5.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 import transaction
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 очередь для каждого канала.
ВАЖНО: Использует transaction.on_commit() чтобы гарантировать,
что уведомление сохранено в БД перед отправкой в Celery.
"""
if not created:
return # Обрабатываем только новые уведомления
notification = instance
user = notification.recipient
# Проверяем что получатель указан
if not user:
logger.warning(
f'⚠️ Notification {notification.id} has no recipient, skipping delivery'
)
return
# Получаем настройки пользователя
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()
# КРИТИЧНО: Отправляем задачи ПОСЛЕ коммита транзакции
# Без этого Celery не найдет уведомление в БД (race condition)
# Django документация: https://docs.djangoproject.com/en/5.0/topics/db/transactions/#performing-actions-after-commit
def send_to_channels():
"""Отправка по каналам после успешного коммита транзакции"""
if is_dnd:
logger.debug(f'User {user.id} is in DND period, only web notifications (silent)')
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)
transaction.on_commit(send_to_channels)
# === 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)