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:
parent
4cf6828536
commit
dc4e983a74
@ -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,
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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 - перенос строки (стандартное поведение)
|
||||
});
|
||||
|
||||
// События скролла
|
||||
|
||||
@ -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) {
|
||||
// Успех - закрываем модал
|
||||
|
||||
@ -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 || 'Неизвестная ошибка'));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user