update admin panel and visual
This commit is contained in:
+217
-12
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user