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))