feat(static): improve static files handling and permissions in Docker
- Add script to set correct permissions on static files after collectstatic - Introduce collectstatic command in entrypoint with permission fixing - Add WhiteNoise middleware for efficient static file serving without DB access - Configure WhiteNoise static files storage backend in settings - Set STATIC_ROOT path properly for Docker container environment - Add fallback static files serving in Django urls for production without nginx - Enhance inventory_detail.html scripts to log errors if JS files or components fail to load - Add whitenoise package to requirements for static file serving support
This commit is contained in:
@@ -138,6 +138,12 @@ run_migrations() {
|
|||||||
echo "Collecting static files..."
|
echo "Collecting static files..."
|
||||||
python manage.py collectstatic --noinput
|
python manage.py collectstatic --noinput
|
||||||
|
|
||||||
|
# Устанавливаем права ПОСЛЕ collectstatic
|
||||||
|
echo "Setting permissions on static files..."
|
||||||
|
STATIC_ROOT="/app/myproject/staticfiles"
|
||||||
|
find "$STATIC_ROOT" -type d -exec chmod 755 {} \; 2>/dev/null || true
|
||||||
|
find "$STATIC_ROOT" -type f -exec chmod 644 {} \; 2>/dev/null || true
|
||||||
|
|
||||||
echo "Ensuring public tenant exists..."
|
echo "Ensuring public tenant exists..."
|
||||||
python /app/docker/create_public_tenant.py
|
python /app/docker/create_public_tenant.py
|
||||||
}
|
}
|
||||||
@@ -225,6 +231,17 @@ case "$1" in
|
|||||||
run_migrations
|
run_migrations
|
||||||
create_superuser
|
create_superuser
|
||||||
;;
|
;;
|
||||||
|
collectstatic)
|
||||||
|
wait_for_postgres
|
||||||
|
setup_directories
|
||||||
|
echo "Collecting static files..."
|
||||||
|
python manage.py collectstatic --noinput
|
||||||
|
echo "Setting permissions on static files..."
|
||||||
|
STATIC_ROOT="/app/myproject/staticfiles"
|
||||||
|
find "$STATIC_ROOT" -type d -exec chmod 755 {} \; 2>/dev/null || true
|
||||||
|
find "$STATIC_ROOT" -type f -exec chmod 644 {} \; 2>/dev/null || true
|
||||||
|
echo "Static files collected and permissions set."
|
||||||
|
;;
|
||||||
shell)
|
shell)
|
||||||
exec python manage.py shell
|
exec python manage.py shell
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -284,12 +284,25 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Подключаем JavaScript -->
|
<!-- Подключаем JavaScript -->
|
||||||
<script src="{% static 'products/js/product-search-picker.js' %}"></script>
|
<script src="{% static 'products/js/product-search-picker.js' %}" onerror="console.error('Failed to load product-search-picker.js');"></script>
|
||||||
<script src="{% static 'inventory/js/inventory_detail.js' %}"></script>
|
<script src="{% static 'inventory/js/inventory_detail.js' %}" onerror="console.error('Failed to load inventory_detail.js');"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Проверка загрузки ProductSearchPicker
|
||||||
|
if (typeof ProductSearchPicker === 'undefined') {
|
||||||
|
console.error('ProductSearchPicker is not defined. Check if product-search-picker.js loaded correctly.');
|
||||||
|
console.error('Script URL: {% static "products/js/product-search-picker.js" %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Инициализация компонента поиска товаров
|
// Инициализация компонента поиска товаров
|
||||||
{% if inventory.status != 'completed' %}
|
{% if inventory.status != 'completed' %}
|
||||||
|
const pickerElement = document.querySelector('#inventory-product-picker');
|
||||||
|
if (!pickerElement) {
|
||||||
|
console.error('Picker container #inventory-product-picker not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const picker = ProductSearchPicker.init('#inventory-product-picker', {
|
const picker = ProductSearchPicker.init('#inventory-product-picker', {
|
||||||
onAddSelected: function(product, instance) {
|
onAddSelected: function(product, instance) {
|
||||||
if (product) {
|
if (product) {
|
||||||
@@ -298,6 +311,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!picker) {
|
||||||
|
console.error('Failed to initialize ProductSearchPicker');
|
||||||
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
// Инициализация обработчиков
|
// Инициализация обработчиков
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ SHOW_PUBLIC_IF_NO_TENANT_FOUND = True
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
'whitenoise.middleware.WhiteNoiseMiddleware', # Static files first (no DB access needed)
|
||||||
'django_tenants.middleware.main.TenantMainMiddleware', # ОБЯЗАТЕЛЬНО ПЕРВЫМ!
|
'django_tenants.middleware.main.TenantMainMiddleware', # ОБЯЗАТЕЛЬНО ПЕРВЫМ!
|
||||||
'myproject.admin_access_middleware.TenantAdminAccessMiddleware', # SECURITY: Ограничение доступа к админке
|
'myproject.admin_access_middleware.TenantAdminAccessMiddleware', # SECURITY: Ограничение доступа к админке
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
@@ -234,12 +235,23 @@ STATIC_URL = '/static/'
|
|||||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
STATICFILES_DIRS = [BASE_DIR / 'static']
|
||||||
|
|
||||||
# В production используем внешнюю директорию для nginx
|
# В production используем внешнюю директорию для nginx
|
||||||
if not DEBUG:
|
# В Docker контейнере BASE_DIR = /app, но структура проекта: /app/myproject/
|
||||||
# Внутри контейнера путь всегда /app/staticfiles (куда мы смонтировали volume)
|
# Поэтому STATIC_ROOT должен быть /app/myproject/staticfiles
|
||||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
if str(BASE_DIR) == '/app': # В Docker контейнере
|
||||||
else:
|
STATIC_ROOT = BASE_DIR / 'myproject' / 'staticfiles'
|
||||||
|
else: # Локальная разработка
|
||||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
# Whitenoise storage
|
||||||
|
STORAGES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "products.utils.storage.TenantAwareFileSystemStorage",
|
||||||
|
},
|
||||||
|
"staticfiles": {
|
||||||
|
"BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# MEDIA FILES (User uploads)
|
# MEDIA FILES (User uploads)
|
||||||
@@ -256,8 +268,7 @@ elif str(BASE_DIR) == '/app': # В Docker контейнере
|
|||||||
else: # Локальная разработка
|
else: # Локальная разработка
|
||||||
MEDIA_ROOT = BASE_DIR / 'media'
|
MEDIA_ROOT = BASE_DIR / 'media'
|
||||||
|
|
||||||
# Custom file storage for tenant-aware file organization
|
|
||||||
DEFAULT_FILE_STORAGE = 'products.utils.storage.TenantAwareFileSystemStorage'
|
|
||||||
|
|
||||||
# Время жизни временных файлов фото (TTL) до авто-удаления, в часах
|
# Время жизни временных файлов фото (TTL) до авто-удаления, в часах
|
||||||
TEMP_MEDIA_TTL_HOURS = 24
|
TEMP_MEDIA_TTL_HOURS = 24
|
||||||
|
|||||||
@@ -45,3 +45,9 @@ else:
|
|||||||
'document_root': settings.MEDIA_ROOT,
|
'document_root': settings.MEDIA_ROOT,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
# Fallback для статических файлов в production (если nginx не настроен или не может прочитать файлы)
|
||||||
|
urlpatterns += [
|
||||||
|
re_path(r'^static/(?P<path>.*)$', serve, {
|
||||||
|
'document_root': settings.STATIC_ROOT,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|||||||
@@ -36,3 +36,9 @@ else:
|
|||||||
'document_root': settings.MEDIA_ROOT,
|
'document_root': settings.MEDIA_ROOT,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
# Fallback для статических файлов в production (если nginx не настроен или не может прочитать файлы)
|
||||||
|
urlpatterns += [
|
||||||
|
re_path(r'^static/(?P<path>.*)$', serve, {
|
||||||
|
'document_root': settings.STATIC_ROOT,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|||||||
@@ -36,3 +36,4 @@ Unidecode==1.4.0
|
|||||||
vine==5.1.0
|
vine==5.1.0
|
||||||
wcwidth==0.2.14
|
wcwidth==0.2.14
|
||||||
gunicorn==21.2.0
|
gunicorn==21.2.0
|
||||||
|
whitenoise==6.6.0
|
||||||
|
|||||||
Reference in New Issue
Block a user