Files
teacher_site/consultations/admin.py
T
2026-08-02 22:56:29 +04:00

228 lines
9.3 KiB
Python

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('<span class="status-chip status-confirmed">Занято: {}</span>', booking.name)
if slot.start_at < timezone.now():
return format_html('<span class="status-chip status-muted">Прошло</span>')
if not slot.is_active:
return format_html('<span class="status-chip status-muted">Скрыто</span>')
return format_html('<span class="status-chip status-new">Свободно</span>')
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(
'<span class="status-chip {}">{}</span>',
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)