278 lines
9.8 KiB
Python
278 lines
9.8 KiB
Python
"""
|
||
Административная панель для пользователей.
|
||
"""
|
||
from django import forms
|
||
from django.contrib import admin
|
||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||
from django.contrib.auth.forms import UserChangeForm as BaseUserChangeForm, UserCreationForm as BaseUserCreationForm
|
||
from django.utils.translation import gettext_lazy as _
|
||
from .models import User, Client, Parent, Mentor, MentorStudentConnection
|
||
from .utils import normalize_phone
|
||
|
||
|
||
class UserChangeForm(BaseUserChangeForm):
|
||
class Meta(BaseUserChangeForm.Meta):
|
||
model = User
|
||
|
||
def clean_phone(self):
|
||
value = self.cleaned_data.get('phone', '') or ''
|
||
return normalize_phone(value) if value else ''
|
||
|
||
|
||
class UserCreationForm(BaseUserCreationForm):
|
||
class Meta(BaseUserCreationForm.Meta):
|
||
model = User
|
||
|
||
def clean_phone(self):
|
||
value = self.cleaned_data.get('phone', '') or ''
|
||
return normalize_phone(value) if value else ''
|
||
|
||
|
||
class ClientMentorInline(admin.TabularInline):
|
||
"""Инлайн для связи ментор — студент (на странице ментора)."""
|
||
model = Client.mentors.through
|
||
fk_name = 'user'
|
||
extra = 0
|
||
verbose_name = _('Студент')
|
||
verbose_name_plural = _('Связь ментор — студент')
|
||
autocomplete_fields = ['client']
|
||
|
||
|
||
@admin.register(User)
|
||
class UserAdmin(BaseUserAdmin):
|
||
"""Административная панель для модели User."""
|
||
form = UserChangeForm
|
||
add_form = UserCreationForm
|
||
|
||
list_display = [
|
||
'email', 'first_name', 'last_name', 'role',
|
||
'is_active', 'email_verified', 'created_at'
|
||
]
|
||
list_filter = [
|
||
'role', 'is_active', 'is_staff', 'is_superuser',
|
||
'email_verified', 'created_at'
|
||
]
|
||
search_fields = ['email', 'first_name', 'last_name', 'phone', 'telegram_username']
|
||
ordering = ['-created_at']
|
||
|
||
fieldsets = (
|
||
(None, {
|
||
'fields': ('email', 'password')
|
||
}),
|
||
(_('Персональная информация'), {
|
||
'fields': (
|
||
'first_name', 'last_name', 'birth_date',
|
||
'avatar', 'bio', 'phone'
|
||
)
|
||
}),
|
||
(_('Роль и права'), {
|
||
'fields': (
|
||
'role', 'is_active', 'is_staff',
|
||
'is_superuser', 'groups', 'user_permissions'
|
||
)
|
||
}),
|
||
(_('Telegram'), {
|
||
'fields': ('telegram_id', 'telegram_username')
|
||
}),
|
||
(_('Верификация'), {
|
||
'fields': ('email_verified', 'email_verification_token')
|
||
}),
|
||
(_('Настройки'), {
|
||
'fields': (
|
||
'timezone', 'language',
|
||
'notifications_enabled', 'email_notifications',
|
||
'telegram_notifications'
|
||
)
|
||
}),
|
||
(_('Онбординг'), {
|
||
'fields': ('onboarding_tours_seen',),
|
||
'description': 'Прогресс подсказок по платформе (JSON). Чтобы сбросить — очистите поле или укажите {}.'
|
||
}),
|
||
(_('Блокировка'), {
|
||
'fields': ('is_blocked', 'blocked_reason', 'blocked_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
(_('Важные даты'), {
|
||
'fields': ('last_login', 'last_activity', 'date_joined', 'created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
add_fieldsets = (
|
||
(None, {
|
||
'classes': ('wide',),
|
||
'fields': (
|
||
'email', 'password1', 'password2', 'first_name',
|
||
'last_name', 'role', 'is_staff', 'is_active'
|
||
),
|
||
}),
|
||
)
|
||
|
||
readonly_fields = ['created_at', 'updated_at', 'last_login', 'date_joined']
|
||
|
||
def get_readonly_fields(self, request, obj=None):
|
||
"""Сделать некоторые поля только для чтения после создания."""
|
||
# Email теперь можно редактировать в админке
|
||
return self.readonly_fields
|
||
|
||
|
||
@admin.register(Mentor)
|
||
class MentorAdmin(BaseUserAdmin):
|
||
"""Отдельная админка только для менторов."""
|
||
form = UserChangeForm
|
||
add_form = UserCreationForm
|
||
|
||
list_display = [
|
||
'email', 'first_name', 'last_name',
|
||
'clients_display', 'is_active', 'universal_code', 'created_at'
|
||
]
|
||
list_filter = ['is_active', 'email_verified', 'created_at']
|
||
search_fields = ['email', 'first_name', 'last_name', 'phone', 'universal_code']
|
||
ordering = ['-created_at']
|
||
inlines = [ClientMentorInline]
|
||
|
||
fieldsets = (
|
||
(None, {'fields': ('email', 'password')}),
|
||
(_('Персональная информация'), {
|
||
'fields': ('first_name', 'last_name', 'birth_date', 'avatar', 'bio', 'phone')
|
||
}),
|
||
(_('Роль и права'), {
|
||
'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')
|
||
}),
|
||
(_('Telegram'), {'fields': ('telegram_id', 'telegram_username')}),
|
||
(_('Верификация'), {'fields': ('email_verified', 'email_verification_token')}),
|
||
(_('Настройки'), {
|
||
'fields': ('timezone', 'language', 'universal_code',
|
||
'notifications_enabled', 'email_notifications', 'telegram_notifications',
|
||
'ai_trust_draft', 'ai_trust_publish')
|
||
}),
|
||
(_('Онбординг'), {
|
||
'fields': ('onboarding_tours_seen',),
|
||
'description': 'Прогресс подсказок (JSON). Чтобы сбросить — очистите или введите {}.'
|
||
}),
|
||
(_('Важные даты'), {
|
||
'fields': ('last_login', 'last_activity', 'date_joined', 'created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
add_fieldsets = (
|
||
(None, {
|
||
'classes': ('wide',),
|
||
'fields': ('email', 'password1', 'password2', 'first_name', 'last_name', 'is_staff', 'is_active'),
|
||
}),
|
||
)
|
||
|
||
readonly_fields = ['universal_code', 'created_at', 'updated_at', 'last_login', 'date_joined']
|
||
|
||
@admin.display(description=_('Студенты'))
|
||
def clients_display(self, obj):
|
||
clients = obj.clients.all()[:5]
|
||
if not clients:
|
||
return '—'
|
||
names = [c.user.get_full_name() or c.user.email for c in clients]
|
||
extra = obj.clients.count() - 5
|
||
if extra > 0:
|
||
names.append(f'+{extra}')
|
||
return ', '.join(names)
|
||
|
||
def save_model(self, request, obj, form, change):
|
||
obj.role = 'mentor'
|
||
super().save_model(request, obj, form, change)
|
||
|
||
|
||
@admin.register(Client)
|
||
class ClientAdmin(admin.ModelAdmin):
|
||
"""Административная панель для модели Client."""
|
||
|
||
list_display = [
|
||
'user', 'mentors_display', 'grade', 'school', 'total_lessons',
|
||
'completed_lessons', 'enrollment_date'
|
||
]
|
||
list_filter = ['enrollment_date', 'created_at']
|
||
search_fields = [
|
||
'user__email', 'user__first_name',
|
||
'user__last_name', 'school', 'grade'
|
||
]
|
||
filter_horizontal = ['mentors']
|
||
readonly_fields = ['enrollment_date', 'created_at', 'updated_at']
|
||
|
||
fieldsets = (
|
||
(_('Пользователь'), {
|
||
'fields': ('user',)
|
||
}),
|
||
(_('Учебная информация'), {
|
||
'fields': ('grade', 'school', 'learning_goals')
|
||
}),
|
||
(_('Менторы'), {
|
||
'fields': ('mentors',)
|
||
}),
|
||
(_('Статистика'), {
|
||
'fields': ('total_lessons', 'completed_lessons')
|
||
}),
|
||
(_('Даты'), {
|
||
'fields': ('enrollment_date', 'created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
@admin.display(description=_('Менторы'))
|
||
def mentors_display(self, obj):
|
||
mentors = obj.mentors.all()[:5]
|
||
if not mentors:
|
||
return '—'
|
||
names = [m.get_full_name() or m.email for m in mentors]
|
||
extra = obj.mentors.count() - 5
|
||
if extra > 0:
|
||
names.append(f'+{extra}')
|
||
return ', '.join(names)
|
||
|
||
|
||
@admin.register(Parent)
|
||
class ParentAdmin(admin.ModelAdmin):
|
||
"""Административная панель для модели Parent."""
|
||
|
||
list_display = [
|
||
'user', 'relation_type', 'can_view_progress',
|
||
'can_view_schedule', 'created_at'
|
||
]
|
||
list_filter = [
|
||
'relation_type', 'can_view_progress',
|
||
'can_view_schedule', 'can_receive_reports', 'created_at'
|
||
]
|
||
search_fields = [
|
||
'user__email', 'user__first_name', 'user__last_name'
|
||
]
|
||
filter_horizontal = ['children']
|
||
readonly_fields = ['created_at', 'updated_at']
|
||
|
||
fieldsets = (
|
||
(_('Пользователь'), {
|
||
'fields': ('user',)
|
||
}),
|
||
(_('Информация о родителе'), {
|
||
'fields': ('relation_type',)
|
||
}),
|
||
(_('Дети'), {
|
||
'fields': ('children',)
|
||
}),
|
||
(_('Права доступа'), {
|
||
'fields': (
|
||
'can_view_progress', 'can_view_schedule',
|
||
'can_receive_reports'
|
||
)
|
||
}),
|
||
(_('Даты'), {
|
||
'fields': ('created_at', 'updated_at'),
|
||
'classes': ('collapse',)
|
||
}),
|
||
)
|
||
|
||
|
||
@admin.register(MentorStudentConnection)
|
||
class MentorStudentConnectionAdmin(admin.ModelAdmin):
|
||
list_display = ['mentor', 'student', 'status', 'initiator', 'created_at']
|
||
list_filter = ['status', 'initiator', 'created_at']
|
||
search_fields = ['mentor__email', 'student__email', 'mentor__first_name', 'student__first_name']
|
||
readonly_fields = ['confirm_token', 'student_confirmed_at', 'parent_confirmed_at', 'created_at', 'updated_at']
|