mod: валидатор форм, парсера и моделей (15) все хелперы валидации и автоматического создания статей подключены музыкальным стилям (модель TbMusicStyle, формам MusicStyleAdminForm и админке MusicStyleAdmin)
This commit is contained in:
@@ -401,6 +401,9 @@ class MusicStyleAdminForm(CodeMirrorFormMixin):
|
||||
"""
|
||||
При инициализации формы подгружаем CodeMirror редактор
|
||||
"""
|
||||
# Извлекаем request из kwargs если он есть
|
||||
self.request = kwargs.pop('request', None)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Конфигурируем поля для CodeMirror
|
||||
@@ -409,8 +412,36 @@ class MusicStyleAdminForm(CodeMirrorFormMixin):
|
||||
self.setup_codemirror_field('j_style_metadata', language='json',
|
||||
css_class='codemirror-width-l codemirror-min-height-5')
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Валидируем форму: проверяем на совпадения (дубликаты) основного поля s_style_name.
|
||||
Используем 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_style_name',
|
||||
metadata_field_name='j_style_metadata',
|
||||
request=self.request,
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
# Админка для TbMusicStyle с кастомной формой MusicStyleAdminForm
|
||||
class MusicStyleAdmin(admin.ModelAdmin):
|
||||
class MusicStyleAdmin(RequestInFormMixin, admin.ModelAdmin):
|
||||
"""Админ для музыкальных стилей"""
|
||||
form = MusicStyleAdminForm
|
||||
|
||||
@@ -437,6 +468,41 @@ class MusicStyleAdmin(admin.ModelAdmin):
|
||||
}),
|
||||
)
|
||||
|
||||
# Дополнительный CSS для валидации и вотчер для отслеживания изменений
|
||||
class Media:
|
||||
css = {'all': ('css/validation-override.css', )} # Стили для обхода валидации
|
||||
js = ('js/form-field-watcher.js', ) # Вотчер для отслеживания изменений полей формы
|
||||
|
||||
def save_model(
|
||||
self,
|
||||
request: HttpRequest,
|
||||
obj: TbMusicStyle,
|
||||
form: forms.ModelForm,
|
||||
change: bool,
|
||||
) -> None:
|
||||
"""
|
||||
Переопределяем save_model для добавления информативных сообщений в админку.
|
||||
|
||||
Максимально поджарый код - все сложности с получением старого значения
|
||||
и определением типа операции делает хелпер generate_admin_save_message().
|
||||
"""
|
||||
# Стандартное сохранение записи через Django
|
||||
# (в т.ч. автоматическое создание связанной статьи в методе save модели TbMusicStyle)
|
||||
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_style_to_article,
|
||||
obj_field_name='s_style_name',
|
||||
article_title_field='s_article_title',
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# АДМИНКА для Исполнителей/Групп/Артистов, таблица TbArtist
|
||||
@@ -537,10 +603,10 @@ class ArtistAdmin(RequestInFormMixin, admin.ModelAdmin):
|
||||
и определением типа операции делает хелпер generate_admin_save_message().
|
||||
"""
|
||||
# Стандартное сохранение записи через Django
|
||||
# (в т.ч. автоматическое создание связанной статьи в методе save модели TbLabel)
|
||||
# (в т.ч. автоматическое создание связанной статьи в методе save модели TbArtist)
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
# Генерируем и отправляем информативное сообщение об сохранении
|
||||
# Генерируем и отправляем информативное сообщение о сохранении
|
||||
# Хелпер сам:
|
||||
# - определяет тип операции (create vs update)
|
||||
# - формирует нужное сообщение (success vs warning)
|
||||
@@ -659,7 +725,7 @@ class LabelAdmin(RequestInFormMixin, admin.ModelAdmin):
|
||||
# (в т.ч. автоматическое создание связанной статьи в методе save модели TbLabel)
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
# Генерируем и отправляем информативное сообщение об сохранении
|
||||
# Генерируем и отправляем информативное сообщение о сохранении
|
||||
# Хелпер сам:
|
||||
# - определяет тип операции (create vs update)
|
||||
# - формирует нужное сообщение (success vs warning)
|
||||
|
||||
@@ -565,6 +565,42 @@ class TbMusicStyle(models.Model):
|
||||
def __str__(self):
|
||||
return self.s_style_name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Переопределяем save для управления синонимами музыкального стиля и создания связанной статьи.
|
||||
|
||||
При сохранении музыкального стиля (создание и обновление):
|
||||
1. Управляем синонимами:
|
||||
- Для новых музыкальных стилей: добавляем текущий s_style_name в SYNONYM
|
||||
- При изменении s_style_name: добавляем как старый, так и новый s_style_name в SYNONYM
|
||||
- При редактировании: используем j_style_metadata из формы (приоритет админу)
|
||||
2. Если статья не привязана - создаём новую статью музыкального стиля автоматически
|
||||
3. Генерируем технический заголовок и slug для статьи
|
||||
"""
|
||||
# ===== ВАЛИДАЦИЯ НА ДУБЛИКАТЫ =====
|
||||
# Проверяем ДО работы с синонимами и метаданными!
|
||||
# Страховка: защита от прямого вызова save() минуя админку или (в будущем) парсер
|
||||
validate_and_raise_for_duplicates(self, 's_style_name', 'j_style_metadata')
|
||||
|
||||
# ===== УПРАВЛЕНИЕ СИНОНИМАМИ =====
|
||||
# Обновляем список синонимов в метаданных (универсальный хелпер для всех моделей)
|
||||
update_synonyms_in_metadata(self, 's_style_name', 'j_style_metadata')
|
||||
|
||||
# ===== СОЗДАНИЕ ИЛИ ПОЛУЧЕНИЕ СВЯЗАННОЙ СТАТЬИ =====
|
||||
# Используем универсальный хелпер для создания/поиска статьи
|
||||
# Хелпер сам проверит через обратный FK. Не дублирует статьи, даже если админ переименовал
|
||||
article = create_or_get_related_article(
|
||||
self,
|
||||
TbArticle.ArticleType.STYLE,
|
||||
's_style_name',
|
||||
'j_style_metadata',
|
||||
'k_style_to_article' # ← Явно передаем имя FK поля (избегаем "магии")
|
||||
)
|
||||
self.k_style_to_article = article
|
||||
|
||||
# Вызываем оригинальный save родительского класса
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Музыкальный стиль'
|
||||
verbose_name_plural = 'Музыкальные стили'
|
||||
|
||||
Reference in New Issue
Block a user