mod: в функциях корректно установлены значения по умолчанию и добавлены внутренние валидаторы наличия полей в моделях.
This commit is contained in:
@@ -583,6 +583,7 @@ class LabelAdmin(RequestInFormMixin, admin.ModelAdmin):
|
|||||||
is_new=not change, # Django: change=False для новых, True для существующих
|
is_new=not change, # Django: change=False для новых, True для существующих
|
||||||
related_article=obj.k_label_to_article,
|
related_article=obj.k_label_to_article,
|
||||||
obj_field_name='s_label',
|
obj_field_name='s_label',
|
||||||
|
article_title_field='s_article_title',
|
||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
|||||||
@@ -161,7 +161,10 @@ def safe_html_special_symbols(s: str) -> str:
|
|||||||
return normalize_string(result)
|
return normalize_string(result)
|
||||||
|
|
||||||
|
|
||||||
def make_slug(slug_it: str, max_length: int | None = None, slug_default: str = "content") -> str:
|
def make_slug(
|
||||||
|
slug_it: str,
|
||||||
|
max_length: int = SLUG_MAX_LENGTH,
|
||||||
|
slug_default: str = "content") -> str:
|
||||||
"""Готовит чистый slug из HTML/Unicode текста (включая русский текст).
|
"""Готовит чистый slug из HTML/Unicode текста (включая русский текст).
|
||||||
|
|
||||||
Преобразует текст в URL-friendly slug:
|
Преобразует текст в URL-friendly slug:
|
||||||
@@ -188,8 +191,6 @@ def make_slug(slug_it: str, max_length: int | None = None, slug_default: str = "
|
|||||||
if not slug_it:
|
if not slug_it:
|
||||||
return f"{slug_default}-{random.randint(1, 4095):03x}"
|
return f"{slug_default}-{random.randint(1, 4095):03x}"
|
||||||
|
|
||||||
max_length = max_length or SLUG_MAX_LENGTH
|
|
||||||
|
|
||||||
# Вычисляем минимальную длину fallback'а: "slug_default-xyz"
|
# Вычисляем минимальную длину fallback'а: "slug_default-xyz"
|
||||||
min_fallback_length = len(slug_default) + 1 + 3 # "-" и 3 hex-символа
|
min_fallback_length = len(slug_default) + 1 + 3 # "-" и 3 hex-символа
|
||||||
|
|
||||||
|
|||||||
@@ -503,8 +503,8 @@ def build_search_report(
|
|||||||
def validate_entity_for_admin_form(
|
def validate_entity_for_admin_form(
|
||||||
form_instance: ModelForm,
|
form_instance: ModelForm,
|
||||||
cleaned_data: dict,
|
cleaned_data: dict,
|
||||||
main_field_name: str = 's_label',
|
main_field_name: str,
|
||||||
metadata_field_name: str = 'j_label_metadata',
|
metadata_field_name: str,
|
||||||
request: HttpRequest | None = None,
|
request: HttpRequest | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -516,13 +516,18 @@ def validate_entity_for_admin_form(
|
|||||||
Используется во всех админских forms: LabelAdminForm, ArtistAdminForm, MusicStyleAdminForm и т.д.
|
Используется во всех админских forms: LabelAdminForm, ArtistAdminForm, MusicStyleAdminForm и т.д.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
form_instance: Экземпляр формы (self из clean методе)
|
form_instance: Экземпляр формы (self из clean методе). Обязателен!
|
||||||
cleaned_data: Очищенные данные формы
|
cleaned_data: Очищенные данные формы. Обязателен!
|
||||||
main_field_name: Имя основного поля ('s_label', 's_artist', 's_style_name')
|
main_field_name: Имя основного поля модели. ОБЯЗАТЕЛЕН!
|
||||||
metadata_field_name: Имя поля метаданных ('j_label_metadata', 'j_artist_metadata')
|
Примеры: 's_label' (для TbLabel), 's_artist' (для TbArtist), 's_style_name' (для TbMusicStyle)
|
||||||
|
Если поле не существует в модели, будет выброшено AttributeError.
|
||||||
|
metadata_field_name: Имя поля метаданных модели. ОБЯЗАТЕЛЕН!
|
||||||
|
Примеры: 'j_label_metadata', 'j_artist_metadata', 'j_style_synonyms'
|
||||||
|
Если поле не существует в модели, будет выброшено AttributeError.
|
||||||
request: HTTP request объект (опционально, используется для проверки GET параметра ignore_validate)
|
request: HTTP request объект (опционально, используется для проверки GET параметра ignore_validate)
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
AttributeError: Если main_field_name или metadata_field_name не существуют в модели
|
||||||
ValidationError: Если найдены совпадения (дубликаты) и GET параметр не установлен
|
ValidationError: Если найдены совпадения (дубликаты) и GET параметр не установлен
|
||||||
|
|
||||||
Пример использования в LabelAdminForm:
|
Пример использования в LabelAdminForm:
|
||||||
@@ -531,8 +536,8 @@ def validate_entity_for_admin_form(
|
|||||||
validate_entity_for_admin_form(
|
validate_entity_for_admin_form(
|
||||||
self,
|
self,
|
||||||
cleaned_data,
|
cleaned_data,
|
||||||
main_field_name='s_label',
|
main_field_name='s_label', # ОБЯЗАТЕЛЕН!
|
||||||
metadata_field_name='j_label_metadata',
|
metadata_field_name='j_label_metadata', # ОБЯЗАТЕЛЕН!
|
||||||
request=self.request if hasattr(self, 'request') else None,
|
request=self.request if hasattr(self, 'request') else None,
|
||||||
)
|
)
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
@@ -541,6 +546,16 @@ def validate_entity_for_admin_form(
|
|||||||
# Получаем класс модели из метаинформации формы
|
# Получаем класс модели из метаинформации формы
|
||||||
model_class = form_instance.Meta.model # type: ignore
|
model_class = form_instance.Meta.model # type: ignore
|
||||||
|
|
||||||
|
# === ВАЛИДАЦИЯ ПАРАМЕТРОВ ===
|
||||||
|
|
||||||
|
# Проверяем, что поля существуют в модели
|
||||||
|
for field_name in [main_field_name, metadata_field_name]:
|
||||||
|
if not hasattr(model_class, field_name):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Model '{model_class.__name__}' has no field '{field_name}'. "
|
||||||
|
f"Please provide valid field names from the model."
|
||||||
|
)
|
||||||
|
|
||||||
# Получаем значения из формы
|
# Получаем значения из формы
|
||||||
main_field_value = cleaned_data.get(main_field_name)
|
main_field_value = cleaned_data.get(main_field_name)
|
||||||
metadata_dict = cleaned_data.get(metadata_field_name) or {}
|
metadata_dict = cleaned_data.get(metadata_field_name) or {}
|
||||||
@@ -956,60 +971,74 @@ def generate_admin_save_message(
|
|||||||
obj: Model,
|
obj: Model,
|
||||||
is_new: bool,
|
is_new: bool,
|
||||||
related_article: Model | None,
|
related_article: Model | None,
|
||||||
obj_field_name: str = 's_label',
|
obj_field_name: str,
|
||||||
article_title_field: str = 's_article_title',
|
article_title_field: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Генерирует и отправляет информативное сообщение об сохранении объекта в админке.
|
Генерирует и отправляет информативное сообщение об сохранении объекта в админке.
|
||||||
|
|
||||||
Функция анализирует тип операции (создание/редактирование/переименование) и
|
Функция анализирует тип операции (создание/редактирование) и статус связанной статьи,
|
||||||
отправляет соответствующее сообщение с ссылкой на связанную статью.
|
отправляя соответствующее сообщение (success или warning) с ссылкой на статью.
|
||||||
|
|
||||||
|
Использует Django messages framework для отправки сообщений:
|
||||||
|
- GREEN SUCCESS: при успешном создании/обновлении
|
||||||
|
- YELLOW WARNING: при создании без связанной статьи (ошибка!)
|
||||||
|
|
||||||
Использует Django messages framework для отправки (success/warning в зависимости от ситуации).
|
|
||||||
Ссылка на статью открывается в новой вкладке для удобства админа.
|
Ссылка на статью открывается в новой вкладке для удобства админа.
|
||||||
|
|
||||||
Параметры:
|
Параметры:
|
||||||
-----------
|
-----------
|
||||||
request : HttpRequest
|
request : HttpRequest
|
||||||
Объект HTTP-запроса для отправки сообщений через Django messages framework
|
Объект HTTP-запроса для отправки сообщений через Django messages framework.
|
||||||
|
Обязателен!
|
||||||
|
|
||||||
obj : django.db.models.Model
|
obj : django.db.models.Model
|
||||||
Сохраненный объект модели (уже сохранен в БД).
|
Сохраненный объект модели (уже сохранен в БД). Обязателен!
|
||||||
Из этого объекта автоматически извлекается verbose_name модели через obj._meta.verbose_name
|
Из этого объекта автоматически извлекается verbose_name модели через obj._meta.verbose_name
|
||||||
Также используется для получения старого значения при редактировании.
|
Также используется для получения старого значения при редактировании.
|
||||||
|
|
||||||
is_new : bool
|
is_new : bool
|
||||||
True если создается новая запись, False если редактируется существующая.
|
True если создается новая запись, False если редактируется существующая. Обязателен!
|
||||||
Определяет тип сообщения (green success для нового или warning для переименования).
|
При создании (is_new=True): отправляет success или warning в зависимости от наличия статьи.
|
||||||
|
При обновлении (is_new=False): отправляет success с информацией о текущем состоянии.
|
||||||
|
|
||||||
related_article : django.db.models.Model или None
|
related_article : django.db.models.Model или None
|
||||||
Связанная статья (если есть). Если None, ссылка не добавляется в сообщение.
|
Связанная статья для нового объекта или при редактировании.
|
||||||
|
При создании (is_new=True):
|
||||||
|
- Если present: отправляет GREEN SUCCESS и ссылку на статью
|
||||||
|
- Если None: отправляет YELLOW WARNING (это ошибка!)
|
||||||
|
При редактировании (is_new=False):
|
||||||
|
- Если present: отправляет SUCCESS со ссылкой на статью
|
||||||
|
- Если None: просто SUCCESS без ссылки
|
||||||
Обычно это поле вида k_model_to_article из модели.
|
Обычно это поле вида k_model_to_article из модели.
|
||||||
При редактировании может содержать информацию о типе и содержимом статьи.
|
|
||||||
|
|
||||||
obj_field_name : str, опционально
|
obj_field_name : str
|
||||||
Имя основного поля объекта для получения текущего значения.
|
Имя основного поля объекта для получения текущего значения. ОБЯЗАТЕЛЕН!
|
||||||
По умолчанию 's_label' (для TbLabel).
|
Примеры: 's_label' (для TbLabel), 's_artist' (для TbArtist), 's_style_name' (для TbMusicStyle)
|
||||||
Примеры: 's_artist' (для TbArtist), 's_style_name' (для TbMusicStyle)
|
Если поле не существует в модели, будет выброшено AttributeError.
|
||||||
|
|
||||||
article_title_field : str, опционально
|
article_title_field : str
|
||||||
Имя поля статьи для получения названия статьи.
|
Имя поля статьи для получения названия статьи. ОБЯЗАТЕЛЕН!
|
||||||
По умолчанию 's_article_title' (для TbArticle).
|
Обычно это 's_article_title' для всех моделей статей.
|
||||||
Обычно это поле одинаково у всех моделей статей.
|
Если поле не существует в связанной статье, будет выброшено AttributeError.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AttributeError: Если obj_field_name не существует в модели obj
|
||||||
|
AttributeError: Если article_title_field не существует в related_article (если он не None)
|
||||||
|
|
||||||
Отправляемые сообщения:
|
Отправляемые сообщения:
|
||||||
-----------------------
|
-----------------------
|
||||||
Новая запись (is_new=True):
|
Новая запись с статьей (is_new=True, related_article существует):
|
||||||
- GREEN SUCCESS: "Лейбл «NAME» создан успешно. Статья создана автоматически."
|
- GREEN SUCCESS: "Лейбл «NAME» создан успешно. Связанная статья «TITLE» создана автоматически."
|
||||||
- С ссылкой: "Проверить/Отредактировать статью →"
|
- С ссылкой: "Проверить/Отредактировать статью →"
|
||||||
|
|
||||||
Переименование (is_new=False, имя изменилось):
|
Новая запись БЕЗ статьи (is_new=True, related_article=None):
|
||||||
- YELLOW WARNING: "Лейбл «OLD» переименован на «NEW». ПРОВЕРЬТЕ СВЯЗАННУЮ СТАТЬЮ."
|
- YELLOW WARNING: "Лейбл «NAME» создан успешно. [ОЙ-ОЙ-ОЙ] СТАТЬЯ НЕ БЫЛА СОЗДАНА. ЭТО НЕ ДОЛЖНО БЫЛО СЛУЧИТЬСЯ!"
|
||||||
- С ссылкой: "Проверить/Отредактировать статью →"
|
- Сигнализирует об ошибке в процессе создания связанной статьи
|
||||||
|
|
||||||
Обновление (is_new=False, имя не изменилось):
|
Обновление (is_new=False):
|
||||||
- GREEN SUCCESS: "Лейбл «NAME» обновлен. Связанная статья: «ARTICLE_TITLE»."
|
- GREEN SUCCESS: "Лейбл «NAME» обновлен. Связанная статья: «ARTICLE_TITLE»."
|
||||||
- С ссылкой: "Проверить/Отредактировать статью →"
|
- С ссылкой: "Проверить/Отредактировать статью →" (если статья существует)
|
||||||
|
|
||||||
Пример использования в admin.py (максимально чистый и поджарый код):
|
Пример использования в admin.py (максимально чистый и поджарый код):
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
@@ -1018,19 +1047,34 @@ def generate_admin_save_message(
|
|||||||
# Основной механизм сохранения Django (создает статью если нужно)
|
# Основной механизм сохранения Django (создает статью если нужно)
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
# Отправляем информативное сообщение админу
|
# Отправляем информативное сообщение админу с явным указанием полей
|
||||||
# verbose_name берется автоматически из obj._meta.verbose_name
|
|
||||||
# старое значение получается из БД при необходимости
|
|
||||||
generate_admin_save_message(
|
generate_admin_save_message(
|
||||||
request=request,
|
request=request,
|
||||||
obj=obj,
|
obj=obj,
|
||||||
is_new=not change, # Django: change=False для новых, True для существующих
|
is_new=not change, # Django: change=False для новых, True для существующих
|
||||||
related_article=obj.k_label_to_article,
|
related_article=obj.k_label_to_article,
|
||||||
obj_field_name='s_label',
|
obj_field_name='s_label', # ОБЯЗАТЕЛЕН! Имя поля для основного названия
|
||||||
|
article_title_field='s_article_title', # ОБЯЗАТЕЛЕН! Имя поля для названия статьи
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
# === ВАЛИДАЦИЯ ПАРАМЕТРОВ ===
|
||||||
|
|
||||||
|
# Проверяем, что obj_field_name существует в объекте модели
|
||||||
|
if not hasattr(obj, obj_field_name):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Model '{obj.__class__.__name__}' has no field '{obj_field_name}'. "
|
||||||
|
f"Please provide a valid field name from the model."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Если есть связанная статья, проверяем что article_title_field в ней существует
|
||||||
|
if related_article and not hasattr(related_article, article_title_field):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Related article model '{related_article.__class__.__name__}' has no field '{article_title_field}'. "
|
||||||
|
f"Please provide a valid field name for the article title."
|
||||||
|
)
|
||||||
|
|
||||||
# Получаем текущее значение основного поля
|
# Получаем текущее значение основного поля
|
||||||
current_field_value = getattr(obj, obj_field_name, '')
|
current_field_value = getattr(obj, obj_field_name)
|
||||||
|
|
||||||
# Инициализируем для использования в сообщениях (если related_article есть, перезапишем)
|
# Инициализируем для использования в сообщениях (если related_article есть, перезапишем)
|
||||||
article_title = ''
|
article_title = ''
|
||||||
@@ -1039,7 +1083,7 @@ def generate_admin_save_message(
|
|||||||
|
|
||||||
# Формируем ссылку на редактирование статьи и собираем информацию о ней
|
# Формируем ссылку на редактирование статьи и собираем информацию о ней
|
||||||
if related_article:
|
if related_article:
|
||||||
article_title = getattr(related_article, article_title_field, 'Статья')
|
article_title = getattr(related_article, article_title_field)
|
||||||
article_link = (f' <a href=\'../../../tbarticle/{related_article.pk}/change/\''
|
article_link = (f' <a href=\'../../../tbarticle/{related_article.pk}/change/\''
|
||||||
f' target=\'_blank\'>Проверить/Отредактировать статью →</a>')
|
f' target=\'_blank\'>Проверить/Отредактировать статью →</a>')
|
||||||
|
|
||||||
@@ -1054,9 +1098,10 @@ def generate_admin_save_message(
|
|||||||
if related_article:
|
if related_article:
|
||||||
msg += (f' Связанная статья «{article_title}» создана автоматически. >>'
|
msg += (f' Связанная статья «{article_title}» создана автоматически. >>'
|
||||||
f' {article_info} {article_link}')
|
f' {article_info} {article_link}')
|
||||||
|
messages.success(request, mark_safe(msg))
|
||||||
else:
|
else:
|
||||||
msg += ' [ОЙ-ОЙ-ОЙ] СТАТЬЯ НЕ БЫЛА СОЗДАНА. ЭТО НЕ ДОЛЖНО БЫЛО СЛУЧИТЬСЯ!'
|
msg += ' [ОЙ-ОЙ-ОЙ] СТАТЬЯ НЕ БЫЛА СОЗДАНА. ЭТО НЕ ДОЛЖНО БЫЛО СЛУЧИТЬСЯ!'
|
||||||
messages.success(request, mark_safe(msg))
|
messages.warning(request, mark_safe(msg))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# РЕДАКТИРОВАНИЕ СУЩЕСТВУЮЩЕЙ ЗАПИСИ
|
# РЕДАКТИРОВАНИЕ СУЩЕСТВУЮЩЕЙ ЗАПИСИ
|
||||||
|
|||||||
Reference in New Issue
Block a user