🐛(backend) serve the static files of the admin without whitenoise

We previously removed whitenoise because its middleware is not
compatible is not compatible with the ASGI application. But without it,
static file for the admin are not served anymore. This commit allow to
serve them using the static view from django. This view should not be
used in production but we are only using it for the admin, which is not
used a lot and a settings is also added to disable it if you want to
serve static from a dedicated nginx for example.
This commit is contained in:
Manuel Raynaud
2026-09-04 15:08:40 +02:00
parent 1debd291d8
commit 03bd7f1170
5 changed files with 80 additions and 0 deletions
+1
View File
@@ -14,6 +14,7 @@ and this project adheres to
- 🐛(frontend) hide Leave in the doc menu when not logged in #2626
- 🐛(backend) allow to configure settings DATA_UPLOAD_MAX_MEMORY_SIZE
- 🐛(backend) serve the static files of the admin without whitenoise
## [v5.6.0] - 2026-09-03
+1
View File
@@ -132,6 +132,7 @@ These are the environment variables you can set for the `impress-backend` contai
| SEARCH_INDEXER_SECRET | Token required for indexation queries | |
| INDEXING_URL | Find application endpoint for indexation | |
| SENTRY_DSN | Sentry host | |
| SERVE_STATIC_FILES | Serve the files collected in STATIC_ROOT from Django. Disable it when a web server or a CDN serves /static/ instead | True |
| SESSION_COOKIE_AGE | duration of the cookie session | 60*60*12 |
| SIGNUP_NEW_USER_TO_MARKETING_EMAIL | Register new user to the marketing onboarding. If True, see env LASUITE_MARKETING_* system | False |
| SILK_ENABLED | Enable the django-silk request/SQL/cProfile profiler and its /silk/ UI. OFF by default; never enable against production with real users. See documentation/profiling.md | False |
@@ -0,0 +1,55 @@
"""Test the serving of the files collected in STATIC_ROOT."""
import pytest
from core.tests.utils.urls import reload_urls
pytestmark = pytest.mark.django_db
@pytest.fixture(autouse=True)
def static_root(settings, tmp_path):
"""Point STATIC_ROOT to a directory holding a single collected file."""
static_file = tmp_path / "admin" / "css" / "login.css"
static_file.parent.mkdir(parents=True)
static_file.write_text("body {}", encoding="utf-8")
settings.STATIC_ROOT = str(tmp_path)
yield tmp_path
# The URLs are built from the settings, so restore the settings and reload
# them to leave the URLs as the next tests expect them.
settings.finalize()
reload_urls()
def test_urls_static_served(client, settings):
"""Collected static files are served, the admin has no assets otherwise."""
settings.SERVE_STATIC_FILES = True
reload_urls()
response = client.get("/static/admin/css/login.css")
assert response.status_code == 200
assert b"".join(response.streaming_content) == b"body {}"
def test_urls_static_unknown_file(client, settings):
"""A file missing from STATIC_ROOT is a 404."""
settings.SERVE_STATIC_FILES = True
reload_urls()
response = client.get("/static/admin/css/unknown.css")
assert response.status_code == 404
def test_urls_static_disabled(client, settings):
"""Serving static files can be delegated to a web server or a CDN."""
settings.SERVE_STATIC_FILES = False
reload_urls()
response = client.get("/static/admin/css/login.css")
assert response.status_code == 404
+8
View File
@@ -146,6 +146,13 @@ class Base(Configuration):
# Static files (CSS, JavaScript, Images)
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(DATA_DIR, "static")
# Let Django itself serve the files collected in STATIC_ROOT. The admin is
# unusable without them and, since whitenoise was removed, nothing else
# serves STATIC_URL. Disable it when a web server or a CDN is placed in
# front of the application to serve them.
SERVE_STATIC_FILES = values.BooleanValue(
True, environ_name="SERVE_STATIC_FILES", environ_prefix=None
)
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(DATA_DIR, "media")
MEDIA_BASE_URL = values.Value(
@@ -1371,6 +1378,7 @@ class Test(Base):
# Static files are not used in the test environment
# Tests are raising warnings because the /data/static directory does not exist
STATIC_ROOT = None
SERVE_STATIC_FILES = False
CELERY_TASK_ALWAYS_EAGER = values.BooleanValue(True)
+15
View File
@@ -1,10 +1,13 @@
"""URL configuration for the impress project"""
import re
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import include, path, re_path
from django.views.static import serve as serve_static
from drf_spectacular.views import (
SpectacularJSONAPIView,
@@ -29,6 +32,18 @@ if settings.DEBUG:
+ staticfiles_urlpatterns()
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
)
elif settings.SERVE_STATIC_FILES:
# Whitenoise used to serve the files collected in STATIC_ROOT. It was removed,
# so serve them from Django instead, otherwise the admin has no assets at all.
# Django's `static()` helper only builds this route when DEBUG is on, hence the
# explicit pattern.
urlpatterns += [
re_path(
rf"^{re.escape(settings.STATIC_URL.lstrip('/'))}(?P<path>.*)$",
serve_static,
{"document_root": settings.STATIC_ROOT},
),
]
if settings.USE_SWAGGER or settings.DEBUG: