- Добавлена отправка сообщения через WebSocket после создания в API - Используется channel_layer.group_send для рассылки всем участникам - Сообщение с вложениями теперь появляется в чате мгновенно - Не требуется перезагрузка страницы Импорты: - async_to_sync для синхронного вызова асинхронного кода - get_channel_layer для доступа к WebSocket - serialize_message для форматирования данных
165 lines
5.7 KiB
Python
165 lines
5.7 KiB
Python
# backend/api/v1/communications/views.py
|
||
"""API views для чатов и сообщений"""
|
||
|
||
from asgiref.sync import async_to_sync
|
||
from channels.layers import get_channel_layer
|
||
from django.contrib.auth.decorators import login_required
|
||
from django.db import transaction
|
||
from django.http import JsonResponse
|
||
from django.shortcuts import get_object_or_404
|
||
from django.views.decorators.csrf import csrf_protect
|
||
from django.views.decorators.http import require_POST
|
||
|
||
from communications.models import (
|
||
Chat,
|
||
ChatMembership,
|
||
Message,
|
||
MessageAttachment,
|
||
)
|
||
from communications.consumers import serialize_message
|
||
|
||
|
||
@csrf_protect
|
||
@login_required
|
||
@require_POST
|
||
def upload_message_with_attachments(request):
|
||
"""Отправка сообщения с вложениями"""
|
||
chat_id = request.POST.get('chat_id')
|
||
content = request.POST.get('content', '').strip()
|
||
|
||
if not chat_id:
|
||
return JsonResponse(
|
||
{'ok': False, 'error': 'chat_id required'},
|
||
status=400
|
||
)
|
||
|
||
chat = get_object_or_404(Chat, pk=chat_id)
|
||
|
||
# Проверка доступа к чату
|
||
# Для групповых, канальных и объявлений проверяем ChatMembership
|
||
if chat.type in ['group', 'channel', 'announcement']:
|
||
membership = ChatMembership.objects.filter(
|
||
chat=chat,
|
||
user=request.user,
|
||
is_active=True,
|
||
can_send_messages=True
|
||
).first()
|
||
|
||
if not membership:
|
||
return JsonResponse(
|
||
{'ok': False, 'error': 'Cannot send messages to this chat'},
|
||
status=403
|
||
)
|
||
# Для личных чатов проверяем, что пользователь - участник
|
||
elif chat.type == 'private':
|
||
if not chat.participants.filter(id=request.user.id).exists():
|
||
return JsonResponse(
|
||
{'ok': False, 'error': 'You are not a participant'},
|
||
status=403
|
||
)
|
||
# Для чатов отдела проверяем принадлежность к отделу
|
||
elif chat.type == 'department':
|
||
if chat.department:
|
||
from employees.models import EmployeeDepartment
|
||
is_member = EmployeeDepartment.objects.filter(
|
||
department=chat.department,
|
||
employee=request.user,
|
||
is_active=True
|
||
).exists()
|
||
if not is_member and chat.department.head_id != request.user.id:
|
||
return JsonResponse(
|
||
{'ok': False, 'error': 'Not a department member'},
|
||
status=403
|
||
)
|
||
# Глобальный чат доступен всем активным пользователям
|
||
elif chat.type == 'global':
|
||
if not request.user.is_active:
|
||
return JsonResponse(
|
||
{'ok': False, 'error': 'User is not active'},
|
||
status=403
|
||
)
|
||
|
||
# Если нет ни текста, ни файлов
|
||
if not content and not request.FILES:
|
||
return JsonResponse(
|
||
{'ok': False, 'error': 'Content or files required'},
|
||
status=400
|
||
)
|
||
|
||
with transaction.atomic():
|
||
# Создаем сообщение
|
||
message = Message.objects.create(
|
||
chat=chat,
|
||
author=request.user,
|
||
content=content or '',
|
||
has_attachments=bool(request.FILES)
|
||
)
|
||
|
||
# Обрабатываем все загруженные файлы
|
||
attachments = []
|
||
for file_key in request.FILES:
|
||
uploaded_file = request.FILES[file_key]
|
||
|
||
# Определение типа файла
|
||
mime_type = uploaded_file.content_type or ''
|
||
if mime_type.startswith('image/'):
|
||
file_type = 'image'
|
||
elif mime_type.startswith('video/'):
|
||
file_type = 'video'
|
||
elif mime_type.startswith('audio/'):
|
||
file_type = 'audio'
|
||
else:
|
||
file_type = 'file'
|
||
|
||
attachment = MessageAttachment.objects.create(
|
||
message=message,
|
||
file=uploaded_file,
|
||
file_type=file_type,
|
||
file_name=uploaded_file.name,
|
||
file_size=uploaded_file.size,
|
||
mime_type=mime_type
|
||
)
|
||
|
||
attachments.append({
|
||
'id': attachment.id,
|
||
'file_name': attachment.file_name,
|
||
'file_type': attachment.file_type,
|
||
'file_url': attachment.file.url,
|
||
'file_size': attachment.file_size,
|
||
'mime_type': attachment.mime_type
|
||
})
|
||
|
||
# Отправляем сообщение через WebSocket всем участникам чата
|
||
channel_layer = get_channel_layer()
|
||
group_name = f"chat_{chat.id}"
|
||
|
||
# Сериализуем сообщение для WebSocket
|
||
message_data = serialize_message(message)
|
||
|
||
async_to_sync(channel_layer.group_send)(
|
||
group_name,
|
||
{
|
||
"type": "chat.message",
|
||
"chat_id": chat.id,
|
||
"payload": message_data
|
||
}
|
||
)
|
||
|
||
avatar_url = None
|
||
if hasattr(request.user, 'avatar') and request.user.avatar:
|
||
avatar_url = request.user.avatar.url
|
||
|
||
return JsonResponse({
|
||
'ok': True,
|
||
'message_id': message.id,
|
||
'chat_id': chat.id,
|
||
'author': {
|
||
'id': request.user.id,
|
||
'full_name': request.user.get_full_name(),
|
||
'avatar': avatar_url
|
||
},
|
||
'content': message.content,
|
||
'created_at': message.created_at.isoformat(),
|
||
'attachments': attachments
|
||
})
|