Добавлена обработка ValidationError в AJAX API и Bootstrap alert на странице списка заказов
Проблема:
На странице списка заказов (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 на обеих страницах (форма редактирования + список)!
This commit is contained in:
@@ -8,6 +8,9 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<!-- Контейнер для динамических alert-сообщений -->
|
||||||
|
<div id="js-alert-container"></div>
|
||||||
|
|
||||||
<!-- Фильтры сверху -->
|
<!-- Фильтры сверху -->
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -242,6 +245,28 @@
|
|||||||
(function() {
|
(function() {
|
||||||
const csrfToken = '{{ csrf_token }}';
|
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 = `
|
||||||
|
<i class="bi bi-exclamation-triangle-fill"></i> ${message}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
`;
|
||||||
|
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) {
|
async function updateStatus(orderNumber, statusId) {
|
||||||
const body = new URLSearchParams({ status_id: statusId }).toString();
|
const body = new URLSearchParams({ status_id: statusId }).toString();
|
||||||
const resp = await fetch(`/orders/api/${orderNumber}/set-status/`, {
|
const resp = await fetch(`/orders/api/${orderNumber}/set-status/`, {
|
||||||
@@ -278,6 +303,7 @@
|
|||||||
select.addEventListener('change', async function() {
|
select.addEventListener('change', async function() {
|
||||||
const statusId = select.value || '';
|
const statusId = select.value || '';
|
||||||
const selectedOption = select.options[select.selectedIndex];
|
const selectedOption = select.options[select.selectedIndex];
|
||||||
|
const originalIndex = select.selectedIndex;
|
||||||
select.disabled = true;
|
select.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -294,10 +320,20 @@
|
|||||||
select.style.display = 'none';
|
select.style.display = 'none';
|
||||||
badge.style.display = 'inline-block';
|
badge.style.display = 'inline-block';
|
||||||
} else {
|
} 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) {
|
} catch (e) {
|
||||||
alert('Ошибка сервера при обновлении статуса');
|
showAlert('Ошибка сервера при обновлении статуса', 'danger');
|
||||||
|
// Возвращаем предыдущее значение
|
||||||
|
select.selectedIndex = originalIndex;
|
||||||
|
select.style.display = 'none';
|
||||||
|
badge.style.display = 'inline-block';
|
||||||
} finally {
|
} finally {
|
||||||
select.disabled = false;
|
select.disabled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -684,6 +684,14 @@ def set_order_status(request, order_number):
|
|||||||
'color': status.color
|
'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:
|
except ValueError as e:
|
||||||
# Ошибка в сигналах (например, не удалось создать Sale)
|
# Ошибка в сигналах (например, не удалось создать Sale)
|
||||||
# Транзакция откатилась, статус НЕ изменился
|
# Транзакция откатилась, статус НЕ изменился
|
||||||
|
|||||||
Reference in New Issue
Block a user