feat(communications): Phase 2 - Update views, signals, rules for GenericFK
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
This commit is contained in:
parent
66f6473e6e
commit
f56e256a2a
@ -42,6 +42,9 @@ class ChatAdmin(admin.ModelAdmin):
|
||||
"created_at"
|
||||
)
|
||||
list_filter = ("type", "is_main", "created_at")
|
||||
# Поддержка поиска по старому и новому полю
|
||||
# department__name - старое поле (DEPRECATED)
|
||||
# Для context_object поиск невозможен через GenericFK
|
||||
search_fields = ("name", "description", "department__name")
|
||||
ordering = ("-created_at",)
|
||||
filter_horizontal = ("participants",)
|
||||
@ -49,8 +52,11 @@ class ChatAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ("created_at",)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj and obj.is_main:
|
||||
return self.readonly_fields + ("type", "department", "is_main")
|
||||
# Главные чаты (is_main=True или flags['is_primary']=True) нельзя редактировать
|
||||
if obj:
|
||||
is_primary = obj.is_main or (obj.flags and obj.flags.get('is_primary'))
|
||||
if is_primary:
|
||||
return self.readonly_fields + ("type", "department", "is_main", "flags", "context_object_id", "context_content_type")
|
||||
return self.readonly_fields
|
||||
|
||||
|
||||
|
||||
@ -12,8 +12,26 @@ class CommunicationsConfig(AppConfig):
|
||||
import communications.rules # django-rules: регистрация предикатов и правил доступа
|
||||
|
||||
def create_main_global_chat(sender, **kwargs):
|
||||
"""
|
||||
Создает главный глобальный чат при первом запуске.
|
||||
|
||||
Использует flags['is_primary'] вместо is_main.
|
||||
Для обратной совместимости также устанавливает is_main=True.
|
||||
"""
|
||||
from communications.models import Chat
|
||||
from django.db.models import Q
|
||||
|
||||
if not Chat.objects.filter(type="global", is_main=True).exists():
|
||||
Chat.objects.create(type="global", is_main=True)
|
||||
# Проверяем по обоим полям (старому и новому)
|
||||
if not Chat.objects.filter(
|
||||
Q(type="global") & (
|
||||
Q(is_main=True) | Q(flags__is_primary=True)
|
||||
)
|
||||
).exists():
|
||||
Chat.objects.create(
|
||||
type="global",
|
||||
# NEW: flags
|
||||
flags={'is_primary': True},
|
||||
# DEPRECATED: для обратной совместимости
|
||||
is_main=True
|
||||
)
|
||||
|
||||
|
||||
@ -366,7 +366,9 @@ class Chat(models.Model):
|
||||
if self.type == "group":
|
||||
return self.name or "Групповой чат"
|
||||
if self.type == "department":
|
||||
return f"Чат отдела: {self.department or '—'}"
|
||||
# Проверяем context_object (GenericFK) или старое поле department
|
||||
dept = self.context_object if self.context_object else self.department
|
||||
return f"Чат отдела: {dept or '—'}"
|
||||
if self.type == "channel":
|
||||
return self.name or "Канал"
|
||||
if self.type == "announcement":
|
||||
|
||||
@ -144,8 +144,11 @@ def get_chat_name(chat) -> str:
|
||||
if chat.type == 'global':
|
||||
return 'Глобальный чат'
|
||||
|
||||
if chat.type == 'department' and chat.department:
|
||||
return f'Чат отдела: {chat.department.name}'
|
||||
if chat.type == 'department':
|
||||
# Проверяем context_object (GenericFK) или старое поле department
|
||||
dept = chat.context_object if hasattr(chat, 'context_object') and chat.context_object else getattr(chat, 'department', None)
|
||||
if dept:
|
||||
return f'Чат отдела: {dept.name}'
|
||||
|
||||
if chat.type == 'private':
|
||||
# Для приватного чата можно вернуть имена участников
|
||||
|
||||
@ -111,12 +111,25 @@ def is_public_chat(user, chat):
|
||||
|
||||
@rules.predicate
|
||||
def is_department_chat(user, chat):
|
||||
"""Чат отдела пользователя"""
|
||||
"""
|
||||
Чат отдела пользователя.
|
||||
|
||||
Проверяет как старое поле department (DEPRECATED),
|
||||
так и новое context_object (GenericFK).
|
||||
"""
|
||||
if chat is None or not hasattr(user, 'department'):
|
||||
return False
|
||||
|
||||
if hasattr(chat, 'department'):
|
||||
return chat.department == user.department
|
||||
# 1. Проверяем старое поле department (для обратной совместимости)
|
||||
if hasattr(chat, 'department') and chat.department:
|
||||
if chat.department == user.department:
|
||||
return True
|
||||
|
||||
# 2. Проверяем новое поле context_object (GenericFK)
|
||||
if hasattr(chat, 'context_object') and chat.context_object:
|
||||
from employees.models import Department
|
||||
if isinstance(chat.context_object, Department):
|
||||
return chat.context_object == user.department
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# backend\communications\signals.py
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db.models import F
|
||||
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
|
||||
@ -11,16 +12,41 @@ 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:
|
||||
if not Chat.objects.filter(
|
||||
type="department", department=instance, is_main=True
|
||||
).exists():
|
||||
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}"
|
||||
)
|
||||
|
||||
|
||||
@ -76,14 +76,15 @@ def user_can_access_chat(chat: Chat, user) -> bool:
|
||||
print(f"[CHAT DEBUG] Group chat, participants={in_participants}, membership={in_membership}, result={result}")
|
||||
return result
|
||||
|
||||
if chat.type == "department" and chat.department_id:
|
||||
result = chat.get_participants.filter(pk=user.pk).exists()
|
||||
if chat.type == "department":
|
||||
# Проверяем через get_participants() (поддерживает и department, и context_object)
|
||||
result = chat.get_participants().filter(pk=user.pk).exists()
|
||||
print(f"[CHAT DEBUG] Department chat, result={result}")
|
||||
return result
|
||||
|
||||
if chat.type in ("channel", "announcement"):
|
||||
# Для каналов и объявлений может быть include_all или membership
|
||||
if chat.include_all_employees:
|
||||
# Для каналов и объявлений может быть include_all_users или membership
|
||||
if chat.include_all_users:
|
||||
return user.is_active
|
||||
# Проверяем и participants и membership для гибкости
|
||||
result = (
|
||||
@ -136,14 +137,21 @@ class ChatListView(LoginRequiredMixin, ListView):
|
||||
# Получаем ID чатов, где пользователь является участником
|
||||
# через membership
|
||||
from communications.models import ChatMembership
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
membership_chat_ids = ChatMembership.objects.filter(
|
||||
user=user
|
||||
).values_list('chat_id', flat=True)
|
||||
|
||||
# Для department: проверяем и старое поле department, и новое context_object
|
||||
dept_ct = ContentType.objects.get_for_model(departments.model)
|
||||
dept_ids = list(departments.values_list('id', flat=True))
|
||||
|
||||
qs = (
|
||||
Chat.objects.filter(
|
||||
Q(type="global")
|
||||
| Q(type="department", department__in=departments)
|
||||
| Q(type="department", department__in=departments) # старое поле
|
||||
| Q(type="department", context_content_type=dept_ct, context_object_id__in=dept_ids) # новое поле
|
||||
| Q(type="private", participants=user)
|
||||
| Q(id__in=membership_chat_ids) # group/channel/announcement
|
||||
)
|
||||
@ -229,6 +237,8 @@ class ChatDetailView(LoginRequiredMixin, DetailView, FormView):
|
||||
|
||||
# Получаем ID чатов через membership
|
||||
from communications.models import ChatMembership
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
membership_chat_ids = list(
|
||||
ChatMembership.objects.filter(user=user).values_list('chat_id', flat=True)
|
||||
)
|
||||
@ -241,10 +251,14 @@ class ChatDetailView(LoginRequiredMixin, DetailView, FormView):
|
||||
f"User departments: {list(departments.values_list('id', flat=True))}, "
|
||||
f"membership_chat_ids: {membership_chat_ids}"
|
||||
)
|
||||
|
||||
# Для department: проверяем и старое поле, и новое
|
||||
dept_ct = ContentType.objects.get_for_model(departments.model)
|
||||
|
||||
qs = Chat.objects.filter(
|
||||
Q(type="global")
|
||||
| Q(type="department", department__in=departments)
|
||||
| Q(type="department", department__in=departments) # старое поле
|
||||
| Q(type="department", context_content_type=dept_ct, context_object_id__in=dept_ids) # новое поле
|
||||
| Q(type="private", participants=user)
|
||||
| Q(id__in=membership_chat_ids)
|
||||
).distinct()
|
||||
@ -470,11 +484,12 @@ def chat_mark_read(request, pk: int):
|
||||
return True
|
||||
if c.type in ("private", "group"): # Групповые и личные чаты
|
||||
return c.participants.filter(pk=u.pk).exists()
|
||||
if c.type == "department" and c.department_id:
|
||||
return c.get_participants.filter(pk=u.pk).exists()
|
||||
if c.type == "department":
|
||||
# Используем get_participants() - поддерживает и department, и context_object
|
||||
return c.get_participants().filter(pk=u.pk).exists()
|
||||
if c.type in ("channel", "announcement"):
|
||||
# Для каналов и объявлений проверяем participants или include_all
|
||||
if c.include_all_employees:
|
||||
# Для каналов и объявлений проверяем participants или include_all_users
|
||||
if c.include_all_users:
|
||||
return u.is_active
|
||||
return c.participants.filter(pk=u.pk).exists()
|
||||
return False
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user