mod: модели (09) работа с синонимами вынесена в хелпер внутри utils.py. Подключен в модели TbLabel в метод save() .
This commit is contained in:
@@ -239,10 +239,9 @@
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from filer.fields.image import FilerImageField
|
from filer.fields.image import FilerImageField
|
||||||
from filer.fields.file import FilerFileField
|
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
|
from lpon_site.settings import KEY_SYNONYM
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
@@ -771,47 +770,13 @@ class TbLabel(models.Model):
|
|||||||
# ===== ВАЛИДАЦИЯ НА ДУБЛИКАТЫ =====
|
# ===== ВАЛИДАЦИЯ НА ДУБЛИКАТЫ =====
|
||||||
# Проверяем ДО работы с синонимами и метаданными!
|
# Проверяем ДО работы с синонимами и метаданными!
|
||||||
# Страховка: защита от прямого вызова save() минуя админку или (в будущем) парсер
|
# Страховка: защита от прямого вызова save() минуя админку или (в будущем) парсер
|
||||||
validate_and_raise_for_duplicates(instance=self, main_field_name='s_label',
|
validate_and_raise_for_duplicates(self, 's_label', 'j_label_metadata')
|
||||||
metadata_field_name='j_label_metadata')
|
|
||||||
|
|
||||||
# Определяем новый ли это лейбл или обновление существующего
|
# ===== УПРАВЛЕНИЕ СИНОНИМАМИ =====
|
||||||
is_new = self.pk is None
|
# Обновляем список синонимов в метаданных (универсальный хелпер для всех моделей)
|
||||||
|
update_synonyms_in_metadata(self, 's_label', 'j_label_metadata')
|
||||||
# Получаем старое значение 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]))
|
|
||||||
|
|
||||||
|
# ===== СОЗДАНИЕ СВЯЗАННОЙ СТАТЬИ =====
|
||||||
# Если статья не привязана (но может быть пустой из-за blank=True)
|
# Если статья не привязана (но может быть пустой из-за blank=True)
|
||||||
if not self.k_label_to_article:
|
if not self.k_label_to_article:
|
||||||
# Генерируем техническое название для статьи (для админа)
|
# Генерируем техническое название для статьи (для админа)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from html import unescape
|
|||||||
from etpgrf.config import HANGING_PUNCTUATION_SPACE_CHARS as SPACE_CHARS
|
from etpgrf.config import HANGING_PUNCTUATION_SPACE_CHARS as SPACE_CHARS
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from lpon_site.settings import (
|
from lpon_site.settings import (
|
||||||
SLUG_MAX_LENGTH,
|
SLUG_MAX_LENGTH, KEY_SYNONYM,
|
||||||
VALIDATE_KEY__MATCH_TYPE, VALIDATE_KEY__MODEL, VALIDATE_KEY__VALUE,
|
VALIDATE_KEY__MATCH_TYPE, VALIDATE_KEY__MODEL, VALIDATE_KEY__VALUE,
|
||||||
ValidateMatchType
|
ValidateMatchType
|
||||||
)
|
)
|
||||||
@@ -441,3 +441,94 @@ def validate_and_raise_for_duplicates(
|
|||||||
f"{duplicates_result.get(VALIDATE_KEY__MATCH_TYPE)}"
|
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]))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user