BREAKING CHANGES (backward compatible): - All code updated to support both old and new fields - context_object (GenericFK) and flags now primary - department and is_main still work (DEPRECATED) FILES UPDATED: 1. signals.py: - create_main_department_chat() uses context_object + flags - Creates both old and new fields for compatibility - Checks existence using Q() for both fields 2. views.py (4 critical updates): - has_user_access_to_chat(): uses get_participants() (supports both) - ChatListView.get_queryset(): checks department OR context_object - ChatDetailView.get_queryset(): checks department OR context_object - mark_as_read_ajax(): uses get_participants() and include_all_users 3. rules.py: - is_department_chat(): checks both department and context_object - Supports GenericFK for Department context 4. apps.py: - create_main_global_chat(): checks and sets flags['is_primary'] - Backward compatible with is_main 5. admin.py: - get_readonly_fields(): checks flags['is_primary'] OR is_main - Prevents editing primary chats 6. notifications/config.py: - get_chat_display_name(): uses context_object OR department 7. models.py: - Chat.__str__(): uses context_object OR department TESTED: ✅ Signal creates chat with context_object + flags ✅ get_participants() works with both fields ✅ __str__ displays correctly ✅ is_department_chat rule works ✅ Backward compatibility preserved
170 lines
6.5 KiB
Python
170 lines
6.5 KiB
Python
# backend\communications\signals.py
|
||
from django.contrib.contenttypes.models import ContentType
|
||
from django.core.files.base import ContentFile
|
||
from django.db.models import F, Q
|
||
from django.db.models.signals import post_save, pre_save
|
||
from django.dispatch import receiver
|
||
from employees.models import Department
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@receiver(post_save, sender=Department)
|
||
def create_main_department_chat(sender, instance, created, **kwargs):
|
||
"""
|
||
Создает главный чат для нового отдела.
|
||
|
||
Использует context_object (GenericFK) и flags['is_primary'].
|
||
Для обратной совместимости также заполняет department и is_main (DEPRECATED).
|
||
"""
|
||
from communications.models import Chat
|
||
|
||
if created:
|
||
dept_ct = ContentType.objects.get_for_model(Department)
|
||
|
||
# Проверка: существует ли уже чат для этого отдела
|
||
# (проверяем и старые, и новые поля)
|
||
existing = Chat.objects.filter(
|
||
Q(type="department") & (
|
||
Q(department=instance) |
|
||
Q(context_content_type=dept_ct, context_object_id=instance.id)
|
||
) & (
|
||
Q(is_main=True) | Q(flags__is_primary=True)
|
||
)
|
||
)
|
||
|
||
if not existing.exists():
|
||
# Создаем чат с ОБОИМИ способами привязки (для совместимости)
|
||
Chat.objects.create(
|
||
type="department",
|
||
# NEW: GenericFK
|
||
context_content_type=dept_ct,
|
||
context_object_id=instance.id,
|
||
# NEW: flags
|
||
flags={'is_primary': True},
|
||
# DEPRECATED: старые поля (для обратной совместимости)
|
||
department=instance,
|
||
is_main=True,
|
||
# Имя чата
|
||
name=f"Основной чат {instance.name}"
|
||
)
|
||
|
||
|
||
@receiver(pre_save, sender='communications.Chat')
|
||
def compress_and_cleanup_chat_avatar(sender, instance, **kwargs):
|
||
"""
|
||
Сжимает новый аватар чата и удаляет старый файл
|
||
"""
|
||
if not instance.avatar:
|
||
return
|
||
|
||
# Если это новый объект (нет pk), просто сжимаем
|
||
if not instance.pk:
|
||
try:
|
||
from common.image_utils import compress_avatar
|
||
compressed = compress_avatar(instance.avatar.read())
|
||
if compressed:
|
||
instance.avatar.save(
|
||
instance.avatar.name,
|
||
ContentFile(compressed),
|
||
save=False
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Error compressing new chat avatar: {e}")
|
||
return
|
||
|
||
# Проверяем, изменился ли файл аватара
|
||
try:
|
||
old_instance = sender.objects.get(pk=instance.pk)
|
||
except sender.DoesNotExist:
|
||
return
|
||
|
||
old_avatar = old_instance.avatar
|
||
new_avatar = instance.avatar
|
||
|
||
# Если путь изменился - это новый файл
|
||
if old_avatar and new_avatar and old_avatar.name != new_avatar.name:
|
||
try:
|
||
# Сжимаем новый аватар
|
||
from common.image_utils import compress_avatar
|
||
compressed = compress_avatar(new_avatar.read())
|
||
if compressed:
|
||
new_avatar.save(
|
||
new_avatar.name,
|
||
ContentFile(compressed),
|
||
save=False
|
||
)
|
||
|
||
# Удаляем старый файл
|
||
if old_avatar.name:
|
||
old_avatar.delete(save=False)
|
||
logger.info(
|
||
f"Deleted old chat avatar: {old_avatar.name}"
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Error processing chat avatar: {e}")
|
||
|
||
|
||
@receiver(post_save, sender='communications.Message')
|
||
def increment_unread_count_on_new_message(sender, instance, created, **kwargs):
|
||
"""
|
||
Инкрементирует unread_count для всех участников чата (кроме автора)
|
||
при создании нового сообщения.
|
||
|
||
Денормализация: вместо подсчета COUNT(*) в реальном времени,
|
||
обновляем кешированный счетчик инкрементом.
|
||
"""
|
||
if not created or instance.is_deleted:
|
||
return
|
||
|
||
from communications.models import ChatReadState
|
||
|
||
try:
|
||
# Получаем всех участников чата
|
||
chat = instance.chat
|
||
participants = chat.get_participants.exclude(id=instance.author_id)
|
||
|
||
# Инкрементируем счетчик для существующих ChatReadState
|
||
# Только для тех, кто еще не прочитал это сообщение
|
||
updated_count = ChatReadState.objects.filter(
|
||
chat=chat,
|
||
user__in=participants
|
||
).exclude(
|
||
# Пропускаем тех, кто уже прочитал дальше этого сообщения
|
||
last_read_message_id__gte=instance.id
|
||
).update(
|
||
unread_count=F('unread_count') + 1
|
||
)
|
||
|
||
# Создаем ChatReadState для участников, у которых его еще нет
|
||
existing_users = ChatReadState.objects.filter(
|
||
chat=chat,
|
||
user__in=participants
|
||
).values_list('user_id', flat=True)
|
||
|
||
new_users = participants.exclude(id__in=existing_users)
|
||
|
||
if new_users.exists():
|
||
# Создаем ChatReadState с unread_count=1 для новых участников
|
||
ChatReadState.objects.bulk_create([
|
||
ChatReadState(
|
||
chat=chat,
|
||
user=user,
|
||
last_read_message=None,
|
||
unread_count=1
|
||
)
|
||
for user in new_users
|
||
], ignore_conflicts=True)
|
||
|
||
logger.info(
|
||
f"[increment_unread] Created {new_users.count()} new ChatReadState for msg={instance.id}"
|
||
)
|
||
|
||
logger.debug(
|
||
f"[increment_unread] Updated {updated_count} ChatReadState for msg={instance.id} in chat={chat.id}"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error incrementing unread_count for message {instance.id}: {e}", exc_info=True)
|