Fix: чаты и LDAP

- Добавлен CSRF токен в модальные окна (createDepartmentModal, createChatModal)
- Исправлена работа групповых чатов (group/channel/announcement):
  * Добавлена проверка доступа через ChatMembership в views и consumers
  * Групповые чаты теперь отображаются в списке
  * WebSocket подключается корректно без бесконечных перезагрузок
- API create_chat теперь читает JSON вместо POST данных
- Изменена отправка сообщений: Enter вместо Ctrl+Enter, Shift+Enter для переноса строки
- Добавлен сертификат LDAP (corp-ca.pem)
- Отключен LDAP в локальном .env для разработки (USE_SQLITE=True, LDAP_ENABLED=False)
This commit is contained in:
admin i 2025-11-27 17:23:13 +03:00
parent 4cf6828536
commit dc4e983a74
6 changed files with 94 additions and 9 deletions

View File

@ -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,

View File

@ -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:

View File

@ -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):

View File

@ -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 - перенос строки (стандартное поведение)
});
// События скролла

View File

@ -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) {
// Успех - закрываем модал

View File

@ -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 || 'Неизвестная ошибка'));