mod: валидатор форм, парсера и моделей (14) все хелперы валидации и автоматического создания статей подключены исполнителям (модель TbArtist, формам ArtistAdminForm и админке ArtistAdmin)

This commit is contained in:
2026-07-01 21:12:27 +03:00
parent 429d2fb417
commit 9522d3fe69
2 changed files with 145 additions and 38 deletions
+109 -38
View File
@@ -4,6 +4,7 @@
from typing import Any
from django import forms
from django.forms import Textarea
from django.http import HttpRequest
from django.contrib import admin
from django.utils.html import format_html, mark_safe
from easy_thumbnails.files import get_thumbnailer
@@ -336,7 +337,13 @@ class ImageAdmin(admin.ModelAdmin):
_display_title_text.short_description = 'TITLE из filer'
def save_model(self, request, obj, form, change):
def save_model(
self,
request: HttpRequest,
obj: TbImage,
form: forms.ModelForm,
change: bool,
) -> None:
"""
Переопределяем save_model для обновления метаданных filer_image.
@@ -448,6 +455,9 @@ class ArtistAdminForm(CodeMirrorFormMixin):
"""
При инициализации формы подгружаем CodeMirror редактор
"""
# Извлекаем request из kwargs если он есть
self.request = kwargs.pop('request', None)
super().__init__(*args, **kwargs)
# Конфигурируем поля для CodeMirror
@@ -456,8 +466,36 @@ class ArtistAdminForm(CodeMirrorFormMixin):
self.setup_codemirror_field('j_artist_metadata', language='json',
css_class='codemirror-width-l codemirror-min-height-5')
def clean(self):
"""
Валидируем форму: проверяем на совпадения (дубликаты) основного поля s_artist.
Используем GET параметр ignore_validate для пропуска валидации при переотправке.
"""
# Получаем очищенные данные формы (может быть None, но обычно это dict)
cleaned_data: dict[str, Any] | None = super().clean()
# Если clean() вернул None, возвращаем пустой dict (для совместимости)
if cleaned_data is None:
cleaned_data = {}
# После проверки выше, очищены данные гарантированно dict[str, Any]
assert isinstance(cleaned_data, dict), "cleaned_data должен быть словарём"
# Используем универсальный хелпер для проверки дубликатов
# Модель берется автоматически из self.Meta.model
# Передаем request для проверки GET параметра ignore_validate
validate_entity_for_admin_form(
self,
cleaned_data,
main_field_name='s_artist',
metadata_field_name='j_artist_metadata',
request=self.request,
)
return cleaned_data
# Админка для TbArtist с кастомной формой ArtistAdminForm
class ArtistAdmin(admin.ModelAdmin):
class ArtistAdmin(RequestInFormMixin, admin.ModelAdmin):
"""Админ для артистов"""
form = ArtistAdminForm # Используем кастомную форму с CodeMirror
@@ -480,6 +518,41 @@ class ArtistAdmin(admin.ModelAdmin):
}),
)
# Дополнительный CSS для валидации и вотчер для отслеживания изменений
class Media:
css = {'all': ('css/validation-override.css', )} # Стили для обхода валидации
js = ('js/form-field-watcher.js', ) # Вотчер для отслеживания изменений полей формы
def save_model(
self,
request: HttpRequest,
obj: TbArtist,
form: forms.ModelForm,
change: bool,
) -> None:
"""
Переопределяем save_model для добавления информативных сообщений в админку.
Максимально поджарый код - все сложности с получением старого значения
и определением типа операции делает хелпер generate_admin_save_message().
"""
# Стандартное сохранение записи через Django
# (в т.ч. автоматическое создание связанной статьи в методе save модели TbLabel)
super().save_model(request, obj, form, change)
# Генерируем и отправляем информативное сообщение об сохранении
# Хелпер сам:
# - определяет тип операции (create vs update)
# - формирует нужное сообщение (success vs warning)
generate_admin_save_message(
request=request,
obj=obj,
is_new=not change, # Django: change=False для новых, True для существующих
related_article=obj.k_artist_to_article,
obj_field_name='s_artist',
article_title_field='s_article_title',
)
# ================
# АДМИН-ПАНЕЛЬ ДЛЯ ЛЕЙБЛОВ/ИЗДАТЕЛЕЙ
@@ -544,46 +617,11 @@ class LabelAdmin(RequestInFormMixin, admin.ModelAdmin):
"""Админ для лейблов с поддержкой передачи request в форму"""
form = LabelAdminForm # Используем кастомную форму с CodeMirror
# Дополнительный CSS для валидации и вотчер для отслеживания изменений
class Media:
css = {
'all': (
'css/validation-override.css', # Стили для обхода валидации
)
}
js = (
'js/form-field-watcher.js', # Вотчер для отслеживания изменений полей формы
)
list_display = ('id', 's_label', 't_label_created')
list_display_links = ('id', 's_label',)
search_fields = ('s_label',)
readonly_fields = ('t_label_created', 't_label_updated')
def save_model(self, request, obj, form, change):
"""
Переопределяем save_model для добавления информативных сообщений в админку.
Максимально поджарый код - все сложности с получением старого значения
и определением типа операции делает хелпер generate_admin_save_message().
"""
# Стандартное сохранение записи через Django
# (в т.ч. автоматическое создание связанной статьи в методе save модели TbLabel)
super().save_model(request, obj, form, change)
# Генерируем и отправляем информативное сообщение об сохранении
# Хелпер сам:
# - определяет тип операции (create vs update)
# - формирует нужное сообщение (success vs warning)
generate_admin_save_message(
request=request,
obj=obj,
is_new=not change, # Django: change=False для новых, True для существующих
related_article=obj.k_label_to_article,
obj_field_name='s_label',
article_title_field='s_article_title',
)
fieldsets = (
('Основные данные о лейбле/издателе', {
'fields': ('s_label', 'j_label_metadata',),
@@ -599,6 +637,40 @@ class LabelAdmin(RequestInFormMixin, admin.ModelAdmin):
}),
)
# Дополнительный CSS для валидации и вотчер для отслеживания изменений
class Media:
css = {'all': ('css/validation-override.css', )} # Стили для обхода валидации
js = ('js/form-field-watcher.js', ) # Вотчер для отслеживания изменений полей формы
def save_model(
self,
request: HttpRequest,
obj: TbLabel,
form: forms.ModelForm,
change: bool,
) -> None:
"""
Переопределяем save_model для добавления информативных сообщений в админку.
Максимально поджарый код - все сложности с получением старого значения
и определением типа операции делает хелпер generate_admin_save_message().
"""
# Стандартное сохранение записи через Django
# (в т.ч. автоматическое создание связанной статьи в методе save модели TbLabel)
super().save_model(request, obj, form, change)
# Генерируем и отправляем информативное сообщение об сохранении
# Хелпер сам:
# - определяет тип операции (create vs update)
# - формирует нужное сообщение (success vs warning)
generate_admin_save_message(
request=request,
obj=obj,
is_new=not change, # Django: change=False для новых, True для существующих
related_article=obj.k_label_to_article,
obj_field_name='s_label',
article_title_field='s_article_title',
)
# ================
@@ -754,4 +826,3 @@ admin.site.register(TbOfferHistory, OfferHistoryAdmin)
# Кастомизация админ-сайта через ready() в apps.py (переименование через verbose_name)
# ============================================================================