update admin panel and visual

This commit is contained in:
greyjoy
2026-08-02 22:56:29 +04:00
parent b53aa305e8
commit 024e28eee4
16 changed files with 866 additions and 309 deletions
+217 -12
View File
@@ -1,22 +1,227 @@
from django.contrib import admin
from datetime import timedelta
from .models import ConsultationRequest
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
@admin.register(ConsultationRequest)
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 = (
"name",
"student_name",
"subject",
"exam",
"preferred_date",
"preferred_time",
"phone",
"is_processed",
"appointment_time",
"format",
"status_badge",
"contact_details",
)
list_filter = ("subject", "exam", "is_processed", "preferred_date")
search_fields = ("name", "phone", "contact", "comment")
list_editable = ("is_processed",)
readonly_fields = ("created_at",)
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)