Corp/backend/feed/notification_signals.py.old
lizerk 7e410ebff8 refactor(feed): реструктуризация notifications в модульную архитектуру
Проблема:
- Монолитный notification_signals.py (~200 строк) смешивал разные concerns
- Сложно поддерживать и понимать логику уведомлений
- Отсутствовала интеграция с API для уведомлений о лайках

Решение:
Разделен на модульную структуру feed/notifications/:
- config.py - NotificationVerbs, MessageTemplates, ActionURLs, truncate_text
- handlers.py - notify_new_post(), notify_comment(), notify_post_reaction(), get_post_recipients()
- signals.py - Django signal receivers (@receiver)
- __init__.py - публичный API модуля

Изменения:
- Создан: feed/notifications/ модуль с 4 файлами
- Переименован: notification_signals.py → notification_signals.py.old
- Изменен: apps.py - обновлен импорт на feed.notifications
- Изменен: api/v1/feed/views.py - добавлен вызов notify_post_reaction() в метод like()

Преимущества:
- ✅ Единообразие с requests_app и procurement
- ✅ Четкое разделение ответственности (SRP)
- ✅ Легче тестировать отдельные компоненты
- ✅ Публичный API через __init__.py
- ✅ Уведомления о лайках теперь работают
- ✅ Централизованные шаблоны сообщений

Верификация:
- python manage.py check - успешно
2026-03-10 13:05:58 +03:00

186 lines
6.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.

"""
Signals для автоматической генерации уведомлений в модуле Feed.
Обрабатывает события:
- Новая публикация (для подписчиков/отдела/компании)
- Комментарий к публикации
- Реакция (лайк) на публикацию - через отдельный механизм
"""
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Post, Comment
from .constants import TYPE_COMPANY, TYPE_DEPARTMENT, TYPE_EMPLOYEE
from notifications.signals import notify
Employee = get_user_model()
@receiver(post_save, sender=Post)
def create_post_notification(sender, instance, created, **kwargs):
"""
Создает уведомления при создании новой публикации асинхронно.
Уведомления отправляются в зависимости от типа:
- TYPE_COMPANY - всем сотрудникам
- TYPE_DEPARTMENT - сотрудникам отдела
- TYPE_EMPLOYEE - подписчикам автора (если реализовано)
"""
if not created:
return
# Отправляем уведомления напрямую - channels.py автоматически отправит через Celery
notify_new_post(instance)
@receiver(post_save, sender=Comment)
def create_comment_notification(sender, instance, created, **kwargs):
"""
Создает уведомления при добавлении комментария к публикации.
"""
if not created:
return
comment = instance
post = comment.post
comment_author = comment.author
# Уведомляем автора публикации (если комментарий не от него)
if post.author.id != comment_author.id:
notify.send(
sender=comment_author,
recipient=post.author,
verb='feed_post_comment',
action_object=post,
description=(
f'{comment_author.get_full_name() or comment_author.username}'
f' прокомментировал: {truncate_text(comment.text, 80)}'
),
action_url='/',
data={
'title': 'Новый комментарий к вашей публикации',
'post_id': post.id,
'post_title': post.title,
'comment_id': comment.id,
'author_id': comment_author.id,
}
)
# ===== Вспомогательные функции =====
def notify_new_post(post):
"""
Отправляет уведомления о новой публикации.
"""
recipients = get_post_recipients(post)
# Исключаем автора
recipients = [r for r in recipients if r.id != post.author.id]
# Определяем контекст публикации
if post.type == TYPE_COMPANY:
context = 'компании'
elif post.type == TYPE_DEPARTMENT and post.department:
context = f'отдела {post.department.name}'
else:
context = (
f'от {post.author.get_full_name() or post.author.username}'
)
for recipient in recipients:
notify.send(
sender=post.author,
recipient=recipient,
verb='feed_new_post',
action_object=post,
description=f'{post.title}',
action_url='/',
data={
'title': f'Новая публикация {context}',
'post_id': post.id,
'post_type': post.type,
'post_title': post.title,
'author_id': post.author.id,
'department_id': (
post.department.id if post.department else None
),
}
)
def get_post_recipients(post):
"""
Возвращает список получателей уведомлений для публикации.
- TYPE_COMPANY - все активные сотрудники
- TYPE_DEPARTMENT - сотрудники отдела
- TYPE_EMPLOYEE - подписчики (если реализовано)
"""
if post.type == TYPE_COMPANY:
# Новость компании - всем
return list(Employee.objects.filter(is_active=True))
elif post.type == TYPE_DEPARTMENT and post.department:
# Новость отдела - сотрудникам отдела
return list(post.department.employees.filter(is_active=True))
elif post.type == TYPE_EMPLOYEE:
# Личная публикация - подписчикам
# TODO: реализовать систему подписок
# Пока отправляем всем активным или никому
return []
return []
def truncate_text(text, max_length=100):
"""
Обрезает текст до указанной длины.
"""
if not text:
return ''
text = text.strip()
if len(text) <= max_length:
return text
return text[:max_length - 3] + '...'
# ===== Функция для уведомлений о лайках =====
# Используется во views при добавлении лайка
def notify_post_reaction(post, user):
"""
Создает уведомление о реакции (лайке) на публикацию.
Вызывается вручную из view, так как лайки не имеют отдельной модели.
Args:
post: Публикация, которую лайкнули
user: Пользователь, который поставил лайк
"""
# Уведомляем автора (если лайк не от него)
if post.author.id != user.id:
notify.send(
sender=user,
recipient=post.author,
verb='feed_post_reaction',
action_object=post,
description=(
f'{user.get_full_name() or user.username} '
f'понравилась публикация "{post.title}"'
),
action_url='/',
data={
'title': 'Ваша публикация понравилась',
'post_id': post.id,
'post_title': post.title,
'user_id': user.id,
'reaction_type': 'like',
}
)