diff --git a/lpon_site/frontend/utils.py b/lpon_site/frontend/utils.py
index 4ffa02d..e6fc8b4 100644
--- a/lpon_site/frontend/utils.py
+++ b/lpon_site/frontend/utils.py
@@ -550,27 +550,54 @@ def remove_conflicting_synonyms_from_duplicates(
duplicate_record.save(update_fields=[metadata_field_name])
-def build_duplicates_report(duplicates_queryset, search_words, metadata_field_name, main_field_name):
+def build_search_report(cleaned_data, main_field_name, metadata_dict,
+ duplicates_queryset, metadata_field_name):
"""
- Формирует красивый отчет о найденных совпадениях слов в дубликатах для админки.
+ Собирает поисковые слова из основного поля и синонимов текущей записи,
+ затем формирует красивый отчет о совпадениях этих слов в дубликатах.
- Для каждого поискового слова показывает в каких записях и в каких полях/синонимах оно найдено,
- с выделением найденного слова в оригинальном тексте.
+ Универсальная функция, используется для кейсов:
+ - IS_DUPLICATE: для поиска совпадений слов в других записях
+ - FIND_IN_SYNONYM: основное поле совпадает с синонимами других записей
+ - PARTIAL_MATCH: слова совпадают (частичное совпадение)
+ - PARTIAL_MATCH__RISK_SHORT_WORDS: частичное совпадение с короткими словами
Args:
+ cleaned_data: Очищенные данные формы
+ main_field_name: Имя основного поля ('s_label', 's_artist', 's_style_name')
+ metadata_dict: Словарь с метаданными текущей записи
duplicates_queryset: QuerySet записей, в которых нашли совпадения
- search_words: Set/List слов для поиска (нормализованные слова)
- metadata_field_name: Имя поля метаданных ('j_label_metadata' и т.д.)
- main_field_name: Имя основного поля ('s_label' и т.д.)
+ metadata_field_name: Имя поля метаданных ('j_label_metadata', 'j_artist_metadata')
Returns:
- str: HTML строка с отчетом о совпадениях, например:
- "beatles: встречается в #123 'The Beatles' → основное поле: 'The Beatles'
- atlantic: встречается в #456 'Atlantic Records' → основное поле: 'Atlantic Records'"
+ str: HTML строка с отчетом о совпадениях со ссылками и выделением слов
"""
+ # Инициализируем набор поисковых слов
+ search_words = set()
+
+ # Собираем слова из основного поля
+ # Применяем супер-нормализацию (удаление пунктуации, спецсимволов)
+ main_value_normalized = super_normalize_string(cleaned_data.get(main_field_name, ''))
+ # Разбиваем на слова по пробельным символам
+ search_words.update(re.split(r'\s+', main_value_normalized))
+
+ # Собираем слова из синонимов метаданных
+ metadata_synonyms = metadata_dict.get(KEY_SYNONYM) or []
+ for synonym in metadata_synonyms:
+ # Применяем супер-нормализацию к каждому синониму
+ syn_normalized = super_normalize_string(synonym)
+ # Добавляем все слова этого синонима в набор
+ search_words.update(re.split(r'\s+', syn_normalized))
+
+ # Удаляем пустые строки из набора слов
+ # (они могут появиться при разбиении на пробелы)
+ search_words.discard('')
+
+ # ===== ФОРМИРОВАНИЕ ОТЧЕТА О СОВПАДЕНИЯХ =====
+ # Собираем HTML отчет со списком найденных совпадений
report_lines = []
- # Для каждого поискового слова
+ # Для каждого поискового слова (в алфавитном порядке)
for search_word in sorted(search_words):
matches_for_word = []
@@ -583,18 +610,19 @@ def build_duplicates_report(duplicates_queryset, search_words, metadata_field_na
dup_metadata = getattr(dup, metadata_field_name) or {}
dup_synonyms = dup_metadata.get(KEY_SYNONYM) or []
- # Список всех значений для поиска (основное поле + синонимы)
- all_values = [main_field_value] + dup_synonyms
-
# Ищем слово в основном поле и синонимах (case-insensitive)
found_locations = []
+ found_synonyms = [] # Собираем все найденные синонимы отдельно
# Проверяем основное поле
if main_field_value:
- # Используем регулярное выражение для case-insensitive поиска и выделения
+ # Ищем подстроку (LIKE поиск как в SQL) - case-insensitive
+ # lambda функция выделяет найденное слово в ВЕРХНЕМ регистре для лучшей акцентуации
+ # m.group(0) содержит найденный текст, .upper() переводит его в верхний регистр
+ # Используем простой regex без \b границ для поиска подстрок (как LIKE в SQL)
highlighted_main = re.sub(
- rf'\b{re.escape(search_word)}\b',
- f'{search_word}',
+ re.escape(search_word),
+ lambda m: f'{m.group(0).upper()}', # Выделяем в верхнем регистре
main_field_value,
flags=re.IGNORECASE
)
@@ -604,15 +632,23 @@ def build_duplicates_report(duplicates_queryset, search_words, metadata_field_na
# Проверяем синонимы
for synonym in dup_synonyms:
if synonym:
- # Ищем слово в синониме (case-insensitive)
+ # Ищем подстроку в синониме (LIKE поиск как в SQL) - case-insensitive
+ # lambda функция выделяет найденное слово в ВЕРХНЕМ регистре для лучшей акцентуации
+ # Используем простой regex без \b границ для поиска подстрок (как LIKE в SQL)
highlighted_syn = re.sub(
- rf'\b{re.escape(search_word)}\b',
- f'{search_word}',
+ re.escape(search_word),
+ lambda m: f'{m.group(0).upper()}', # Выделяем в верхнем регистре
synonym,
flags=re.IGNORECASE
)
if highlighted_syn != synonym: # Совпадение найдено
- found_locations.append(f"синоним: "{highlighted_syn}"")
+ # Собираем синонимы в отдельный список вместо добавления по одному
+ found_synonyms.append(f""{highlighted_syn}"")
+
+ # Если найдены синонимы с совпадениями - добавляем их одной строкой
+ if found_synonyms:
+ synonyms_html = ", ".join(found_synonyms)
+ found_locations.append(f"синонимы: {synonyms_html}")
# Если слово найдено в этой записи - добавляем в отчет
if found_locations:
@@ -625,12 +661,20 @@ def build_duplicates_report(duplicates_queryset, search_words, metadata_field_na
# Если слово найдено хотя бы в одной записи - добавляем строку в отчет
if matches_for_word:
- matches_html = "
".join(matches_for_word)
- report_lines.append(f"{search_word}: встречается в
{matches_html}")
+ # Формируем вложенный список для каждого найденного дубликата
+ matches_items = "".join(
+ f"