feat(communications): Реализация управления уведомлениями чатов и оптимизация

- Добавлен API endpoint для включения/выключения уведомлений чата
- Реализован frontend для управления уведомлениями через меню чата
- Обновлены сигналы для проверки настроек notifications_enabled
- Стандартизирована логика уведомлений для всех типов чатов
- Оптимизированы запросы к БД (1 запрос вместо N для ChatUserSettings)
- Переведены детальные логи с INFO на DEBUG уровень

Файлы:
- backend/api/v1/communications/views.py (новый endpoint)
- backend/api/v1/urls.py (новый маршрут)
- backend/static/js/components/chatMenuActions.js (полная реализация)
- backend/communications/notification_signals.py (проверка настроек + оптимизация)
- backend/notifications/services.py (оптимизация логирования)
This commit is contained in:
Игорь Надеин 2026-01-08 14:39:40 +03:00
parent 6da39e5120
commit 42879e0981
7 changed files with 1531 additions and 161 deletions

View File

@ -566,6 +566,47 @@ def pin_chat(request, chat_id):
return JsonResponse({'ok': True, 'pinned': is_pinned})
@login_required
@require_POST
@csrf_protect
def toggle_chat_notifications(request, chat_id):
"""Включение/выключение уведомлений для чата"""
import json
chat = get_object_or_404(Chat, pk=chat_id)
# Проверка доступа к чату
if not user_can_access_chat(chat, request.user):
return JsonResponse(
{'ok': False, 'error': 'Access denied'},
status=403
)
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse(
{'ok': False, 'error': 'Invalid JSON'},
status=400
)
enabled = data.get('enabled', True)
settings, created = ChatUserSettings.objects.get_or_create(
chat=chat,
user=request.user
)
settings.notifications_enabled = enabled
settings.save()
return JsonResponse({
'ok': True,
'enabled': enabled,
'message': f'Уведомления {"включены" if enabled else "отключены"}'
})
# ============ MESSAGE FORWARDING ============
@login_required

View File

