mod: валидатор форм, парсера и моделей (07) избежания дублей в синонимах других записей
This commit is contained in:
@@ -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')
|
update_synonyms_in_metadata(self, 's_label', 'j_label_metadata')
|
||||||
print("DEBUG save: j_label_metadata ПОСЛЕ:", self.j_label_metadata)
|
|
||||||
|
|
||||||
# ===== СОЗДАНИЕ СВЯЗАННОЙ СТАТЬИ =====
|
# ===== СОЗДАНИЕ СВЯЗАННОЙ СТАТЬИ =====
|
||||||
# Если статья не привязана (но может быть пустой из-за blank=True)
|
# Если статья не привязана (но может быть пустой из-за blank=True)
|
||||||
|
|||||||
@@ -332,18 +332,9 @@ def validate_entity_for_admin_form(form_instance, cleaned_data,
|
|||||||
"""
|
"""
|
||||||
from django.utils.html import mark_safe
|
from django.utils.html import mark_safe
|
||||||
|
|
||||||
# ПЕРЕД ВАЛИДАЦИЕЙ: проверяем, нажата ли submit-кнопка с измененным value='ignore_validate'
|
# ПЕРЕД ВАЛИДАЦИЕЙ: проверяем GET параметр ignore_validate
|
||||||
# Если пользователь нажал нашу кнопку подтверждения, она меняет value админских кнопок на 'ignore_validate'
|
# Если пользователь нажал красную кнопку, addGetParam добавит GET параметр к URL
|
||||||
print("DEBUG validate: request.POST keys =", list(request.POST.keys()) if request else "NO REQUEST")
|
if request and request.GET.get('ignore_validate') == '1':
|
||||||
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: ПРОПУСКАЕМ ВАЛИДАЦИЮ")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Получаем класс модели из метаинформации формы
|
# Получаем класс модели из метаинформации формы
|
||||||
@@ -412,23 +403,22 @@ def validate_entity_for_admin_form(form_instance, cleaned_data,
|
|||||||
|
|
||||||
# Кнопка подтверждения создания несмотря на синонимы
|
# Кнопка подтверждения создания несмотря на синонимы
|
||||||
# При клике добавляет класс force-ignore-validation ко всем submit-кнопкам
|
# При клике добавляет класс force-ignore-validation ко всем submit-кнопкам
|
||||||
# Это активирует режим игнорирования валидации
|
# Вотчер видит этот класс и добавляет onclick обработчик к кнопкам
|
||||||
# Затем пользователь должен нажать стандартную кнопку сохранения
|
# Onclick обработчик вызывает addGetParam() перед отправкой формы
|
||||||
confirmation_button = '''
|
confirmation_button = '''
|
||||||
<div class="confirmation-button-container" style="display: block; margin-top: 15px;">
|
<div class="confirmation-button-container" style="display: block; margin-top: 15px;">
|
||||||
<br>
|
<br>
|
||||||
<button type="button"
|
<button type="button" onclick="
|
||||||
onclick="
|
// Добавляем класс force-ignore-validation ко всем submit-кнопкам
|
||||||
document.querySelectorAll('input[type=submit]').forEach(function(btn) {
|
document.querySelectorAll('input[type=submit]').forEach(function(btn) {
|
||||||
btn.value = 'ignore_validate';
|
|
||||||
btn.classList.add('force-ignore-validation');
|
btn.classList.add('force-ignore-validation');
|
||||||
});
|
});
|
||||||
"
|
"
|
||||||
style="padding: 10px 15px; background: #e74c3c; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
style="padding: 10px 15px; background: #e74c3c; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||||||
Я уверен, создать несмотря на синонимы
|
Я уверен! Создать несмотря на синонимы.
|
||||||
</button>
|
</button>
|
||||||
<em style="display: block; margin-top: 8px; color: #666; font-size: 12px;">
|
<em style="display: block; margin-top: 8px; color: #666; font-size: 12px;">
|
||||||
Теперь нажмите кнопку сохранения чтобы создать лейбл
|
Теперь нажмите стандартные кнопки сохранения снизу, чтобы сохранить.
|
||||||
</em>
|
</em>
|
||||||
</div>
|
</div>
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Отображает пользователю что валидация была обойдена
|
* Отображает пользователю что валидация была обойдена
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Кнопки в режиме обхода валидации (при появлении ошибки синонимов) */
|
/* Кнопки в режиме обхода валидации (при появлении ошибки и предупреждений синонимов и схожести) */
|
||||||
input[type=submit].force-ignore-validation {
|
input[type=submit].force-ignore-validation {
|
||||||
background-color: #f39c12 !important; /* Оранжевый/жёлтый цвет */
|
background-color: #f39c12 !important; /* Оранжевый/жёлтый цвет */
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|||||||
@@ -1,24 +1,38 @@
|
|||||||
/**
|
/**
|
||||||
* Вотчер для отслеживания изменений полей формы и сброса состояния submit-кнопок.
|
* Вотчер для отслеживания изменений полей формы и управления режимом обхода валидации.
|
||||||
*
|
*
|
||||||
* Используется для валидации с поддержкой игнорирования:
|
* Используется для валидации с поддержкой игнорирования:
|
||||||
* 1. Сохраняет оригинальные значения submit-кнопок при загрузке
|
* 1. Добавляет функцию addGetParam для динамического добавления GET параметров к form action
|
||||||
* 2. Отслеживает изменения всех типов полей в форме:
|
* 2. Отслеживает изменения всех типов полей в форме:
|
||||||
* - Стандартные: input (все типы кроме submit), textarea, select
|
* - Стандартные: input (все типы кроме submit), textarea, select
|
||||||
* - CodeMirror редакторы (div.codemirror)
|
* - CodeMirror редакторы (div.codemirror)
|
||||||
* - Редактируемое содержимое (contenteditable элементы)
|
* - Редактируемое содержимое (contenteditable элементы)
|
||||||
* 3. При изменении данных:
|
* 3. При изменении данных:
|
||||||
* - Восстанавливает оригинальные значения submit-кнопок
|
* - Удаляет класс force-ignore-validation ко кнопок (возвращает нормальный цвет)
|
||||||
|
* - Удаляет GET параметр из action кнопок
|
||||||
* - Скрывает сообщения об ошибках валидации (.errornote, .errorlist)
|
* - Скрывает сообщения об ошибках валидации (.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() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Сохраняем оригинальные значения submit-кнопок администратора
|
// Находим все submit-кнопки администратора
|
||||||
let originalValues = {};
|
|
||||||
let submitButtons = document.querySelectorAll('input[type=submit]');
|
let submitButtons = document.querySelectorAll('input[type=submit]');
|
||||||
|
|
||||||
// Если нет submit-кнопок, выходим (не админская форма)
|
// Если нет submit-кнопок, выходим (не админская форма)
|
||||||
@@ -26,26 +40,50 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запоминаем оригинальные значения каждой submit-кнопки
|
// Находим форму
|
||||||
submitButtons.forEach(function(btn) {
|
let form = document.querySelector('form');
|
||||||
originalValues[btn.name] = btn.value;
|
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]');
|
let formInputs = document.querySelectorAll('input:not([type=submit]), textarea, select, .codemirror, [contenteditable]');
|
||||||
|
|
||||||
// Функция которая срабатывает при любом изменении
|
// Функция которая срабатывает при любом изменении
|
||||||
function handleChange() {
|
function handleChange() {
|
||||||
// При изменении любого поля восстанавливаем оригинальные значения submit-кнопок
|
// При изменении любого поля:
|
||||||
|
// 1. Удаляем класс force-ignore-validation (кнопки вернут нормальный цвет)
|
||||||
|
// 2. Удаляем onclick обработчик
|
||||||
|
// 3. Восстанавливаем оригинальный action формы
|
||||||
submitButtons.forEach(function(btn) {
|
submitButtons.forEach(function(btn) {
|
||||||
btn.value = originalValues[btn.name];
|
if (btn.classList.contains('force-ignore-validation')) {
|
||||||
// Удаляем класс force-ignore-validation при редактировании
|
btn.classList.remove('force-ignore-validation');
|
||||||
btn.classList.remove('force-ignore-validation');
|
removeOnclickHandler(btn);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
form.setAttribute('action', originalAction);
|
||||||
|
|
||||||
// Скрываем сообщения об ошибках валидации
|
// Скрываем сообщения об ошибках валидации
|
||||||
let errorNotes = document.querySelectorAll('.errornote, .errorlist');
|
let errorNotes = document.querySelectorAll('.errornote, .errorlist');
|
||||||
@@ -60,5 +98,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
input.addEventListener('change', handleChange);
|
input.addEventListener('change', handleChange);
|
||||||
input.addEventListener('input', 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'] });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user