From a30d7f54831ec7848f6250f0352233204c2ce1bc Mon Sep 17 00:00:00 2001 From: erjemin Date: Sat, 20 Jun 2026 21:03:21 +0300 Subject: [PATCH] =?UTF-8?q?mod:=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20(?= =?UTF-8?q?09)=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81=20=D1=81?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=BD=D0=B8=D0=BC=D0=B0=D0=BC=D0=B8=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D0=B0=20=D0=B2=20=D1=85?= =?UTF-8?q?=D0=B5=D0=BB=D0=BF=D0=B5=D1=80=20=D0=B2=D0=BD=D1=83=D1=82=D1=80?= =?UTF-8?q?=D0=B8=20utils.py.=20=D0=9F=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B2=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20?= =?UTF-8?q?TbLabel=20=D0=B2=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20save()=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lpon_site/frontend/models.py | 47 +++--------------- lpon_site/frontend/utils.py | 93 +++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 42 deletions(-) diff --git a/lpon_site/frontend/models.py b/lpon_site/frontend/models.py index 76c1060..00866a0 100644 --- a/lpon_site/frontend/models.py +++ b/lpon_site/frontend/models.py @@ -239,10 +239,9 @@ from django.db import models from django.db.models import F -from django.core.exceptions import ValidationError from filer.fields.image import FilerImageField from filer.fields.file import FilerFileField -from frontend.utils import make_slug, validate_and_raise_for_duplicates +from frontend.utils import make_slug, validate_and_raise_for_duplicates, update_synonyms_in_metadata from lpon_site.settings import KEY_SYNONYM import datetime import logging @@ -771,47 +770,13 @@ class TbLabel(models.Model): # ===== ВАЛИДАЦИЯ НА ДУБЛИКАТЫ ===== # Проверяем ДО работы с синонимами и метаданными! # Страховка: защита от прямого вызова save() минуя админку или (в будущем) парсер - validate_and_raise_for_duplicates(instance=self, main_field_name='s_label', - metadata_field_name='j_label_metadata') + validate_and_raise_for_duplicates(self, 's_label', 'j_label_metadata') - # Определяем новый ли это лейбл или обновление существующего - is_new = self.pk is None - - # Получаем старое значение s_label из БД (для случая, если это редактирование, а не создание нового лейбла) - old_s_label = None - if not is_new: - try: - old_instance = TbLabel.objects.get(pk=self.pk) - old_s_label = old_instance.s_label - except TbLabel.DoesNotExist: - # На случай если что-то пошло не так, считаем это новым - is_new = True - - # Инициализируем j_label_metadata если оно пусто (хотя в моделях есть defaul-значение) - if not self.j_label_metadata: - self.j_label_metadata = {} - - # Убеждаемся, что ключ 'SYNONYM' существует и это список - if KEY_SYNONYM not in self.j_label_metadata or not isinstance(self.j_label_metadata[KEY_SYNONYM], list): - self.j_label_metadata[KEY_SYNONYM] = [] - - # Добавляем синонимы: текущий s_label и старый (если при редактировании он изменился) - # Это происходит при создании новой записи И при изменении s_label - if is_new or old_s_label != self.s_label: - # Если лейбл был обновлен и s_label изменился - добавляем старый вариант - if old_s_label and old_s_label not in self.j_label_metadata[KEY_SYNONYM]: - self.j_label_metadata[KEY_SYNONYM].append(old_s_label) - - # Добавляем текущий s_label если его еще нет в синонимах - if self.s_label not in self.j_label_metadata[KEY_SYNONYM]: - self.j_label_metadata[KEY_SYNONYM].append(self.s_label) - - # Очищаем дубликаты в списке синонимов, сохраняя порядок - # (может случиться если пользователь вручную редактировал метаданные) - # Используем dict.fromkeys() для сохранения порядка элементов - if KEY_SYNONYM in self.j_label_metadata and isinstance(self.j_label_metadata[KEY_SYNONYM], list): - self.j_label_metadata[KEY_SYNONYM] = list(dict.fromkeys(self.j_label_metadata[KEY_SYNONYM])) + # ===== УПРАВЛЕНИЕ СИНОНИМАМИ ===== + # Обновляем список синонимов в метаданных (универсальный хелпер для всех моделей) + update_synonyms_in_metadata(self, 's_label', 'j_label_metadata') + # ===== СОЗДАНИЕ СВЯЗАННОЙ СТАТЬИ ===== # Если статья не привязана (но может быть пустой из-за blank=True) if not self.k_label_to_article: # Генерируем техническое название для статьи (для админа) diff --git a/lpon_site/frontend/utils.py b/lpon_site/frontend/utils.py index 810a7f6..87ac51e 100644 --- a/lpon_site/frontend/utils.py +++ b/lpon_site/frontend/utils.py @@ -10,7 +10,7 @@ from html import unescape from etpgrf.config import HANGING_PUNCTUATION_SPACE_CHARS as SPACE_CHARS from django.core.exceptions import ValidationError from lpon_site.settings import ( - SLUG_MAX_LENGTH, + SLUG_MAX_LENGTH, KEY_SYNONYM, VALIDATE_KEY__MATCH_TYPE, VALIDATE_KEY__MODEL, VALIDATE_KEY__VALUE, ValidateMatchType ) @@ -441,3 +441,94 @@ def validate_and_raise_for_duplicates( f"{duplicates_result.get(VALIDATE_KEY__MATCH_TYPE)}" ) + +def update_synonyms_in_metadata( + instance, + main_field_name: str, + metadata_field_name: str, +) -> None: + """ + Обновляет список синонимов в метаданных экземпляра модели. + + Универсальный хелпер для управления синонимами во всех моделях (TbLabel, TbArtist, TbMusicStyle и т.д.) + + Логика: + - При создании новой записи: добавляет текущее значение поля в SYNONYM + - При редактировании: если значение поля изменилось, добавляет ОБА (старое и новое) в SYNONYM + - Очищает дубликаты в списке синонимов, сохраняя порядок + - Использует KEY_SYNONYM из settings как ключ в metadata словаре + + Args: + instance: Экземпляр модели (self из save методе). Обязателен! + main_field_name: Имя основного поля ('s_label', 's_artist', 's_style_name'). Обязателен! + metadata_field_name: Имя поля метаданных ('j_label_metadata', 'j_artist_metadata'). Обязателен! + + Пример использования в TbLabel.save(): + def save(self, *args, **kwargs): + validate_and_raise_for_duplicates(self, 's_label', 'j_label_metadata') + update_synonyms_in_metadata(self, 's_label', 'j_label_metadata') + # ... остальная логика save() + super().save(*args, **kwargs) + + Пример использования в TbArtist.save(): + def save(self, *args, **kwargs): + validate_and_raise_for_duplicates(self, 's_artist', 'j_artist_metadata') + update_synonyms_in_metadata(self, 's_artist', 'j_artist_metadata') + # ... остальная логика save() + super().save(*args, **kwargs) + """ + model_class = instance.__class__ + + # Проверяем, что указанные поля существуют в модели + for field_name in [main_field_name, metadata_field_name]: + if not hasattr(instance, field_name): + raise AttributeError( + f"{model_class.__name__} instance has no attribute '{field_name}'. " + f"Check that main_field_name and metadata_field_name are correct." + ) + + # ===== ОПРЕДЕЛЯЕМ, ЭТО СОЗДАНИЕ ИЛИ РЕДАКТИРОВАНИЕ ===== + # Получаем текущее значение основного поля + current_field_value = getattr(instance, main_field_name) + + # Определяем новая ли это запись или обновление + is_new = instance.pk is None + + # Получаем старое значение поля (для редактирования) + old_field_value = None + if not is_new: + try: + old_instance = model_class.objects.get(pk=instance.pk) + old_field_value = getattr(old_instance, main_field_name) + except model_class.DoesNotExist: + # На случай если что-то пошло не так, считаем это новым + is_new = True + + # ===== ИНИЦИАЛИЗИРУЕМ МЕТАДАННЫЕ ===== + # Инициализируем metadata если оно пусто + metadata_dict = getattr(instance, metadata_field_name) + if not metadata_dict: + metadata_dict = {} + setattr(instance, metadata_field_name, metadata_dict) + + # Убеждаемся, что ключ 'SYNONYM' существует и это список + if KEY_SYNONYM not in metadata_dict or not isinstance(metadata_dict[KEY_SYNONYM], list): + metadata_dict[KEY_SYNONYM] = [] + + # ===== ДОБАВЛЯЕМ СИНОНИМЫ ===== + # Добавляем синонимы при создании ИЛИ если значение поля изменилось + if is_new or old_field_value != current_field_value: + # Если поле было обновлено и значение изменилось - добавляем старое значение + if old_field_value and old_field_value not in metadata_dict[KEY_SYNONYM]: + metadata_dict[KEY_SYNONYM].append(old_field_value) + + # Добавляем текущее значение если его еще нет в синонимах + if current_field_value not in metadata_dict[KEY_SYNONYM]: + metadata_dict[KEY_SYNONYM].append(current_field_value) + + # ===== ОЧИЩАЕМ ДУБЛИКАТЫ ===== + # Удаляем дубликаты в списке синонимов, сохраняя порядок + # (может случиться если пользователь вручную редактировал метаданные) + if KEY_SYNONYM in metadata_dict and isinstance(metadata_dict[KEY_SYNONYM], list): + metadata_dict[KEY_SYNONYM] = list(dict.fromkeys(metadata_dict[KEY_SYNONYM])) +