Corp/backend/notifications/email_sender.py
Игорь Надеин d7c025f120 feat: Add department-based document distribution with department picker in UI
- Added departments ManyToMany field to Document model
- Implemented department selection in document create/edit modals
- Added automatic notification creation for all department employees
- Fixed document filtering to include uploaded documents in 'My Documents'
- Fixed acknowledgements API to include department recipients
- Added bulk notification support with configurable threshold
- Removed acknowledge button, added visual highlighting for unread documents
- Implemented server-side acknowledgment flow with redirect to file
- Fixed mobile responsive layout for document actions
- Removed temporary test files and documentation
2025-11-21 12:46:43 +03:00

231 lines
8.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.

"""
Email sender для системы уведомлений.
Отправляет красивые HTML письма с уведомлениями.
"""
import logging
from typing import Optional
from django.conf import settings
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from common.emails import send_templated_mail
logger = logging.getLogger(__name__)
class EmailNotificationSender:
"""
Класс для отправки email уведомлений.
Использует HTML шаблоны для красивого оформления.
"""
# Маппинг категорий на иконки для email
CATEGORY_ICONS = {
'communications': '💬',
'documents': '📄',
'requests': '📋',
'calendar': '📅',
'department': '👥',
'profile': '👤',
'feed': '📰',
'system': '⚙️',
}
# Маппинг категорий на цвета
CATEGORY_COLORS = {
'communications': '#0d6efd', # primary
'documents': '#198754', # success
'requests': '#ffc107', # warning
'calendar': '#dc3545', # danger
'department': '#6f42c1', # purple
'profile': '#0dcaf0', # info
'feed': '#fd7e14', # orange
'system': '#6c757d', # secondary
}
@classmethod
def send_notification_email(
cls,
notification,
recipient_email: str,
custom_subject: Optional[str] = None,
) -> bool:
"""
Отправляет email уведомление пользователю.
Args:
notification: объект Notification
recipient_email: email получателя
custom_subject: кастомная тема письма (опционально)
Returns:
True если отправлено успешно, False иначе
"""
try:
# Получаем категорию
category_code = notification.notification_type.category.code
category_name = notification.notification_type.category.name
# Формируем тему письма
if custom_subject:
subject = custom_subject
else:
icon = cls.CATEGORY_ICONS.get(category_code, '🔔')
subject = f"{icon} {notification.title}"
# Формируем контекст для шаблона
context = {
'notification': notification,
'category_name': category_name,
'category_icon': cls.CATEGORY_ICONS.get(category_code, '🔔'),
'category_color': cls.CATEGORY_COLORS.get(category_code, '#0d6efd'),
'action_url': cls._get_full_url(notification.action_url),
'action_text': notification.action_text or 'Посмотреть',
'site_name': getattr(settings, 'SITE_NAME', 'EUSRR'),
'site_url': cls._get_site_url(),
}
# Отправляем email
send_templated_mail(
subject=subject,
to=[recipient_email],
template_base='notifications/email/notification',
context=context,
)
logger.info(
f"Email уведомление отправлено: {notification.id} -> {recipient_email}"
)
return True
except Exception as e:
logger.error(
f"Ошибка отправки email уведомления {notification.id}: {e}",
exc_info=True
)
return False
@classmethod
def send_digest_email(
cls,
recipient_email: str,
notifications: list,
digest_type: str = 'daily',
) -> bool:
"""
Отправляет дайджест уведомлений.
Args:
recipient_email: email получателя
notifications: список объектов Notification
digest_type: тип дайджеста ('daily' или 'weekly')
Returns:
True если отправлено успешно, False иначе
"""
try:
if not notifications:
return False
# Группируем по категориям
grouped = {}
for notif in notifications:
category = notif.notification_type.category.code
if category not in grouped:
grouped[category] = []
grouped[category].append(notif)
# Формируем тему
digest_name = 'Ежедневный' if digest_type == 'daily' else 'Еженедельный'
subject = f"📬 {digest_name} дайджест уведомлений ({len(notifications)})"
# Формируем контекст
context = {
'notifications': notifications,
'grouped_notifications': grouped,
'digest_type': digest_type,
'digest_name': digest_name,
'total_count': len(notifications),
'category_icons': cls.CATEGORY_ICONS,
'category_colors': cls.CATEGORY_COLORS,
'site_name': getattr(settings, 'SITE_NAME', 'EUSRR'),
'site_url': cls._get_site_url(),
}
# Отправляем дайджест
send_templated_mail(
subject=subject,
to=[recipient_email],
template_base='notifications/email/digest',
context=context,
)
logger.info(
f"Дайджест отправлен: {len(notifications)} уведомлений -> {recipient_email}"
)
return True
except Exception as e:
logger.error(
f"Ошибка отправки дайджеста на {recipient_email}: {e}",
exc_info=True
)
return False
@staticmethod
def _get_site_url() -> str:
"""Получает базовый URL сайта из настроек."""
# Пробуем получить из настроек
if hasattr(settings, 'SITE_URL'):
return settings.SITE_URL
# Формируем из ALLOWED_HOSTS
if settings.ALLOWED_HOSTS and settings.ALLOWED_HOSTS[0] != '*':
host = settings.ALLOWED_HOSTS[0]
protocol = 'https' if getattr(settings, 'SECURE_SSL_REDIRECT', False) else 'http'
return f"{protocol}://{host}"
return 'http://localhost:9000'
@staticmethod
def _get_full_url(path: str) -> str:
"""Преобразует относительный URL в абсолютный."""
if not path:
return ''
if path.startswith('http://') or path.startswith('https://'):
return path
site_url = EmailNotificationSender._get_site_url()
return f"{site_url}{path}"
@classmethod
def test_email_connection(cls, test_email: str) -> bool:
"""
Тестирует подключение к email серверу.
Args:
test_email: email для отправки тестового письма
Returns:
True если успешно, False иначе
"""
try:
from django.core.mail import send_mail
send_mail(
subject='🔔 Тест системы уведомлений EUSRR',
message='Это тестовое письмо. Если вы его получили, значит настройки email работают корректно.',
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[test_email],
fail_silently=False,
)
logger.info(f"Тестовое письмо успешно отправлено на {test_email}")
return True
except Exception as e:
logger.error(f"Ошибка отправки тестового письма: {e}", exc_info=True)
return False