diff --git a/lpon_site/frontend/admin.py b/lpon_site/frontend/admin.py index 0a77f37..d67a450 100644 --- a/lpon_site/frontend/admin.py +++ b/lpon_site/frontend/admin.py @@ -10,7 +10,7 @@ from .models import ( TbImage, TbArticle, TbArtist, TbItem, TbLabel, TbSeller, TbOffer, TbSource, TbOfferHistory, TbMusicStyle ) -from .utils import validate_entity_for_admin_form +from .utils import validate_entity_for_admin_form, generate_admin_save_message # ============================================================================ @@ -353,6 +353,7 @@ class MusicStyleAdmin(admin.ModelAdmin): list_display_links = ('id', 's_style_name',) search_fields = ('s_style_name', 'j_style_synonyms',) readonly_fields = ('t_style_created', 't_style_updated',) + fieldsets = ( ('Основные данные о музыкальном стиле', { 'fields': ('s_style_name', 'j_style_synonyms',), @@ -542,6 +543,30 @@ class LabelAdmin(RequestInFormMixin, admin.ModelAdmin): 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', + ) + fieldsets = ( ('Основные данные о лейбле/издателе', { 'fields': ('s_label', 'j_label_metadata',), diff --git a/lpon_site/frontend/utils.py b/lpon_site/frontend/utils.py index e6fc8b4..fd931c2 100644 --- a/lpon_site/frontend/utils.py +++ b/lpon_site/frontend/utils.py @@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError from django.db.models import QuerySet from django.db.models.expressions import RawSQL from django.utils.html import mark_safe +from django.contrib import messages from lpon_site.settings import ( SLUG_MAX_LENGTH, KEY_SYNONYM, VALIDATE_KEY__MATCH_TYPE, VALIDATE_KEY__MODEL, VALIDATE_KEY__VALUE, @@ -1222,3 +1223,113 @@ def update_synonyms_in_metadata( # иначе Django может не сохранить изменения setattr(instance, metadata_field_name, metadata_dict) + +def generate_admin_save_message(request, obj, is_new, related_article, + obj_field_name='s_label', + article_title_field='s_article_title'): + """ + Генерирует и отправляет информативное сообщение об сохранении объекта в админке. + + Функция анализирует тип операции (создание/редактирование/переименование) и + отправляет соответствующее сообщение с ссылкой на связанную статью. + + Использует Django messages framework для отправки (success/warning в зависимости от ситуации). + Ссылка на статью открывается в новой вкладке для удобства админа. + + Параметры: + ----------- + request : HttpRequest + Объект HTTP-запроса для отправки сообщений через Django messages framework + + obj : django.db.models.Model + Сохраненный объект модели (уже сохранен в БД). + Из этого объекта автоматически извлекается verbose_name модели через obj._meta.verbose_name + Также используется для получения старого значения при редактировании. + + is_new : bool + True если создается новая запись, False если редактируется существующая. + Определяет тип сообщения (green success для нового или warning для переименования). + + related_article : django.db.models.Model или None + Связанная статья (если есть). Если None, ссылка не добавляется в сообщение. + Обычно это поле вида k_model_to_article из модели. + При редактировании может содержать информацию о типе и содержимом статьи. + + obj_field_name : str, опционально + Имя основного поля объекта для получения текущего значения. + По умолчанию 's_label' (для TbLabel). + Примеры: 's_artist' (для TbArtist), 's_style_name' (для TbMusicStyle) + + article_title_field : str, опционально + Имя поля статьи для получения названия статьи. + По умолчанию 's_article_title' (для TbArticle). + Обычно это поле одинаково у всех моделей статей. + + Отправляемые сообщения: + ----------------------- + Новая запись (is_new=True): + - GREEN SUCCESS: "Лейбл «NAME» создан успешно. Статья создана автоматически." + - С ссылкой: "Проверить/Отредактировать статью →" + + Переименование (is_new=False, имя изменилось): + - YELLOW WARNING: "Лейбл «OLD» переименован на «NEW». ПРОВЕРЬТЕ СВЯЗАННУЮ СТАТЬЮ." + - С ссылкой: "Проверить/Отредактировать статью →" + + Обновление (is_new=False, имя не изменилось): + - GREEN SUCCESS: "Лейбл «NAME» обновлен. Связанная статья: «ARTICLE_TITLE»." + - С ссылкой: "Проверить/Отредактировать статью →" + + Пример использования в admin.py (максимально чистый и поджарый код): + ----------------------------------------------------------------------- + class LabelAdmin(admin.ModelAdmin): + def save_model(self, request, obj, form, change): + # Основной механизм сохранения Django (создает статью если нужно) + super().save_model(request, obj, form, change) + + # Отправляем информативное сообщение админу + # verbose_name берется автоматически из obj._meta.verbose_name + # старое значение получается из БД при необходимости + 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', + ) + """ + # Получаем текущее значение основного поля + current_field_value = getattr(obj, obj_field_name, '') + + # Инициализируем для использования в сообщениях (если related_article есть, перезапишем) + article_title = '' + article_link = '' + article_info = '' + + # Формируем ссылку на редактирование статьи и собираем информацию о ней + if related_article: + article_title = getattr(related_article, article_title_field, 'Статья') + article_link = (f' Проверить/Отредактировать статью →') + + # Собираем информацию о статье для более подробного сообщения + article_info = (f' [{getattr(related_article, 'l_article_type', '???')}]' + f' ({getattr(related_article, 's_article_title_html', '???')})') + + # Генерируем сообщение в зависимости от типа операции + if is_new: + # СОЗДАНИЕ НОВОЙ ЗАПИСИ - показываем зеленый успех + msg = f'[OK] {obj._meta.verbose_name} «{current_field_value}» создан успешно.' + if related_article: + msg += (f' Связанная статья «{article_title}» создана автоматически. >>' + f' {article_info} {article_link}') + else: + msg += ' [ОЙ-ОЙ-ОЙ] СТАТЬЯ НЕ БЫЛА СОЗДАНА. ЭТО НЕ ДОЛЖНО БЫЛО СЛУЧИТЬСЯ!' + messages.success(request, mark_safe(msg)) + + else: + # РЕДАКТИРОВАНИЕ СУЩЕСТВУЮЩЕЙ ЗАПИСИ + # Название изменилось или нет - показываем информацию о текущем состоянии и связанной статье + msg = f'[OK] {obj._meta.verbose_name} «{current_field_value}» обновлен.' + if related_article: + msg += f' Связанная статья: «{article_title}» >> {article_info} {article_link}' + messages.success(request, mark_safe(msg))