661 lines
31 KiB
Python
661 lines
31 KiB
Python
"""
|
||
Management команда для проверки и очистки чатов с некорректными типами.
|
||
|
||
Использование:
|
||
# Найти все чаты с некорректными типами
|
||
python manage.py check_chats --find
|
||
|
||
# Проверить конкретный чат
|
||
python manage.py check_chats --check 6
|
||
|
||
# Удалить все чаты с некорректными типами
|
||
python manage.py check_chats --cleanup
|
||
|
||
# Удалить конкретный чат
|
||
python manage.py check_chats --delete 6
|
||
"""
|
||
|
||
from django.core.management.base import BaseCommand, CommandError
|
||
from communications.models import Chat
|
||
|
||
VALID_TYPES = ["private", "group", "channel", "announcement", "global", "comments"]
|
||
|
||
|
||
class Command(BaseCommand):
|
||
help = 'Проверка и очистка чатов с некорректными типами'
|
||
|
||
def add_arguments(self, parser):
|
||
parser.add_argument(
|
||
'--find',
|
||
action='store_true',
|
||
help='Найти все чаты с некорректными типами',
|
||
)
|
||
parser.add_argument(
|
||
'--check',
|
||
type=int,
|
||
metavar='CHAT_ID',
|
||
help='Проверить конкретный чат по ID',
|
||
)
|
||
parser.add_argument(
|
||
'--fix',
|
||
action='store_true',
|
||
help='Исправить все чаты с некорректными типами (заменить на group)',
|
||
)
|
||
parser.add_argument(
|
||
'--change-type',
|
||
nargs=2,
|
||
metavar=('CHAT_ID', 'NEW_TYPE'),
|
||
help='Изменить тип конкретного чата (например: --change-type 6 group)',
|
||
)
|
||
parser.add_argument(
|
||
'--cleanup',
|
||
action='store_true',
|
||
help='Удалить все чаты с некорректными типами',
|
||
)
|
||
parser.add_argument(
|
||
'--delete',
|
||
type=int,
|
||
metavar='CHAT_ID',
|
||
help='Удалить конкретный чат по ID',
|
||
)
|
||
parser.add_argument(
|
||
'--stats',
|
||
action='store_true',
|
||
help='Показать статистику чатов по типам',
|
||
)
|
||
parser.add_argument(
|
||
'--list-type',
|
||
type=str,
|
||
metavar='TYPE',
|
||
help='Показать все чаты указанного типа (например: private)',
|
||
)
|
||
parser.add_argument(
|
||
'--check-visibility',
|
||
type=int,
|
||
metavar='USER_ID',
|
||
help='Проверить видимость чатов для конкретного пользователя (как в API)',
|
||
)
|
||
parser.add_argument(
|
||
'--migrate-memberships',
|
||
action='store_true',
|
||
help='Создать ChatMembership для всех участников из participants',
|
||
)
|
||
parser.add_argument(
|
||
'--export',
|
||
type=str,
|
||
metavar='TYPE',
|
||
help='Экспортировать чаты указанного типа в JSON файл',
|
||
)
|
||
parser.add_argument(
|
||
'--output',
|
||
type=str,
|
||
default='chats_export.json',
|
||
help='Путь к файлу для экспорта (по умолчанию: chats_export.json)',
|
||
)
|
||
parser.add_argument(
|
||
'--target-type',
|
||
type=str,
|
||
default='group',
|
||
choices=VALID_TYPES,
|
||
help='Целевой тип для исправления (по умолчанию: group)',
|
||
)
|
||
parser.add_argument(
|
||
'--no-confirm',
|
||
action='store_true',
|
||
help='Не запрашивать подтверждение при удалении/изменении',
|
||
)
|
||
|
||
def handle(self, *args, **options):
|
||
if options['stats']:
|
||
self.show_stats()
|
||
elif options['check_visibility']:
|
||
self.check_visibility(options['check_visibility'])
|
||
elif options['migrate_memberships']:
|
||
self.migrate_memberships(no_confirm=options['no_confirm'])
|
||
elif options['export']:
|
||
self.export_chats(options['export'], options['output'])
|
||
elif options['list_type']:
|
||
self.list_chats_by_type(options['list_type'])
|
||
elif options['find']:
|
||
self.find_invalid_chats()
|
||
elif options['check']:
|
||
self.check_chat(options['check'])
|
||
elif options['fix']:
|
||
self.fix_invalid_chats(
|
||
target_type=options['target_type'],
|
||
no_confirm=options['no_confirm']
|
||
)
|
||
elif options['change_type']:
|
||
chat_id = int(options['change_type'][0])
|
||
new_type = options['change_type'][1]
|
||
self.change_chat_type(chat_id, new_type, no_confirm=options['no_confirm'])
|
||
elif options['cleanup']:
|
||
self.cleanup_invalid_chats(no_confirm=options['no_confirm'])
|
||
elif options['delete']:
|
||
self.delete_chat(options['delete'], no_confirm=options['no_confirm'])
|
||
else:
|
||
self.stdout.write(self.style.ERROR('Укажите действие: --stats, --check-visibility, --migrate-memberships, --export, --list-type, --find, --check, --fix, --change-type, --cleanup или --delete'))
|
||
self.stdout.write('Используйте --help для справки')
|
||
|
||
def show_stats(self):
|
||
"""Показывает статистику чатов по типам"""
|
||
from django.db.models import Count
|
||
|
||
total_chats = Chat.objects.count()
|
||
self.stdout.write(self.style.SUCCESS(f'📊 Статистика чатов (всего: {total_chats})\n'))
|
||
|
||
# Статистика по типам
|
||
stats = Chat.objects.values('type').annotate(count=Count('id')).order_by('-count')
|
||
|
||
type_labels = {
|
||
'private': 'Личные диалоги',
|
||
'group': 'Групповые чаты',
|
||
'channel': 'Каналы',
|
||
'announcement': 'Объявления',
|
||
'global': 'Глобальные',
|
||
'comments': 'Комментарии',
|
||
}
|
||
|
||
valid_count = 0
|
||
invalid_count = 0
|
||
|
||
for stat in stats:
|
||
chat_type = stat['type']
|
||
count = stat['count']
|
||
label = type_labels.get(chat_type, f'❌ {chat_type} (некорректный)')
|
||
|
||
if chat_type in VALID_TYPES:
|
||
valid_count += count
|
||
self.stdout.write(f' ✅ {label}: {count}')
|
||
else:
|
||
invalid_count += count
|
||
self.stdout.write(self.style.ERROR(f' ❌ {label}: {count}'))
|
||
|
||
if invalid_count > 0:
|
||
self.stdout.write(self.style.WARNING(f'\n⚠️ Найдено {invalid_count} чатов с некорректными типами'))
|
||
self.stdout.write(self.style.NOTICE('Для исправления используйте: python manage.py check_chats --fix'))
|
||
else:
|
||
self.stdout.write(self.style.SUCCESS(f'\n✅ Все {valid_count} чатов имеют корректные типы'))
|
||
|
||
def check_visibility(self, user_id):
|
||
"""Проверяет видимость чатов для пользователя (эмулирует API логику)"""
|
||
from django.contrib.auth import get_user_model
|
||
from django.db.models import Q
|
||
from communications.models import ChatMembership
|
||
|
||
User = get_user_model()
|
||
|
||
try:
|
||
user = User.objects.get(id=user_id)
|
||
except User.DoesNotExist:
|
||
raise CommandError(f'❌ Пользователь с ID {user_id} не найден')
|
||
|
||
user_name = f"{getattr(user, 'last_name', '')} {getattr(user, 'first_name', '')}".strip()
|
||
self.stdout.write(self.style.SUCCESS(f'🔍 Проверка видимости чатов для пользователя:'))
|
||
self.stdout.write(f' ID: {user.id}')
|
||
self.stdout.write(f' Имя: {user_name or user.email}')
|
||
self.stdout.write(f' Email: {user.email}\n')
|
||
|
||
# Точная логика из ChatViewSet.get_queryset()
|
||
all_chats = Chat.objects.filter(
|
||
Q(memberships__user=user, memberships__is_active=True)
|
||
| Q(include_all_users=True)
|
||
| Q(created_by=user)
|
||
).distinct().order_by('-created_at')
|
||
|
||
# Отдельно чаты для list action (без comments)
|
||
list_chats = all_chats.exclude(type='comments')
|
||
|
||
total_visible = all_chats.count()
|
||
list_visible = list_chats.count()
|
||
comments_hidden = total_visible - list_visible
|
||
|
||
self.stdout.write(self.style.SUCCESS(f'📊 Итого:'))
|
||
self.stdout.write(f' Всего доступных чатов: {total_visible}')
|
||
self.stdout.write(f' Видимых в списке API: {list_visible}')
|
||
if comments_hidden > 0:
|
||
self.stdout.write(self.style.WARNING(f' Скрыто (type=comments): {comments_hidden}'))
|
||
|
||
# Группировка по типам
|
||
self.stdout.write(f'\n📋 Детали по типам:')
|
||
|
||
for chat_type in ['private', 'group', 'channel', 'global', 'announcement', 'comments']:
|
||
type_chats = all_chats.filter(type=chat_type)
|
||
count = type_chats.count()
|
||
|
||
if count == 0:
|
||
continue
|
||
|
||
visible_in_list = 'да' if chat_type != 'comments' else 'нет (скрыт в list)'
|
||
type_label = {
|
||
'private': 'Личные',
|
||
'group': 'Групповые',
|
||
'channel': 'Каналы',
|
||
'announcement': 'Объявления',
|
||
'global': 'Глобальные',
|
||
'comments': 'Комментарии',
|
||
}.get(chat_type, chat_type)
|
||
|
||
self.stdout.write(f'\n {type_label}: {count} шт. (в списке: {visible_in_list})')
|
||
|
||
# Проверяем причину видимости для каждого чата
|
||
for chat in type_chats[:5]: # Показываем первые 5
|
||
reasons = []
|
||
|
||
# Проверка membership
|
||
membership = ChatMembership.objects.filter(
|
||
chat=chat,
|
||
user=user,
|
||
is_active=True
|
||
).first()
|
||
|
||
if membership:
|
||
role_emoji = {'owner': '👑', 'admin': '🔴', 'moderator': '🟠', 'member': '🟢', 'guest': '⚪'}.get(membership.role, '❓')
|
||
reasons.append(f"membership:{role_emoji}{membership.role}")
|
||
|
||
if chat.include_all_users:
|
||
reasons.append("include_all_users")
|
||
|
||
if chat.created_by_id == user.id:
|
||
reasons.append("created_by")
|
||
|
||
reason_str = " | ".join(reasons) if reasons else "❌ ПРИЧИНА НЕ НАЙДЕНА!"
|
||
self.stdout.write(f' • ID:{chat.id} {chat.name or "[без названия]"} → {reason_str}')
|
||
|
||
if count > 5:
|
||
self.stdout.write(f' ... и еще {count - 5} чатов')
|
||
|
||
# Проверяем чаты, где user является участником, но is_active=False
|
||
inactive_memberships = ChatMembership.objects.filter(
|
||
user=user,
|
||
is_active=False
|
||
).select_related('chat')
|
||
|
||
if inactive_memberships.exists():
|
||
self.stdout.write(self.style.ERROR(f'\n⚠️ НЕАКТИВНЫЕ УЧАСТИЯ (is_active=False): {inactive_memberships.count()}'))
|
||
for membership in inactive_memberships[:10]:
|
||
chat = membership.chat
|
||
self.stdout.write(f' • ID:{chat.id} {chat.name or "[без названия]"} (тип: {chat.type}, роль: {membership.role})')
|
||
|
||
def migrate_memberships(self, no_confirm=False):
|
||
"""Создает ChatMembership для всех участников из participants"""
|
||
from communications.models import ChatMembership
|
||
|
||
self.stdout.write(self.style.SUCCESS('🔄 Миграция participants → ChatMembership\n'))
|
||
|
||
# Находим все чаты с участниками
|
||
chats_with_participants = Chat.objects.prefetch_related('participants').all()
|
||
|
||
total_chats = 0
|
||
total_created = 0
|
||
total_existing = 0
|
||
total_participants = 0
|
||
|
||
for chat in chats_with_participants:
|
||
participants = chat.participants.all()
|
||
if not participants.exists():
|
||
continue
|
||
|
||
total_chats += 1
|
||
chat_created = 0
|
||
|
||
for participant in participants:
|
||
total_participants += 1
|
||
|
||
# Проверяем, есть ли уже membership
|
||
membership, created = ChatMembership.objects.get_or_create(
|
||
chat=chat,
|
||
user=participant,
|
||
defaults={
|
||
'role': 'admin' if chat.created_by_id == participant.id else 'member',
|
||
'is_active': True,
|
||
'invited_by': chat.created_by,
|
||
}
|
||
)
|
||
|
||
if created:
|
||
total_created += 1
|
||
chat_created += 1
|
||
else:
|
||
total_existing += 1
|
||
# Если membership существует, но неактивный - активируем
|
||
if not membership.is_active:
|
||
membership.is_active = True
|
||
membership.save(update_fields=['is_active'])
|
||
self.stdout.write(f' ✅ Активирован ID:{chat.id} для пользователя {participant.email}')
|
||
|
||
if chat_created > 0:
|
||
self.stdout.write(f' 📁 Чат ID:{chat.id} "{chat.name or "[без названия]"}" → создано {chat_created} memberships')
|
||
|
||
self.stdout.write(self.style.SUCCESS(f'\n✅ Миграция завершена:'))
|
||
self.stdout.write(f' Обработано чатов: {total_chats}')
|
||
self.stdout.write(f' Обработано участников: {total_participants}')
|
||
self.stdout.write(f' Создано memberships: {total_created}')
|
||
self.stdout.write(f' Уже существовали: {total_existing}')
|
||
|
||
if total_created > 0:
|
||
self.stdout.write(self.style.NOTICE(f'\n💡 Теперь эти чаты будут видны в API'))
|
||
|
||
def export_chats(self, chat_type, output_file):
|
||
"""Экспортирует чаты указанного типа в JSON файл"""
|
||
import json
|
||
from django.core.serializers.json import DjangoJSONEncoder
|
||
|
||
chats = Chat.objects.filter(type=chat_type).order_by('-created_at')
|
||
|
||
if not chats.exists():
|
||
self.stdout.write(self.style.WARNING(f'Chats of type "{chat_type}" not found'))
|
||
return
|
||
|
||
export_data = []
|
||
for chat in chats:
|
||
participants_list = []
|
||
for p in chat.participants.all():
|
||
participants_list.append({
|
||
'id': p.id,
|
||
'email': p.email,
|
||
'first_name': getattr(p, 'first_name', ''),
|
||
'last_name': getattr(p, 'last_name', ''),
|
||
})
|
||
|
||
chat_data = {
|
||
'id': chat.id,
|
||
'name': chat.name,
|
||
'type': chat.type,
|
||
'created_at': chat.created_at.isoformat() if chat.created_at else None,
|
||
'participants_count': chat.participants.count(),
|
||
'participants': participants_list,
|
||
'messages_count': chat.messages.count() if hasattr(chat, 'messages') else 0,
|
||
}
|
||
|
||
if hasattr(chat, 'last_message') and chat.last_message:
|
||
last_msg_date = getattr(chat.last_message, 'created_at', None)
|
||
if last_msg_date:
|
||
chat_data['last_message_at'] = last_msg_date.isoformat()
|
||
|
||
export_data.append(chat_data)
|
||
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
json.dump({
|
||
'total': len(export_data),
|
||
'type': chat_type,
|
||
'exported_at': DjangoJSONEncoder().default(None),
|
||
'chats': export_data
|
||
}, f, ensure_ascii=False, indent=2, cls=DjangoJSONEncoder)
|
||
|
||
self.stdout.write(self.style.SUCCESS(f'Exported {len(export_data)} chats to {output_file}'))
|
||
self.stdout.write(f'Type: {chat_type}')
|
||
self.stdout.write(f'File size: {len(json.dumps(export_data))} bytes')
|
||
|
||
def list_chats_by_type(self, chat_type):
|
||
"""Показывает все чаты указанного типа"""
|
||
chats = Chat.objects.filter(type=chat_type).order_by('-created_at')
|
||
|
||
if not chats.exists():
|
||
self.stdout.write(self.style.WARNING(f'⚠️ Чатов типа "{chat_type}" не найдено'))
|
||
return
|
||
|
||
count = chats.count()
|
||
type_label = {
|
||
'private': 'Личных диалогов',
|
||
'group': 'Групповых чатов',
|
||
'channel': 'Каналов',
|
||
'announcement': 'Объявлений',
|
||
'global': 'Глобальных чатов',
|
||
'comments': 'Чатов комментариев',
|
||
}.get(chat_type, f'Чатов типа "{chat_type}"')
|
||
|
||
self.stdout.write(self.style.SUCCESS(f'📋 {type_label}: {count}\n'))
|
||
|
||
for chat in chats:
|
||
participants_count = chat.participants.count()
|
||
messages_count = chat.messages.count() if hasattr(chat, 'messages') else 'N/A'
|
||
|
||
# Заголовок чата
|
||
self.stdout.write(self.style.SUCCESS(f'═══ ID {chat.id}: {chat.name or "[без названия]"} ═══'))
|
||
|
||
# Базовые поля
|
||
self.stdout.write(f' 📌 Тип: {chat.type}')
|
||
self.stdout.write(f' 📅 Создан: {chat.created_at.strftime("%Y-%m-%d %H:%M")}')
|
||
|
||
# Создатель
|
||
if chat.created_by:
|
||
creator_name = f"{getattr(chat.created_by, 'last_name', '')} {getattr(chat.created_by, 'first_name', '')}".strip()
|
||
self.stdout.write(f' 👤 Создатель: {creator_name or chat.created_by.email}')
|
||
else:
|
||
self.stdout.write(f' 👤 Создатель: [не указан]')
|
||
|
||
# Критичные флаги
|
||
flags = []
|
||
if getattr(chat, 'is_blocked', False):
|
||
flags.append('🚫 ЗАБЛОКИРОВАН')
|
||
if getattr(chat, 'is_main', False):
|
||
flags.append('⭐ ОСНОВНОЙ')
|
||
if getattr(chat, 'include_all_users', False):
|
||
flags.append('👥 ВСЕ ПОЛЬЗОВАТЕЛИ')
|
||
|
||
if flags:
|
||
self.stdout.write(f' 🚩 Флаги: {" | ".join(flags)}')
|
||
|
||
# Контекстный объект
|
||
if hasattr(chat, 'context_content_type') and chat.context_content_type:
|
||
ctx = f"{chat.context_content_type.model}"
|
||
if hasattr(chat, 'context_object_id') and chat.context_object_id:
|
||
ctx += f" (ID: {chat.context_object_id})"
|
||
self.stdout.write(f' 🔗 Контекст: {ctx}')
|
||
|
||
# Аватар
|
||
if chat.avatar:
|
||
self.stdout.write(f' 🖼️ Аватар: {chat.avatar.url if hasattr(chat.avatar, "url") else "есть"}')
|
||
|
||
# Описание
|
||
if chat.description:
|
||
desc = chat.description[:60] + '...' if len(chat.description) > 60 else chat.description
|
||
self.stdout.write(f' 📝 Описание: {desc}')
|
||
|
||
# Участники
|
||
self.stdout.write(f' 👥 Участников: {participants_count}')
|
||
|
||
if participants_count > 0:
|
||
participants = chat.participants.all()
|
||
for p in participants:
|
||
full_name = f"{getattr(p, 'last_name', '')} {getattr(p, 'first_name', '')}".strip()
|
||
email = getattr(p, 'email', 'N/A')
|
||
user_id = p.id
|
||
|
||
# Проверяем membership для детальной инфы
|
||
membership_info = ""
|
||
try:
|
||
from communications.models import ChatMembership
|
||
membership = ChatMembership.objects.filter(chat=chat, user=p).first()
|
||
if membership:
|
||
role_emoji = {'owner': '👑', 'admin': '🔴', 'moderator': '🟠', 'member': '🟢', 'guest': '⚪'}.get(membership.role, '❓')
|
||
membership_info = f" {role_emoji}{membership.role}"
|
||
if not membership.is_active:
|
||
membership_info += " [НЕАКТИВЕН]"
|
||
except:
|
||
pass
|
||
|
||
if full_name:
|
||
self.stdout.write(f' • ID:{user_id} {full_name} ({email}){membership_info}')
|
||
else:
|
||
self.stdout.write(f' • ID:{user_id} {email}{membership_info}')
|
||
|
||
# Сообщения
|
||
self.stdout.write(f' 💬 Сообщений: {messages_count}')
|
||
|
||
# Последнее сообщение
|
||
if hasattr(chat, 'last_message') and chat.last_message:
|
||
last_msg = chat.last_message
|
||
last_msg_date = getattr(last_msg, 'created_at', None)
|
||
if last_msg_date:
|
||
last_msg_preview = getattr(last_msg, 'content', '')[:50]
|
||
self.stdout.write(f' 💭 Последнее сообщение: {last_msg_date.strftime("%Y-%m-%d %H:%M")}')
|
||
if last_msg_preview:
|
||
self.stdout.write(f' "{last_msg_preview}..."')
|
||
|
||
# JSON поля
|
||
if hasattr(chat, 'flags') and chat.flags:
|
||
self.stdout.write(f' 🏴 Flags: {chat.flags}')
|
||
|
||
if hasattr(chat, 'extra_data') and chat.extra_data:
|
||
self.stdout.write(f' 📦 Extra data: {chat.extra_data}')
|
||
|
||
self.stdout.write('')
|
||
|
||
self.stdout.write(self.style.SUCCESS(f'Всего найдено: {count} чатов'))
|
||
|
||
def find_invalid_chats(self):
|
||
"""Находит все чаты с некорректными типами"""
|
||
invalid_chats = Chat.objects.exclude(type__in=VALID_TYPES)
|
||
|
||
if not invalid_chats.exists():
|
||
self.stdout.write(self.style.SUCCESS('✅ Все чаты имеют корректные типы'))
|
||
return
|
||
|
||
count = invalid_chats.count()
|
||
self.stdout.write(self.style.WARNING(f'⚠️ Найдено {count} чатов с некорректными типами:\n'))
|
||
|
||
for chat in invalid_chats:
|
||
self.stdout.write(f'ID {chat.id}:')
|
||
self.stdout.write(f' Название: {chat.name}')
|
||
self.stdout.write(f' Тип: {chat.type}')
|
||
self.stdout.write(f' Участников: {chat.participants.count()}')
|
||
self.stdout.write(f' Создан: {chat.created_at}')
|
||
self.stdout.write('')
|
||
|
||
self.stdout.write(self.style.WARNING(f'Всего найдено: {count} чатов'))
|
||
self.stdout.write(self.style.NOTICE('Для удаления используйте: python manage.py check_chats --cleanup'))
|
||
|
||
def check_chat(self, chat_id):
|
||
"""Проверяет конкретный чат"""
|
||
try:
|
||
chat = Chat.objects.get(id=chat_id)
|
||
except Chat.DoesNotExist:
|
||
raise CommandError(f'❌ Чат с ID {chat_id} не найден')
|
||
|
||
self.stdout.write(f'Чат ID {chat_id}:')
|
||
self.stdout.write(f' Название: {chat.name}')
|
||
self.stdout.write(f' Тип: {chat.type}')
|
||
self.stdout.write(f' Участников: {chat.participants.count()}')
|
||
self.stdout.write(f' Создан: {chat.created_at}')
|
||
|
||
if chat.type not in VALID_TYPES:
|
||
self.stdout.write(self.style.ERROR(f'\n⚠️ Чат имеет некорректный тип: {chat.type}'))
|
||
self.stdout.write(self.style.NOTICE(f'Допустимые типы: {", ".join(VALID_TYPES)}'))
|
||
self.stdout.write(self.style.NOTICE(f'Для изменения типа используйте: python manage.py check_chats --change-type {chat_id} group'))
|
||
self.stdout.write(self.style.NOTICE(f'Для удаления используйте: python manage.py check_chats --delete {chat_id}'))
|
||
else:
|
||
self.stdout.write(self.style.SUCCESS(f'\n✅ Чат в порядке, тип корректен'))
|
||
|
||
def fix_invalid_chats(self, target_type='group', no_confirm=False):
|
||
"""Исправляет все чаты с некорректными типами"""
|
||
invalid_chats = Chat.objects.exclude(type__in=VALID_TYPES)
|
||
|
||
if not invalid_chats.exists():
|
||
self.stdout.write(self.style.SUCCESS('✅ Все чаты имеют корректные типы'))
|
||
return
|
||
|
||
count = invalid_chats.count()
|
||
self.stdout.write(self.style.WARNING(f'⚠️ Найдено {count} чатов с некорректными типами\n'))
|
||
self.stdout.write(f'Чаты для исправления (будет установлен тип: {target_type}):')
|
||
|
||
for chat in invalid_chats:
|
||
self.stdout.write(f' • ID {chat.id}: {chat.name} (текущий тип: {chat.type} → новый тип: {target_type})')
|
||
|
||
if not no_confirm:
|
||
confirm = input(f'\n⚠️ Изменить тип у {count} чатов на "{target_type}"? (yes/no): ')
|
||
if confirm.lower() not in ['yes', 'y', 'да']:
|
||
self.stdout.write(self.style.ERROR('❌ Отменено'))
|
||
return
|
||
|
||
updated = invalid_chats.update(type=target_type)
|
||
self.stdout.write(self.style.SUCCESS(f'\n✅ Успешно обновлено {updated} чатов'))
|
||
self.stdout.write(f'Всем чатам установлен тип: {target_type}')
|
||
|
||
def change_chat_type(self, chat_id, new_type, no_confirm=False):
|
||
"""Изменяет тип конкретного чата"""
|
||
try:
|
||
chat = Chat.objects.get(id=chat_id)
|
||
except Chat.DoesNotExist:
|
||
raise CommandError(f'❌ Чат с ID {chat_id} не найден')
|
||
|
||
if new_type not in VALID_TYPES:
|
||
raise CommandError(
|
||
f'❌ Некорректный тип: {new_type}\n'
|
||
f'Допустимые типы: {", ".join(VALID_TYPES)}'
|
||
)
|
||
|
||
self.stdout.write(f'Изменение типа чата ID {chat_id}:')
|
||
self.stdout.write(f' Название: {chat.name}')
|
||
self.stdout.write(f' Текущий тип: {chat.type}')
|
||
self.stdout.write(f' Новый тип: {new_type}')
|
||
self.stdout.write(f' Участников: {chat.participants.count()}')
|
||
|
||
if chat.type == new_type:
|
||
self.stdout.write(self.style.WARNING(f'\n⚠️ Чат уже имеет тип "{new_type}"'))
|
||
return
|
||
|
||
if not no_confirm:
|
||
confirm = input(f'\n⚠️ Изменить тип с "{chat.type}" на "{new_type}"? (yes/no): ')
|
||
if confirm.lower() not in ['yes', 'y', 'да']:
|
||
self.stdout.write(self.style.ERROR('❌ Отменено'))
|
||
return
|
||
|
||
old_type = chat.type
|
||
chat.type = new_type
|
||
chat.save(update_fields=['type'])
|
||
self.stdout.write(self.style.SUCCESS(f'\n✅ Тип чата "{chat.name}" изменен с "{old_type}" на "{new_type}"'))
|
||
|
||
def cleanup_invalid_chats(self, no_confirm=False):
|
||
"""Удаляет все чаты с некорректными типами"""
|
||
invalid_chats = Chat.objects.exclude(type__in=VALID_TYPES)
|
||
|
||
if not invalid_chats.exists():
|
||
self.stdout.write(self.style.SUCCESS('✅ Все чаты имеют корректные типы'))
|
||
return
|
||
|
||
count = invalid_chats.count()
|
||
self.stdout.write(self.style.WARNING(f'⚠️ Найдено {count} чатов с некорректными типами\n'))
|
||
self.stdout.write('Чаты для удаления:')
|
||
|
||
for chat in invalid_chats:
|
||
self.stdout.write(f' • ID {chat.id}: {chat.name} (тип: {chat.type})')
|
||
|
||
if not no_confirm:
|
||
confirm = input(f'\n⚠️ Удалить {count} чатов? (yes/no): ')
|
||
if confirm.lower() not in ['yes', 'y', 'да']:
|
||
self.stdout.write(self.style.ERROR('❌ Отменено'))
|
||
return
|
||
|
||
deleted, details = invalid_chats.delete()
|
||
self.stdout.write(self.style.SUCCESS(f'\n✅ Успешно удалено {deleted} объектов'))
|
||
|
||
# Показываем детали удаления
|
||
if details:
|
||
self.stdout.write('\nУдалено объектов по типам:')
|
||
for model, count in details.items():
|
||
self.stdout.write(f' • {model}: {count}')
|
||
|
||
def delete_chat(self, chat_id, no_confirm=False):
|
||
"""Удаляет конкретный чат"""
|
||
try:
|
||
chat = Chat.objects.get(id=chat_id)
|
||
except Chat.DoesNotExist:
|
||
raise CommandError(f'❌ Чат с ID {chat_id} не найден')
|
||
|
||
self.stdout.write(f'Удаление чата ID {chat_id}:')
|
||
self.stdout.write(f' Название: {chat.name}')
|
||
self.stdout.write(f' Тип: {chat.type}')
|
||
self.stdout.write(f' Участников: {chat.participants.count()}')
|
||
|
||
if not no_confirm:
|
||
confirm = input('\n⚠️ Подтвердите удаление (yes/no): ')
|
||
if confirm.lower() not in ['yes', 'y', 'да']:
|
||
self.stdout.write(self.style.ERROR('❌ Отменено'))
|
||
return
|
||
|
||
chat_name = chat.name
|
||
chat.delete()
|
||
self.stdout.write(self.style.SUCCESS(f'\n✅ Чат "{chat_name}" (ID {chat_id}) успешно удален'))
|