Проблема: - Монолитный 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 - успешно
186 lines
6.6 KiB
Python
186 lines
6.6 KiB
Python
"""
|
||
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',
|
||
}
|
||
)
|