chore: Remove backup file

This commit is contained in:
Игорь Надеин 2026-01-09 15:50:00 +03:00
parent b229718aef
commit 92113521e5

View File

@ -1,764 +0,0 @@
# communications/api_views.py
"""API для расширенных функций мессенджера"""
from django.contrib.auth.decorators import login_required
from django.db import models, transaction
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.views.decorators.http import require_POST
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from .models import (
Chat,
ChatMembership,
ChatUserSettings,
ForwardedMessage,
Message,
MessageAttachment,
MessageReply,
)
# ============ CHAT MANAGEMENT ============
@login_required
@require_POST
def create_chat(request):
"""
Создание нового чата
(group, department, channel, announcement, global)
"""
import json
# Читаем данные из FormData или JSON
if request.content_type and 'multipart/form-data' in request.content_type:
# FormData (с файлами)
chat_type = request.POST.get('type', 'group')
name = request.POST.get('name', '').strip()
description = request.POST.get('description', '').strip()
department_id = request.POST.get('department_id')
include_all = request.POST.get('include_all_employees') == 'true'
is_main = request.POST.get('is_main') == 'true'
avatar = request.FILES.get('avatar')
participant_ids = request.POST.getlist('participant_ids')
else:
# JSON (без файлов - legacy)
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()
department_id = data.get('department_id')
include_all = data.get('include_all_employees', False)
is_main = data.get('is_main', False)
avatar = None
participant_ids = data.get('participant_ids', [])
# Валидация типа чата
valid_types = ['group', 'department', 'channel', 'announcement', 'global']
if chat_type not in valid_types:
types_str = ", ".join(valid_types)
return JsonResponse(
{
'ok': False,
'error': f'Invalid chat type. Must be one of: {types_str}'
},
status=400
)
# Валидация для announcement - только 1 на пользователя
if chat_type == 'announcement':
existing = Chat.objects.filter(
type='announcement',
created_by=request.user
).first()
if existing:
if existing.is_blocked:
return JsonResponse(
{
'ok': False,
'error': 'Ваш чат объявлений заблокирован администратором'
},
status=403
)
else:
return JsonResponse(
{
'ok': False,
'error': 'У вас уже есть чат объявлений',
'existing_chat_id': existing.id
},
status=400
)
# Валидация названия
if not name and chat_type != 'private':
# Для announcement генерируем название автоматически
if chat_type == 'announcement':
user_name = request.user.get_full_name() or request.user.username
name = f"Объявления от {user_name}"
else:
return JsonResponse(
{'ok': False, 'error': 'Name is required'}, status=400
)
# Валидация для чата отдела
if chat_type == 'department':
if not department_id:
return JsonResponse(
{
'ok': False,
'error': 'Department is required for department chat'
},
status=400
)
try:
from employees.models import Department
department = Department.objects.get(id=department_id)
except Department.DoesNotExist:
return JsonResponse(
{'ok': False, 'error': 'Department not found'},
status=400
)
else:
department = None
# Валидация для основного чата
if is_main:
if chat_type == 'global':
# Проверяем, нет ли уже основного глобального чата
if Chat.objects.filter(type='global', is_main=True).exists():
return JsonResponse(
{'ok': False, 'error': 'Main global chat already exists'},
status=400
)
elif chat_type == 'department' and department:
# Проверяем, нет ли уже основного чата для этого отдела
exists = Chat.objects.filter(
type='department',
department=department,
is_main=True
).exists()
if exists:
return JsonResponse(
{
'ok': False,
'error': 'Main department chat already exists'
},
status=400
)
# Создаем чат
chat = Chat.objects.create(
type=chat_type,
name=name,
description=description,
created_by=request.user,
department=department,
include_all_employees=include_all,
is_main=is_main,
can_reply=(chat_type != 'announcement') # Для announcement нельзя
)
# Добавляем аватар если был загружен
if avatar:
chat.avatar = avatar
chat.save() # Создатель - владелец с полными правами
ChatMembership.objects.create(
chat=chat,
user=request.user,
role='owner',
can_send_messages=True,
can_add_members=True,
can_remove_members=True,
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
# Для чатов с include_all_employees - добавляем всех активных
if include_all:
from employees.models import Employee
all_employees = Employee.objects.filter(is_active=True).exclude(
id=request.user.id
)
for employee in all_employees:
# Для announcement и channel - обычные участники не могут писать
if chat_type == 'announcement':
can_send = False # Только создатель может писать
elif chat_type == 'channel':
can_send = False # Только модераторы могут писать
else:
can_send = True
ChatMembership.objects.get_or_create(
chat=chat,
user=employee,
defaults={
'role': 'member',
'can_send_messages': can_send
}
)
# Для чата отдела - добавляем всех сотрудников отдела
if chat_type == 'department' and department:
from employees.models import EmployeeDepartment
dept_employees = EmployeeDepartment.objects.filter(
department=department,
is_active=True
).select_related('employee')
for ed in dept_employees:
if ed.employee != request.user: # Создателя уже добавили
ChatMembership.objects.get_or_create(
chat=chat,
user=ed.employee,
defaults={
'role': 'member',
'can_send_messages': True
}
)
return JsonResponse({
'ok': True,
'chat_id': chat.id,
'chat_type': chat.type,
'name': chat.name
})
@login_required
@require_POST
def block_announcement(request, chat_id):
"""
Блокирует announcement чат (только для staff/superuser)
После блокировки чат скрывается у всех пользователей
"""
if not (request.user.is_staff or request.user.is_superuser):
return JsonResponse(
{'ok': False, 'error': 'Permission denied'}, status=403
)
chat = get_object_or_404(Chat, pk=chat_id, type='announcement')
if chat.is_blocked:
return JsonResponse(
{'ok': False, 'error': 'Chat already blocked'}, status=400
)
chat.is_blocked = True
chat.blocked_at = timezone.now()
chat.blocked_by = request.user
chat.save()
return JsonResponse({
'ok': True,
'message': f'Announcement "{chat.name}" blocked'
})
@login_required
@require_POST
def unblock_announcement(request, chat_id):
"""
Разблокирует announcement чат (только для staff/superuser)
"""
if not (request.user.is_staff or request.user.is_superuser):
return JsonResponse(
{'ok': False, 'error': 'Permission denied'}, status=403
)
chat = get_object_or_404(Chat, pk=chat_id, type='announcement')
if not chat.is_blocked:
return JsonResponse(
{'ok': False, 'error': 'Chat is not blocked'}, status=400
)
chat.is_blocked = False
chat.blocked_at = None
chat.blocked_by = None
chat.save()
return JsonResponse({
'ok': True,
'message': f'Announcement "{chat.name}" unblocked'
})
@login_required
@require_POST
def update_chat(request, chat_id):
"""Обновление названия/описания/аватара чата"""
chat = get_object_or_404(Chat, pk=chat_id)
# Проверка прав (только owner/admin)
membership = ChatMembership.objects.filter(
chat=chat,
user=request.user,
role__in=['owner', 'admin']
).first()
if not membership:
return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403)
# Обновление полей
if 'name' in request.POST:
chat.name = request.POST['name'].strip()
if 'description' in request.POST:
chat.description = request.POST['description'].strip()
if 'avatar' in request.FILES:
chat.avatar = request.FILES['avatar']
chat.save()
return JsonResponse({'ok': True})
# ============ CHAT USER SETTINGS ============
@login_required
@require_POST
def pin_chat(request, chat_id):
"""Закрепление/открепление чата"""
chat = get_object_or_404(Chat, pk=chat_id)
is_pinned = request.POST.get('pinned', 'true').lower() == 'true'
settings, created = ChatUserSettings.objects.get_or_create(
chat=chat,
user=request.user
)
settings.is_pinned = is_pinned
if is_pinned:
settings.pinned_at = timezone.now()
# Определяем порядок
max_order = ChatUserSettings.objects.filter(
user=request.user,
is_pinned=True
).aggregate(max_order=models.Max('pin_order'))['max_order'] or 0
settings.pin_order = max_order + 1
else:
settings.pinned_at = None
settings.pin_order = 0
settings.save()
return JsonResponse({'ok': True, 'pinned': is_pinned})
@login_required
@require_POST
def set_chat_notifications(request, chat_id):
"""Включение/выключение уведомлений"""
chat = get_object_or_404(Chat, pk=chat_id)
enabled = request.POST.get('enabled', 'true').lower() == '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 ACTIONS ============
@login_required
@require_POST
def edit_message(request, message_id):
"""Редактирование сообщения"""
message = get_object_or_404(Message, pk=message_id)
if message.author != request.user:
return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403)
new_content = request.POST.get('content', '').strip()
if not new_content:
return JsonResponse({'ok': False, 'error': 'Empty content'}, status=400)
# Сохраняем историю
if not message.is_edited:
message.edit_history = []
message.edit_history.append({
'timestamp': timezone.now().isoformat(),
'old_content': message.content
})
message.content = new_content
message.is_edited = True
message.edited_at = timezone.now()
message.save()
return JsonResponse({'ok': True, 'edited_at': message.edited_at.isoformat()})
@login_required
@require_POST
def delete_message(request, message_id):
"""Удаление сообщения"""
import logging
logger = logging.getLogger(__name__)
logger.info(
"[DELETE_MESSAGE] API called: message_id=%s, user=%s",
message_id, request.user.username
)
message = get_object_or_404(Message, pk=message_id)
logger.info(
"[DELETE_MESSAGE] Message found: chat_id=%s, author=%s",
message.chat_id, message.author.username
)
# Проверка: автор или модератор/админ чата
can_delete = False
if message.author == request.user:
can_delete = True
logger.info("[DELETE_MESSAGE] User is author, can delete")
else:
membership = ChatMembership.objects.filter(
chat=message.chat,
user=request.user,
role__in=['owner', 'admin', 'moderator']
).first()
if membership:
can_delete = True
logger.info(
"[DELETE_MESSAGE] User has moderator role: %s",
membership.role
)
if not can_delete:
logger.warning(
"[DELETE_MESSAGE] Permission denied for user %s",
request.user.username
)
return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403)
logger.info("[DELETE_MESSAGE] Marking message as deleted")
message.is_deleted = True
message.deleted_at = timezone.now()
message.deleted_by = request.user
message.save()
logger.info(
"[DELETE_MESSAGE] Message deleted successfully: %s", message_id
)
# Отправляем WebSocket событие всем участникам чата
logger.info(
"[DELETE_MESSAGE] Sending WebSocket event to chat group: chat_%s",
message.chat_id
)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
f"chat_{message.chat_id}",
{
"type": "chat.message_deleted",
"chat_id": message.chat_id,
"message_id": message_id
}
)
logger.info(
"[DELETE_MESSAGE] WebSocket event sent for message_id=%s", message_id
)
return JsonResponse({'ok': True})
@login_required
@require_POST
def pin_message(request, message_id):
"""Закрепление сообщения в чате"""
message = get_object_or_404(Message, pk=message_id)
# Проверка прав
membership = ChatMembership.objects.filter(
chat=message.chat,
user=request.user,
can_pin_messages=True
).first()
if not membership:
return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403)
is_pinned = request.POST.get('pinned', 'true').lower() == 'true'
message.is_pinned = is_pinned
if is_pinned:
message.pinned_by = request.user
message.pinned_at = timezone.now()
else:
message.pinned_by = None
message.pinned_at = None
message.save()
return JsonResponse({'ok': True, 'pinned': is_pinned})
# ============ ATTACHMENTS ============
# Примечание: endpoint для загрузки сообщений с файлами
# перенесён в api/v1/communications/views.py
@login_required
@require_POST
def upload_attachment(request):
"""Загрузка вложения к существующему сообщению"""
if 'file' not in request.FILES:
return JsonResponse({'ok': False, 'error': 'No file'}, status=400)
message_id = request.POST.get('message_id')
if not message_id:
return JsonResponse(
{'ok': False, 'error': 'message_id required'},
status=400
)
message = get_object_or_404(Message, pk=message_id)
if message.author != request.user:
return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403)
uploaded_file = request.FILES['file']
# Определение типа файла
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
)
message.has_attachments = True
message.save()
return JsonResponse({
'ok': True,
'attachment_id': attachment.id,
'file_name': attachment.file_name,
'file_type': attachment.file_type,
'file_url': attachment.file.url
})
# ============ FORWARDING ============
@login_required
@require_POST
def forward_message(request, message_id):
"""Пересылка сообщения в другой чат"""
original = get_object_or_404(Message, pk=message_id)
target_chat_id = request.POST.get('target_chat_id')
if not target_chat_id:
return JsonResponse({'ok': False, 'error': 'target_chat_id required'}, status=400)
target_chat = get_object_or_404(Chat, pk=target_chat_id)
# Проверка доступа к целевому чату
membership = ChatMembership.objects.filter(
chat=target_chat,
user=request.user,
can_send_messages=True
).first()
if not membership:
return JsonResponse({'ok': False, 'error': 'Cannot send to this chat'}, status=403)
with transaction.atomic():
# Создаем новое сообщение
forwarded = Message.objects.create(
chat=target_chat,
author=request.user,
content=original.content,
is_forwarded=True
)
# Определяем оригинальное сообщение в цепочке
if hasattr(original, 'forward_info'):
original_message = original.forward_info.original_message
forward_count = original.forward_info.forward_count + 1
else:
original_message = original
forward_count = 1
# Создаем информацию о пересылке
ForwardedMessage.objects.create(
message=forwarded,
original_message=original_message,
immediate_source=original,
original_chat=original.chat,
original_author=original.author,
forwarded_by=request.user,
forward_count=forward_count,
preserved_content=original.content,
preserved_author_name=original.author.get_full_name()
)
return JsonResponse({'ok': True, 'new_message_id': forwarded.id})
# ============ REPLIES ============
@login_required
@require_POST
def reply_to_message(request, message_id):
"""Ответ на сообщение"""
replied_to = get_object_or_404(Message, pk=message_id)
content = request.POST.get('content', '').strip()
reply_type = request.POST.get('reply_type', 'inline')
if not content:
return JsonResponse({'ok': False, 'error': 'Content required'}, status=400)
# Создаем сообщение-ответ
reply = Message.objects.create(
chat=replied_to.chat,
author=request.user,
content=content,
reply_to=replied_to
)
# Создаем расширенную информацию об ответе
MessageReply.objects.create(
message=reply,
replied_to=replied_to,
is_cross_chat_reply=False,
preserved_text=replied_to.content[:200],
preserved_author_name=replied_to.author.get_full_name(),
reply_type=reply_type
)
# Если это тред, обновляем счетчик
if reply_type == 'thread':
if replied_to.thread_root:
root = replied_to.thread_root
else:
root = replied_to
reply.thread_root = root
reply.save()
root.thread_reply_count += 1
root.save()
return JsonResponse({
'ok': True,
'reply_id': reply.id,
'replied_to_id': replied_to.id
})
# ============ CHAT MEMBERSHIP ============
@login_required
@require_POST
def add_member(request, chat_id):
"""Добавление участника в чат"""
chat = get_object_or_404(Chat, pk=chat_id)
user_id = request.POST.get('user_id')
if not user_id:
return JsonResponse({'ok': False, 'error': 'user_id required'}, status=400)
# Проверка прав
membership = ChatMembership.objects.filter(
chat=chat,
user=request.user,
can_add_members=True
).first()
if not membership:
return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403)
from employees.models import Employee
new_user = get_object_or_404(Employee, pk=user_id)
# Создаем или активируем участие
new_membership, created = ChatMembership.objects.get_or_create(
chat=chat,
user=new_user,
defaults={
'role': 'member',
'invited_by': request.user,
'can_send_messages': True
}
)
if not created and not new_membership.is_active:
new_membership.is_active = True
new_membership.left_at = None
new_membership.save()
return JsonResponse({'ok': True, 'created': created})
@login_required
@require_POST
def remove_member(request, chat_id):
"""Удаление участника из чата"""
chat = get_object_or_404(Chat, pk=chat_id)
user_id = request.POST.get('user_id')
if not user_id:
return JsonResponse({'ok': False, 'error': 'user_id required'}, status=400)
# Проверка прав
membership = ChatMembership.objects.filter(
chat=chat,
user=request.user,
can_remove_members=True
).first()
if not membership:
return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403)
target_membership = ChatMembership.objects.filter(
chat=chat,
user_id=user_id
).first()
if target_membership:
target_membership.is_active = False
target_membership.left_at = timezone.now()
target_membership.save()
return JsonResponse({'ok': True})