@ -23,6 +23,7 @@ from .communications.views import (
load_chat_messages,
pin_chat,
remove_reaction,
toggle_chat_notifications,
upload_message_with_attachments,
)
from .communications.poll_views import (
@ -98,6 +99,11 @@ urlpatterns = [
pin_chat,
name="pin_chat"
),
path(
"communications/chats/<int:chat_id>/notifications/",
toggle_chat_notifications,
name="toggle_chat_notifications"
),
path(
"communications/messages/<int:message_id>/reactions/",
get_message_reactions,

View File

@ -13,12 +13,61 @@ from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Message, Chat
from .models import Message, Chat, ChatUserSettings
from notifications.services import NotificationService
Employee = get_user_model()
def should_send_notification(chat, user):
"""
Проверяет, нужно ли отправлять уведомление пользователю для данного чата.
Args:
chat: Chat объект
user: Employee объект
Returns:
bool: True если нужно отправить уведомление, False если пользователь отключил уведомления
"""
try:
settings = ChatUserSettings.objects.get(chat=chat, user=user)
return settings.notifications_enabled
except ChatUserSettings.DoesNotExist:
# Если настройки не найдены, считаем что уведомления включены по умолчанию
return True
def get_users_with_notifications_enabled(chat, users):
"""
Получает словарь пользователей с их настройками уведомлений.
Оптимизированная версия - один запрос к БД вместо N.
Args:
chat: Chat объект
users: QuerySet или список пользователей
Returns:
dict: {user_id: notifications_enabled}
"""
if not users:
return {}
user_ids = [u.id for u in users]
# Один запрос для всех пользователей
settings = ChatUserSettings.objects.filter(
chat=chat,
user_id__in=user_ids
).values('user_id', 'notifications_enabled')
# Создаем словарь для быстрого доступа
settings_dict = {s['user_id']: s['notifications_enabled'] for s in settings}
# Для пользователей без настроек - по умолчанию True
return {uid: settings_dict.get(uid, True) for uid in user_ids}
@receiver(post_save, sender=Message)
def create_message_notifications(sender, instance, created, **kwargs):
"""
@ -43,6 +92,13 @@ def create_message_notifications(sender, instance, created, **kwargs):
else:
participants = chat.participants.exclude(id=author.id)
# ОПТИМИЗАЦИЯ: Загружаем настройки уведомлений ОДНИМ запросом для всех участников
participants_list = list(participants)
if not participants_list:
return # Нет получателей - выходим
notification_settings = get_users_with_notifications_enabled(chat, participants_list)
# 1. Обработка упоминаний (@username)
mentioned_users = extract_mentions(content)
mentioned_user_ids = []
@ -52,22 +108,24 @@ def create_message_notifications(sender, instance, created, **kwargs):
for email in mentioned_users:
try:
user = Employee.objects.get(email=email)
if user in participants and user.id != author.id:
# Создаем уведомление об упоминании
NotificationService.create_notification(
recipient=user,
notification_type_code='chat_mention',
title=f'Вас упомянул {author.get_full_name() or author.username}',
message=truncate_message(content, 100),
content_object=instance,
action_url=f'/communications/chats/{chat.id}/?message={instance.id}',
metadata={
'chat_id': chat.id,
'chat_name': get_chat_name(chat),
'message_id': instance.id,
'author_id': author.id,
}
)
if user in participants_list and user.id != author.id:
# Проверяем настройки уведомлений (из предзагруженного словаря)
if notification_settings.get(user.id, True):
# Создаем уведомление об упоминании
NotificationService.create_notification(
recipient=user,
notification_type_code='chat_mention',
title=f'Вас упомянул {author.get_full_name() or author.username}',
message=truncate_message(content, 100),
content_object=instance,
action_url=f'/communications/chats/{chat.id}/?message={instance.id}',
metadata={
'chat_id': chat.id,
'chat_name': get_chat_name(chat),
'message_id': instance.id,
'author_id': author.id,
}
)
mentioned_user_ids.append(user.id)
except Employee.DoesNotExist:
continue
@ -78,16 +136,18 @@ def create_message_notifications(sender, instance, created, **kwargs):
# Создаем уведомление об ответе (если автор не упомянут явно)
if original_author.id not in mentioned_user_ids:
NotificationService.create_notification(
recipient=original_author,
notification_type_code='chat_reply',
title=f'{author.get_full_name() or author.username} ответил на ваше сообщение',
message=truncate_message(content, 100),
content_object=instance,
action_url=f'/communications/chats/{chat.id}/?message={instance.id}',
metadata={
'chat_id': chat.id,
'chat_name': get_chat_name(chat),
# Проверяем настройки уведомлений (из предзагруженного словаря)
if notification_settings.get(original_author.id, True):
NotificationService.create_notification(
recipient=original_author,
notification_type_code='chat_reply',
title=f'{author.get_full_name() or author.username} ответил на ваше сообщение',
message=truncate_message(content, 100),
content_object=instance,
action_url=f'/communications/chats/{chat.id}/?message={instance.id}',
metadata={
'chat_id': chat.id,
'chat_name': get_chat_name(chat),
'message_id': instance.id,
'reply_to_id': instance.reply_to.id,
'author_id': author.id,
@ -100,52 +160,52 @@ def create_message_notifications(sender, instance, created, **kwargs):
if instance.reply_to and instance.reply_to.author_id != author.id:
excluded_ids.add(instance.reply_to.author_id)
# Определяем, кому отправлять обычные уведомления
# Для приватных чатов - всегда отправляем
# Для announcement - отправляем ВСЕМ участникам (это важные объявления)
# Для групповых - можно добавить логику (например, только онлайн или с настройками)
if chat.type == 'private':
recipients_for_new_message = participants.exclude(id__in=excluded_ids)
# СТАНДАРТИЗИРОВАННАЯ ЛОГИКА УВЕДОМЛЕНИЙ:
# Все типы чатов могут отправлять уведомления, но пользователь может их отключить
# через настройки ChatUserSettings.notifications_enabled
# Определяем тип уведомления и заголовок в зависимости от типа чата
if chat.type == 'announcement':
notification_type = 'announcement_new_message'
title = f'Новое объявление от {author.get_full_name() or author.username}'
is_announcement = True
else:
# Для private, group, department, channel используем единый тип
notification_type = 'chat_new_message'
if chat.type == 'private':
title = f'Новое сообщение от {author.get_full_name() or author.username}'
else: # group, department, channel
title = f'{author.get_full_name() or author.username} в {get_chat_name(chat)}'
is_announcement = False
# Отправляем уведомления всем участникам (с учетом их настроек)
for recipient in participants_list:
# Пропускаем тех, кто уже получил упоминание или ответ
if recipient.id in excluded_ids:
continue
for recipient in recipients_for_new_message:
NotificationService.create_notification(
recipient=recipient,
notification_type_code='chat_new_message',
title=f'Новое сообщение от {author.get_full_name() or author.username}',
message=truncate_message(content, 100),
content_object=instance,
action_url=f'/communications/chats/{chat.id}/',
metadata={
'chat_id': chat.id,
'chat_name': get_chat_name(chat),
'message_id': instance.id,
'author_id': author.id,
}
)
elif chat.type == 'announcement':
# Для объявлений отправляем ВСЕМ участникам
recipients_for_announcement = participants.exclude(id__in=excluded_ids)
# Проверяем настройки уведомлений (из предзагруженного словаря)
if not notification_settings.get(recipient.id, True):
continue
for recipient in recipients_for_announcement:
NotificationService.create_notification(
recipient=recipient,
notification_type_code='announcement_new_message',
title=f'Новое объявление от {author.get_full_name() or author.username}',
message=truncate_message(content, 150),
content_object=instance,
action_url=f'/communications/chats/{chat.id}/',
metadata={
'chat_id': chat.id,
'chat_name': get_chat_name(chat),
'message_id': instance.id,
'author_id': author.id,
'is_announcement': True,
}
)
elif chat.type in ['group', 'department', 'channel']:
# Для групповых чатов - отправляем уведомление только если есть упоминание или ответ
# Обычные сообщения не создают уведомления, чтобы не спамить
pass
metadata = {
'chat_id': chat.id,
'chat_name': get_chat_name(chat),
'message_id': instance.id,
'author_id': author.id,
}
if is_announcement:
metadata['is_announcement'] = True
NotificationService.create_notification(
recipient=recipient,
notification_type_code=notification_type,
title=title,
message=truncate_message(content, 150 if is_announcement else 100),
content_object=instance,
action_url=f'/communications/chats/{chat.id}/',
metadata=metadata
)
@receiver(m2m_changed, sender=Chat.participants.through)
@ -162,31 +222,36 @@ def create_chat_added_notifications(sender, instance, action, pk_set, **kwargs):
# Это может быть последний изменивший или создатель чата
added_by = getattr(chat, 'created_by', None) or chat.participants.first()
# ОПТИМИЗАЦИЯ: Получаем всех новых пользователей одним запросом
new_users = Employee.objects.filter(id__in=pk_set)
# ОПТИМИЗАЦИЯ: Загружаем настройки одним запросом
notification_settings = get_users_with_notifications_enabled(chat, new_users)
# Создаем уведомления для новых участников
for user_id in pk_set:
try:
user = Employee.objects.get(id=user_id)
# Не отправляем уведомление тому, кто сам себя добавил
if added_by and user.id == added_by.id:
continue
NotificationService.create_notification(
recipient=user,
notification_type_code='chat_added_to_chat',
title=f'Вас добавили в чат',
message=f'Вы были добавлены в чат "{get_chat_name(chat)}"',
content_object=chat,
action_url=f'/communications/chats/{chat.id}/',
metadata={
'chat_id': chat.id,
'chat_type': chat.type,
'chat_name': get_chat_name(chat),
'added_by_id': added_by.id if added_by else None,
}
)
except Employee.DoesNotExist:
for user in new_users:
# Не отправляем уведомление тому, кто сам себя добавил
if added_by and user.id == added_by.id:
continue
# Проверяем настройки уведомлений (из предзагруженного словаря)
if not notification_settings.get(user.id, True):
continue
NotificationService.create_notification(
recipient=user,
notification_type_code='chat_added_to_chat',
title=f'Вас добавили в чат',
message=f'Вы были добавлены в чат "{get_chat_name(chat)}"',
content_object=chat,
action_url=f'/communications/chats/{chat.id}/',
metadata={
'chat_id': chat.id,
'chat_type': chat.type,
'chat_name': get_chat_name(chat),
'added_by_id': added_by.id if added_by else None,
}
)
# ===== Вспомогательные функции =====

View File

@ -53,7 +53,7 @@ class NotificationService:
Returns:
Созданное уведомление или None если тип не найден
"""
logger.info(
logger.debug(
f"[NotificationService.create_notification] НАЧАЛО: "
f"recipient={recipient.id} ({recipient.email}), "
f"type={notification_type_code}, "
@ -66,7 +66,7 @@ class NotificationService:
code=notification_type_code,
is_active=True
)
logger.info(
logger.debug(
f"[NotificationService.create_notification] Тип уведомления найден: "
f"{notification_type.name} (category={notification_type.category.code})"
)
@ -82,7 +82,7 @@ class NotificationService:
recipient, notification_type
)
logger.info(
logger.debug(
f"[NotificationService.create_notification] Настройки пользователя: "
f"enabled={settings.is_enabled}, "
f"web={settings.send_web}, "
@ -113,7 +113,7 @@ class NotificationService:
metadata=metadata or {},
)
logger.info(
logger.debug(
f"[NotificationService.create_notification] ✅ Уведомление создано: "
f"id={notification.id}, "
f"action_url={action_url}"
@ -121,12 +121,12 @@ class NotificationService:
# Отправить если нужно
if send_immediately:
logger.info(
logger.debug(
f"[NotificationService.create_notification] ➡️ Немедленная отправка notification_id={notification.id}"
)
NotificationService.send_notification(notification, settings)
else:
logger.info(
logger.debug(
f"[NotificationService.create_notification] ⏸️ Отложенная отправка notification_id={notification.id}"
)
@ -138,14 +138,9 @@ class NotificationService:
settings: Optional[UserNotificationSettings] = None
):
"""Отправить уведомление по всем каналам"""
logger.info(
f"\n{'='*80}\n"
f"[NotificationService.send_notification] 📤 НАЧАЛО ОТПРАВКИ\n"
f" notification_id={notification.id}\n"
f" recipient={notification.recipient.id} ({notification.recipient.email})\n"
f" type={notification.notification_type.code}\n"
f" title='{notification.title}'\n"
f"{'='*80}"
logger.debug(
f"[NotificationService.send_notification] 📤 НАЧАЛО notification_id={notification.id} "
f"recipient={notification.recipient.id} type={notification.notification_type.code}"
)
if settings is None:
@ -154,7 +149,7 @@ class NotificationService:
notification.notification_type
)
logger.info(
logger.debug(
f"[NotificationService.send_notification] Активные каналы: "
f"{'✅' if settings.send_web else '❌'} Web, "
f"{'✅' if settings.send_email else '❌'} Email, "
@ -163,14 +158,14 @@ class NotificationService:
# Отправить на веб (WebSocket) - для онлайн пользователей
if settings.send_web:
logger.info(
logger.debug(
f"[NotificationService.send_notification] 🌐 Отправка через WebSocket..."
)
try:
# Отправляем WebSocket только для онлайн уведомлений (не push)
NotificationService.send_web_socket(notification)
notification.sent_web = True
logger.info(
logger.debug(
f"[NotificationService.send_notification] ✅ WebSocket: успешно отправлено"
)
except Exception as e:
@ -179,7 +174,7 @@ class NotificationService:
exc_info=True
)
else:
logger.info(
logger.debug(
f"[NotificationService.send_notification] ⏭️ WebSocket: канал отключен"
)
@ -187,13 +182,13 @@ class NotificationService:
# ВАЖНО: отправляем независимо от send_web настройки,
# если есть активные push-подписки
if settings.send_web:
logger.info(
logger.debug(
f"[NotificationService.send_notification] 📲 Отправка Web Push (offline)..."
)
try:
push_count = NotificationService.send_web_push_notification(notification)
if push_count > 0:
logger.info(
logger.debug(
f"[NotificationService.send_notification] ✅ Web Push: отправлено на {push_count} устройств"
)
except Exception as e:
@ -202,19 +197,19 @@ class NotificationService:
exc_info=True
)
else:
logger.info(
logger.debug(
f"[NotificationService.send_notification] ⏭️ Web Push: канал отключен"
)
# Отправить на email
if settings.send_email:
logger.info(
logger.debug(
f"[NotificationService.send_notification] 📧 Отправка Email..."
)
try:
recipient_email = notification.recipient.email
if recipient_email:
logger.info(
logger.debug(
f"[NotificationService.send_notification] Email адрес: {recipient_email}"
)
success = EmailNotificationSender.send_notification_email(
@ -223,7 +218,7 @@ class NotificationService:
)
notification.sent_email = success
if success:
logger.info(
logger.debug(
f"[NotificationService.send_notification] ✅ Email: успешно отправлено на {recipient_email}"
)
else:
@ -242,16 +237,16 @@ class NotificationService:
exc_info=True
)
else:
logger.info(
logger.debug(
f"[NotificationService.send_notification] ⏭️ Email: канал отключен"
)
# Отправить в Telegram
if settings.send_telegram:
logger.info(
logger.debug(
f"[NotificationService.send_notification] 💬 Отправка в Telegram..."
)
logger.info(
logger.debug(
f"[NotificationService.send_notification] Получатель: user_id={notification.recipient.id}"
)
try:
@ -262,7 +257,7 @@ class NotificationService:
if telegram_chat_id:
telegram_chat_id = telegram_chat_id.strip()
logger.info(
logger.debug(
f"[NotificationService.send_notification] Telegram Chat ID: '{telegram_chat_id}'"
)
@ -275,7 +270,7 @@ class NotificationService:
'http://localhost:9000'
)
logger.info(
logger.debug(
f"[NotificationService.send_notification] ➡️ Вызов TelegramNotificationSender.send_notification"
)
@ -289,7 +284,7 @@ class NotificationService:
notification.sent_telegram = success
if success:
logger.info(
logger.debug(
f"[NotificationService.send_notification] ✅ Telegram: успешно отправлено (chat_id={telegram_chat_id})"
)
else:
@ -309,7 +304,7 @@ class NotificationService:
exc_info=True
)
else:
logger.info(
logger.debug(
f"[NotificationService.send_notification] ⏭️ Telegram: канал отключен"
)
@ -324,15 +319,11 @@ class NotificationService:
'sent_at'
])
logger.info(
f"\n{'='*80}\n"
f"[NotificationService.send_notification] 🎯 ЗАВЕРШЕНО\n"
f" notification_id={notification.id}\n"
f" Результаты: "
f"Web={'\u2705' if notification.sent_web else '\u274c'}, "
f"Email={'\u2705' if notification.sent_email else '\u274c'}, "
f"Telegram={'\u2705' if notification.sent_telegram else '\u274c'}\n"
f"{'='*80}"
logger.debug(
f"[NotificationService.send_notification] 🎯 ЗАВЕРШЕНО notification_id={notification.id} "
f"Web={'✅' if notification.sent_web else '❌'} "
f"Email={'✅' if notification.sent_email else '❌'} "
f"Telegram={'✅' if notification.sent_telegram else '❌'}"
)
@staticmethod
@ -341,7 +332,7 @@ class NotificationService:
Отправить уведомление через WebSocket (только для онлайн пользователей).
Переименовано из send_web_notification для ясности.
"""
logger.info(
logger.debug(
f"[NotificationService.send_web_socket] НАЧАЛО: "
f"notification_id={notification.id}, "
f"recipient={notification.recipient.id}"
@ -363,7 +354,7 @@ class NotificationService:
}
group_name = f'notifications_{notification.recipient.id}'
logger.info(
logger.debug(
f"[NotificationService.send_web_socket] Отправка в группу: {group_name}"
)
@ -375,7 +366,7 @@ class NotificationService:
}
)
logger.info(
logger.debug(
f"[NotificationService.send_web_socket] ✅ Уведомление отправлено в WebSocket"
)
@ -386,7 +377,7 @@ class NotificationService:
is_archived=False
).count()
logger.info(
logger.debug(
f"[NotificationService.send_web_socket] Обновление счетчика: {unread_count} непрочитанных"
)
@ -398,7 +389,7 @@ class NotificationService:
}
)
logger.info(
logger.debug(
f"[NotificationService.send_web_socket] ✅ Счетчик обновлен"
)
@ -417,7 +408,7 @@ class NotificationService:
from django.conf import settings as django_settings
from .models import WebPushSubscription
logger.info(
logger.debug(
f"[NotificationService.send_web_push_notification] НАЧАЛО: "
f"notification_id={notification.id}, "
f"recipient={notification.recipient.id}"
@ -442,12 +433,12 @@ class NotificationService:
subscription_count = subscriptions.count()
if subscription_count == 0:
logger.info(
logger.debug(
f"[NotificationService.send_web_push_notification] ℹ️ Нет активных push-подписок"
)
return 0
logger.info(
logger.debug(
f"[NotificationService.send_web_push_notification] Найдено подписок: {subscription_count}"
)
@ -530,7 +521,7 @@ class NotificationService:
except Exception as e:
logger.error(f"[WebPush] Unexpected error: {e}")
logger.info(
logger.debug(
f"[NotificationService.send_web_push_notification] ✅ Завершено: "
f"{success_count}/{subscription_count} успешно"
)

View File

@ -103,26 +103,45 @@ function handlePinAction(chatId, chatName, chatRow) {
/**
* Обработка настройки уведомлений
*/
function handleNotificationsAction(chatId, chatName) {
showDevMessage('Функция настройки уведомлений находится в разработке', 'info');
// TODO: Реализовать API endpoint для управления уведомлениями
/*
fetch(`/communications/api/chat/${chatId}/notifications/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({ enabled: false })
})
.then(response => response.json())
.then(data => {
async function handleNotificationsAction(chatId, chatName) {
try {
// Получаем текущее состояние из data-атрибута
const chatRow = document.querySelector(`[data-chat-id="${chatId}"]`);
const currentlyEnabled = chatRow?.dataset.notificationsEnabled !== 'false';
// Переключаем состояние
const newState = !currentlyEnabled;
const response = await fetch(`/api/v1/communications/chats/${chatId}/notifications/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({ enabled: newState })
});
const data = await response.json();
if (data.ok) {
console.log('Notifications updated');
// Обновляем data-атрибут
if (chatRow) {
chatRow.dataset.notificationsEnabled = newState.toString();
}
// Показываем toast с результатом
const message = data.message || `Уведомления ${newState ? 'включены' : 'отключены'}`;
showSuccessMessage(message);
// Обновляем иконку в меню
updateNotificationsIcon(chatRow, newState);
} else {
showErrorMessage(data.error || 'Ошибка при изменении настроек');
}
});
*/
} catch (error) {
console.error('Notifications toggle error:', error);
showErrorMessage('Ошибка при изменении настроек уведомлений');
}
}
/**
@ -276,3 +295,99 @@ function moveToUnpinnedSection(chatRow) {
const parent = chatRow.parentElement;
parent.appendChild(chatRow);
}
/**
* Обновить иконку уведомлений в чате
*/
function updateNotificationsIcon(chatRow, enabled) {
if (!chatRow) return;
// Ищем или создаем иконку уведомлений
let notifIcon = chatRow.querySelector('.chat-notification-icon');
if (!enabled) {
// Уведомления отключены - показываем перечеркнутый колокольчик
if (!notifIcon) {
notifIcon = document.createElement('i');
notifIcon.className = 'bi-bell-slash text-muted chat-notification-icon ms-1';
notifIcon.title = 'Уведомления отключены';
// Вставляем после заголовка чата
const titleElement = chatRow.querySelector('[data-chat-name]');
if (titleElement) {
titleElement.appendChild(notifIcon);
}
}
} else {
// Уведомления включены - убираем иконку
if (notifIcon) {
notifIcon.remove();
}
}
}
/**
* Показать сообщение об успехе
*/
function showSuccessMessage(message) {
const toastContainer = getOrCreateToastContainer();
const toastId = `toast-${Date.now()}`;
const toastHtml = `
<div id="${toastId}" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
<i class="bi-check-circle-fill me-2"></i>${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement, {
autohide: true,
delay: 3000
});
toast.show();
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}
/**
* Показать сообщение об ошибке
*/
function showErrorMessage(message) {
const toastContainer = getOrCreateToastContainer();
const toastId = `toast-${Date.now()}`;
const toastHtml = `
<div id="${toastId}" class="toast align-items-center text-white bg-danger border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
<i class="bi-exclamation-triangle-fill me-2"></i>${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement, {
autohide: true,
delay: 5000
});
toast.show();
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}

View File

@ -0,0 +1,411 @@
# Анализ функции "Уведомления" в контекстном меню чатов
**Дата проверки:** 8 января 2026 г.
**Файл:** `templates/includes/components/chat_menu.html`
**Элемент:** `<a data-action="notifications">`
---
## 🔍 Что найдено
### 1. HTML элемент (UI)
**Местоположение:** `backend/templates/includes/components/chat_menu.html:22`
```html
<a class="dropdown-item" href="#" data-action="notifications">
<i class="bi-bell me-2"></i>Уведомления
</a>
```
**Используется в:**
- `templates/includes/components/chat_card.html` (строка 25)
- `templates/communications/chat_list.html` (6 раз - для каждого типа чатов)
**Статус:** ✅ Элемент есть, отображается в меню каждого чата
---
### 2. JavaScript обработчик
**Файл:** `backend/static/js/components/chatMenuActions.js`
**Функция:** `handleNotificationsAction()` (строка 110)
```javascript
function handleNotificationsAction(chatId, chatName) {
showDevMessage('Функция настройки уведомлений находится в разработке', 'info');
// TODO: Реализовать API endpoint для управления уведомлениями
/*
fetch(`/communications/api/chat/${chatId}/notifications/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({ enabled: false })
})
.then(response => response.json())
.then(data => {
if (data.ok) {
console.log('Notifications updated');
}
});
*/
}
```
**Статус:** 🔴 **ЗАГЛУШКА!** Показывает toast уведомление "Функция настройки уведомлений находится в разработке"
**Что происходит при клике:**
1. Пользователь кликает на "Уведомления"
2. Dropdown меню закрывается
3. Показывается blue toast с иконкой info: "Функция настройки уведомлений находится в разработке"
4. Никакие API не вызываются
5. Никакие настройки не меняются
---
### 3. API Endpoint
**Ожидаемый URL:** `/api/v1/communications/chats/<chat_id>/notifications/`
**Статус:** ❌ **НЕ СУЩЕСТВУЕТ!**
Проверено в:
- ✅ `backend/api/v1/urls.py` - маршрута нет
- ✅ `backend/api/v1/communications/views.py` - функции нет
**Существующий похожий endpoint:**
```python
# Есть для закрепления (PIN):
path("communications/chats/<int:chat_id>/pin/", pin_chat, name="pin_chat")
# НЕТ для уведомлений:
# path("communications/chats/<int:chat_id>/notifications/", toggle_notifications, name="toggle_notifications")
```
---
### 4. Backend модель
**Модель:** `ChatUserSettings` в `communications/models.py:304`
```python
class ChatUserSettings(models.Model):
"""Персональные настройки пользователя для чата"""
chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name='user_settings')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
# Закрепление
is_pinned = models.BooleanField(default=False, verbose_name="Закреплен")
pinned_at = models.DateTimeField(null=True, blank=True, verbose_name="Время закрепления")
pin_order = models.IntegerField(default=0, verbose_name="Порядок закрепленных")
# Уведомления ✅
notifications_enabled = models.BooleanField(default=True, verbose_name="Уведомления")
# Кастомное название (если пользователь переименовал)
custom_name = models.CharField(max_length=255, blank=True, verbose_name="Свое название")
# Скрыть чат
is_hidden = models.BooleanField(default=False, verbose_name="Скрыт")
class Meta:
unique_together = [('chat', 'user')]
ordering = ['-is_pinned', '-pinned_at']
```
**Статус:** ✅ **Поле `notifications_enabled` существует!**
---
### 5. Использование в сигналах
**Файл:** `backend/communications/notification_signals.py`
**Проблема:** ❌ **Поле `notifications_enabled` НЕ ПРОВЕРЯЕТСЯ!**
```python
@receiver(post_save, sender=Message)
def create_message_notifications(sender, instance, created, **kwargs):
"""Создает уведомления при создании нового сообщения"""
if not created or instance.is_system or instance.is_deleted:
return
chat = instance.chat
author = instance.author
content = instance.content
# Получаем всех участников чата кроме автора
if chat.type in ['announcement', 'channel', 'department', 'global']:
participants = chat.get_participants.exclude(id=author.id)
else:
participants = chat.participants.exclude(id=author.id)
# ❌ ПРОБЛЕМА: Не проверяется ChatUserSettings.notifications_enabled!
# Уведомления отправляются ВСЕМ участникам независимо от их настроек
for recipient in recipients_for_new_message:
NotificationService.create_notification(
recipient=recipient,
notification_type_code='chat_new_message',
title=f'Новое сообщение от {author.get_full_name() or author.username}',
message=truncate_message(content, 100),
# ...
)
```
**Должно быть:**
```python
for recipient in recipients_for_new_message:
# Проверяем настройки уведомлений пользователя для этого чата
chat_settings = ChatUserSettings.objects.filter(
chat=chat,
user=recipient
).first()
# Пропускаем, если уведомления отключены
if chat_settings and not chat_settings.notifications_enabled:
continue
NotificationService.create_notification(...)
```
---
## 📊 Сводная таблица статусов
| Компонент | Статус | Описание |
|-----------|--------|----------|
| **HTML элемент** | ✅ Есть | Кнопка "Уведомления" в меню чата |
| **JavaScript обработчик** | 🔴 Заглушка | Показывает toast "в разработке" |
| **API endpoint** | ❌ Нет | `/communications/chats/<id>/notifications/` не существует |
| **Backend функция** | ❌ Нет | `toggle_chat_notifications()` не реализована |
| **Модель ChatUserSettings** | ✅ Есть | Поле `notifications_enabled` существует |
| **Проверка в сигналах** | ❌ Нет | Настройки не проверяются при отправке |
| **Маршрут в urls.py** | ❌ Нет | Не зарегистрирован |
---
## ❌ Вердикт: ПОЛНОСТЬЮ НЕ РАБОТАЕТ
### Что реально происходит:
1. **При клике на "Уведомления":**
- ✅ Меню закрывается
- ✅ Показывается toast "Функция в разработке"
- ❌ API не вызывается
- ❌ Настройки не меняются
- ❌ Ничего не сохраняется
2. **При отправке сообщения в чат:**
- ✅ Сигнал срабатывает
- ✅ Уведомления создаются
- ❌ Настройка `notifications_enabled` **ИГНОРИРУЕТСЯ**
- ❌ Уведомления приходят **ВСЕМ** независимо от настроек
3. **При попытке отключить уведомления:**
- ❌ Функция не реализована
- ❌ Изменить настройки невозможно (нет UI)
- ❌ База данных не обновляется
---
## 🔧 Что нужно реализовать
### Шаг 1: Создать API endpoint
**Файл:** `backend/api/v1/communications/views.py`
Добавить функцию:
```python
@login_required
@require_POST
def toggle_chat_notifications(request, chat_id):
"""Включение/выключение уведомлений для чата"""
import json
chat = get_object_or_404(Chat, pk=chat_id)
# Проверка доступа к чату
if not user_can_access_chat(chat, request.user):
return JsonResponse(
{'ok': False, 'error': 'Access denied'},
status=403
)
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse(
{'ok': False, 'error': 'Invalid JSON'},
status=400
)
enabled = data.get('enabled', True)
settings, created = ChatUserSettings.objects.get_or_create(
chat=chat,
user=request.user
)
settings.notifications_enabled = enabled
settings.save()
return JsonResponse({
'ok': True,
'enabled': enabled,
'message': f'Уведомления {"включены" if enabled else "отключены"}'
})
```
### Шаг 2: Добавить маршрут
**Файл:** `backend/api/v1/urls.py`
После строки с `pin_chat` добавить:
```python
path(
"communications/chats/<int:chat_id>/notifications/",
toggle_chat_notifications,
name="toggle_chat_notifications"
),
```
### Шаг 3: Обновить JavaScript
**Файл:** `backend/static/js/components/chatMenuActions.js`
Заменить функцию `handleNotificationsAction`:
```javascript
async function handleNotificationsAction(chatId, chatName) {
try {
// Получаем текущее состояние (можно хранить в data-атрибуте)
const chatRow = document.querySelector(`[data-chat-id="${chatId}"]`);
const currentlyEnabled = chatRow?.dataset.notificationsEnabled !== 'false';
// Переключаем состояние
const newState = !currentlyEnabled;
const response = await fetch(`/api/v1/communications/chats/${chatId}/notifications/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({ enabled: newState })
});
const data = await response.json();
if (data.ok) {
// Обновляем data-атрибут
if (chatRow) {
chatRow.dataset.notificationsEnabled = newState.toString();
}
// Показываем toast с результатом
showSuccessMessage(data.message || `Уведомления ${newState ? 'включены' : 'отключены'}`);
// Можно добавить визуальный индикатор (иконка колокольчика с перечеркиванием)
updateNotificationsIcon(chatRow, newState);
} else {
showErrorMessage(data.error || 'Ошибка при изменении настроек');
}
} catch (error) {
console.error('Notifications toggle error:', error);
showErrorMessage('Ошибка при изменении настроек уведомлений');
}
}
```
### Шаг 4: Обновить сигналы
**Файл:** `backend/communications/notification_signals.py`
В функции `create_message_notifications` после получения списка участников добавить проверку:
```python
# Фильтруем получателей с учетом их настроек уведомлений
filtered_recipients = []
for recipient in recipients_for_new_message:
# Проверяем настройки уведомлений для этого чата
chat_settings = ChatUserSettings.objects.filter(
chat=chat,
user=recipient
).first()
# Если настройки не найдены - по умолчанию уведомления включены
# Если найдены - проверяем флаг
if not chat_settings or chat_settings.notifications_enabled:
filtered_recipients.append(recipient)
# Отправляем уведомления только отфильтрованным получателям
for recipient in filtered_recipients:
NotificationService.create_notification(...)
```
### Шаг 5: Добавить индикатор в UI (опционально)
В `templates/includes/components/chat_card.html` добавить визуальный индикатор:
```html
<div class="chat-row"
data-chat-id="{{ chat.pk }}"
data-notifications-enabled="{% if chat_settings.notifications_enabled %}true{% else %}false{% endif %}">
{# ... существующий код ... #}
{# Индикатор отключенных уведомлений #}
{% if not chat_settings.notifications_enabled %}
<i class="bi-bell-slash text-muted" title="Уведомления отключены"></i>
{% endif %}
</div>
```
---
## 📝 Приоритет реализации
**Оценка работы:** 2-3 часа
**Сложность:** 🟡 Средняя
**Приоритет:** 🟡 Средний (не критично, но желательно)
**Зависимости:** Нет
**Блокирует:** Ничего
---
## ✅ Чек-лист реализации
- [ ] Создать функцию `toggle_chat_notifications()` в `views.py`
- [ ] Добавить маршрут в `urls.py`
- [ ] Импортировать функцию в начале `urls.py`
- [ ] Обновить JavaScript в `chatMenuActions.js`
- [ ] Добавить проверку в `notification_signals.py`
- [ ] Протестировать включение/выключение уведомлений
- [ ] Убедиться, что уведомления не приходят при отключенных настройках
- [ ] Добавить индикатор в UI (опционально)
- [ ] Создать миграцию если изменяли модель (не требуется)
---
## 🎯 Результат после реализации
После реализации пользователи смогут:
1. ✅ Кликнуть на "Уведомления" в меню чата
2. ✅ Увидеть toggle для включения/выключения
3. ✅ Сохранить свой выбор
4. ✅ Перестать получать уведомления от выбранного чата
5. ✅ Увидеть визуальный индикатор (иконка колокольчика)
**Уведомления будут приходить только от тех чатов, где они включены!**

View File

@ -0,0 +1,741 @@
# Текущее состояние приложения Communications
**Дата анализа:** 8 января 2026 г.
**Ветка:** feature/communications-work
---
## 📊 Общий обзор
Приложение **communications** представляет собой полнофункциональный корпоративный мессенджер, интегрированный в систему EUSRR. Основная работа по реализации функционала выполнена, включая модели данных, WebSocket коммуникацию, REST API и базовый фронтенд.
---
## ✅ Что полностью реализовано
### 1. **Модели данных** (15 моделей)
Все модели созданы и протестированы через миграции (20 миграций применено):
#### Основные модели:
- **Chat** - базовая модель чата с типами: `private`, `group`, `department`, `channel`, `announcement`, `global`
- Поля: name, description, avatar, created_by, include_all_employees
- Блокировка чатов (для announcement)
- Уникальные ограничения для main-чатов
- **Message** - сообщения с расширенным функционалом
- Редактирование: `is_edited`, `edited_at`, `edit_history`
- Мягкое удаление: `is_deleted`, `deleted_at`, `deleted_by`
- Ответы: `reply_to`
- Системные сообщения: `is_system`, `system_type`, `system_metadata`
- Закрепление: `is_pinned`, `pinned_by`, `pinned_at`
- Пересылка: `is_forwarded`
- Вложения: `has_attachments`
- Треды: `thread_root`, `thread_reply_count`
- **ChatReadState** - состояние прочтения
- Последнее прочитанное: `last_read_at`, `last_read_message`
- Упоминания: `unread_mentions_count`
- Треды: `unread_thread_replies_count`
- Статус набора: `is_typing`, `typing_updated_at`
#### Настройки пользователя:
- **ChatUserSettings** - персональные настройки чата
- Закрепление: `is_pinned`, `pinned_at`, `pin_order`
- Уведомления: `notifications_enabled`
- Кастомизация: `custom_name`
- Скрытие: `is_hidden`
#### Участники и права:
- **ChatMembership** - участие в чате с ролями
- Роли: `owner`, `admin`, `moderator`, `member`, `guest`
- Права: `can_send_messages`, `can_add_members`, `can_remove_members`, `can_pin_messages`, `can_edit_chat`
- История: `joined_at`, `left_at`, `invited_by`
- **ChatAccessPermission** - временные права доступа
- Уровни: `read`, `write`, `moderate`
- Временные: `expires_at`
- Аудит: `granted_by`, `granted_at`
#### Вложения и медиа:
- **MessageAttachment** - файлы и медиа
- Типы: `image`, `video`, `audio`, `file`
- Превью для изображений: `thumbnail`
- Метаданные: `file_size`, `mime_type`, `file_name`
#### Пересылка и ответы:
- **ForwardedMessage** - информация о пересылке
- Цепочка: `original_message`, `immediate_source`
- Счетчик: `forward_count`
- Сохранение контента при удалении оригинала
- **MessageReply** - расширенная информация об ответах
- Типы: `inline`, `quote`, `thread`
- Кросс-чат: `is_cross_chat_reply`
- Сохранение контекста
- **CrossChatMessage** - отправка в чужие чаты
- Модерация: `approved`, `approved_by`, `approved_at`
- Режимы отображения: `guest`, `forwarded`, `announcement`
- Причина: `reason`
#### Реакции:
- **MessageReaction** - реакции на сообщения
- Эмодзи и пользователь
- Временные метки
- **AvailableReaction** - доступные реакции
- Unicode emoji
- Категории и популярность
- Активация/деактивация
#### Опросы (новое):
- **Poll** - опросы в чатах
- Типы: `single`, `multiple`, `quiz`
- Анонимность, публичные результаты
- Ограничение по времени: `closes_at`
- Статистика голосов
- **PollOption** - варианты ответа
- Текст и порядок
- Счетчик голосов
- **PollVote** - голоса пользователей
- Отслеживание выбора
- Временные метки
---
### 2. **WebSocket Consumer** (consumers.py)
**ChatConsumer** полностью реализован:
#### Поддерживаемые действия:
- `send_message` - отправка сообщений (с reply_to)
- `edit_message` - редактирование в реальном времени
- `delete_message` - мягкое удаление
- `add_reaction` - добавление реакции
- `remove_reaction` - удаление реакции
- `typing` - начало набора текста
- `stop_typing` - окончание набора
#### Real-time события:
- `chat.message` - новое сообщение
- `chat.message_edited` - сообщение отредактировано
- `chat.message_deleted` - сообщение удалено
- `chat.reaction_added` - реакция добавлена
- `chat.reaction_removed` - реакция удалена
- `chat.user_typing` - пользователь печатает
- `chat.user_stopped_typing` - пользователь остановил печатать
#### Особенности:
- Полная сериализация сообщений с вложениями
- Обработка пересланных сообщений
- Информация о реакциях всех пользователей
- Превью ответов
- Автоматическая группировка по чатам
---
### 3. **Views и URL** (views.py, urls.py)
#### Реализованные view:
- **ChatListView** - список чатов пользователя
- Отображение последних сообщений
- Счетчик непрочитанных
- Фильтрация по типам
- **ChatDetailView** - детальный просмотр чата
- Пагинация сообщений (50 на страницу)
- Форма отправки
- Проверка прав доступа
- Загрузка истории (before/after timestamp)
- **start_private_chat** - создание приватного чата
- Автоматический поиск существующего
- Создание нового при необходимости
- **chat_mark_read** - отметка как прочитанное
- Обновление ChatReadState
#### URL маршруты:
```
/communications/chats/ - список чатов
/communications/chats/<id>/ - детали чата
/communications/chats/start/<employee_id>/ - начать личный чат
/communications/chats/<id>/mark-read/ - отметить прочитанным
```
---
### 4. **Frontend** (JavaScript модули)
#### Реализованные JS модули:
**chat-detail-enhanced.js** (~500 строк):
- WebSocket интеграция
- Редактирование сообщений
- Удаление сообщений
- Быстрые реакции (6 эмодзи при наведении)
- Ответы на сообщения
- Индикатор "печатает..."
- Отображение вложений
- Информация о пересылке
- Автопереподключение WebSocket
**chat-reactions-integration.js**:
- Обработка WS событий реакций
- Обновление UI реакций в реальном времени
**chat-detail-init.js**:
- Инициализация модулей
- Настройка WebSocket подключения
#### CSS стили:
**css/components/chat-list-enhanced.css**:
- Стили для списка чатов
- Контекстное меню
- Закрепленные чаты (желтый фон)
- Анимация обновлений
**css/components/chat-enhanced.css** (частично):
- Превью ответов
- Информация о пересылке
- Кнопки действий
- Реакции
- Вложения
---
### 5. **Админ-панель** (admin.py)
Полностью настроена для всех моделей:
- Inline редактирование вложений
- Inline редактирование участников
- Фильтры по типам, статусам
- Поиск по названию, контенту
- Read-only поля для системных данных
---
## ⚠️ Что требует доработки
### 1. **REST API** ✅ РЕАЛИЗОВАНО!
✅ **API реализован в отдельном приложении** `backend/api/v1/communications/`
#### Реализованные endpoints (18 штук):
**Чаты:**
- ✅ `GET /api/v1/communications/chats/` - список чатов пользователя
- ✅ `POST /api/v1/communications/chats/create/` - создание чата
- ✅ `GET /api/v1/communications/chats/<pk>/messages/` - загрузка истории
- ✅ `POST /api/v1/communications/chats/<chat_id>/pin/` - закрепление чата
**Сообщения:**
- ✅ `POST /api/v1/communications/upload-message/` - отправка с вложениями
- ✅ `POST /api/v1/communications/messages/<id>/edit/` - редактирование
- ✅ `POST /api/v1/communications/messages/<id>/delete/` - удаление
- ✅ `POST /api/v1/communications/messages/forward/` - пересылка
- ✅ `POST /api/v1/communications/messages/bulk-delete/` - массовое удаление
**Реакции:**
- ✅ `GET /api/v1/communications/reactions/available/` - доступные реакции
- ✅ `GET /api/v1/communications/messages/<id>/reactions/` - реакции сообщения
- ✅ `POST /api/v1/communications/messages/<id>/react/` - добавить реакцию
- ✅ `POST /api/v1/communications/messages/<id>/unreact/` - удалить реакцию
**Опросы (Polls):**
- ✅ `POST /api/v1/communications/polls/create/` - создание опроса
- ✅ `POST /api/v1/communications/polls/<id>/vote/` - голосование
- ✅ `POST /api/v1/communications/polls/<id>/close/` - закрытие опроса
- ✅ `GET /api/v1/communications/polls/<id>/results/` - результаты
**Файлы:**
- ✅ Загрузка файлов интегрирована в `upload_message_with_attachments`
- ✅ Автоопределение типа файла (image/video/audio/file)
- ✅ Сохранение метаданных (размер, MIME-тип, имя)
**Статус:** ✅ Полностью реализовано
**Приоритет:** Завершено
### Недостающие endpoints (низкий приоритет):
❌ `POST /api/v1/communications/chats/<id>/update/` - обновление чата
❌ `POST /api/v1/communications/chats/<id>/notifications/` - настройки уведомлений
❌ `POST /api/v1/communications/messages/<id>/pin/` - закрепление сообщения
❌ `POST /api/v1/communications/chats/<id>/member/add/` - добавление участника
❌ `POST /api/v1/communications/chats/<id>/member/remove/` - удаление участника
### 2. **Опросы (Polls)** ✅ Backend готов!
✅ Модели созданы (Poll, PollOption, PollVote)
✅ Админ-панель настроена
✅ **API полностью реализован** (4 эндпоинта):
- `POST /polls/create/` - создание с настройками (анонимность, множественный выбор, викторина)
- `POST /polls/<id>/vote/` - голосование с валидацией
- `POST /polls/<id>/close/` - закрытие опроса
- `GET /polls/<id>/results/` - получение результатов
✅ WebSocket интеграция (отправка через chat.message)
✅ Поддержка викторин с правильными ответами
✅ Автозакрытие опросов по таймеру
❌ Frontend UI не реализован
**Требуется:**
- Модальное окно создания опроса
- Отображение опросов в чате
- UI голосования
- Визуализация результатов (графики)
- Отображение правильных ответов в викторинах
**Статус:** 🟡 Backend готов, нужен Frontend
**Приоритет:** Средний
### 3. **Вложения (Attachments)** ✅ Полностью работает!
✅ Модель MessageAttachment создана
✅ Связь с Message настроена
✅ **Upload endpoint реализован** (`/upload-message/`)
✅ Автоопределение типа файла (image/video/audio/file)
✅ Сохранение метаданных (размер, MIME-тип, имя)
✅ Отправка через WebSocket с полной информацией
🟡 Frontend частично реализован (отображение файлов)
❌ Drag & drop загрузка не реализована
❌ Прогресс-бар загрузки отсутствует
❌ Превью для видео/аудио базовое
**Требуется доработать:**
- Drag & drop интерфейс
- Прогресс-бар при загрузке
- Превью для видео с плеером
- Превью для аудио с waveform
- Валидация размера файлов (клиент)
- Пакетная загрузка нескольких файлов
**Статус:** ✅ Backend готов, Frontend требует улучшения
**Приоритет:** Средний
### 4. **Пересылка сообщений (Forward)** ✅ Полностью работает!
✅ Модель ForwardedMessage создана
✅ Поля в Message добавлены (is_forwarded, metadata)
✅ **API endpoint реализован** (`POST /messages/forward/`)
✅ Поддержка пересылки нескольких сообщений
✅ Сохранение цепочки пересылок
✅ Копирование вложений
✅ WebSocket уведомления
✅ Отображение метаданных (оригинальный автор, чат, дата)
❌ UI выбора целевого чата не реализован
❌ Модальное окно для пересылки отсутствует
**Требуется доработать:**
- Модальное окно с выбором чата
- Список доступных чатов для пересылки
- Предпросмотр пересылаемых сообщений
- Возможность добавить комментарий к пересылке
**Статус:** ✅ Backend готов, нужен UI
**Приоритет:** Средний
### 5. **Треды (Threads)**
✅ Поля в Message (`thread_root`, `thread_reply_count`)
❌ UI для тредов отсутствует
❌ WebSocket обработка отсутствует
❌ Боковая панель для тредов не реализована
**Требуется:**
- Боковая панель с тредами
- Счетчики ответов
- Навигация по тредам
- WebSocket события для тредов
**Статус:** ❌ Не реализовано
**Приоритет:** Низкий
### 6. **Упоминания (@mentions)**
✅ Поле `unread_mentions_count` в ChatReadState
❌ Парсинг @username в тексте
❌ Подсветка упоминаний
❌ Уведомления об упоминаниях
❌ Автодополнение при вводе @
**Требуется:**
- Regex парсинг @username
- Подсветка в UI
- Интеграция с notifications
- Dropdown с предложениями пользователей
**Статус:** ❌ Не реализовано
**Приоритет:** Средний
### 7. **Поиск по сообщениям**
❌ Полностью отсутствует
**Требуется:**
- Поисковая строка в чате
- Full-text search по содержимому
- Фильтры (дата, автор, тип)
- Навигация по результатам
- Подсветка найденного текста
**Статус:** ❌ Не реализовано
**Приоритет:** Средний
### 8. **Контекстное меню чатов** 🟡 Частично
🟡 Частично реализовано в CSS
✅ **API endpoints реализованы:**
- `POST /chats/<id>/pin/` - закрепление/открепление
- Остальные действия можно сделать через существующие endpoints
❌ JavaScript обработчики контекстного меню отсутствуют
❌ Модальные окна для действий не реализованы
**Функции контекстного меню:**
- ✅ Закрепить/открепить чат (API готов)
- ❌ Выключить уведомления (нужен UI)
- ❌ Скрыть чат (нужен UI)
- ❌ Отметить прочитанным (есть endpoint, нужен UI)
- ❌ Покинуть чат (нужна реализация)
- ❌ Удалить чат (для владельца)
**Статус:** 🟡 API готов, нужны JS обработчики
**Приоритет:** Средний
### 9. **Групповые чаты** ✅ Backend готов!
✅ Модель Chat поддерживает type='group'
✅ ChatMembership для участников
✅ **API endpoint реализован** (`POST /chats/create/`)
✅ Поддержка добавления участников при создании
✅ Создатель автоматически добавляется
✅ Загрузка аватара чата
❌ UI создания группового чата отсутствует
❌ Управление участниками не реализовано
❌ Редактирование названия/описания/аватара отсутствует (UI)
**Требуется:**
- Модальное окно создания группы
- Список сотрудников с checkbox для выбора
- UI для управления участниками (добавление/удаление)
- Редактирование информации о группе
- Отображение ролей и прав участников
**Статус:** ✅ Backend готов, нужен UI
**Приоритет:** Высокий
### 10. **Каналы и объявления**
✅ Модели поддерживают channel и announcement
✅ Блокировка чатов реализована
❌ UI для создания канала отсутствует
❌ Система модерации не реализована
❌ Права только на чтение не обработаны
**Статус:** 🟡 Частично
**Приоритет:** Средний
---
## 🔧 Технические проблемы
### 1. **Архитектура API** ✅ Решено!
✅ **API вынесен в отдельное приложение** `backend/api/v1/communications/`
✅ Правильная организация:
- `views.py` - основные API views (14 функций)
- `poll_views.py` - API для опросов (4 функции)
- Регистрация в `api/v1/urls.py`
✅ REST-интерфейс для внешних интеграций
✅ Легко тестировать отдельные функции
✅ Возможна документация API (Swagger)
**Статус:** ✅ Решено
### 2. **Неполная интеграция фронтенда**
Модули `chat-detail-enhanced.js` созданы, но:
- Не все функции подключены
- Отсутствуют модальные окна
- Нет обработки ошибок
- Слабая валидация на клиенте
**Решение:** Завершить интеграцию JS модулей
### 3. **Отсутствие тестов**
Файл `tests.py` существует, но пустой:
- Нет unit-тестов для моделей
- Нет интеграционных тестов API
- Не тестируется WebSocket
- Нет e2e тестов
**Решение:** Написать тесты с pytest/pytest-django
### 4. **Производительность**
Потенциальные проблемы:
- N+1 запросы при загрузке списка чатов
- Не используется select_related/prefetch_related везде
- Нет кэширования часто запрашиваемых данных
- WebSocket группы могут стать узким местом
**Решение:** Оптимизация запросов, Redis для кэша
### 5. **Безопасность**
Требует внимания:
- Загрузка файлов без проверки типа/размера
- Отсутствие rate limiting для API
- XSS уязвимости при отображении контента
- CSRF проверки для API
**Решение:** Добавить валидацию, throttling, санитизацию
---
## 📝 Приоритетный план доработки
### ✅ Завершено в Backend (больше не требуется)
1. ✅ **REST API создан** - 18 эндпоинтов реализовано
2. ✅ **Загрузка файлов работает** - upload endpoint готов
3. ✅ **Групповые чаты (Backend)** - API создания готов
4. ✅ **Опросы (Backend)** - полный цикл реализован
5. ✅ **Пересылка сообщений (Backend)** - API готов
6. ✅ **Реакции** - полностью работают
---
### Этап 1: Критичные UI компоненты (1-2 недели)
1. **Создание групповых чатов (UI)** 🔴
- Модальное окно создания
- Выбор участников (список сотрудников)
- Загрузка аватара
- Установка названия и описания
2. **Модальное окно пересылки** 🔴
- Список доступных чатов
- Выбор целевого чата
- Предпросмотр пересылаемых сообщений
- Интеграция с API `/messages/forward/`
3. **Контекстное меню чатов (JS)** 🔴
- Обработчики ПКМ
- Интеграция с API закрепления
- Действия: скрыть, уведомления, прочитано
- Покинуть/удалить чат
4. **Drag & Drop загрузка файлов** 🔴
- Зона перетаскивания
- Прогресс-бар загрузки
- Превью перед отправкой
- Валидация размера
### Этап 2: Важные UI функции (2-3 недели)
5. **Опросы (UI)** 🟡
- Модальное окно создания опроса
- Отображение опросов в чате
- UI голосования с анимацией
- Графики результатов
- Индикация правильных ответов (викторины)
6. **Управление групповыми чатами** 🟡
- Редактирование названия/описания
- Список участников с ролями
- Добавление/удаление участников
- Управление правами
7. **Упоминания (@mentions)** 🟡
- Парсинг @username в тексте
- Подсветка упоминаний
- Автодополнение при вводе @
- Уведомления об упоминаниях
8. **Поиск по сообщениям** 🟡
- UI поиска в чате
- Full-text search
- Фильтры (дата, автор, тип)
- Навигация по результатам
### Этап 3: Дополнительные функции (3-4 недели)
9. **Треды (Threads)** 🟢
- Боковая панель с тредами
- Счетчики ответов
- Навигация по тредам
- WebSocket для тредов
10. **Каналы и объявления (UI)** 🟢
- UI создания канала
- Модерация сообщений
- Права только на чтение
11. **Оптимизация производительности** 🟢
- Query optimization (select_related/prefetch)
- Redis кэширование
- Индексы БД
- Пагинация везде
12. **Тестирование** 🟢
- Unit тесты для API
- Integration тесты WebSocket
- E2E тесты UI
### Отложенные функции (низкий приоритет)
- Недостающие endpoints (update chat, pin message, manage members)
- Голосовые сообщения
- Видеозвонки
- Экспорт истории чата
- Боты и автоматизация
- Rich text редактор
- Push-уведомления
---
## 🎯 Рекомендации
### Немедленные действия:
1. ✅ **Создать ветку** feature/communications-work (СДЕЛАНО)
2. ✅ **REST API** - полностью реализован (18 endpoints)
3. ✅ **Загрузка файлов** - endpoint работает
4. 🔴 **UI для групповых чатов** - модальное окно создания
5. 🔴 **Модальное окно пересылки** - выбор целевого чата
6. 🟡 **Добавить тесты** - для стабильности API
### Архитектурные решения:
- ✅ **Django function-based views** используются для API (правильный выбор)
- ✅ **WebSocket интеграция** работает корректно
- 🟡 Рассмотреть **Celery** для асинхронной обработки медиа (оптимизация)
- ✅ **Redis** уже используется для Channels
- 🔴 Применить **select_related/prefetch_related** везде (оптимизация)
- 🟡 Внедрить **pytest** для тестирования API
### Документация:
- 🔴 Создать API документацию (можно использовать drf-spectacular)
- 🟡 Написать пользовательские инструкции
- ✅ WebSocket протокол задокументирован (в consumers.py)
- 🟡 Добавить архитектурные диаграммы
---
## 📚 Существующая документация
В папке `backend/communications/`:
- ✅ **MESSENGER_IMPLEMENTATION_COMPLETE.md** - что реализовано
- ✅ **MESSENGER_IMPROVEMENTS.md** - планы улучшений
- ❌ **MESSENGER_INTEGRATION_GUIDE.md** - пусто
- ❌ **MODERN_CHAT_LIST_REDESIGN.md** - пусто
**Требуется обновить документацию под текущее состояние.**
---
## 🔍 Структура файлов
```
backend/communications/
├── models.py ✅ (15 моделей, 1248 строк)
├── views.py ✅ (4 view, 530 строк)
├── consumers.py ✅ (WebSocket, 841 строк)
├── urls.py ✅ (5 маршрутов)
├── admin.py ✅ (админки для всех моделей)
├── forms.py ✅
├── signals.py ✅
├── notification_signals.py ✅
├── context_processors.py ✅
├── tests.py ❌ (пустой)
├── management/
│ └── commands/
│ ├── init_reactions.py ✅
│ └── update_department_chat_names.py ✅
└── migrations/ ✅ (20 миграций)
backend/api/v1/communications/ 🆕
├── views.py ✅ (14 API функций, 1037 строк)
├── poll_views.py ✅ (4 API функции, 398 строк)
└── __init__.py ✅
backend/api/v1/
└── urls.py ✅ (18 маршрутов для communications)
static/js/
├── chat-detail-enhanced.js ✅
├── chat-detail-init.js ✅
├── chat-reactions-integration.js ✅
└── chat-list-enhanced.js.old ❓
static/css/components/
├── chat-enhanced.css 🟡 (частично)
└── chat-list-enhanced.css ✅
templates/communications/
├── chat_list.html ✅
└── chat_detail.html ✅
```
---
## 📊 Статистика кода
- **Python файлы:** ~5900 строк
- communications/: ~3500 строк
- api/v1/communications/: ~1435 строк
- consumers.py: ~841 строк
- **JavaScript:** ~800 строк
- **CSS:** ~400 строк
- **Моделей:** 15
- **Миграций:** 20 (все применены ✅)
- **Django Views:** 4
- **API Endpoints:** 18 ✅
- **WebSocket actions:** 7
- **WS events:** 7
- **URL маршрутов:** 5 (UI) + 18 (API)
---
## ✅ Выводы
### Что хорошо:
1. ✅ Качественная архитектура моделей
2. ✅ Полная WebSocket реализация
3. ✅ **REST API полностью реализован** (18 endpoints)
4. ✅ Расширенная админ-панель
5. ✅ Миграции применены и работают
6. ✅ Базовый фронтенд реализован
7. ✅ **Опросы (Polls) backend готов**
8. ✅ **Загрузка файлов работает**
9. ✅ **Пересылка сообщений backend готов**
### Что требует работы:
1. 🔴 **UI для создания групповых чатов** - критично
2. 🔴 **Модальное окно пересылки** - критично
3. 🔴 **Контекстное меню чатов (JS)** - важно
4. 🟡 **UI для опросов** - важно
5. 🟡 **Drag & Drop загрузки** - улучшение UX
6. 🟡 **Управление участниками групп** - важно
7. ❌ **Нет тестов** - рискованно для продакшена
8. 🟡 **Упоминания (@mentions)** - дополнительная функция
9. 🟡 **Поиск по сообщениям** - улучшение UX
### Готовность к продакшену:
**~80%** - backend практически готов, требуется доработка UI компонентов.
**Backend готовность: 95%**
**Frontend готовность: 60%**
---
**Следующий шаг:** Начать с UI для создания групповых чатов и модального окна пересылки.