Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
be28b3d
Implement dynamic redirects based on site path in ingress
luskay595 Jun 10, 2025
42e02f6
Merge branch 'production' into fix/dynamic-index-redirect
luskay595 Jun 24, 2025
d52da56
Fix templates & views (partie 1)
luskay595 Jul 2, 2025
1998a31
correction pour uv
luskay595 Jul 3, 2025
0ae04e3
Merge pull request #5 from luskay595/test-tmp
luskay595 Jul 3, 2025
631861a
Set WhiteNoise disabled by default
luskay595 Jul 3, 2025
268f5f6
Merge pull request #6 from luskay595/codex/renommer-force_script_name…
luskay595 Jul 3, 2025
c592034
Use FORCE_SCRIPT_NAME with SITE_BASE_PATH env
luskay595 Jul 3, 2025
361e69f
Merge branch 'test-tmp' into codex/renommer-force_script_name-et-ajou…
luskay595 Jul 3, 2025
ef631a7
Merge pull request #7 from luskay595/codex/renommer-force_script_name…
luskay595 Jul 3, 2025
c96c2ac
correction lien page sitemap
luskay595 Jul 3, 2025
3bfcea0
Merge pull request #9 from luskay595/test-tmp
luskay595 Jul 3, 2025
9789032
Implement dynamic redirects based on site path in ingress
luskay595 Jun 10, 2025
05149d2
Fix templates & views (partie 1)
luskay595 Jul 2, 2025
81ebcfa
correction pour uv
luskay595 Jul 3, 2025
7102638
Use FORCE_SCRIPT_NAME with SITE_BASE_PATH env
luskay595 Jul 3, 2025
17b60b0
Set WhiteNoise disabled by default
luskay595 Jul 3, 2025
2cf81ad
correction lien page sitemap
luskay595 Jul 3, 2025
da2fcf7
Merge branch 'fix/dynamic-index-redirect' of github.com:luskay595/sit…
Ash-Crow Aug 26, 2025
6c9e0e5
Move the new parameters to a more sensible place in the .env.example …
Ash-Crow Aug 26, 2025
24a7e31
Move the new parameters to a more sensible place in the .env.example …
Ash-Crow Aug 26, 2025
485abcb
remove dependency to django-extensions
Ash-Crow Aug 27, 2025
1f5cda2
Remove duplicate entry
Ash-Crow Aug 27, 2025
27c77bc
Back to use FORCE_SCRIPT_NAME; various code improvements ; add way to…
Ash-Crow Aug 28, 2025
015b549
Fix missing load statement
Ash-Crow Aug 28, 2025
1c467dc
Further improvements to the settings
Ash-Crow Aug 28, 2025
0dbd58a
Fixes
Ash-Crow Aug 28, 2025
92c982b
Fix CSRF_TRUSTED_ORIGINS definition
Ash-Crow Aug 28, 2025
68e0e83
update doc
Ash-Crow Sep 2, 2025
ca4301f
Add tests and fix code according to test
Ash-Crow Sep 2, 2025
28270c1
Renamed USE_WHITENOISE to SF_USE_WHITENOISE for consistency
Ash-Crow Sep 2, 2025
dbc0bc6
Fix errors with the admin paths
Ash-Crow Sep 4, 2025
dab597f
Workflow doesn't run
Ash-Crow Sep 4, 2025
38bfa9c
Fix ruff issue
Ash-Crow Sep 4, 2025
b79a80a
Update tests params
Ash-Crow Sep 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ MEDIA_ROOT=medias

# USE_DOCKER: Set 1 to use Docker
USE_DOCKER=0
# Set 1 to use uv
# USE_UV: Set 1 to use uv
USE_UV=0
# SF_USE_WHITENOISE: Set 1 or True to use Whitenoise
SF_USE_WHITENOISE=0

DATABASE_NAME=djdb
DATABASE_USER=dju
Expand All @@ -30,6 +32,9 @@ S3_BUCKET_REGION=eu-west-3
# S3_LOCATION: If the S3 bucket is shared, add a unique folder name
S3_LOCATION=

# (Optional) FORCE_SCRIPT_NAME: used to allow to the site to be served under a sub-path
FORCE_SCRIPT_NAME=

