Remove Nginx-specific static and media serving, and serve media through Django using WhiteNoise and FileResponse.

This commit is contained in:
krek0 2026-04-24 22:26:03 +02:00
parent aa348d2b04
commit 92e1336f80
6 changed files with 46 additions and 23 deletions

View file

@ -17,9 +17,6 @@ run and to maintain.
```bash ```bash
sudo apt install git gettext python3-django python3-django-allauth python3-django-crispy-forms python3-docutils python3-exifread python3-pil sudo apt install git gettext python3-django python3-django-allauth python3-django-crispy-forms python3-docutils python3-exifread python3-pil
# Only for production
sudo apt install nginx uwsgi uwsgi-plugin-python3 python3-certbot-nginx
``` ```
2. **Cloning.** 2. **Cloning.**
@ -33,19 +30,8 @@ run and to maintain.
3. **Configuration (production only).** 3. **Configuration (production only).**
```bash ```bash
# Only for production
sudo mkdir static media sudo mkdir static media
sudo cp docs/maintenance.html static/maintenance.html
sudo chown www-data:www-data -R static media
sudo chmod g+rwx -R static media
sudo chmod +x maintenance_tool.sh sudo chmod +x maintenance_tool.sh
sudo cp docs/uwsgi_photos.ini /etc/uwsgi/apps-available/uwsgi_photos.ini
sudo ln -s /etc/uwsgi/apps-available/uwsgi_photos.ini /etc/uwsgi/apps-enabled/
sudo cp docs/nginx_photos_maintenance /etc/nginx/sites-available/photos.crans.org
sudo ln -s /etc/nginx/sites-available/photos.crans.org /etc/nginx/sites-enabled/
sudo cp docs/letsencrypt_photos.crans.org /etc/letsencrypt/conf.d/photos.crans.org
sudo cp docs/renewal-hooks_post_nginx /etc/letsencrypt/renewal-hooks/post/nginx
sudo certbot --config /etc/letsencrypt/conf.d/photos.crans.org.ini certonly
``` ```
4. **Database (production only).** 4. **Database (production only).**
@ -82,7 +68,6 @@ run and to maintain.
6. *Enjoy \o/* 6. *Enjoy \o/*
In production, the NGINX site should now work.
In development, you can launch the development server using: In development, you can launch the development server using:
```bash ```bash

View file

@ -46,6 +46,9 @@ ADMINS = [tuple(a.split(":")) for a in config("ADMINS", default="", cast=Csv())
SESSION_COOKIE_SECURE = not DEBUG SESSION_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_SECURE = not DEBUG CSRF_COOKIE_SECURE = not DEBUG
# Trust Caddy's forwarded proto header
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Remember HTTPS for 1 year # Remember HTTPS for 1 year
SECURE_HSTS_SECONDS = 31536000 SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_INCLUDE_SUBDOMAINS = True
@ -77,6 +80,7 @@ if DEBUG:
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
@ -189,6 +193,18 @@ STATIC_ROOT = os.path.join(BASE_DIR, "static/")
MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/" MEDIA_URL = "/media/"
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "photo21.storage.CompressedManifestStorage",
},
}
WHITENOISE_MANIFEST_STRICT = False
LOCALE_PATHS = [os.path.join(BASE_DIR, "photo21/locale")] LOCALE_PATHS = [os.path.join(BASE_DIR, "photo21/locale")]
FIXTURE_DIRS = [os.path.join(BASE_DIR, "photo21/fixtures")] FIXTURE_DIRS = [os.path.join(BASE_DIR, "photo21/fixtures")]

12
photo21/storage.py Normal file
View file

@ -0,0 +1,12 @@
from whitenoise.storage import CompressedManifestStaticFilesStorage
class CompressedManifestStorage(CompressedManifestStaticFilesStorage):
"""Like CompressedManifestStaticFilesStorage but silently skips missing
referenced files (e.g. source maps not included in the package)."""
def hashed_name(self, name, content=None, filename=None):
try:
return super().hashed_name(name, content, filename)
except ValueError:
return name

View file

@ -2,21 +2,29 @@
# Copyright (C) 2021-2022 Amicale des élèves de l'ENS Paris-Saclay # Copyright (C) 2021-2022 Amicale des élèves de l'ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import os
from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse from django.http import FileResponse, Http404
from django.views.generic import ListView, View from django.views.generic import ListView, View
from photologue.models import Gallery from photologue.models import Gallery
class MediaAccess(View):
class MediaAccess(LoginRequiredMixin, View):
def get(self, request, path): def get(self, request, path):
response = HttpResponse() if not request.user.is_authenticated and not request.session.get('public_gallery_access'):
# Content-type will be detected by nginx from django.contrib.auth.views import redirect_to_login
del response["Content-Type"] return redirect_to_login(request.get_full_path())
response["X-Accel-Redirect"] = "/protected/media/" + path media_root = os.path.realpath(settings.MEDIA_ROOT)
response["Cache-Control"] = 'max-age=2678400' file_path = os.path.realpath(os.path.join(media_root, path))
if not file_path.startswith(media_root + os.sep):
raise Http404
if not os.path.isfile(file_path):
raise Http404
response = FileResponse(open(file_path, 'rb'))
response['Cache-Control'] = 'max-age=2678400'
return response return response

View file

@ -237,6 +237,7 @@ class GalleryPublicView(DetailView):
if request.user.is_authenticated: if request.user.is_authenticated:
gallery = self.get_object() gallery = self.get_object()
return redirect("photologue:pl-gallery", slug=gallery.slug) return redirect("photologue:pl-gallery", slug=gallery.slug)
request.session['public_gallery_access'] = True
request.guest_mode = True request.guest_mode = True
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View file

@ -6,3 +6,4 @@ ExifRead>=2.1.2
Pillow>=6.0.0 Pillow>=6.0.0
django-debug-toolbar>=3.2.0 django-debug-toolbar>=3.2.0
python-decouple>=3.6 python-decouple>=3.6
whitenoise>=6.0