from datetime import timedelta from django.contrib import admin, messages from django.contrib.auth.admin import GroupAdmin, UserAdmin from django.contrib.auth.models import Group, User from django.db.models import Count, Q from django.utils import timezone from django.utils.html import format_html from .models import ACTIVE_BOOKING_STATUSES, ConsultationRequest, ConsultationSlot class TeacherAdminSite(admin.AdminSite): site_header = "Кабинет преподавателя" site_title = "Игорь Безрукавов" index_title = "Обзор записей" index_template = "admin/index.html" def index(self, request, extra_context=None): today = timezone.localdate() now = timezone.now() requests = ConsultationRequest.objects.all() extra_context = { **(extra_context or {}), "dashboard": { "new_requests": requests.filter(status=ConsultationRequest.Status.NEW).count(), "today_confirmed": requests.filter( status=ConsultationRequest.Status.CONFIRMED, preferred_date=today ).count(), "upcoming": requests.filter( status__in=ACTIVE_BOOKING_STATUSES, slot__start_at__gte=now, ).count(), "completed_month": requests.filter( status=ConsultationRequest.Status.COMPLETED, preferred_date__year=today.year, preferred_date__month=today.month, ).count(), }, "today": today, "next_bookings": requests.filter( status__in=ACTIVE_BOOKING_STATUSES, slot__start_at__gte=now, ).select_related("slot")[:6], } return super().index(request, extra_context=extra_context) teacher_admin_site = TeacherAdminSite(name="teacher_admin") teacher_admin_site.register(User, UserAdmin) teacher_admin_site.register(Group, GroupAdmin) @admin.register(ConsultationSlot, site=teacher_admin_site) class ConsultationSlotAdmin(admin.ModelAdmin): list_display = ( "start_at", "duration_minutes", "subject", "format", "booking_state", "is_active", ) list_filter = ("subject", "format", "is_active", "start_at") search_fields = ("teacher_note",) list_editable = ("is_active",) date_hierarchy = "start_at" ordering = ("start_at",) readonly_fields = ("created_at",) actions = ("activate_slots", "deactivate_slots", "duplicate_next_week") @admin.display(description="Состояние") def booking_state(self, slot): booking = next( (item for item in slot.bookings.all() if item.status in ACTIVE_BOOKING_STATUSES), None, ) if booking: return format_html('Занято: {}', booking.name) if slot.start_at < timezone.now(): return format_html('Прошло') if not slot.is_active: return format_html('Скрыто') return format_html('Свободно') def get_queryset(self, request): return super().get_queryset(request).prefetch_related("bookings") @admin.action(description="Открыть выбранные слоты для записи") def activate_slots(self, request, queryset): count = queryset.update(is_active=True) self.message_user(request, f"Открыто слотов: {count}.", messages.SUCCESS) @admin.action(description="Скрыть выбранные слоты с сайта") def deactivate_slots(self, request, queryset): count = queryset.update(is_active=False) self.message_user(request, f"Скрыто слотов: {count}.", messages.SUCCESS) @admin.action(description="Создать копии на следующую неделю") def duplicate_next_week(self, request, queryset): created = 0 for slot in queryset: next_start = slot.start_at + timedelta(days=7) if not ConsultationSlot.objects.filter(start_at=next_start).exists(): ConsultationSlot.objects.create( start_at=next_start, duration_minutes=slot.duration_minutes, subject=slot.subject, format=slot.format, is_active=slot.is_active, teacher_note=slot.teacher_note, ) created += 1 self.message_user(request, f"Создано слотов: {created}.", messages.SUCCESS) @admin.register(ConsultationRequest, site=teacher_admin_site) class ConsultationRequestAdmin(admin.ModelAdmin): list_display = ( "student_name", "subject", "exam", "appointment_time", "format", "status_badge", "contact_details", ) list_filter = ("status", "subject", "exam", "format", "student_grade", "preferred_date", "reminder_sent") search_fields = ("name", "phone", "contact", "comment", "teacher_note") date_hierarchy = "preferred_date" list_select_related = ("slot",) readonly_fields = ("created_at", "updated_at", "appointment_summary") actions = ("mark_confirmed", "mark_completed", "mark_cancelled", "mark_reminder_sent") fieldsets = ( ( "Ученик и контакты", { "fields": (("name", "student_grade"), ("phone", "contact"), "consent_given"), }, ), ( "Запрос", { "fields": (("subject", "exam", "format"), "comment"), }, ), ( "Время консультации", { "fields": ("slot", "appointment_summary", ("preferred_date", "preferred_time")), }, ), ( "Работа с заявкой", { "fields": ("status", "meeting_link", "teacher_note", "cancellation_reason", "reminder_sent"), }, ), ( "Служебные данные", { "classes": ("collapse",), "fields": ("created_at", "updated_at"), }, ), ) @admin.display(description="Ученик", ordering="name") def student_name(self, request): grade = f", {request.student_grade} класс" if request.student_grade else "" return f"{request.name}{grade}" @admin.display(description="Консультация", ordering="preferred_date") def appointment_time(self, request): if not request.preferred_date: return "Время не выбрано" return f"{request.preferred_date:%d.%m.%Y}, {request.preferred_time:%H:%M}" @admin.display(description="Статус", ordering="status") def status_badge(self, request): classes = { ConsultationRequest.Status.NEW: "status-new", ConsultationRequest.Status.CONFIRMED: "status-confirmed", ConsultationRequest.Status.COMPLETED: "status-completed", ConsultationRequest.Status.CANCELLED: "status-cancelled", ConsultationRequest.Status.NO_SHOW: "status-muted", } return format_html( '{}', classes[request.status], request.get_status_display(), ) @admin.display(description="Контакты") def contact_details(self, request): contact = f" · {request.contact}" if request.contact else "" return f"{request.phone}{contact}" @admin.display(description="Выбранное время") def appointment_summary(self, request): if request.slot: return str(request.slot) if request.preferred_date: return self.appointment_time(request) return "Не выбрано" @admin.action(description="Подтвердить выбранные записи") def mark_confirmed(self, request, queryset): count = queryset.exclude(status=ConsultationRequest.Status.CANCELLED).update( status=ConsultationRequest.Status.CONFIRMED ) self.message_user(request, f"Подтверждено записей: {count}.", messages.SUCCESS) @admin.action(description="Отметить проведёнными") def mark_completed(self, request, queryset): count = queryset.update(status=ConsultationRequest.Status.COMPLETED) self.message_user(request, f"Проведено консультаций: {count}.", messages.SUCCESS) @admin.action(description="Отменить выбранные записи") def mark_cancelled(self, request, queryset): count = queryset.update(status=ConsultationRequest.Status.CANCELLED) self.message_user(request, f"Отменено записей: {count}.", messages.SUCCESS) @admin.action(description="Отметить напоминание отправленным") def mark_reminder_sent(self, request, queryset): count = queryset.update(reminder_sent=True) self.message_user(request, f"Напоминание отмечено у записей: {count}.", messages.SUCCESS)