monor: валидатор форм, парсера и моделей (18) комментарии
This commit is contained in:
@@ -709,7 +709,7 @@ class LabelAdmin(RequestInFormMixin, admin.ModelAdmin):
|
|||||||
|
|
||||||
# Обогащение Media из формы (CodeMirrorFormMixin)
|
# Обогащение Media из формы (CodeMirrorFormMixin)
|
||||||
# Используем наследование для расширения, а не перекрытия
|
# Используем наследование для расширения, а не перекрытия
|
||||||
# Это гарантирует что все CSS/JS из формы будут загружены
|
# Это гарантирует, что все CSS/JS из формы будут загружены
|
||||||
class Media(LabelAdminForm.Media):
|
class Media(LabelAdminForm.Media):
|
||||||
css = {'all': (*LabelAdminForm.Media.css['all'], 'css/validation-override.css', )}
|
css = {'all': (*LabelAdminForm.Media.css['all'], 'css/validation-override.css', )}
|
||||||
js = (*LabelAdminForm.Media.js, 'js/form-field-watcher.js', )
|
js = (*LabelAdminForm.Media.js, 'js/form-field-watcher.js', )
|
||||||
@@ -858,7 +858,7 @@ class SourceAdminForm(CodeMirrorFormMixin):
|
|||||||
self.setup_codemirror_field('j_source_metadata', language='json',
|
self.setup_codemirror_field('j_source_metadata', language='json',
|
||||||
css_class='codemirror-width-l codemirror-min-height-5')
|
css_class='codemirror-width-l codemirror-min-height-5')
|
||||||
|
|
||||||
#
|
|
||||||
class SourceAdmin(admin.ModelAdmin):
|
class SourceAdmin(admin.ModelAdmin):
|
||||||
"""Админ для источников"""
|
"""Админ для источников"""
|
||||||
form = SourceAdminForm # Используем кастомную форму с CodeMirror
|
form = SourceAdminForm # Используем кастомную форму с CodeMirror
|
||||||
@@ -871,19 +871,55 @@ class SourceAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Остальные ModelAdmin классы
|
# АДМИНКА РЕЛИЗОВ/АЛЬБОМОВ/ТОВАРОВ
|
||||||
# ============================================================================
|
#
|
||||||
|
# Кастомная форма
|
||||||
|
class ItemAdminForm(CodeMirrorFormMixin):
|
||||||
|
"""
|
||||||
|
Кастомная форма для админки источников данных (TbItem).
|
||||||
|
Добавляет виджеты CodeMirror для текстовых полей
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = TbItem
|
||||||
|
fields = ('id', 's_item', 'k_item_to_artist', 'k_item_to_style', 'k_item_to_article',
|
||||||
|
's_item_date', 't_item_date', 'i_discogs_master_id', 'j_item_metadata')
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
При инициализации формы подгружаем CodeMirror редактор
|
||||||
|
"""
|
||||||
|
# Извлекаем request из kwargs если он есть
|
||||||
|
self.request = kwargs.pop('request', None)
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Конфигурируем поля для CodeMirror
|
||||||
|
self.setup_codemirror_field('s_item', language='text',
|
||||||
|
css_class='codemirror-width-l codemirror-no-lines')
|
||||||
|
self.setup_codemirror_field('s_item_date', language='text',
|
||||||
|
css_class='codemirror-width-s codemirror-no-lines')
|
||||||
|
self.setup_codemirror_field('i_discogs_master_id', language='text',
|
||||||
|
css_class='codemirror-width-l codemirror-no-lines')
|
||||||
|
self.setup_codemirror_field('j_item_metadata', language='json',
|
||||||
|
css_class='codemirror-width-l codemirror-min-height-5')
|
||||||
|
|
||||||
|
|
||||||
class ItemAdmin(admin.ModelAdmin):
|
class ItemAdmin(RequestInFormMixin, admin.ModelAdmin):
|
||||||
"""Админ для товаров"""
|
"""Админ для товаров"""
|
||||||
|
form = ItemAdminForm # Используем кастомную форму с CodeMirror
|
||||||
|
|
||||||
list_display = ('id', 's_item', 't_item_date', 't_item_created')
|
list_display = ('id', 's_item', 't_item_date', 't_item_created')
|
||||||
|
list_display_links = ('id', 's_item',)
|
||||||
list_filter = ('t_item_date', 't_item_created')
|
list_filter = ('t_item_date', 't_item_created')
|
||||||
search_fields = ('s_item',)
|
search_fields = ('s_item',)
|
||||||
filter_horizontal = ('k_item_to_artist', 'k_item_to_style')
|
filter_horizontal = ('k_item_to_artist', 'k_item_to_style')
|
||||||
readonly_fields = ('t_item_created', 't_item_updated')
|
readonly_fields = ('t_item_created', 't_item_updated')
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Остальные ModelAdmin классы
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
class OfferAdmin(admin.ModelAdmin):
|
class OfferAdmin(admin.ModelAdmin):
|
||||||
"""Админ для предложений"""
|
"""Админ для предложений"""
|
||||||
list_display = ('id', 's_offer', 'k_offer_to_item', 'f_offer_price', 'i_offer_quantity', 'i_offer_views')
|
list_display = ('id', 's_offer', 'k_offer_to_item', 'f_offer_price', 'i_offer_quantity', 'i_offer_views')
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ class TbMusicStyle(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='Метаданные',
|
verbose_name='Метаданные',
|
||||||
help_text='В основном список синонимов/вариантов названия из Discogs, MusicBrainz и т.д. для матчинга.'
|
help_text='В основном список синонимов/вариантов названия из Discogs, MusicBrainz и т.д. для матчинга.'
|
||||||
' Пример: <tt>{"SYNONYM": ["rock", "Rock Music", "Rock & Roll", "Hard Rock"]}</tt>.',
|
' Пример: <tt>{"SYN_EN": ["rock", "Rock Music", "Rock & Roll", "Hard Rock"]}</tt>.',
|
||||||
)
|
)
|
||||||
t_style_created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="Дата создания")
|
t_style_created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="Дата создания")
|
||||||
t_style_updated = models.DateTimeField(auto_now=True, editable=False, verbose_name="Дата обновления")
|
t_style_updated = models.DateTimeField(auto_now=True, editable=False, verbose_name="Дата обновления")
|
||||||
@@ -571,8 +571,8 @@ class TbMusicStyle(models.Model):
|
|||||||
|
|
||||||
При сохранении музыкального стиля (создание и обновление):
|
При сохранении музыкального стиля (создание и обновление):
|
||||||
1. Управляем синонимами:
|
1. Управляем синонимами:
|
||||||
- Для новых музыкальных стилей: добавляем текущий s_style_name в SYNONYM
|
- Для новых музыкальных стилей: добавляем текущий s_style_name в SYN_EN
|
||||||
- При изменении s_style_name: добавляем как старый, так и новый s_style_name в SYNONYM
|
- При изменении s_style_name: добавляем как старый, так и новый s_style_name в SYN_EN
|
||||||
- При редактировании: используем j_style_metadata из формы (приоритет админу)
|
- При редактировании: используем j_style_metadata из формы (приоритет админу)
|
||||||
2. Если статья не привязана - создаём новую статью музыкального стиля автоматически
|
2. Если статья не привязана - создаём новую статью музыкального стиля автоматически
|
||||||
3. Генерируем технический заголовок и slug для статьи
|
3. Генерируем технический заголовок и slug для статьи
|
||||||
@@ -641,7 +641,7 @@ class TbArtist(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
verbose_name='Метаданные JSON',
|
verbose_name='Метаданные JSON',
|
||||||
help_text='В основном список синонимов/вариантов названия из Discogs, MusicBrainz и т.д. для матчинга.'
|
help_text='В основном список синонимов/вариантов названия из Discogs, MusicBrainz и т.д. для матчинга.'
|
||||||
' Пример: <tt>{"SYNONYM": ["The Beatles", "Beatles", "Beatles, The"]}</tt>.',
|
' Пример: <tt>{"SYN_EN": ["The Beatles", "Beatles", "Beatles, The"]}</tt>.',
|
||||||
)
|
)
|
||||||
t_artist_created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="Дата создания",)
|
t_artist_created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="Дата создания",)
|
||||||
t_artist_updated = models.DateTimeField(auto_now=True, editable=False, verbose_name="Дата обновления",
|
t_artist_updated = models.DateTimeField(auto_now=True, editable=False, verbose_name="Дата обновления",
|
||||||
@@ -656,8 +656,8 @@ class TbArtist(models.Model):
|
|||||||
|
|
||||||
При сохранении исполнителя (создание и обновление):
|
При сохранении исполнителя (создание и обновление):
|
||||||
1. Управляем синонимами:
|
1. Управляем синонимами:
|
||||||
- Для новых исполнителей: добавляем текущий s_artist в SYNONYM
|
- Для новых исполнителей: добавляем текущий s_artist в SYN_EN
|
||||||
- При изменении s_artist: добавляем как старый, так и новый s_artist в SYNONYM
|
- При изменении s_artist: добавляем как старый, так и новый s_artist в SYN_EN
|
||||||
- При редактировании: используем j_artist_metadata из формы (приоритет админу)
|
- При редактировании: используем j_artist_metadata из формы (приоритет админу)
|
||||||
2. Если статья не привязана - создаём новую статью исполнителя автоматически
|
2. Если статья не привязана - создаём новую статью исполнителя автоматически
|
||||||
3. Генерируем технический заголовок и slug для статьи
|
3. Генерируем технический заголовок и slug для статьи
|
||||||
@@ -780,6 +780,42 @@ class TbItem(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Item {self.id:0>4}: {self.s_item}"
|
return f"Item {self.id:0>4}: {self.s_item}"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Переопределяем save для управления синонимами релиза (альбома) или просто товара и создания связанной статьи.
|
||||||
|
|
||||||
|
При сохранении релиза/альбома (создание и обновление):
|
||||||
|
1. Управляем синонимами альбома/релиза/товара:
|
||||||
|
- Для новых альбомов/релиза/товара: добавляем в SYN_EN
|
||||||
|
- При изменении s_item: добавляем как старый, так и новый s_item в SYN_EN
|
||||||
|
- При редактировании: используем j_item_metadata из формы (приоритет админу)
|
||||||
|
2. Если статья не привязана - создаём новую автоматически
|
||||||
|
3. Генерируем технический заголовок и slug для статьи
|
||||||
|
"""
|
||||||
|
# ===== ВАЛИДАЦИЯ НА ДУБЛИКАТЫ =====
|
||||||
|
# Проверяем ДО работы с синонимами и метаданными!
|
||||||
|
# Страховка: защита от прямого вызова save() минуя админку или (в будущем) парсер
|
||||||
|
validate_and_raise_for_duplicates(self, 's_item', 'j_item_metadata')
|
||||||
|
|
||||||
|
# ===== УПРАВЛЕНИЕ СИНОНИМАМИ =====
|
||||||
|
# Обновляем список синонимов в метаданных (универсальный хелпер для всех моделей)
|
||||||
|
update_synonyms_in_metadata(self, 's_item', 'j_item_metadata')
|
||||||
|
|
||||||
|
# ===== СОЗДАНИЕ ИЛИ ПОЛУЧЕНИЕ СВЯЗАННОЙ СТАТЬИ =====
|
||||||
|
# Используем универсальный хелпер для создания/поиска статьи
|
||||||
|
# Хелпер сам проверит через обратный FK, не дублирует статьи даже если админ переименовал
|
||||||
|
article = create_or_get_related_article(
|
||||||
|
self,
|
||||||
|
TbArticle.ArticleType.ITEM,
|
||||||
|
's_item',
|
||||||
|
'j_item_metadata',
|
||||||
|
'k_item_to_article' # ← Явно передаем имя FK поля (избегаем "магии")
|
||||||
|
)
|
||||||
|
self.k_item_to_article = article
|
||||||
|
|
||||||
|
# Вызываем оригинальный save родительского класса
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Товар в каталоге (релиз, носитель, аксессуар)'
|
verbose_name = 'Товар в каталоге (релиз, носитель, аксессуар)'
|
||||||
verbose_name_plural = 'Товары в каталоге'
|
verbose_name_plural = 'Товары в каталоге'
|
||||||
@@ -826,7 +862,7 @@ class TbLabel(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
verbose_name='Метаданные',
|
verbose_name='Метаданные',
|
||||||
help_text='JSON: страна лейбла, официальный сайт и т.д. Включая список синонимов/вариантов названия для матчинга.'
|
help_text='JSON: страна лейбла, официальный сайт и т.д. Включая список синонимов/вариантов названия для матчинга.'
|
||||||
' Пример: <tt>{"SYNONYM": ["Island", "Island Records", "Vertigo France"]}</tt>.'
|
' Пример: <tt>{"SYN_EN": ["Island", "Island Records", "Vertigo France"]}</tt>.'
|
||||||
,
|
,
|
||||||
)
|
)
|
||||||
t_label_created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="Дата создания",)
|
t_label_created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="Дата создания",)
|
||||||
@@ -841,8 +877,8 @@ class TbLabel(models.Model):
|
|||||||
|
|
||||||
При сохранении лейбла (создание и обновление):
|
При сохранении лейбла (создание и обновление):
|
||||||
1. Управляем синонимами лейбла:
|
1. Управляем синонимами лейбла:
|
||||||
- Для новых лейблов: добавляем текущий s_label в SYNONYM
|
- Для новых лейблов: добавляем текущий s_label в SYN_EN
|
||||||
- При изменении s_label: добавляем как старый, так и новый s_label в SYNONYM
|
- При изменении s_label: добавляем как старый, так и новый s_label в SYN_EN
|
||||||
- При редактировании: используем j_label_metadata из формы (приоритет админу)
|
- При редактировании: используем j_label_metadata из формы (приоритет админу)
|
||||||
2. Если статья не привязана - создаём новую автоматически
|
2. Если статья не привязана - создаём новую автоматически
|
||||||
3. Генерируем технический заголовок и slug для статьи
|
3. Генерируем технический заголовок и slug для статьи
|
||||||
|
|||||||
Reference in New Issue
Block a user