uchill/backend/apps/materials/admin.py

379 lines
11 KiB
Python

"""
Административная панель для материалов.
"""
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse
from .models import Material, MaterialFolder, MaterialTag, MaterialAccess, StorageQuota
@admin.register(Material)
class MaterialAdmin(admin.ModelAdmin):
"""Админ интерфейс для материалов."""
list_display = [
'title',
'owner_link',
'material_type_badge',
'file_size_display',
'folder_link',
'access_type_badge',
'views_count',
'downloads_count',
'created_at'
]
list_filter = [
'material_type',
'access_type',
'is_featured',
'is_deleted',
'created_at'
]
search_fields = [
'title',
'description',
'file_name',
'owner__email'
]
readonly_fields = [
'file_name',
'file_size',
'file_type',
'views_count',
'downloads_count',
'created_at',
'updated_at',
'deleted_at'
]
filter_horizontal = ['tags', 'shared_with']
fieldsets = (
('Основная информация', {
'fields': (
'title',
'description',
'owner'
)
}),
('Файл', {
'fields': (
'file',
'file_name',
'file_size',
'file_type',
'url'
)
}),
('Организация', {
'fields': (
'material_type',
'folder',
'tags'
)
}),
('Связи', {
'fields': (
'lesson',
'homework'
)
}),
('Доступ', {
'fields': (
'access_type',
'shared_with',
'allow_download',
'is_featured'
)
}),
('Статистика', {
'fields': (
'views_count',
'downloads_count'
)
}),
('Удаление', {
'fields': (
'is_deleted',
'deleted_at'
)
}),
('Временные метки', {
'fields': (
'created_at',
'updated_at'
)
})
)
actions = ['make_public', 'make_private', 'feature_materials']
def owner_link(self, obj):
"""Ссылка на владельца."""
url = reverse('admin:users_user_change', args=[obj.owner.id])
return format_html('<a href="{}">{}</a>', url, obj.owner.get_full_name())
owner_link.short_description = 'Владелец'
def folder_link(self, obj):
"""Ссылка на папку."""
if obj.folder:
url = reverse('admin:materials_materialfolder_change', args=[obj.folder.id])
return format_html('<a href="{}">{}</a>', url, obj.folder.name)
return '-'
folder_link.short_description = 'Папка'
def material_type_badge(self, obj):
"""Бейдж типа материала."""
colors = {
'document': '#007bff',
'presentation': '#28a745',
'video': '#dc3545',
'audio': '#ffc107',
'image': '#17a2b8',
'archive': '#6c757d',
'link': '#6610f2',
'other': '#343a40'
}
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
colors.get(obj.material_type, '#000'),
obj.get_material_type_display()
)
material_type_badge.short_description = 'Тип'
def access_type_badge(self, obj):
"""Бейдж типа доступа."""
colors = {
'private': '#6c757d',
'public': '#28a745',
'lesson': '#17a2b8',
'clients': '#ffc107'
}
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
colors.get(obj.access_type, '#000'),
obj.get_access_type_display()
)
access_type_badge.short_description = 'Доступ'
def file_size_display(self, obj):
"""Отображение размера файла."""
if obj.file_size:
size_mb = obj.file_size / (1024 * 1024)
if size_mb > 1:
return f"{size_mb:.2f} МБ"
size_kb = obj.file_size / 1024
return f"{size_kb:.2f} КБ"
return '-'
file_size_display.short_description = 'Размер'
@admin.action(description='Сделать публичными')
def make_public(self, request, queryset):
"""Сделать материалы публичными."""
queryset.update(access_type='public')
@admin.action(description='Сделать приватными')
def make_private(self, request, queryset):
"""Сделать материалы приватными."""
queryset.update(access_type='private')
@admin.action(description='Добавить в избранное')
def feature_materials(self, request, queryset):
"""Добавить материалы в избранное."""
queryset.update(is_featured=True)
@admin.register(MaterialFolder)
class MaterialFolderAdmin(admin.ModelAdmin):
"""Админ интерфейс для папок."""
list_display = [
'name',
'owner_link',
'parent',
'path_display',
'is_public',
'materials_count',
'created_at'
]
list_filter = [
'is_public',
'created_at'
]
search_fields = [
'name',
'description',
'owner__email'
]
readonly_fields = [
'materials_count',
'created_at',
'updated_at'
]
filter_horizontal = ['shared_with']
def owner_link(self, obj):
"""Ссылка на владельца."""
url = reverse('admin:users_user_change', args=[obj.owner.id])
return format_html('<a href="{}">{}</a>', url, obj.owner.get_full_name())
owner_link.short_description = 'Владелец'
def path_display(self, obj):
"""Путь папки."""
return obj.get_path()
path_display.short_description = 'Путь'
@admin.register(MaterialTag)
class MaterialTagAdmin(admin.ModelAdmin):
"""Админ интерфейс для тегов."""
list_display = [
'name',
'slug',
'color_display',
'materials_count'
]
search_fields = ['name', 'slug']
readonly_fields = ['materials_count']
def color_display(self, obj):
"""Отображение цвета."""
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
obj.color,
obj.color
)
color_display.short_description = 'Цвет'
@admin.register(MaterialAccess)
class MaterialAccessAdmin(admin.ModelAdmin):
"""Админ интерфейс для логов доступа."""
list_display = [
'material_link',
'user_link',
'action_badge',
'ip_address',
'created_at'
]
list_filter = [
'action',
'created_at'
]
search_fields = [
'material__title',
'user__email',
'ip_address'
]
readonly_fields = [
'material',
'user',
'action',
'ip_address',
'user_agent',
'created_at'
]
def material_link(self, obj):
"""Ссылка на материал."""
url = reverse('admin:materials_material_change', args=[obj.material.id])
return format_html('<a href="{}">{}</a>', url, obj.material.title)
material_link.short_description = 'Материал'
def user_link(self, obj):
"""Ссылка на пользователя."""
url = reverse('admin:users_user_change', args=[obj.user.id])
return format_html('<a href="{}">{}</a>', url, obj.user.get_full_name())
user_link.short_description = 'Пользователь'
def action_badge(self, obj):
"""Бейдж действия."""
colors = {
'view': '#17a2b8',
'download': '#28a745',
'share': '#ffc107'
}
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
colors.get(obj.action, '#000'),
obj.get_action_display()
)
action_badge.short_description = 'Действие'
@admin.register(StorageQuota)
class StorageQuotaAdmin(admin.ModelAdmin):
"""Админ интерфейс для квот хранилища."""
list_display = [
'user_link',
'total_quota_display',
'used_space_display',
'used_percentage_display',
'available_space_display',
'updated_at'
]
search_fields = ['user__email']
readonly_fields = [
'user',
'used_space',
'created_at',
'updated_at'
]
actions = ['recalculate_quotas']
def user_link(self, obj):
"""Ссылка на пользователя."""
url = reverse('admin:users_user_change', args=[obj.user.id])
return format_html('<a href="{}">{}</a>', url, obj.user.get_full_name())
user_link.short_description = 'Пользователь'
def total_quota_display(self, obj):
"""Общая квота."""
return f"{obj.total_quota / (1024*1024*1024):.2f} ГБ"
total_quota_display.short_description = 'Квота'
def used_space_display(self, obj):
"""Использовано."""
return f"{obj.used_space / (1024*1024):.2f} МБ"
used_space_display.short_description = 'Использовано'
def used_percentage_display(self, obj):
"""Процент использования."""
percentage = obj.get_used_percentage()
color = '#28a745' if percentage < 70 else ('#ffc107' if percentage < 90 else '#dc3545')
return format_html(
'<span style="color: {}; font-weight: bold;">{:.1f}%</span>',
color,
percentage
)
used_percentage_display.short_description = 'Использовано %'
def available_space_display(self, obj):
"""Доступно."""
return f"{obj.get_available_space() / (1024*1024):.2f} МБ"
available_space_display.short_description = 'Доступно'
@admin.action(description='Пересчитать квоты')
def recalculate_quotas(self, request, queryset):
"""Пересчитать квоты."""
for quota in queryset:
quota.recalculate()