294 lines
10 KiB
Python
294 lines
10 KiB
Python
"""
|
||
Административная панель для расписания.
|
||
"""
|
||
from django.contrib import admin
|
||
from django.utils.html import format_html
|
||
from django.utils.translation import gettext_lazy as _
|
||
from .models import Lesson, LessonTemplate, TimeSlot, Availability, Subject, MentorSubject
|
||
|
||
|
||
@admin.register(Lesson)
|
||
class LessonAdmin(admin.ModelAdmin):
|
||
"""Административная панель для занятий."""
|
||
|
||
list_display = [
|
||
'title', 'mentor', 'client', 'start_time',
|
||
'duration', 'status_badge', 'created_at'
|
||
]
|
||
list_filter = [
|
||
'status', 'start_time', 'mentor', 'subject', 'mentor_subject', 'created_at'
|
||
]
|
||
search_fields = [
|
||
'title', 'description', 'subject__name', 'mentor_subject__name', 'subject_name',
|
||
'mentor__email', 'mentor__first_name', 'mentor__last_name',
|
||
'client__user__email', 'client__user__first_name', 'client__user__last_name'
|
||
]
|
||
date_hierarchy = 'start_time'
|
||
readonly_fields = [
|
||
'end_time', 'created_at', 'updated_at',
|
||
'cancelled_at'
|
||
]
|
||
|
||
fieldsets = (
|
||
(_('Участники'), {
|
||
'fields': ('mentor', 'client')
|
||
}),
|
||
(_('Время'), {
|
||
'fields': ('start_time', 'duration', 'end_time')
|
||
}),
|
||
(_('Информация'), {
|
||
'fields': ('title', 'description', 'subject', 'mentor_subject', 'subject_name', 'template')
|
||
}),
|
||
(_('Статус'), {
|
||
'fields': ('status',)
|
||
}),
|
||
(_('Отмена'), {
|
||
'fields': (
|
||
'cancelled_by', 'cancellation_reason', 'cancelled_at'
|
||
),
|
||
'classes': ('collapse',)
|
||
}),
|
||
(_('Перенос'), {
|
||
'fields': ('rescheduled_from',),
|
||
'classes': ('collapse',)
|
||
}),
|
||
(_('Дополнительно'), {
|
||
'fields': (
|
||
'meeting_url', 'mentor_notes',
|
||
'reminder_sent'
|
||
),
|
||
'classes': ('collapse',)
|
||
}),
|
||
(_('Даты'), {
|
||
'fields': ('created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
def status_badge(self, obj):
|
||
"""Отображение статуса с цветом."""
|
||
colors = {
|
||
'scheduled': '#3B82F6', # синий
|
||
'in_progress': '#10B981', # зеленый
|
||
'completed': '#6B7280', # серый
|
||
'cancelled': '#EF4444', # красный
|
||
'rescheduled': '#F59E0B', # оранжевый
|
||
}
|
||
color = colors.get(obj.status, '#6B7280')
|
||
return format_html(
|
||
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
|
||
color,
|
||
obj.get_status_display()
|
||
)
|
||
status_badge.short_description = 'Статус'
|
||
|
||
def get_queryset(self, request):
|
||
"""Оптимизация запросов."""
|
||
return super().get_queryset(request).select_related(
|
||
'mentor', 'client', 'client__user', 'template',
|
||
'cancelled_by', 'rescheduled_from', 'subject', 'mentor_subject'
|
||
)
|
||
|
||
|
||
@admin.register(LessonTemplate)
|
||
class LessonTemplateAdmin(admin.ModelAdmin):
|
||
"""Административная панель для шаблонов занятий."""
|
||
|
||
list_display = [
|
||
'title', 'mentor', 'subject', 'duration',
|
||
'is_active', 'lessons_count', 'created_at'
|
||
]
|
||
list_filter = ['is_active', 'subject', 'mentor_subject', 'created_at']
|
||
search_fields = [
|
||
'title', 'description', 'subject__name', 'mentor_subject__name', 'subject_name',
|
||
'mentor__email', 'mentor__first_name', 'mentor__last_name'
|
||
]
|
||
readonly_fields = ['created_at', 'updated_at']
|
||
|
||
fieldsets = (
|
||
(_('Владелец'), {
|
||
'fields': ('mentor',)
|
||
}),
|
||
(_('Информация'), {
|
||
'fields': ('title', 'description', 'subject', 'mentor_subject', 'subject_name', 'duration')
|
||
}),
|
||
(_('Настройки'), {
|
||
'fields': ('is_active', 'meeting_url', 'color')
|
||
}),
|
||
(_('Даты'), {
|
||
'fields': ('created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
def lessons_count(self, obj):
|
||
"""Количество занятий созданных из шаблона."""
|
||
return obj.lessons.count()
|
||
lessons_count.short_description = 'Занятий создано'
|
||
|
||
def get_queryset(self, request):
|
||
"""Оптимизация запросов."""
|
||
return super().get_queryset(request).select_related('mentor', 'subject', 'mentor_subject')
|
||
|
||
|
||
@admin.register(TimeSlot)
|
||
class TimeSlotAdmin(admin.ModelAdmin):
|
||
"""Административная панель для временных слотов."""
|
||
|
||
list_display = [
|
||
'mentor', 'start_time', 'end_time',
|
||
'availability_badge', 'is_recurring', 'created_at'
|
||
]
|
||
list_filter = [
|
||
'is_available', 'is_booked', 'is_recurring',
|
||
'start_time', 'created_at'
|
||
]
|
||
search_fields = [
|
||
'mentor__email', 'mentor__first_name', 'mentor__last_name'
|
||
]
|
||
date_hierarchy = 'start_time'
|
||
readonly_fields = ['created_at', 'updated_at']
|
||
|
||
fieldsets = (
|
||
(_('Ментор'), {
|
||
'fields': ('mentor',)
|
||
}),
|
||
(_('Время'), {
|
||
'fields': ('start_time', 'end_time')
|
||
}),
|
||
(_('Статус'), {
|
||
'fields': ('is_available', 'is_booked', 'lesson')
|
||
}),
|
||
(_('Повторение'), {
|
||
'fields': ('is_recurring', 'recurring_day')
|
||
}),
|
||
(_('Даты'), {
|
||
'fields': ('created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
def availability_badge(self, obj):
|
||
"""Отображение доступности."""
|
||
if obj.is_booked:
|
||
return format_html(
|
||
'<span style="background-color: #EF4444; color: white; padding: 3px 10px; border-radius: 3px;">Забронирован</span>'
|
||
)
|
||
elif obj.is_available:
|
||
return format_html(
|
||
'<span style="background-color: #10B981; color: white; padding: 3px 10px; border-radius: 3px;">Доступен</span>'
|
||
)
|
||
else:
|
||
return format_html(
|
||
'<span style="background-color: #6B7280; color: white; padding: 3px 10px; border-radius: 3px;">Недоступен</span>'
|
||
)
|
||
availability_badge.short_description = 'Доступность'
|
||
|
||
def get_queryset(self, request):
|
||
"""Оптимизация запросов."""
|
||
return super().get_queryset(request).select_related('mentor', 'lesson')
|
||
|
||
|
||
@admin.register(Availability)
|
||
class AvailabilityAdmin(admin.ModelAdmin):
|
||
"""Административная панель для доступности."""
|
||
|
||
list_display = [
|
||
'mentor', 'day_display', 'time_range',
|
||
'is_recurring', 'is_active', 'created_at'
|
||
]
|
||
list_filter = [
|
||
'is_active', 'is_recurring', 'day_of_week', 'created_at'
|
||
]
|
||
search_fields = [
|
||
'mentor__email', 'mentor__first_name', 'mentor__last_name', 'notes'
|
||
]
|
||
readonly_fields = ['created_at', 'updated_at']
|
||
|
||
fieldsets = (
|
||
(_('Ментор'), {
|
||
'fields': ('mentor',)
|
||
}),
|
||
(_('Время'), {
|
||
'fields': ('day_of_week', 'specific_date', 'start_time', 'end_time')
|
||
}),
|
||
(_('Тип'), {
|
||
'fields': ('is_recurring', 'is_active')
|
||
}),
|
||
(_('Исключения'), {
|
||
'fields': ('exception_dates', 'notes'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
(_('Даты'), {
|
||
'fields': ('created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
def day_display(self, obj):
|
||
"""Отображение дня."""
|
||
if obj.is_recurring and obj.day_of_week is not None:
|
||
return obj.get_day_of_week_display()
|
||
elif obj.specific_date:
|
||
return obj.specific_date.strftime('%d.%m.%Y')
|
||
return '-'
|
||
day_display.short_description = 'День'
|
||
|
||
def time_range(self, obj):
|
||
"""Отображение временного диапазона."""
|
||
return f"{obj.start_time.strftime('%H:%M')} - {obj.end_time.strftime('%H:%M')}"
|
||
time_range.short_description = 'Время'
|
||
|
||
def get_queryset(self, request):
|
||
"""Оптимизация запросов."""
|
||
return super().get_queryset(request).select_related('mentor')
|
||
|
||
|
||
@admin.register(Subject)
|
||
class SubjectAdmin(admin.ModelAdmin):
|
||
"""Административная панель для предметов."""
|
||
|
||
list_display = ['name', 'is_active', 'lessons_count', 'created_at']
|
||
list_filter = ['is_active', 'created_at']
|
||
search_fields = ['name']
|
||
readonly_fields = ['created_at', 'updated_at']
|
||
|
||
fieldsets = (
|
||
(_('Информация'), {
|
||
'fields': ('name', 'is_active')
|
||
}),
|
||
(_('Даты'), {
|
||
'fields': ('created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
def lessons_count(self, obj):
|
||
"""Количество занятий с этим предметом."""
|
||
return obj.lessons.count()
|
||
lessons_count.short_description = 'Занятий'
|
||
|
||
|
||
@admin.register(MentorSubject)
|
||
class MentorSubjectAdmin(admin.ModelAdmin):
|
||
"""Административная панель для кастомных предметов менторов."""
|
||
|
||
list_display = ['name', 'mentor', 'usage_count', 'created_at']
|
||
list_filter = ['created_at']
|
||
search_fields = ['name', 'mentor__email', 'mentor__first_name', 'mentor__last_name']
|
||
readonly_fields = ['created_at', 'updated_at']
|
||
|
||
fieldsets = (
|
||
(_('Информация'), {
|
||
'fields': ('mentor', 'name', 'usage_count')
|
||
}),
|
||
(_('Даты'), {
|
||
'fields': ('created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
def get_queryset(self, request):
|
||
"""Оптимизация запросов."""
|
||
return super().get_queryset(request).select_related('mentor')
|