diff --git a/backend/communications/api_views.py b/backend/communications/api_views.py index f8c75ffa..db4506fb 100644 --- a/backend/communications/api_views.py +++ b/backend/communications/api_views.py @@ -25,15 +25,28 @@ from .models import ( @require_POST def create_chat(request): """Создание нового чата (group, channel, announcement)""" - chat_type = request.POST.get('type', 'group') - name = request.POST.get('name', '').strip() - description = request.POST.get('description', '').strip() + import json + + # Читаем JSON из тела запроса + try: + data = json.loads(request.body) + except json.JSONDecodeError: + return JsonResponse({'ok': False, 'error': 'Invalid JSON'}, status=400) + + chat_type = data.get('type', 'group') + name = data.get('name', '').strip() + description = data.get('description', '').strip() + participant_ids = data.get('participant_ids', []) if chat_type not in ['group', 'channel', 'announcement']: - return JsonResponse({'ok': False, 'error': 'Invalid chat type'}, status=400) + return JsonResponse( + {'ok': False, 'error': 'Invalid chat type'}, status=400 + ) if not name: - return JsonResponse({'ok': False, 'error': 'Name is required'}, status=400) + return JsonResponse( + {'ok': False, 'error': 'Name is required'}, status=400 + ) chat = Chat.objects.create( type=chat_type, @@ -53,6 +66,22 @@ def create_chat(request): can_pin_messages=True ) + # Добавляем участников для группового чата + if chat_type == 'group' and participant_ids: + from employees.models import Employee + for emp_id in participant_ids: + try: + employee = Employee.objects.get(id=emp_id) + if employee != request.user: # Создателя уже добавили + ChatMembership.objects.create( + chat=chat, + user=employee, + role='member', + can_send_messages=True + ) + except Employee.DoesNotExist: + pass + return JsonResponse({ 'ok': True, 'chat_id': chat.id, diff --git a/backend/communications/consumers.py b/backend/communications/consumers.py index d3e4f003..232aa2cc 100644 --- a/backend/communications/consumers.py +++ b/backend/communications/consumers.py @@ -328,7 +328,15 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): or dept.head_id == user.id or dept.active_employees.filter(id=user.id).exists() ) - return chat.participants.filter(id=user.id).exists() + if chat.type == "private": + return chat.participants.filter(id=user.id).exists() + if chat.type in ["group", "channel", "announcement"]: + # Проверка через ChatMembership + from communications.models import ChatMembership + return ChatMembership.objects.filter( + chat=chat, user=user + ).exists() + return False @database_sync_to_async def _create_message(self, chat: Chat, user, text: str, reply_to_id=None) -> Message: diff --git a/backend/communications/views.py b/backend/communications/views.py index c1d00675..a7ed2bd7 100644 --- a/backend/communications/views.py +++ b/backend/communications/views.py @@ -70,11 +70,19 @@ class ChatListView(LoginRequiredMixin, ListView): datetime.datetime(1970, 1, 1), timezone.get_current_timezone() ).astimezone(dt_tz.utc) + # Получаем ID чатов, где пользователь является участником + # через membership + from communications.models import ChatMembership + membership_chat_ids = ChatMembership.objects.filter( + user=user + ).values_list('chat_id', flat=True) + qs = ( Chat.objects.filter( Q(type="global") | Q(type="department", department__in=departments) | Q(type="private", participants=user) + | Q(id__in=membership_chat_ids) # group/channel/announcement ) .distinct() .select_related("department") @@ -126,6 +134,12 @@ class ChatDetailView(LoginRequiredMixin, DetailView, FormView): return chat.participants.filter(pk=user.pk).exists() if chat.type == "department" and chat.department_id: return chat.get_participants.filter(pk=user.pk).exists() + if chat.type in ["group", "channel", "announcement"]: + # Проверяем membership для групповых чатов, каналов и объявлений + from communications.models import ChatMembership + return ChatMembership.objects.filter( + chat=chat, user=user + ).exists() return False def get_context_data(self, **kwargs): diff --git a/backend/static/js/components/chatMarkRead.js b/backend/static/js/components/chatMarkRead.js index 85c6819b..7d91c986 100644 --- a/backend/static/js/components/chatMarkRead.js +++ b/backend/static/js/components/chatMarkRead.js @@ -278,10 +278,12 @@ export function initChatMarkRead(options = {}) { // События textarea ta?.addEventListener('input', autosize); ta?.addEventListener('keydown', e => { - if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + if (e.key === 'Enter' && !e.shiftKey) { + // Enter без Shift - отправка сообщения e.preventDefault(); form?.requestSubmit(); } + // Shift+Enter - перенос строки (стандартное поведение) }); // События скролла diff --git a/backend/static/js/components/createDepartmentModal.js b/backend/static/js/components/createDepartmentModal.js index 2580d7f9..528e50db 100644 --- a/backend/static/js/components/createDepartmentModal.js +++ b/backend/static/js/components/createDepartmentModal.js @@ -195,13 +195,21 @@ export function initCreateDepartmentModal(options = {}) { } try { + console.log('Sending request to:', submitUrl); + console.log('Headers:', headers); + console.log('Data:', formData); + const response = await fetch(submitUrl, { method: 'POST', headers: headers, body: JSON.stringify(formData) }); + console.log('Response status:', response.status); + console.log('Response ok:', response.ok); + const data = await response.json(); + console.log('Response data:', data); if (response.ok) { // Успех - закрываем модал diff --git a/backend/templates/communications/chat_list.html b/backend/templates/communications/chat_list.html index 644b101e..08579915 100644 --- a/backend/templates/communications/chat_list.html +++ b/backend/templates/communications/chat_list.html @@ -561,10 +561,34 @@ } }); + // Функция получения CSRF токена + function getCSRFToken() { + // Сначала пробуем найти в скрытом поле формы + const tokenInput = document.querySelector('[name=csrfmiddlewaretoken]'); + if (tokenInput) { + return tokenInput.value; + } + + // Иначе берем из cookie + const name = 'csrftoken'; + let cookieValue = null; + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim(); + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + // Обработка создания чата document.getElementById('createChatBtn')?.addEventListener('click', async () => { const type = chatTypeSelect.value; - const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value; + const csrftoken = getCSRFToken(); if(type === 'private') { // Создание личного чата @@ -611,7 +635,7 @@ if(res.ok) { const data = await res.json(); - window.location.href = `/communications/chat/${data.chat_id}/`; + window.location.href = `/communications/chats/${data.chat_id}/`; } else { const err = await res.json(); alert('Ошибка: ' + (err.error || 'Неизвестная ошибка'));