# (Optional) ProConnect settings
PROCONNECT_ACTIVATED=False
PROCONNECT_DOMAIN=
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ DEBUG=False
DATABASE_URL=postgres://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
USE_UV=1
PROCONNECT_ACTIVATED=True
HOST_PROTO = "http"
4 changes: 1 addition & 3 deletions .github/workflows/ci-main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: 🔮 CI - Main checks

on: [push]
on: [push, pull_request]

jobs:
build:
Expand Down Expand Up @@ -53,5 +53,3 @@ jobs:
run: |
just init
uv run python manage.py create_demo_pages
env:
DJANGO_DEBUG: True
33 changes: 33 additions & 0 deletions ONBOARDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,36 @@ psql -c "CREATE USER sitesfaciles WITH CREATEDB PASSWORD 'votre_mot_de_passe';"
psql -c "CREATE DATABASE sitesfaciles OWNER sitesfaciles;" -U postgres
```


## Fonctionnement depuis un sous-répertoire

Lorsque la variable `FORCE_SCRIPT_NAME` est configurée, le site tourne dans un sous-répertoire, fonctionnalité qui n’est pas gérée par le serveur de développement de base de Django (`runserver`).

Pour tester le fonctionnement en local, il faut donc passer par [gunicorn](https://gunicorn.org/) et [nginx](https://nginx.org/). À cette fin :

* Installer nginx si ce n'est pas déjà fait : https://nginx.org/en/docs/install.html
* Après avoir configuré les variables d’environnement (cf. ci-dessus), lancer la commande suivante pour générer et mettre en place la configuration nginx :

```sh
just nginx-generate-config-file
```

* Lancer le serveur local via gunicorn avec la commande suivante (à la place de `just runserver` donc) :

```sh
just run_gunicorn
```

* Accéder au site via nginx en ajoutant 1 au port utilisé par gunicorn. Par exemple, si le `.env` contient les valeurs suivantes :

```sh
DEBUG=False
HOST_PROTO=http
HOST_URL=sites-faciles.localhost
HOST_PORT=8000
FORCE_SCRIPT_NAME="/pages"
ALLOWED_HOSTS=localhost,0.0.0.0,127.0.0.1,.localhost
CSRF_TRUSTED_ORIGINS="http://127.0.0.1:18000,http://localhost:18000,http://*.localhost:18000"
```

* On peut alors accéder au site via http:/sites-faciles.localhost:18000/pages/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Sites Faciles est développé en utilisant le framework [Django](https://www.dja
En plus des applications déjà citées, le dépôt contient les répertoires suivants :
- **config** : le projet Django proprement dit
- **locale** : la traduction des templates de base et du JS global du site (cf. ci-dessous.) La localisation des apps listées plus haut se fait à l’intérieur de celles-ci.
- **scripts** : scripts shell utilisés par certaines commandes et fichiers de configurations associés.
- **static** : des fichiers statiques communs à l’ensemble du site (CSS global, JS global, quelques images intégrées par défaut) ainsi que la librairie tierce TarteaucitronJS (utilisée pour la gestion des cookies tiers)
- **templates** : les templates de base du site.

Expand Down
7 changes: 4 additions & 3 deletions blog/locale/fr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 15:35+0200\n"
"POT-Creation-Date: 2025-09-02 19:11+0200\n"
"PO-Revision-Date: 2025-08-21 15:41+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
Expand Down Expand Up @@ -109,7 +109,8 @@ msgstr "Filtrer par source"

#: blog/models.py:215
msgid "The source is the organization of the post author"
msgstr "La source est l’organisation à laquelle appartient l’auteur de l’article"
msgstr ""
"La source est l’organisation à laquelle appartient l’auteur de l’article"

#: blog/models.py:228
msgid "Show filters"
Expand All @@ -120,7 +121,7 @@ msgid "Blog index"
msgstr "Index de blog"

#: blog/models.py:262 blog/models.py:479 blog/models.py:493
#: blog/templates/blog/tags_list_page.html:18
#: blog/templates/blog/tags_list_page.html:31
msgid "Tags"
msgstr "Étiquettes"

Expand Down
2 changes: 1 addition & 1 deletion blog/templates/blog/blog_entry_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<meta property="og:title"
content="{% if page.seo_title %}{{ page.seo_title }}{% else %}{{ page.title }}{% endif %}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{ page.full_url }}" />
<meta property="og:url" content="{% canonical_url %}" />
<meta property="og:description" content="{{ page.search_description }}" />
<meta property="og:locale" content="{{ LANGUAGE_CODE }}" />

Expand Down
2 changes: 1 addition & 1 deletion blog/templates/blog/blog_index_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<meta property="og:title"
content="{% if page.seo_title %}{{ page.seo_title }}{% else %}{{ page.title }}{% endif %}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{ page.full_url }}" />
<meta property="og:url" content="{% canonical_url %}" />
<meta property="og:description" content="{{ page.search_description }}" />
<meta property="og:locale" content="{{ LANGUAGE_CODE }}" />
<link rel="alternate"
Expand Down
13 changes: 13 additions & 0 deletions blog/templates/blog/categories_list_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@
{% endblock description %}
{% endif %}

{% block social_media %}
<meta property="og:site_name"
content="{{ settings.content_manager.CmsDsfrConfig.site_title }}" />
<meta property="og:title" content="{{ title }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ request.build_absolute_uri }}" />
<meta property="og:description" content="{{ search_description }}" />
<meta property="og:locale" content="{{ LANGUAGE_CODE }}" />

<meta name="twitter:title" content="{{ title }}" />
<meta name="twitter:description" content="{{ search_description }}" />
{% endblock social_media %}

{% block content %}
<div class="fr-container fr-my-6w">
{% include "content_manager/blocks/breadcrumbs.html" %}
Expand Down
13 changes: 13 additions & 0 deletions blog/templates/blog/tags_list_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@
{% endblock description %}
{% endif %}

{% block social_media %}
<meta property="og:site_name"
content="{{ settings.content_manager.CmsDsfrConfig.site_title }}" />
<meta property="og:title" content="{{ title }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ request.build_absolute_uri }}" />
<meta property="og:description" content="{{ search_description }}" />
<meta property="og:locale" content="{{ LANGUAGE_CODE }}" />

<meta name="twitter:title" content="{{ title }}" />
<meta name="twitter:description" content="{{ search_description }}" />
{% endblock social_media %}

{% block content %}
<div class="fr-container fr-my-6w">
{% dsfr_breadcrumb breadcrumb %}
Expand Down
115 changes: 94 additions & 21 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,27 @@
SECRET_KEY = os.getenv("SECRET_KEY")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True if os.getenv("DEBUG") == "True" else False
DEBUG = True if os.getenv("DEBUG") in [1, "True"] else False

ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "127.0.0.1,.localhost").replace(" ", "").split(",")

HOST_PROTO = os.getenv("HOST_PROTO", "https")
HOST_URL = os.getenv("HOST_URL", "localhost")
HOST_PORT = os.getenv("HOST_PORT", "")

# Prefix of the application when served under a sub-path.
# ``FORCE_SCRIPT_NAME`` is the Django setting handling this behaviour:
# https://docs.djangoproject.com/en/5.2/ref/settings/#force-script-name
FORCE_SCRIPT_NAME = os.getenv("FORCE_SCRIPT_NAME", "").rstrip("/")

# Allow enabling WhiteNoise via an environment variable (disabled by default)
SF_USE_WHITENOISE = True if os.getenv("SF_USE_WHITENOISE", False) in [1, "True"] else False

INTERNAL_IPS = [
"127.0.0.1",
]

TESTING = "test" in sys.argv

# Application definition
# Applications definition

INSTALLED_APPS = [
# The order is important for overriding templates and using contexts, please change it carefully.
Expand Down Expand Up @@ -89,13 +96,19 @@
"wagtail.admin",
]

if SF_USE_WHITENOISE:
INSTALLED_APPS.insert(0, "whitenoise.runserver_nostatic")

# Only add these on a dev machine, outside of tests
TESTING = "test" in sys.argv
if not TESTING and DEBUG and "localhost" in HOST_URL:
INSTALLED_APPS += [
"wagtail.contrib.styleguide",
"debug_toolbar",
]

# Middleware definition

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
Expand All @@ -108,6 +121,9 @@
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
]

if SF_USE_WHITENOISE:
MIDDLEWARE.append("whitenoise.middleware.WhiteNoiseMiddleware")

# Only add this on a dev machine, outside of tests
if not TESTING and DEBUG and "localhost" in HOST_URL:
MIDDLEWARE += [
Expand All @@ -118,9 +134,14 @@
def show_toolbar(request):
request.META["wsgi.multithread"] = True
request.META["wsgi.multiprocess"] = True
excluded_urls = ["/pages/preview/", "/pages/preview_loading/", "/edit/preview/"]
excluded_urls = [
"/pages/preview/",
"/pages/preview_loading/",
"/edit/",
"/edit/preview/",
]
excluded = any(request.path.endswith(url) for url in excluded_urls)
return DEBUG and not excluded
return not excluded

DEBUG_TOOLBAR_CONFIG = {
"SHOW_TOOLBAR_CALLBACK": show_toolbar,
Expand Down Expand Up @@ -217,6 +238,20 @@ def show_toolbar(request):
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
}

if SF_USE_WHITENOISE:
if DEBUG:
STORAGES["staticfiles"] = {
"BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
}
# Allow WhiteNoise to load files directly from app directories without
# running ``collectstatic`` each time and reload them on changes.
WHITENOISE_USE_FINDERS = True
WHITENOISE_AUTOREFRESH = True
else:
STORAGES["staticfiles"] = {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
}

STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
Expand Down Expand Up @@ -246,12 +281,30 @@ def show_toolbar(request):
STORAGES["default"] = {
"BACKEND": "django.core.files.storage.FileSystemStorage",
}
MEDIA_URL = "medias/"
MEDIA_ROOT = os.path.join(BASE_DIR, os.getenv("MEDIA_ROOT", ""))
MEDIA_URL = os.getenv("MEDIA_URL", "medias/")

if FORCE_SCRIPT_NAME and not MEDIA_URL.startswith(FORCE_SCRIPT_NAME):
MEDIA_URL = f"{FORCE_SCRIPT_NAME}/{MEDIA_URL}"

STATIC_URL = "static/"
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")

STATIC_URL = os.getenv("STATIC_URL", "static/")

if FORCE_SCRIPT_NAME and not STATIC_URL.startswith(FORCE_SCRIPT_NAME):
STATIC_URL = f"{FORCE_SCRIPT_NAME}/{STATIC_URL}"


# Allow Django to serve statics even in production if needed
SF_PROD_SERVE_STATIC = True if os.getenv("SF_PROD_SERVE_STATIC", False) in [1, "True"] else False
if SF_PROD_SERVE_STATIC:
import mimetypes

mimetypes.add_type("application/javascript", ".js", True)
mimetypes.add_type("text/css", ".css", True)

WHITENOISE_STATIC_PREFIX = STATIC_URL

STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)

# Default primary key field type
Expand All @@ -266,17 +319,23 @@ def show_toolbar(request):

# Base URL to use when referring to full URLs within the Wagtail admin backend -
# e.g. in notification emails. Don't include '/admin' or a trailing slash
WAGTAILADMIN_BASE_URL = f"{HOST_PROTO}://{HOST_URL}"
WAGTAILADMIN_BASE_URL = os.getenv("WAGTAILADMIN_BASE_URL", "")

# If not set in env, we build it from mandatory variables
if not WAGTAILADMIN_BASE_URL:
WAGTAILADMIN_BASE_URL = f"{HOST_PROTO}://{HOST_URL}"

if HOST_PORT:
WAGTAILADMIN_BASE_URL = f"{WAGTAILADMIN_BASE_URL}:{HOST_PORT}"

HOST_PORT = os.getenv("HOST_PORT", "")
if HOST_PORT != "":
WAGTAILADMIN_BASE_URL = f"{WAGTAILADMIN_BASE_URL}:{HOST_PORT}"
WAGTAILAPI_BASE_URL = WAGTAILADMIN_BASE_URL

WAGTAILADMIN_PATH = os.getenv("WAGTAILADMIN_PATH", "cms-admin/")

WAGTAIL_FRONTEND_LOGIN_URL = LOGIN_URL = f"/{WAGTAILADMIN_PATH}login/"
LOGOUT_URL = f"/{WAGTAILADMIN_PATH}logout/"
LOGIN_URL = f"{FORCE_SCRIPT_NAME}/{WAGTAILADMIN_PATH}login/"
LOGOUT_URL = f"{FORCE_SCRIPT_NAME}/{WAGTAILADMIN_PATH}logout/"

WAGTAIL_FRONTEND_LOGIN_URL = LOGIN_URL

WAGTAIL_PASSWORD_REQUIRED_TEMPLATE = "content_manager/password_required.html"

Expand Down Expand Up @@ -319,7 +378,7 @@ def show_toolbar(request):
)

WAGTAILIMAGES_EXTENSIONS = ["gif", "jpg", "jpeg", "png", "webp", "svg"]
SF_SCHEME_DEPENDENT_SVGS = True if os.getenv("SF_SCHEME_DEPENDENT_SVGS", False) == "True" else False
SF_SCHEME_DEPENDENT_SVGS = True if os.getenv("SF_SCHEME_DEPENDENT_SVGS", False) in [1, "True"] else False

# Allows for complex Streamfields without completely removing checks
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10000
Expand All @@ -342,8 +401,8 @@ def show_toolbar(request):
WAGTAIL_PASSWORD_RESET_ENABLED = os.getenv("WAGTAIL_PASSWORD_RESET_ENABLED", False)

# (Optional) ProConnect settings
PROCONNECT_ACTIVATED = True if os.getenv("PROCONNECT_ACTIVATED", False) == "True" else False
OIDC_CREATE_USER = True if os.getenv("PROCONNECT_CREATE_USER", "True") == "True" else False
PROCONNECT_ACTIVATED = True if os.getenv("PROCONNECT_ACTIVATED", False) in [1, "True"] else False
OIDC_CREATE_USER = True if os.getenv("PROCONNECT_CREATE_USER", "True") in [1, "True"] else False
OIDC_RP_CLIENT_ID = os.getenv("PROCONNECT_CLIENT_ID", "")
OIDC_RP_CLIENT_SECRET = os.getenv("PROCONNECT_CLIENT_SECRET", "")
OIDC_RP_SCOPES = os.getenv("PROCONNECT_SCOPES", "openid given_name usual_name email siret uid")
Expand All @@ -362,8 +421,8 @@ def show_toolbar(request):
PROCONNECT_USER_CREATION_FILTER = os.getenv("PROCONNECT_USER_CREATION_FILTER", None)
LASUITE_DOMAINE_API_KEY = os.getenv("LASUITE_DOMAINE_API_KEY", None)

LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"
LOGIN_REDIRECT_URL = f"{FORCE_SCRIPT_NAME}/"
LOGOUT_REDIRECT_URL = f"{FORCE_SCRIPT_NAME}/"

if PROCONNECT_ACTIVATED:
INSTALLED_APPS += [
Expand All @@ -376,9 +435,23 @@ def show_toolbar(request):
"proconnect.backends.OIDCAuthenticationBackend",
]

LOGOUT_URL = "/oidc/logout/"
LOGOUT_URL = f"{FORCE_SCRIPT_NAME}/oidc/logout/"

# CSRF
CSRF_TRUSTED_ORIGINS = []
for host in ALLOWED_HOSTS:
CSRF_TRUSTED_ORIGINS.append("https://" + host)
if host not in ["127.0.0.1", "localhost", ".localhost"]:
if HOST_PORT:
CSRF_TRUSTED_ORIGINS.append(f"{HOST_PROTO}://{host}:{HOST_PORT}")
else:
CSRF_TRUSTED_ORIGINS.append(f"{HOST_PROTO}://{host}")

trusted_origins = os.getenv("CSRF_TRUSTED_ORIGINS", "").replace(" ", "").split(",")
trusted_origins = list(filter(None, trusted_origins))

if len(trusted_origins):
CSRF_TRUSTED_ORIGINS += trusted_origins

# Disable the integrity checksums by default.
# They can clash with Whitenoise and are normally not useful as we serve the statics from a trusted source
DSFR_USE_INTEGRITY_CHECKSUMS = True if os.getenv("DSFR_USE_INTEGRITY_CHECKSUMS") in [1, "True"] else False
3 changes: 3 additions & 0 deletions config/settings_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@

WHITENOISE_AUTOREFRESH = True
WHITENOISE_MANIFEST_STRICT = False

FORCE_SCRIPT_NAME = ""
WAGTAILADMIN_BASE_URL = "http://localhost"
Loading