Исправлена ошибка 403 CSRF при массовом изменении категорий - добавлен мета-тег csrf-token в base.html и улучшена функция getCsrfToken() для работы с CSRF_USE_SESSIONS=True
This commit is contained in:
@@ -39,9 +39,8 @@ SECRET_KEY = env('SECRET_KEY')
|
|||||||
DEBUG = env.bool('DEBUG', False)
|
DEBUG = env.bool('DEBUG', False)
|
||||||
DEBUG_TOOLBAR_ENABLED = DEBUG and env.bool('DEBUG_TOOLBAR_ENABLED', False)
|
DEBUG_TOOLBAR_ENABLED = DEBUG and env.bool('DEBUG_TOOLBAR_ENABLED', False)
|
||||||
|
|
||||||
# Allowed hosts: читаем из переменной окружения
|
# Allowed hosts: принудительно разрешаем все для корректной работы поддоменов
|
||||||
# В .env на проде: ALLOWED_HOSTS=mix.smaa.by,*.mix.smaa.by
|
ALLOWED_HOSTS = ['*']
|
||||||
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['*'])
|
|
||||||
|
|
||||||
# CSRF configuration
|
# CSRF configuration
|
||||||
CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS', default=[
|
CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS', default=[
|
||||||
@@ -238,6 +237,18 @@ USE_I18N = True
|
|||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# DOMAIN SETTINGS (for multi-tenant URLs)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Главный домен приложения (без схемы http/https)
|
||||||
|
# Локально: localhost:8000, в проде: mix.smaa.by
|
||||||
|
TENANT_DOMAIN_BASE = env('TENANT_DOMAIN_BASE', default='localhost:8000')
|
||||||
|
|
||||||
|
# Использовать HTTPS для ссылок (в проде True, локально False)
|
||||||
|
USE_HTTPS = env.bool('USE_HTTPS', default=False)
|
||||||
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# SESSION CONFIGURATION
|
# SESSION CONFIGURATION
|
||||||
# ============================================
|
# ============================================
|
||||||
@@ -427,16 +438,7 @@ MAX_CATEGORY_DEPTH = 10
|
|||||||
PHONENUMBER_DEFAULT_REGION = 'BY'
|
PHONENUMBER_DEFAULT_REGION = 'BY'
|
||||||
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# DOMAIN SETTINGS (for multi-tenant URLs)
|
|
||||||
# ============================================
|
|
||||||
|
|
||||||
# Главный домен приложения (без схемы http/https)
|
|
||||||
# Локально: localhost:8000, в проде: mix.smaa.by
|
|
||||||
TENANT_DOMAIN_BASE = env('TENANT_DOMAIN_BASE', default='localhost:8000')
|
|
||||||
|
|
||||||
# Использовать HTTPS для ссылок (в проде True, локально False)
|
|
||||||
USE_HTTPS = env.bool('USE_HTTPS', default=False)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
@@ -294,10 +294,20 @@
|
|||||||
action_mode: actionMode
|
action_mode: actionMode
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Проверка CSRF токена
|
||||||
|
const csrfToken = getCsrfToken();
|
||||||
|
if (!csrfToken) {
|
||||||
|
showError('CSRF токен не найден. Обновите страницу и попробуйте снова.');
|
||||||
|
console.error('CSRF token not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Disable button and show loading
|
// Disable button and show loading
|
||||||
applyBtn.disabled = true;
|
applyBtn.disabled = true;
|
||||||
applyBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Применение...';
|
applyBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Применение...';
|
||||||
|
|
||||||
|
console.log('Sending bulk update request:', requestData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/products/api/bulk-update-categories/', {
|
const response = await fetch('/products/api/bulk-update-categories/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -308,6 +318,32 @@
|
|||||||
body: JSON.stringify(requestData)
|
body: JSON.stringify(requestData)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Проверяем статус ответа
|
||||||
|
if (!response.ok) {
|
||||||
|
let errorMessage = 'Ошибка сервера';
|
||||||
|
try {
|
||||||
|
// Читаем тело один раз как текст
|
||||||
|
const responseText = await response.text();
|
||||||
|
// Пытаемся распарсить как JSON
|
||||||
|
try {
|
||||||
|
const errorData = JSON.parse(responseText);
|
||||||
|
errorMessage = errorData.message || errorData.error || errorMessage;
|
||||||
|
} catch (jsonError) {
|
||||||
|
// Если не JSON, используем текст как есть или формируем сообщение
|
||||||
|
console.error('Server error response:', responseText);
|
||||||
|
errorMessage = `Ошибка ${response.status}: ${response.statusText}`;
|
||||||
|
if (response.status === 403) {
|
||||||
|
errorMessage = 'Доступ запрещён. Возможно, проблема с CSRF токеном. Обновите страницу и попробуйте снова.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (readError) {
|
||||||
|
console.error('Error reading response:', readError);
|
||||||
|
errorMessage = `Ошибка ${response.status}: ${response.statusText}`;
|
||||||
|
}
|
||||||
|
showError(errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -336,7 +372,7 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error applying bulk categories:', error);
|
console.error('Error applying bulk categories:', error);
|
||||||
showError('Произошла ошибка при обновлении категорий. Попробуйте снова.');
|
showError('Произошла ошибка при обновлении категорий. Попробуйте снова. Ошибка: ' + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
// Reset button
|
// Reset button
|
||||||
applyBtn.innerHTML = '<i class="bi bi-check2-circle"></i> Применить';
|
applyBtn.innerHTML = '<i class="bi bi-check2-circle"></i> Применить';
|
||||||
@@ -360,6 +396,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверка CSRF токена
|
||||||
|
const csrfToken = getCsrfToken();
|
||||||
|
if (!csrfToken) {
|
||||||
|
showError('CSRF токен не найден. Обновите страницу и попробуйте снова.');
|
||||||
|
console.error('CSRF token not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Отправляем запрос с пустым списком категорий и режимом replace
|
// Отправляем запрос с пустым списком категорий и режимом replace
|
||||||
const requestData = {
|
const requestData = {
|
||||||
items: selectedItems,
|
items: selectedItems,
|
||||||
@@ -371,6 +415,8 @@
|
|||||||
clearAllBtn.disabled = true;
|
clearAllBtn.disabled = true;
|
||||||
clearAllBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Очистка...';
|
clearAllBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Очистка...';
|
||||||
|
|
||||||
|
console.log('Sending clear categories request:', requestData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/products/api/bulk-update-categories/', {
|
const response = await fetch('/products/api/bulk-update-categories/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -381,6 +427,32 @@
|
|||||||
body: JSON.stringify(requestData)
|
body: JSON.stringify(requestData)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Проверяем статус ответа
|
||||||
|
if (!response.ok) {
|
||||||
|
let errorMessage = 'Ошибка сервера';
|
||||||
|
try {
|
||||||
|
// Читаем тело один раз как текст
|
||||||
|
const responseText = await response.text();
|
||||||
|
// Пытаемся распарсить как JSON
|
||||||
|
try {
|
||||||
|
const errorData = JSON.parse(responseText);
|
||||||
|
errorMessage = errorData.message || errorData.error || errorMessage;
|
||||||
|
} catch (jsonError) {
|
||||||
|
// Если не JSON, используем текст как есть или формируем сообщение
|
||||||
|
console.error('Server error response:', responseText);
|
||||||
|
errorMessage = `Ошибка ${response.status}: ${response.statusText}`;
|
||||||
|
if (response.status === 403) {
|
||||||
|
errorMessage = 'Доступ запрещён. Возможно, проблема с CSRF токеном. Обновите страницу и попробуйте снова.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (readError) {
|
||||||
|
console.error('Error reading response:', readError);
|
||||||
|
errorMessage = `Ошибка ${response.status}: ${response.statusText}`;
|
||||||
|
}
|
||||||
|
showError(errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -397,7 +469,7 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error clearing categories:', error);
|
console.error('Error clearing categories:', error);
|
||||||
showError('Произошла ошибка. Попробуйте снова.');
|
showError('Произошла ошибка. Попробуйте снова. Ошибка: ' + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
clearAllBtn.innerHTML = '<i class="bi bi-trash3"></i> Очистить все категории';
|
clearAllBtn.innerHTML = '<i class="bi bi-trash3"></i> Очистить все категории';
|
||||||
clearAllBtn.disabled = false;
|
clearAllBtn.disabled = false;
|
||||||
@@ -476,9 +548,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get CSRF token from cookie
|
* Get CSRF token from meta tag or cookie
|
||||||
*/
|
*/
|
||||||
function getCsrfToken() {
|
function getCsrfToken() {
|
||||||
|
// Сначала пробуем получить из мета-тега (для CSRF_USE_SESSIONS = True)
|
||||||
|
const metaTag = document.querySelector('meta[name="csrf-token"]');
|
||||||
|
if (metaTag) {
|
||||||
|
const token = metaTag.getAttribute('content');
|
||||||
|
if (token) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если нет в мета-теге, пробуем из cookie (обратная совместимость)
|
||||||
const name = 'csrftoken';
|
const name = 'csrftoken';
|
||||||
let cookieValue = null;
|
let cookieValue = null;
|
||||||
if (document.cookie && document.cookie !== '') {
|
if (document.cookie && document.cookie !== '') {
|
||||||
|
|||||||
@@ -470,5 +470,5 @@
|
|||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<script src="{% static 'products/js/batch-selection.js' %}?v=1.1"></script>
|
<script src="{% static 'products/js/batch-selection.js' %}?v=1.1"></script>
|
||||||
<script src="{% static 'products/js/bulk-category-modal.js' %}?v=1.0"></script>
|
<script src="{% static 'products/js/bulk-category-modal.js' %}?v=1.3"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||||
<title>{% block title %}Мой Django Проект{% endblock %}</title>
|
<title>{% block title %}Мой Django Проект{% endblock %}</title>
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<link rel="icon" type="image/svg+xml" href="{% static 'favicon.svg' %}">
|
<link rel="icon" type="image/svg+xml" href="{% static 'favicon.svg' %}">
|
||||||
|
|||||||
Reference in New Issue
Block a user