339 lines
9.5 KiB
Python
339 lines
9.5 KiB
Python
"""
|
|
Административная панель для видеоконференций.
|
|
"""
|
|
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from django.urls import reverse
|
|
from .models import VideoRoom, VideoParticipant, VideoCallLog, ScreenRecording
|
|
|
|
|
|
@admin.register(VideoRoom)
|
|
class VideoRoomAdmin(admin.ModelAdmin):
|
|
"""Админ интерфейс для видеокомнат."""
|
|
|
|
list_display = [
|
|
'room_id',
|
|
'lesson_title',
|
|
'mentor_link',
|
|
'client_link',
|
|
'status_badge',
|
|
'duration_display',
|
|
'is_recording',
|
|
'created_at'
|
|
]
|
|
|
|
list_filter = [
|
|
'status',
|
|
'is_recording',
|
|
'created_at',
|
|
'started_at'
|
|
]
|
|
|
|
search_fields = [
|
|
'room_id',
|
|
'lesson__title',
|
|
'mentor__email',
|
|
'mentor__first_name',
|
|
'mentor__last_name',
|
|
'client__email',
|
|
'client__first_name',
|
|
'client__last_name'
|
|
]
|
|
|
|
readonly_fields = [
|
|
'room_id',
|
|
'created_at',
|
|
'updated_at',
|
|
'started_at',
|
|
'ended_at',
|
|
'duration',
|
|
'mentor_joined_at',
|
|
'client_joined_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('Основная информация', {
|
|
'fields': (
|
|
'room_id',
|
|
'lesson',
|
|
'mentor',
|
|
'client',
|
|
'status'
|
|
)
|
|
}),
|
|
('Время', {
|
|
'fields': (
|
|
'created_at',
|
|
'started_at',
|
|
'ended_at',
|
|
'duration',
|
|
'mentor_joined_at',
|
|
'client_joined_at'
|
|
)
|
|
}),
|
|
('Запись', {
|
|
'fields': (
|
|
'is_recording',
|
|
'recording_url'
|
|
)
|
|
}),
|
|
('Техническая информация', {
|
|
'fields': (
|
|
'router_id',
|
|
'max_participants'
|
|
)
|
|
}),
|
|
('Качество', {
|
|
'fields': (
|
|
'quality_rating',
|
|
'quality_issues'
|
|
)
|
|
})
|
|
)
|
|
|
|
def lesson_title(self, obj):
|
|
"""Название занятия."""
|
|
return obj.lesson.title
|
|
lesson_title.short_description = 'Занятие'
|
|
|
|
def mentor_link(self, obj):
|
|
"""Ссылка на ментора."""
|
|
url = reverse('admin:users_user_change', args=[obj.mentor.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.mentor.get_full_name())
|
|
mentor_link.short_description = 'Ментор'
|
|
|
|
def client_link(self, obj):
|
|
"""Ссылка на клиента."""
|
|
url = reverse('admin:users_user_change', args=[obj.client.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.client.get_full_name())
|
|
client_link.short_description = 'Клиент'
|
|
|
|
def status_badge(self, obj):
|
|
"""Бейдж статуса."""
|
|
colors = {
|
|
'waiting': '#ffc107',
|
|
'active': '#28a745',
|
|
'ended': '#6c757d'
|
|
}
|
|
return format_html(
|
|
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
|
|
colors.get(obj.status, '#000'),
|
|
obj.get_status_display()
|
|
)
|
|
status_badge.short_description = 'Статус'
|
|
|
|
def duration_display(self, obj):
|
|
"""Отображение длительности."""
|
|
duration = obj.actual_duration
|
|
if duration:
|
|
hours = duration // 3600
|
|
minutes = (duration % 3600) // 60
|
|
seconds = duration % 60
|
|
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
return '-'
|
|
duration_display.short_description = 'Длительность'
|
|
|
|
|
|
@admin.register(VideoParticipant)
|
|
class VideoParticipantAdmin(admin.ModelAdmin):
|
|
"""Админ интерфейс для участников видео."""
|
|
|
|
list_display = [
|
|
'user_name',
|
|
'room_id',
|
|
'is_connected',
|
|
'is_audio_enabled',
|
|
'is_video_enabled',
|
|
'is_screen_sharing',
|
|
'joined_at',
|
|
'left_at',
|
|
'duration_display'
|
|
]
|
|
|
|
list_filter = [
|
|
'is_connected',
|
|
'is_audio_enabled',
|
|
'is_video_enabled',
|
|
'is_screen_sharing',
|
|
'joined_at'
|
|
]
|
|
|
|
search_fields = [
|
|
'user__email',
|
|
'user__first_name',
|
|
'user__last_name',
|
|
'room__room_id'
|
|
]
|
|
|
|
readonly_fields = [
|
|
'joined_at',
|
|
'left_at',
|
|
'total_duration',
|
|
'reconnection_count'
|
|
]
|
|
|
|
def user_name(self, obj):
|
|
"""Имя пользователя."""
|
|
return obj.user.get_full_name()
|
|
user_name.short_description = 'Пользователь'
|
|
|
|
def room_id(self, obj):
|
|
"""ID комнаты."""
|
|
return str(obj.room.room_id)[:8]
|
|
room_id.short_description = 'Комната'
|
|
|
|
def duration_display(self, obj):
|
|
"""Отображение длительности."""
|
|
if obj.total_duration:
|
|
hours = obj.total_duration // 3600
|
|
minutes = (obj.total_duration % 3600) // 60
|
|
seconds = obj.total_duration % 60
|
|
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
return '-'
|
|
duration_display.short_description = 'Время в звонке'
|
|
|
|
|
|
@admin.register(VideoCallLog)
|
|
class VideoCallLogAdmin(admin.ModelAdmin):
|
|
"""Админ интерфейс для логов видеозвонков."""
|
|
|
|
list_display = [
|
|
'room_id',
|
|
'total_participants',
|
|
'duration_display',
|
|
'average_bitrate',
|
|
'packet_loss_rate',
|
|
'connection_issues',
|
|
'created_at'
|
|
]
|
|
|
|
list_filter = [
|
|
'created_at',
|
|
'connection_issues',
|
|
'audio_issues',
|
|
'video_issues'
|
|
]
|
|
|
|
search_fields = [
|
|
'room__room_id',
|
|
'room__lesson__title'
|
|
]
|
|
|
|
readonly_fields = [
|
|
'room',
|
|
'total_participants',
|
|
'total_duration',
|
|
'average_bitrate',
|
|
'packet_loss_rate',
|
|
'average_jitter',
|
|
'connection_issues',
|
|
'audio_issues',
|
|
'video_issues',
|
|
'metadata',
|
|
'created_at'
|
|
]
|
|
|
|
def room_id(self, obj):
|
|
"""ID комнаты."""
|
|
return str(obj.room.room_id)[:8]
|
|
room_id.short_description = 'Комната'
|
|
|
|
def duration_display(self, obj):
|
|
"""Отображение длительности."""
|
|
if obj.total_duration:
|
|
hours = obj.total_duration // 3600
|
|
minutes = (obj.total_duration % 3600) // 60
|
|
seconds = obj.total_duration % 60
|
|
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
return '-'
|
|
duration_display.short_description = 'Длительность'
|
|
|
|
|
|
@admin.register(ScreenRecording)
|
|
class ScreenRecordingAdmin(admin.ModelAdmin):
|
|
"""Админ интерфейс для записей видео."""
|
|
|
|
list_display = [
|
|
'room_id',
|
|
'status_badge',
|
|
'file_size_display',
|
|
'duration_display',
|
|
'is_public',
|
|
'expires_at',
|
|
'created_at'
|
|
]
|
|
|
|
list_filter = [
|
|
'status',
|
|
'is_public',
|
|
'created_at',
|
|
'processed_at'
|
|
]
|
|
|
|
search_fields = [
|
|
'room__room_id',
|
|
'room__lesson__title',
|
|
'file_path'
|
|
]
|
|
|
|
readonly_fields = [
|
|
'room',
|
|
'file_size',
|
|
'duration',
|
|
'status',
|
|
'processing_error',
|
|
'created_at',
|
|
'processed_at'
|
|
]
|
|
|
|
actions = ['mark_as_public', 'mark_as_private']
|
|
|
|
def room_id(self, obj):
|
|
"""ID комнаты."""
|
|
return str(obj.room.room_id)[:8]
|
|
room_id.short_description = 'Комната'
|
|
|
|
def status_badge(self, obj):
|
|
"""Бейдж статуса."""
|
|
colors = {
|
|
'processing': '#ffc107',
|
|
'ready': '#28a745',
|
|
'failed': '#dc3545'
|
|
}
|
|
return format_html(
|
|
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
|
|
colors.get(obj.status, '#000'),
|
|
obj.get_status_display()
|
|
)
|
|
status_badge.short_description = 'Статус'
|
|
|
|
def file_size_display(self, obj):
|
|
"""Отображение размера файла."""
|
|
if obj.file_size:
|
|
size_mb = obj.file_size / (1024 * 1024)
|
|
if size_mb > 1024:
|
|
return f"{size_mb / 1024:.2f} GB"
|
|
return f"{size_mb:.2f} MB"
|
|
return '-'
|
|
file_size_display.short_description = 'Размер'
|
|
|
|
def duration_display(self, obj):
|
|
"""Отображение длительности."""
|
|
if obj.duration:
|
|
hours = obj.duration // 3600
|
|
minutes = (obj.duration % 3600) // 60
|
|
seconds = obj.duration % 60
|
|
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
return '-'
|
|
duration_display.short_description = 'Длительность'
|
|
|
|
@admin.action(description='Сделать публичными')
|
|
def mark_as_public(self, request, queryset):
|
|
"""Сделать записи публичными."""
|
|
queryset.update(is_public=True)
|
|
|
|
@admin.action(description='Сделать приватными')
|
|
def mark_as_private(self, request, queryset):
|
|
"""Сделать записи приватными."""
|
|
queryset.update(is_public=False)
|