diff --git a/lpon_site/frontend/models.py b/lpon_site/frontend/models.py index 86a9846..00866a0 100644 --- a/lpon_site/frontend/models.py +++ b/lpon_site/frontend/models.py @@ -774,10 +774,7 @@ class TbLabel(models.Model): # ===== УПРАВЛЕНИЕ СИНОНИМАМИ ===== # Обновляем список синонимов в метаданных (универсальный хелпер для всех моделей) - print("DEBUG save: ДО update_synonyms_in_metadata") - print("DEBUG save: j_label_metadata ДО:", self.j_label_metadata) update_synonyms_in_metadata(self, 's_label', 'j_label_metadata') - print("DEBUG save: j_label_metadata ПОСЛЕ:", self.j_label_metadata) # ===== СОЗДАНИЕ СВЯЗАННОЙ СТАТЬИ ===== # Если статья не привязана (но может быть пустой из-за blank=True) diff --git a/lpon_site/frontend/utils.py b/lpon_site/frontend/utils.py index c6153c8..68e71a8 100644 --- a/lpon_site/frontend/utils.py +++ b/lpon_site/frontend/utils.py @@ -332,18 +332,9 @@ def validate_entity_for_admin_form(form_instance, cleaned_data, """ from django.utils.html import mark_safe - # ПЕРЕД ВАЛИДАЦИЕЙ: проверяем, нажата ли submit-кнопка с измененным value='ignore_validate' - # Если пользователь нажал нашу кнопку подтверждения, она меняет value админских кнопок на 'ignore_validate' - print("DEBUG validate: request.POST keys =", list(request.POST.keys()) if request else "NO REQUEST") - if request: - print("DEBUG validate: _save =", repr(request.POST.get('_save'))) - print("DEBUG validate: _addanother =", repr(request.POST.get('_addanother'))) - print("DEBUG validate: _continue =", repr(request.POST.get('_continue'))) - check = any(request.POST.get(btn) == 'ignore_validate' for btn in ['_save', '_addanother', '_continue']) - print("DEBUG validate: check result =", check) - - if request and any(request.POST.get(btn) == 'ignore_validate' for btn in ['_save', '_addanother', '_continue']): - print("DEBUG validate: ПРОПУСКАЕМ ВАЛИДАЦИЮ") + # ПЕРЕД ВАЛИДАЦИЕЙ: проверяем GET параметр ignore_validate + # Если пользователь нажал красную кнопку, addGetParam добавит GET параметр к URL + if request and request.GET.get('ignore_validate') == '1': return # Получаем класс модели из метаинформации формы @@ -412,23 +403,22 @@ def validate_entity_for_admin_form(form_instance, cleaned_data, # Кнопка подтверждения создания несмотря на синонимы # При клике добавляет класс force-ignore-validation ко всем submit-кнопкам - # Это активирует режим игнорирования валидации - # Затем пользователь должен нажать стандартную кнопку сохранения + # Вотчер видит этот класс и добавляет onclick обработчик к кнопкам + # Onclick обработчик вызывает addGetParam() перед отправкой формы confirmation_button = '''
''' diff --git a/public/static/css/validation-override.css b/public/static/css/validation-override.css index f3b9c44..6d45308 100644 --- a/public/static/css/validation-override.css +++ b/public/static/css/validation-override.css @@ -3,7 +3,7 @@ * Отображает пользователю что валидация была обойдена */ -/* Кнопки в режиме обхода валидации (при появлении ошибки синонимов) */ +/* Кнопки в режиме обхода валидации (при появлении ошибки и предупреждений синонимов и схожести) */ input[type=submit].force-ignore-validation { background-color: #f39c12 !important; /* Оранжевый/жёлтый цвет */ color: #fff; diff --git a/public/static/js/form-field-watcher.js b/public/static/js/form-field-watcher.js index 6756aca..508c2e4 100644 --- a/public/static/js/form-field-watcher.js +++ b/public/static/js/form-field-watcher.js @@ -1,24 +1,38 @@ /** - * Вотчер для отслеживания изменений полей формы и сброса состояния submit-кнопок. + * Вотчер для отслеживания изменений полей формы и управления режимом обхода валидации. * * Используется для валидации с поддержкой игнорирования: - * 1. Сохраняет оригинальные значения submit-кнопок при загрузке + * 1. Добавляет функцию addGetParam для динамического добавления GET параметров к form action * 2. Отслеживает изменения всех типов полей в форме: * - Стандартные: input (все типы кроме submit), textarea, select * - CodeMirror редакторы (div.codemirror) * - Редактируемое содержимое (contenteditable элементы) * 3. При изменении данных: - * - Восстанавливает оригинальные значения submit-кнопок + * - Удаляет класс force-ignore-validation ко кнопок (возвращает нормальный цвет) + * - Удаляет GET параметр из action кнопок * - Скрывает сообщения об ошибках валидации (.errornote, .errorlist) - * 4. Это отменяет флаг 'ignore_validate' если пользователь редактирует данные + * 4. Визуально показывает пользователю статус обхода валидации цветом кнопок * * Универсальное решение: работает для любых форм в админке, не только для лейблов. - * Селекторы легко расширяются для поддержки других типов полей. */ +// Функция для добавления GET параметра к form action кнопки +function addGetParam(button, key, value) { + const form = button.form; // Находим форму, в которой лежит кнопка + const baseAction = form.getAttribute('action'); // Получаем текущий action + + // Удаляем старый параметр, если он есть + let cleanAction = baseAction.split('?')[0]; + + // Проверяем, есть ли уже в action другие GET-параметры + const separator = cleanAction === baseAction ? '?' : '&'; + + // Динамически прописываем измененный URL в formAction кнопки + button.formAction = baseAction + separator + key + '=' + value; +} + document.addEventListener('DOMContentLoaded', function() { - // Сохраняем оригинальные значения submit-кнопок администратора - let originalValues = {}; + // Находим все submit-кнопки администратора let submitButtons = document.querySelectorAll('input[type=submit]'); // Если нет submit-кнопок, выходим (не админская форма) @@ -26,26 +40,50 @@ document.addEventListener('DOMContentLoaded', function() { return; } - // Запоминаем оригинальные значения каждой submit-кнопки - submitButtons.forEach(function(btn) { - originalValues[btn.name] = btn.value; - }); + // Находим форму + let form = document.querySelector('form'); + if (!form) { + return; + } + + // Сохраняем оригинальный action формы + let originalAction = form.getAttribute('action'); + + // Функция для добавления onclick обработчика + function addOnclickHandler(btn) { + // Проверяем есть ли уже onclick + if (!btn.getAttribute('onclick')) { + btn.setAttribute('onclick', 'if (this.classList.contains("force-ignore-validation")) { ' + + 'const form = this.form; ' + + 'const baseAction = form.getAttribute("action") || ""; ' + + 'let cleanAction = baseAction.split("?")[0]; ' + + 'const separator = cleanAction === baseAction ? "?" : "&"; ' + + 'form.setAttribute("action", baseAction + separator + "ignore_validate=1"); ' + + '}'); + } + } + + // Функция для удаления onclick обработчика + function removeOnclickHandler(btn) { + btn.removeAttribute('onclick'); + } // Отслеживаем изменения всех типов полей в форме - // Селекторы охватывают: - // - text input (кроме submit), textarea, select, checkbox, radio и т.д. - // - CodeMirror редакторы (div.codemirror) - // - contenteditable элементы let formInputs = document.querySelectorAll('input:not([type=submit]), textarea, select, .codemirror, [contenteditable]'); // Функция которая срабатывает при любом изменении function handleChange() { - // При изменении любого поля восстанавливаем оригинальные значения submit-кнопок + // При изменении любого поля: + // 1. Удаляем класс force-ignore-validation (кнопки вернут нормальный цвет) + // 2. Удаляем onclick обработчик + // 3. Восстанавливаем оригинальный action формы submitButtons.forEach(function(btn) { - btn.value = originalValues[btn.name]; - // Удаляем класс force-ignore-validation при редактировании - btn.classList.remove('force-ignore-validation'); + if (btn.classList.contains('force-ignore-validation')) { + btn.classList.remove('force-ignore-validation'); + removeOnclickHandler(btn); + } }); + form.setAttribute('action', originalAction); // Скрываем сообщения об ошибках валидации let errorNotes = document.querySelectorAll('.errornote, .errorlist'); @@ -60,5 +98,30 @@ document.addEventListener('DOMContentLoaded', function() { input.addEventListener('change', handleChange); input.addEventListener('input', handleChange); }); + + // Мониторим изменение класса force-ignore-validation на кнопках + // Используем MutationObserver для отслеживания добавления/удаления класса + const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + let btn = mutation.target; + if (btn.tagName === 'INPUT' && btn.type === 'submit') { + // Если класс добавлен - навешиваем onclick + if (btn.classList.contains('force-ignore-validation')) { + addOnclickHandler(btn); + } + // Если класс удален - удаляем onclick + else if (btn.getAttribute('onclick')) { + removeOnclickHandler(btn); + } + } + } + }); + }); + + // Настраиваем observer для отслеживания изменения класса + submitButtons.forEach(function(btn) { + observer.observe(btn, { attributes: true, attributeFilter: ['class'] }); + }); });