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 @@ -