From 2d253584ba143366288e0ff81636446355211e90 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Fri, 12 Dec 2025 00:18:09 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20ValidationError=20=D0=B2=20AJAX=20API=20=D0=B8=20Boots?= =?UTF-8?q?trap=20alert=20=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D0=B5=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проблема: На странице списка заказов (order_list) при изменении статуса 'на лету': - ValidationError показывался через alert() - страшно - Сообщение содержало служебные элементы - Статус всё равно менялся визуально (нет отката) Решение Backend (views.py): - В set_order_status добавлена обработка ValidationError ПЕРЕД ValueError - Извлекается чистое сообщение (e.messages[0] или str(e)) - Возвращается JSON: {success: false, error: 'чистое сообщение'} Решение Frontend (order_list.html): - Добавлен контейнер для динамических Bootstrap alert - Создана функция showAlert() для показа красивых alert-danger - При ошибке: * Показывается Bootstrap alert с иконкой * Прокрутка к верху страницы * Автоскрытие через 5 секунд * Возврат select к предыдущему значению (откат визуально) - Больше НЕТ страшных alert() Теперь пользователь видит: [красный Bootstrap alert вверху страницы] ⚠️ Заказ 134 был отменён, товары проданы в другом заказе. Невозможно изменить статус. Для новой продажи создайте новый заказ. [X] User-friendly на обеих страницах (форма редактирования + список)! --- .../orders/templates/orders/order_list.html | 40 ++++++++++++++++++- myproject/orders/views.py | 8 ++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/myproject/orders/templates/orders/order_list.html b/myproject/orders/templates/orders/order_list.html index 3fc71f7..2d32d46 100644 --- a/myproject/orders/templates/orders/order_list.html +++ b/myproject/orders/templates/orders/order_list.html @@ -8,6 +8,9 @@ {% endblock %} {% block content %} + +
+
@@ -242,6 +245,28 @@ (function() { const csrfToken = '{{ csrf_token }}'; + // Функция для показа Bootstrap alert + function showAlert(message, type = 'danger') { + const container = document.getElementById('js-alert-container'); + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${type} alert-dismissible fade show`; + alertDiv.setAttribute('role', 'alert'); + alertDiv.innerHTML = ` + ${message} + + `; + container.appendChild(alertDiv); + + // Авто-скрытие через 5 секунд + setTimeout(() => { + alertDiv.classList.remove('show'); + setTimeout(() => alertDiv.remove(), 150); + }, 5000); + + // Прокрутка к верху страницы + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + async function updateStatus(orderNumber, statusId) { const body = new URLSearchParams({ status_id: statusId }).toString(); const resp = await fetch(`/orders/api/${orderNumber}/set-status/`, { @@ -278,6 +303,7 @@ select.addEventListener('change', async function() { const statusId = select.value || ''; const selectedOption = select.options[select.selectedIndex]; + const originalIndex = select.selectedIndex; select.disabled = true; try { @@ -294,10 +320,20 @@ select.style.display = 'none'; badge.style.display = 'inline-block'; } else { - alert(result.error || 'Не удалось обновить статус'); + // Показываем красивый Bootstrap alert + showAlert(result.error || 'Не удалось обновить статус', 'danger'); + // Возвращаем предыдущее значение + select.selectedIndex = originalIndex; + // Прячем select, показываем badge + select.style.display = 'none'; + badge.style.display = 'inline-block'; } } catch (e) { - alert('Ошибка сервера при обновлении статуса'); + showAlert('Ошибка сервера при обновлении статуса', 'danger'); + // Возвращаем предыдущее значение + select.selectedIndex = originalIndex; + select.style.display = 'none'; + badge.style.display = 'inline-block'; } finally { select.disabled = false; } diff --git a/myproject/orders/views.py b/myproject/orders/views.py index 6c19e1d..50a346f 100644 --- a/myproject/orders/views.py +++ b/myproject/orders/views.py @@ -684,6 +684,14 @@ def set_order_status(request, order_number): 'color': status.color } }) + except ValidationError as e: + # Ошибка валидации (например, запрет смены статуса для возвращённого заказа) + # Транзакция откатилась, статус НЕ изменился + # Извлекаем чистое сообщение без служебных элементов + error_message = str(e.message) if hasattr(e, 'message') else str(e) + if hasattr(e, 'messages'): + error_message = e.messages[0] if e.messages else str(e) + return JsonResponse({'success': False, 'error': error_message}, status=400) except ValueError as e: # Ошибка в сигналах (например, не удалось создать Sale) # Транзакция откатилась, статус НЕ изменился