# Кастомная конфигурация Django Admin для Hypn0. # Регистрируем модели с удобным интерфейсом. from django import forms from django.contrib import admin from django.forms import Textarea from django.utils.html import format_html from django.http import HttpRequest from .models import ( TbHypn0Item, TbVote, TbBlogPost ) # ============================================================================ # МИКСИНЫ ДЛЯ АДМИНКИ # ============================================================================ class RequestInFormMixin(admin.ModelAdmin): """ Миксин для передачи request объекта в форму. Используется когда форма нуждается в доступе к request для проверки POST параметров или другой информации о текущем HTTP-запросе. Переопределяет get_form() и передает request в __init__ формы через kwargs. """ def get_form(self, request: HttpRequest, obj=None, **kwargs): """ Переопределяем get_form чтобы передать request в форму. Создаем оборачивающий класс который передаст request в __init__. """ FormClass = super().get_form(request, obj, **kwargs) # Сохраняем request в замыкании для доступа в классе request_ref = request class FormWithRequest(FormClass): """Оборачивающий класс, который передает request при инстанцировании""" def __init__(form_instance, *args, **init_kwargs): # Добавляем request в kwargs перед вызовом __init__ родителя init_kwargs['request'] = request_ref super().__init__(*args, **init_kwargs) return FormWithRequest class CodeMirrorFormMixin(forms.ModelForm): """ Миксин для форм с поддержкой CodeMirror редактора. Предоставляет: - Готовый Media класс с CSS и JS для CodeMirror - Helper метод setup_codemirror_field() для конфигурации полей - Базовые атрибуты для активации CodeMirror Использование: class MyForm(CodeMirrorFormMixin): class Meta: model = MyModel fields = (...) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setup_codemirror_field('field_name', 'json', 'codemirror-width-l') """ # ===== MEDIA КЛАСС ДЛЯ CODEMIRROR ===== class Media: """Подключаем CSS и JS для CodeMirror редактора""" css = { 'all': ('codemirror/codemirror-styles.css',) # Стили для CodeMirror } js = ( 'codemirror/editor.js', # Основной CodeMirror 'codemirror/codemirror-patch.js', # Патч для управления высотой/шириной ) # ===== БАЗОВЫЕ АТРИБУТЫ CODEMIRROR ===== CODEMIRROR_ATTRS_BASE = { 'data-codemirror-editor': '1', 'data-width': '100%', # Ширина для патча (100% займет полную ширину) } def setup_codemirror_field(self, field_name: str, language: str = 'text', css_class: str = 'codemirror-width-l'): """ Конфигурирует поле для использования CodeMirror редактором. Применяет Textarea виджет с нужными атрибутами и CSS классами для CodeMirror. Args: field_name: Имя поля в форме (например, 's_label') language: Язык для подсветки синтаксиса (text, json, html, url и т.д.) css_class: CSS класс для управления размерами (codemirror-width-s, codemirror-width-l и т.д.) Пример: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setup_codemirror_field('s_label', 'text', 'codemirror-width-xl codemirror-no-lines') self.setup_codemirror_field('j_metadata', 'json', 'codemirror-width-l') """ # Собираем атрибуты для поля attrs = { **self.CODEMIRROR_ATTRS_BASE, 'data-language': language, 'class': css_class, } # Применяем Textarea виджет с атрибутами self.fields[field_name].widget = Textarea(attrs=attrs) @admin.register(TbHypn0Item) class TbHypn0ItemAdmin(admin.ModelAdmin): """ Панель управления гипно-картинами (модерация, просмотр статистики, редактирование). Создание новых картин через админку заблокировано (создание только на фронтенде). """ list_display = ( "id", "s_hash_id", "title_preview", "i_level", "i_likes_count", "i_claims_count", "i_views_count", "f_score", "is_public", "d_created_at", ) list_display_links = ("id", "s_hash_id", "title_preview") list_filter = ("i_level", "is_public", "d_created_at") search_fields = ("s_hash_id", "s_title", "s_promo_title", "s_promo_url") readonly_fields = ( "id", "s_hash_id", "i_file_size", "i_likes_count", "i_views_count", "i_claims_count", "i_promo_clicks", "d_created_at", "d_updated_at", "svg_preview", ) fieldsets = ( ( "Основная информация", { "fields": ( "id", "s_hash_id", "s_title", "file_svg", "svg_preview", "i_file_size", "is_public", ) }, ), ( "Модерация и Smart Retention", { "fields": ( "i_level", "f_score", ), "description": "Уровень модерации и рейтинг 'гравитации' для ротации и очистки хранилища.", }, ), ( "Метрики популярности", { "fields": ( "i_likes_count", "i_claims_count", "i_views_count", ), }, ), ( "Промо-блок (Спонсоры и Авторы)", { "fields": ( "s_promo_url", "s_promo_title", "i_promo_clicks", ), "classes": ("collapse",), }, ), ( "Метаданные генерации", { "fields": ("j_metadata",), "classes": ("collapse",), }, ), ( "Временные метки", { "fields": ("d_created_at", "d_updated_at"), "classes": ("collapse",), }, ), ) def has_add_permission(self, request): # Создание картин возможно только с фронтенда (требуется обработка растра и согласие пользователя) return False @admin.display(description="Название (HTML)") def title_preview(self, obj): return format_html(obj.s_title) @admin.display(description="Превью SVG") def svg_preview(self, obj): if obj.file_svg: return format_html( '