diff --git a/lpon_site/frontend/admin.py b/lpon_site/frontend/admin.py
index 783363a..0a77f37 100644
--- a/lpon_site/frontend/admin.py
+++ b/lpon_site/frontend/admin.py
@@ -12,6 +12,40 @@ from .models import (
)
from .utils import validate_entity_for_admin_form
+
+# ============================================================================
+# МИКСИНЫ ДЛЯ АДМИНКИ
+# ============================================================================
+
+class RequestInFormMixin(admin.ModelAdmin):
+ """
+ Миксин для передачи request объекта в форму.
+
+ Используется когда форма нуждается в доступе к request для проверки POST параметров
+ или другой информации о текущем HTTP-запросе.
+
+ Переопределяет get_form() и передает request в __init__ формы через kwargs.
+ """
+
+ def get_form(self, request, obj=None, **kwargs):
+ """
+ Переопределяем get_form чтобы передать request в форму.
+ Создаем оборачивающий класс который передаст request в __init__.
+ """
+ FormClass = super().get_form(request, obj, **kwargs)
+
+ # Сохраняем request в замыкании для доступа в классе
+ request_ref = request
+
+ class FormWithRequest(FormClass):
+ """Оборачивающий класс который передает request при инстанцировании"""
+ def __init__(form_instance, *args, **init_kwargs):
+ # Добавляем request в kwargs перед вызовом __init__ родителя
+ init_kwargs['request'] = request_ref
+ super().__init__(*args, **init_kwargs)
+
+ return FormWithRequest
+
# ============================================================================
# АДМИНИСТРИРОВАНИЕ TbImage
#
@@ -485,19 +519,23 @@ class LabelAdminForm(forms.ModelForm):
return cleaned_data
-# Админ для лейбла (Label)
-class LabelAdmin(admin.ModelAdmin):
- """Админ для лейблов"""
+# Админ для лейбла (Label) через миксин
+class LabelAdmin(RequestInFormMixin, admin.ModelAdmin):
+ """Админ для лейблов с поддержкой передачи request в форму"""
form = LabelAdminForm # Используем кастомную форму с виджетами CodeMirror
# Подключаем JS через Media (правильный способ!)
class Media:
css = {
- 'all': ('codemirror/codemirror-styles.css',) # Стили для CodeMirror
+ 'all': (
+ 'codemirror/codemirror-styles.css', # Стили для CodeMirror
+ 'css/validation-override.css', # Стили для обхода валидации
+ )
}
js = (
'codemirror/editor.js', # Основной CodeMirror
'codemirror/codemirror-patch.js', # Патч для управления высотой/шириной
+ 'js/form-field-watcher.js', # Вотчер для отслеживания изменений полей формы
)
list_display = ('id', 's_label', 't_label_created')
@@ -526,26 +564,6 @@ class LabelAdmin(admin.ModelAdmin):
}),
)
- def get_form(self, request, obj=None, **kwargs):
- """
- Переопределяем get_form чтобы передать request в форму.
- Создаем оборачивающий класс который передаст request в __init__.
- """
- FormClass = super().get_form(request, obj, **kwargs)
-
- # Сохраняем request в замыкании для доступа в классе
- request_ref = request
-
- class FormWithRequest(FormClass):
- """Оборачивающий класс который передает request при инстанцировании"""
- def __init__(form_instance, *args, **init_kwargs):
- # Добавляем request в kwargs перед вызовом __init__ родителя
- init_kwargs['request'] = request_ref
- super().__init__(*args, **init_kwargs)
-
- return FormWithRequest
-
-
# ================
diff --git a/lpon_site/frontend/models.py b/lpon_site/frontend/models.py
index 00866a0..86a9846 100644
--- a/lpon_site/frontend/models.py
+++ b/lpon_site/frontend/models.py
@@ -774,7 +774,10 @@ 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 ea77572..c6153c8 100644
--- a/lpon_site/frontend/utils.py
+++ b/lpon_site/frontend/utils.py
@@ -334,7 +334,16 @@ def validate_entity_for_admin_form(form_instance, cleaned_data,
# ПЕРЕД ВАЛИДАЦИЕЙ: проверяем, нажата ли 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: ПРОПУСКАЕМ ВАЛИДАЦИЮ")
return
# Получаем класс модели из метаинформации формы
@@ -402,47 +411,26 @@ def validate_entity_for_admin_form(form_instance, cleaned_data,
dup_list = ", ".join(dup_links)
# Кнопка подтверждения создания несмотря на синонимы
- # При клике меняет value всех submit-кнопок на 'ignore_validate' и отправляет форму
- # Если пользователь потом меняет данные - вотчер вернет оригинальные значения
+ # При клике добавляет класс force-ignore-validation ко всем submit-кнопкам
+ # Это активирует режим игнорирования валидации
+ # Затем пользователь должен нажать стандартную кнопку сохранения
confirmation_button = '''
-
-
-
- Форма будет переотправлена без проверки синонимов
-
-
-
+ "
+ style="padding: 10px 15px; background: #e74c3c; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
+ Я уверен, создать несмотря на синонимы
+
+
+ Теперь нажмите кнопку сохранения чтобы создать лейбл
+
+
'''
raise ValidationError(
diff --git a/public/static/css/validation-override.css b/public/static/css/validation-override.css
new file mode 100644
index 0000000..f3b9c44
--- /dev/null
+++ b/public/static/css/validation-override.css
@@ -0,0 +1,22 @@
+/**
+ * Стили для кнопок при обходе валидации
+ * Отображает пользователю что валидация была обойдена
+ */
+
+/* Кнопки в режиме обхода валидации (при появлении ошибки синонимов) */
+input[type=submit].force-ignore-validation {
+ background-color: #f39c12 !important; /* Оранжевый/жёлтый цвет */
+ color: #fff;
+ border: 2px solid #e67e22;
+}
+
+input[type=submit].force-ignore-validation:hover {
+ background-color: #e67e22 !important;
+ cursor: pointer;
+}
+
+/* Скрываем красную кнопку подтверждения если она есть */
+.confirmation-button-container {
+ display: none;
+}
+
diff --git a/public/static/js/form-field-watcher.js b/public/static/js/form-field-watcher.js
new file mode 100644
index 0000000..6756aca
--- /dev/null
+++ b/public/static/js/form-field-watcher.js
@@ -0,0 +1,64 @@
+/**
+ * Вотчер для отслеживания изменений полей формы и сброса состояния submit-кнопок.
+ *
+ * Используется для валидации с поддержкой игнорирования:
+ * 1. Сохраняет оригинальные значения submit-кнопок при загрузке
+ * 2. Отслеживает изменения всех типов полей в форме:
+ * - Стандартные: input (все типы кроме submit), textarea, select
+ * - CodeMirror редакторы (div.codemirror)
+ * - Редактируемое содержимое (contenteditable элементы)
+ * 3. При изменении данных:
+ * - Восстанавливает оригинальные значения submit-кнопок
+ * - Скрывает сообщения об ошибках валидации (.errornote, .errorlist)
+ * 4. Это отменяет флаг 'ignore_validate' если пользователь редактирует данные
+ *
+ * Универсальное решение: работает для любых форм в админке, не только для лейблов.
+ * Селекторы легко расширяются для поддержки других типов полей.
+ */
+
+document.addEventListener('DOMContentLoaded', function() {
+ // Сохраняем оригинальные значения submit-кнопок администратора
+ let originalValues = {};
+ let submitButtons = document.querySelectorAll('input[type=submit]');
+
+ // Если нет submit-кнопок, выходим (не админская форма)
+ if (submitButtons.length === 0) {
+ return;
+ }
+
+ // Запоминаем оригинальные значения каждой submit-кнопки
+ submitButtons.forEach(function(btn) {
+ originalValues[btn.name] = btn.value;
+ });
+
+ // Отслеживаем изменения всех типов полей в форме
+ // Селекторы охватывают:
+ // - 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-кнопок
+ submitButtons.forEach(function(btn) {
+ btn.value = originalValues[btn.name];
+ // Удаляем класс force-ignore-validation при редактировании
+ btn.classList.remove('force-ignore-validation');
+ });
+
+ // Скрываем сообщения об ошибках валидации
+ let errorNotes = document.querySelectorAll('.errornote, .errorlist');
+ errorNotes.forEach(function(errorElement) {
+ errorElement.style.display = 'none';
+ });
+ }
+
+ formInputs.forEach(function(input) {
+ // Слушаем оба события: 'change' для обычных input/select
+ // и 'input' для CodeMirror и других редакторов
+ input.addEventListener('change', handleChange);
+ input.addEventListener('input', handleChange);
+ });
+});
+