diff --git a/.gitignore b/.gitignore
index 964eec36e..3e085fbd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,6 +61,7 @@ local_settings.py
db.sqlite3
db.sqlite3-journal
/static/
+/media/
# Flask stuff:
instance/
diff --git a/INSTALL.md b/INSTALL.md
index 1a7e6b374..e8e729c4c 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -1,4 +1,4 @@
-# Installation de django-DSFR
+# Installation de Django-DSFR
## Installation basique
@@ -47,24 +47,3 @@ FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
- Inclure les tags dans votre fichier `base.html` (voir par exemple sur [base.html](https://github.com/numerique-gouv/django-dsfr/blob/main/example_app/templates/example_app/base.html))
- Lancer le serveur (`python manage.py runserver`) et aller sur [http://127.0.0.1:8000/](http://127.0.0.1:8000/)
-
-
-## Installation avancée (optionnelle)
-### Utilisation de la conf en admin
-- Ajoutez le `context_processor` au fichier `settings.py` :
-
-```{ .python }
-TEMPLATES = [
- {
- [...]
- "OPTIONS": {
- "context_processors": [
- [...]
- "dsfr.context_processors.site_config",
- ],
- },
- },
-]
-```
-
-- Créez un objet "DsfrConfig" dans le panneau d’administration
diff --git a/Makefile b/Makefile
index 340317288..ab1f6918e 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,7 @@ update_dsfr:
make collectstatic
static_server:
- python -m http.server $(local_port) -d docs/
+ python -m http.server 1$(local_port) -d docs/
export_static:
poetry run python manage.py migrate
diff --git a/doc/footer.md b/doc/footer.md
new file mode 100644
index 000000000..9e8499c76
--- /dev/null
+++ b/doc/footer.md
@@ -0,0 +1,48 @@
+Le pied de page est géré grâce à une balise `include` dans le fichier `base.html`. Si vous n’avez pas besoin de le personnaliser, vous n’avez rien à faire.
+
+Il est alors possible de personnaliser la description ainsi que le bloc-marque via la configuration du site dans l’administration de Django.
+
+-
+ Voir la page de documentation du composant sur le Système de Design de l’État
+ Ouvre une nouvelle fenêtre
+
+-
+ Voir la page d’exemple du Système de Design de l’État
+ Ouvre une nouvelle fenêtre
+
+
+## Personnaliser
+
+Il est possible de l’étendre pour le personnaliser, par exemple pour ajouter le sélecteur de thème :
+
+```{.django}
+
+{% extends "dsfr/base.html" %}
+
+
+{% block footer %}
+ {% include "/blocks/footer.html" %}
+{% endblock footer %}
+
+```
+
+```
+
+{% extends "dsfr/footer.html" %}
+{% block footer_links %}
+ {{ block.super }}
+
+{% endblock footer_links %}
+```
+
+## Blocs dépréciés
+- Le bloc `brand`, qui ne permet pas toutes les personnalisations nécessaires, va être supprimé à terme. Les personnalisations sont à mettre dans le nouveau bloc `footer_brand`.
+- Même chose pour le bloc `footer_content`, à remplacer à terme par le nouveau bloc `footer_description`.
diff --git a/doc/header.md b/doc/header.md
new file mode 100644
index 000000000..27975603f
--- /dev/null
+++ b/doc/header.md
@@ -0,0 +1,66 @@
+L’en-tête est géré grâce à une balise `include` dans le fichier `base.html`. Si vous n’avez pas besoin de le personnaliser, vous n’avez rien à faire.
+
+Il est alors possible de personnaliser le titre, le sous-titre, ainsi que le bloc-marque, via la configuration du site dans l’administration de Django.
+
+-
+ Voir la page de documentation du composant sur le Système de Design de l’État
+ Ouvre une nouvelle fenêtre
+
+-
+ Voir la page d’exemple du Système de Design de l’État
+ Ouvre une nouvelle fenêtre
+
+
+## Composants liés
+Le gabarit d’en-tête est également l’endroit où inclure les composants suivants :
+
+- Navigation principale (navigation) ([Documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/navigation-principale) — [Exemple](https://main--ds-gouv.netlify.app/example/component/navigation/)), à insérer dans le bloc `main_menu`.
+- Sélecteur de langue (translate) : ([Documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/selecteur-de-langue) — [Exemple](https://main--ds-gouv.netlify.app/example/component/translate/)), à insérer dans le bloc `header_tools`.
+
+
+## Personnaliser
+
+Il est possible de l’étendre pour le personnaliser, par exemple pour ajouter la barre de recherche :
+
+```{.django}
+
+{% extends "dsfr/base.html" %}
+
+
+{% block header %}
+ {% include "/blocks/header.html" %}
+{% endblock header %}
+
+```
+
+```
+
+{% extends "dsfr/header.html" %}
+
+{% block header_search %}
+
+{% endblock header_search %}
+```
diff --git a/doc/usage.md b/doc/usage.md
new file mode 100644
index 000000000..ed69356a0
--- /dev/null
+++ b/doc/usage.md
@@ -0,0 +1,68 @@
+## Bien commencer
+
+Pour utiliser Django-DSFR dans votre application après l’installation, vous pouvez commencer à utiliser la balise `{% extends "dsfr/base.html" %}` dans vos [gabarits Django](https://docs.djangoproject.com/fr/5.0/topics/templates/) et mettre votre contenu dans le bloc `{% block content %}{% endblock content %}`.
+
+Vous pouvez faire appel aux [composants du système de design implémentés](/django-dsfr/components/) en appelant la balise `{% load dsfr_tags %}` en haut de vos fichiers.
+
+
+## Étendre les gabarits de base
+Si vous avez besoin d'aller plus loin, vous pouvez étendre `base.html` (et potentiellement `header.html` et `footer.html`).
+
+### Fichier base.html
+Dans le répertoire de votre application, créez le ficher `/templates//base.html` avec le contenu suivant :
+
+```{.django}
+
+{% extends "dsfr/base.html" %}
+
+{% block header %}
+ {% include "/blocks/header.html" %}
+{% endblock header %}
+
+{% block footer %}
+ {% include "/blocks/footer.html" %}
+{% endblock footer %}
+```
+
+### Fichiers header.hml et footer.hml
+
+Voir la documentation de ces composants :
+
+- [En-tête (header)](/django-dsfr/components/header/)
+- [Pied de page (footer)](/django-dsfr/components/footer/)
+
+### Remplacer au lieu d’étendre
+
+Dans le cas où les gabarits fournis se révéleraient trop limités, n’hésitez pas à les remplacer complètement par les vôtres. En plus des composants du Système de design de l’État lui-même, vous pouvez alors utiliser certaines balises pour vous faciliter le travail, notamment :
+
+- [CSS global](/django-dsfr/components/css/)
+- [JS global](/django-dsfr/components/js/)
+- [Messages Django dans une alerte](/django-dsfr/components/django_messages/)
+
+
+## Gestion de la configuration en admin
+
+Vous pouvez personnaliser certains éléments du site via une "configuration du site" dans l’[administration de Django](https://docs.djangoproject.com/fr/5.0/ref/contrib/admin/).
+
+Cela permet notamment aux utilisateurs de les modifier sans passer par un développement.
+
+- Ajoutez le `context_processor` au fichier `settings.py` :
+
+```{ .python }
+TEMPLATES = [
+ {
+ [...]
+ "OPTIONS": {
+ "context_processors": [
+ [...]
+ "dsfr.context_processors.site_config",
+ ],
+ },
+ },
+]
+```
+
+- Créez un objet "DsfrConfig" dans le panneau d’administration (section Système de design de l’État > Configurations.)
+
+## Application d’exemple
+Vous pouvez prendre exemple sur cette application (cf. [code source](https://github.com/numerique-gouv/django-dsfr/tree/main/example_app)). Elle consiste en un générateur pour la présente documentation. Dans la mesure où celle-ci est hébergée de manière statique, un export est fait automatiquement via Django-distill.
diff --git a/dsfr/admin.py b/dsfr/admin.py
index b0daeb1a9..d8e8430b5 100644
--- a/dsfr/admin.py
+++ b/dsfr/admin.py
@@ -5,23 +5,32 @@
@admin.register(DsfrConfig)
class DsfrConfigAdmin(admin.ModelAdmin):
fieldsets = (
- ("Site", {"fields": ("site_title", "site_tagline")}),
+ ("Site", {"fields": ("site_title", "site_tagline", "mourning")}),
(
"En-tête",
{
- "fields": ("header_brand", "header_brand_html"),
+ "fields": ("header_brand", "header_brand_html", "beta_tag"),
},
),
(
"Pied de page",
{
- "fields": ("footer_brand", "footer_brand_html", "footer_description"),
+ "fields": (
+ "footer_brand",
+ "footer_brand_html",
+ "footer_description",
+ "accessibility_status",
+ ),
},
),
(
- "Avancé",
+ "Logo opérateur",
{
- "fields": ("mourning", "accessibility_status"),
+ "fields": (
+ "operator_logo_file",
+ "operator_logo_alt",
+ "operator_logo_width",
+ ),
},
),
)
diff --git a/dsfr/migrations/0004_dsfrconfig_beta_tag_dsfrconfig_operator_logo_alt_and_more.py b/dsfr/migrations/0004_dsfrconfig_beta_tag_dsfrconfig_operator_logo_alt_and_more.py
new file mode 100644
index 000000000..bd1ee61cc
--- /dev/null
+++ b/dsfr/migrations/0004_dsfrconfig_beta_tag_dsfrconfig_operator_logo_alt_and_more.py
@@ -0,0 +1,54 @@
+# Generated by Django 5.0.3 on 2024-03-25 13:39
+
+import dsfr.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("dsfr", "0003_alter_dsfrconfig_accessibility_status"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="dsfrconfig",
+ name="beta_tag",
+ field=models.BooleanField(
+ default=False, verbose_name="Afficher la mention BETA à côté du titre"
+ ),
+ ),
+ migrations.AddField(
+ model_name="dsfrconfig",
+ name="operator_logo_alt",
+ field=models.CharField(
+ blank=True,
+ help_text="Doit impérativement contenir le texte présent dans l’image.",
+ max_length=200,
+ verbose_name="Alternative textuelle du logo",
+ ),
+ ),
+ migrations.AddField(
+ model_name="dsfrconfig",
+ name="operator_logo_file",
+ field=models.FileField(
+ blank=True,
+ null=True,
+ upload_to="logos",
+ validators=[dsfr.models.validate_image_extension],
+ verbose_name="Logo opérateur",
+ ),
+ ),
+ migrations.AddField(
+ model_name="dsfrconfig",
+ name="operator_logo_width",
+ field=models.DecimalField(
+ decimal_places=1,
+ default="0.0",
+ help_text="À ajuster en fonction de la largeur du logo.\n Exemple pour un logo vertical: 3.5, Exemple pour un logo horizontal: 8.",
+ max_digits=3,
+ null=True,
+ verbose_name="Largeur (em)",
+ ),
+ ),
+ ]
diff --git a/dsfr/models.py b/dsfr/models.py
index 5664d78a4..2546a0c28 100644
--- a/dsfr/models.py
+++ b/dsfr/models.py
@@ -1,7 +1,16 @@
+import os
+
from django.db import models
from django.core.exceptions import ValidationError
+def validate_image_extension(value):
+ ext = os.path.splitext(value.name)[1] # [0] returns path+filename
+ valid_extensions = [".jpg", ".jpeg", ".png", ".svg"]
+ if ext.lower() not in valid_extensions:
+ raise ValidationError("Unsupported file extension.")
+
+
class DsfrConfig(models.Model):
A11Y_CHOICES = [
("FULL", "totalement"),
@@ -9,6 +18,15 @@ class DsfrConfig(models.Model):
("NOT", "non"),
]
+ # Site
+ site_title = models.CharField(
+ "Titre du site", max_length=200, default="Titre du site", blank=True
+ )
+ site_tagline = models.CharField(
+ "Sous-titre du site", max_length=200, default="Sous-titre du site", blank=True
+ )
+
+ # Header
header_brand = models.CharField(
"Institution (en-tête)",
max_length=200,
@@ -21,6 +39,9 @@ class DsfrConfig(models.Model):
default="République
française",
blank=True,
)
+ beta_tag = models.BooleanField("Afficher la mention BETA à côté du titre", default=False) # type: ignore
+
+ # Footer
footer_brand = models.CharField(
"Institution (pied)", max_length=200, default="République française", blank=True
)
@@ -30,14 +51,35 @@ class DsfrConfig(models.Model):
default="République
française",
blank=True,
)
- site_title = models.CharField(
- "Titre du site", max_length=200, default="Titre du site", blank=True
+ footer_description = models.TextField("Description", default="", blank=True)
+
+ # Operator logo
+ operator_logo_file = models.FileField(
+ "Logo opérateur",
+ upload_to="logos",
+ blank=True,
+ null=True,
+ validators=[validate_image_extension],
)
- site_tagline = models.CharField(
- "Sous-titre du site", max_length=200, default="Sous-titre du site", blank=True
+ operator_logo_alt = models.CharField(
+ "Alternative textuelle du logo",
+ max_length=200,
+ blank=True,
+ help_text="Doit impérativement contenir le texte présent dans l’image.",
)
- footer_description = models.TextField("Description", default="", blank=True)
- mourning = models.BooleanField("Mise en berne", default=False)
+ operator_logo_width = models.DecimalField(
+ "Largeur (em)",
+ max_digits=3,
+ decimal_places=1,
+ null=True,
+ default="0.0",
+ help_text="""À ajuster en fonction de la largeur du logo.
+ Exemple pour un logo vertical: 3.5, Exemple pour un logo horizontal: 8.""",
+ )
+
+ # Advanced
+ mourning = models.BooleanField("Mise en berne", default=False) # type: ignore
+
accessibility_status = models.CharField(
"Statut de conformité de l’accessibilité",
max_length=4,
diff --git a/dsfr/templates/dsfr/footer.html b/dsfr/templates/dsfr/footer.html
index 688c70b58..cf346ebf2 100644
--- a/dsfr/templates/dsfr/footer.html
+++ b/dsfr/templates/dsfr/footer.html
@@ -1,38 +1,88 @@
-