- /requests/{id}/ → /requests
- /documents/{id}/ → /documents
- /calendar (вместо /?event_id={id})
- /procurement/orders/{id}/ → /procurement
- /messages (вместо /communications/chats/{id}/)
- / для постов ленты
Теперь клик по уведомлениям не вызывает 404 ошибки
202 lines
7.2 KiB
Python
202 lines
7.2 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.services import NotificationService
|
||
|
||
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
|
||
|
||
from django.db import transaction
|
||
from notifications.tasks import process_post_notifications_task
|
||
|
||
def send_task():
|
||
"""Отложенная отправка задачи после commit транзакции"""
|
||
try:
|
||
process_post_notifications_task.delay(
|
||
post_id=instance.id,
|
||
action='created'
|
||
)
|
||
except Exception as e:
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
logger.warning(
|
||
f"Failed to queue post notifications task: {e}, "
|
||
f"falling back to sync"
|
||
)
|
||
# Fallback на старую логику
|
||
notify_new_post(instance)
|
||
|
||
transaction.on_commit(send_task)
|
||
|
||
|
||
@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:
|
||
NotificationService.create_notification_async(
|
||
recipient=post.author,
|
||
notification_type_code='feed_post_comment',
|
||
title='Новый комментарий к вашей публикации',
|
||
message=(
|
||
f'{comment_author.get_full_name() or comment_author.username}'
|
||
f' прокомментировал: {truncate_text(comment.text, 80)}'
|
||
),
|
||
content_object=post,
|
||
action_url='/',
|
||
metadata={
|
||
'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:
|
||
NotificationService.create_notification_async(
|
||
recipient=recipient,
|
||
notification_type_code='feed_new_post',
|
||
title=f'Новая публикация {context}',
|
||
message=f'{post.title}',
|
||
content_object=post,
|
||
action_url='/',
|
||
metadata={
|
||
'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:
|
||
NotificationService.create_notification_async(
|
||
recipient=post.author,
|
||
notification_type_code='feed_post_reaction',
|
||
title='Ваша публикация понравилась',
|
||
message=(
|
||
f'{user.get_full_name() or user.username} '
|
||
f'понравилась публикация "{post.title}"'
|
||
),
|
||
content_object=post,
|
||
action_url='/',
|
||
metadata={
|
||
'post_id': post.id,
|
||
'post_title': post.title,
|
||
'user_id': user.id,
|
||
'reaction_type': 'like',
|
||
}
|
||
)
|