diff --git a/.env.example b/.env.example index d3ce728b..febb0421 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ HOST_PROTO=http HOST_URL=localhost # use 0.0.0.0 for Docker ALLOWED_HOSTS=localhost, 0.0.0.0 HOST_PORT=8000 +SITE_NAME=Nom du site USE_DOCKER=0 # Set 1 to use Docker @@ -19,3 +20,5 @@ S3_KEY_ID= S3_KEY_SECRET= S3_BUCKET_NAME= S3_BUCKET_REGION=eu-west-3 + +WAGTAIL_2FA_REQUIRED=False diff --git a/Dockerfile b/Dockerfile index f9c29c01..0c75e9a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,7 @@ RUN poetry install COPY --chown=app:app . . -RUN poetry run python manage.py collectstatic --no-input +RUN poetry run python manage.py collectstatic --no-input --ignore=*.sass USER app diff --git a/Makefile b/Makefile index 1c87c0af..03cbd2b7 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,15 @@ web-prompt: test-unit: $(EXEC_CMD) poetry run python manage.py test --settings config.settings_test +.PHONY: collectstatic +collectstatic: + $(EXEC_CMD) poetry run python manage.py collectstatic --noinput --ignore=*.sass + +.PHONY: sass +sass: + $(EXEC_CMD) poetry run python manage.py compilescss + make collectstatic + .PHONY: quality quality: $(EXEC_CMD) poetry run black --check --exclude=venv . @@ -35,7 +44,7 @@ init: $(EXEC_CMD) poetry install $(EXEC_CMD) poetry run pre-commit install $(EXEC_CMD) poetry run python manage.py migrate - $(EXEC_CMD) poetry run python manage.py collectstatic --noinput + make collectstatic $(EXEC_CMD) poetry run python manage.py create_sample_pages .PHONY: runserver diff --git a/README.md b/README.md index c61b10ab..da83929e 100644 --- a/README.md +++ b/README.md @@ -87,3 +87,11 @@ Vous pouvez également générer un rapport sur la couverture de tests : ```sh coverage run manage.py test --settings config.settings_test ``` + +## Configurer l’authentification à deux facteurs (2FA) +Pour activer l’authentification à deux facteurs sur le site, il faut passer la variable `WAGTAIL_2FA_REQUIRED` à True et remplir la variable `SITE_NAME` si elle ne l’est pas déjà. + +``` +SITE_NAME=Nom du site +WAGTAIL_2FA_REQUIRED=True +``` diff --git a/config/settings.py b/config/settings.py index b6f7bbde..0ae0a7b6 100644 --- a/config/settings.py +++ b/config/settings.py @@ -64,6 +64,9 @@ "dsfr", "sass_processor", "content_manager", + "wagtail_2fa", + "django_otp", + "django_otp.plugins.otp_totp", ] if DEBUG: @@ -77,6 +80,7 @@ "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", + "wagtail_2fa.middleware.VerifyUserMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "wagtail.contrib.redirects.middleware.RedirectMiddleware", @@ -158,9 +162,9 @@ # https://whitenoise.evans.io/en/latest/ STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", "sass_processor.finders.CssFinder", - "django.contrib.staticfiles.finders.FileSystemFinder", ] # S3 uploads & MEDIA CONFIGURATION @@ -179,7 +183,8 @@ MEDIA_URL = "medias/" # Django Sass -SASS_PROCESSOR_ROOT = os.path.join(BASE_DIR, "static") +SASS_PROCESSOR_ROOT = os.path.join(BASE_DIR, "static/css") +SASS_PROCESSOR_AUTO_INCLUDE = False STATIC_URL = "static/" STATIC_ROOT = "staticfiles" @@ -194,7 +199,7 @@ # Wagtail settings # https://docs.wagtail.org/en/stable/reference/settings.html -WAGTAIL_SITE_NAME = "Gestionnaire de contenu avec le Système de Design de l'État" +WAGTAIL_SITE_NAME = os.getenv("SITE_NAME", "Gestionnaire de contenu avec le Système de Design de l’État") # 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 @@ -227,3 +232,6 @@ CSRF_TRUSTED_ORIGINS = [] for host in ALLOWED_HOSTS: CSRF_TRUSTED_ORIGINS.append("https://" + host) + +# 2FA, see https://wagtail-2fa.readthedocs.io/en/stable/ +WAGTAIL_2FA_REQUIRED = os.getenv("WAGTAIL_2FA_REQUIRED", False) diff --git a/content_manager/blocks.py b/content_manager/blocks.py index 2b49ac42..5bc463d3 100644 --- a/content_manager/blocks.py +++ b/content_manager/blocks.py @@ -60,7 +60,7 @@ class AlertBlock(blocks.StructBlock): class BadgeBlock(blocks.StructBlock): text = blocks.CharBlock(label="Texte du badge", required=False) color = blocks.ChoiceBlock(label="Couleur de badge", choices=badge_level_choices, required=False) - hide_icon = blocks.BooleanBlock(label="Masquer l'icon du badge", required=False) + hide_icon = blocks.BooleanBlock(label="Masquer l’icône du badge", required=False) class BadgesListBlock(blocks.StreamBlock): @@ -87,24 +87,26 @@ class CardBlock(blocks.StructBlock): label="ou Document", help_text=( "Sélectionnez un document pour rendre la carte cliquable vers " - "celui ci (si le champ `Lien` n'est pas renseigné)." + "celui ci (si le champ « Lien » n’est pas renseigné)." ), required=False, ) class HeroBlock(blocks.StructBlock): - bg_image = ImageChooserBlock(label="Image d'arrière plan") + bg_image = ImageChooserBlock(label="Image d’arrière plan") bg_color = blocks.RegexBlock( - label="Couleur d'arrière plan au format hexa (Ex: #f5f5fe)", + label="Couleur d’arrière plan au format hexa (Ex: #f5f5fe)", regex=r"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", - error_messages={"invalid": "La couleur n'est pas correcte, le format doit être #fff ou #f5f5fe"}, + error_messages={"invalid": "La couleur n’est pas correcte, le format doit être #fff ou #f5f5fe"}, required=False, ) title = blocks.CharBlock(label="Titre") text = blocks.CharBlock(label="Texte", required=False) cta_label = blocks.CharBlock(label="Texte du bouton", required=False) cta_link = blocks.URLBlock(label="Lien du bouton", required=False) + large = blocks.BooleanBlock(label="Large", required=False) + darken = blocks.BooleanBlock(label="Assombrir", required=False) class IframeBlock(blocks.StructBlock): @@ -122,7 +124,7 @@ class IframeBlock(blocks.StructBlock): class ImageAndTextBlock(blocks.StructBlock): image = ImageChooserBlock(label="Illustration (à gauche)") image_ratio = blocks.ChoiceBlock( - label="Largeur de l'image", + label="Largeur de l’image", choices=[ ("3", "3/12"), ("5", "5/12"), @@ -141,7 +143,7 @@ class ImageAndTextBlock(blocks.StructBlock): class ImageBlock(blocks.StructBlock): title = blocks.CharBlock(label="Titre", required=False) image = ImageChooserBlock(label="Illustration") - alt = blocks.CharBlock(label="Texte alternatif (description textuelle de l'image)", required=False) + alt = blocks.CharBlock(label="Texte alternatif (description textuelle de l’image)", required=False) caption = blocks.CharBlock(label="Légende", required=False) url = blocks.URLBlock(label="Lien", required=False) @@ -149,8 +151,8 @@ class ImageBlock(blocks.StructBlock): class QuoteBlock(blocks.StructBlock): image = ImageChooserBlock(label="Illustration (à gauche)", required=False) quote = blocks.CharBlock(label="Citation") - author_name = blocks.CharBlock(label="Nom de l'auteur") - author_title = blocks.CharBlock(label="Titre de l'auteur") + author_name = blocks.CharBlock(label="Nom de l’auteur") + author_title = blocks.CharBlock(label="Titre de l’auteur") class SeparatorBlock(blocks.StructBlock): @@ -159,7 +161,7 @@ class SeparatorBlock(blocks.StructBlock): class StepBlock(blocks.StructBlock): - title = blocks.CharBlock(label="Titre de l'étape") + title = blocks.CharBlock(label="Titre de l’étape") detail = blocks.TextBlock(label="Détail") @@ -169,7 +171,7 @@ class StepsListBlock(blocks.StreamBlock): class StepperBlock(blocks.StructBlock): title = blocks.CharBlock(label="Titre") - total = blocks.IntegerBlock(label="Nombre d'étape") + total = blocks.IntegerBlock(label="Nombre d’étapes") current = blocks.IntegerBlock(label="Étape en cours") steps = StepsListBlock(label="Les étapes") @@ -177,7 +179,7 @@ class StepperBlock(blocks.StructBlock): class TextAndCTA(blocks.StructBlock): text = blocks.RichTextBlock(label="Texte avec mise en forme", required=False) cta_label = blocks.CharBlock( - label="Titre de l'appel à l'action", + label="Titre de l’appel à l’action", help_text="Le lien apparait comme un bouton sous le bloc de texte", required=False, ) @@ -194,7 +196,7 @@ class VideoBlock(blocks.StructBlock): caption = blocks.CharBlock(label="Légende") url = blocks.URLBlock( label="Lien de la vidéo", - help_text="URL au format 'embed' (Ex. : https://www.youtube.com/embed/gLzXOViPX-0)", + help_text="URL au format « embed » (Ex. : https://www.youtube.com/embed/gLzXOViPX-0)", ) @@ -205,16 +207,16 @@ class MultiColumnsBlock(blocks.StreamBlock): video = VideoBlock(label="Vidéo") card = CardBlock(label="Carte") quote = QuoteBlock(label="Citation") - text_cta = TextAndCTA(label="Texte et appel à l'action") + text_cta = TextAndCTA(label="Texte et appel à l’action") iframe = IframeBlock(label="Cadre intégré") class MultiColumnsWithTitleBlock(blocks.StructBlock): - bg_image = ImageChooserBlock(label="Image d'arrière plan", required=False) + bg_image = ImageChooserBlock(label="Image d’arrière plan", required=False) bg_color = blocks.RegexBlock( - label="Couleur d'arrière plan au format hexa (Ex: #f5f5fe)", + label="Couleur d’arrière plan au format hexa (Ex: #f5f5fe)", regex=r"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", - error_messages={"invalid": "La couleur n'est pas correcte, le format doit être #fff ou #f5f5fe"}, + error_messages={"invalid": "La couleur n’est pas correcte, le format doit être #fff ou #f5f5fe"}, required=False, ) title = blocks.CharBlock(label="Titre", required=False) diff --git a/content_manager/migrations/0010_alter_contentpage_body.py b/content_manager/migrations/0010_alter_contentpage_body.py new file mode 100644 index 00000000..bb11ed9a --- /dev/null +++ b/content_manager/migrations/0010_alter_contentpage_body.py @@ -0,0 +1,456 @@ +# Generated by Django 4.2.7 on 2023-12-06 16:48 + +import wagtail.blocks +import wagtail.documents.blocks +import wagtail.fields +import wagtail.images.blocks +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("content_manager", "0009_alter_contentpage_body"), + ] + + operations = [ + migrations.AlterField( + model_name="contentpage", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "hero", + wagtail.blocks.StructBlock( + [ + ("bg_image", wagtail.images.blocks.ImageChooserBlock(label="Image d’arrière plan")), + ( + "bg_color", + wagtail.blocks.RegexBlock( + error_messages={ + "invalid": "La couleur n’est pas correcte, le format doit être #fff ou #f5f5fe" + }, + label="Couleur d’arrière plan au format hexa (Ex: #f5f5fe)", + regex="^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", + required=False, + ), + ), + ("title", wagtail.blocks.CharBlock(label="Titre")), + ("text", wagtail.blocks.CharBlock(label="Texte", required=False)), + ("cta_label", wagtail.blocks.CharBlock(label="Texte du bouton", required=False)), + ("cta_link", wagtail.blocks.URLBlock(label="Lien du bouton", required=False)), + ("large", wagtail.blocks.BooleanBlock(label="Large", required=False)), + ("darken", wagtail.blocks.BooleanBlock(label="Assombrir", required=False)), + ], + label="Section promotionnelle", + ), + ), + ( + "title", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre")), + ("large", wagtail.blocks.BooleanBlock(label="Large", required=False)), + ], + label="Titre de page", + ), + ), + ("paragraph", wagtail.blocks.RichTextBlock(label="Texte avec mise en forme")), + ("paragraphlarge", wagtail.blocks.RichTextBlock(label="Texte avec mise en forme (large)")), + ( + "image", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre", required=False)), + ("image", wagtail.images.blocks.ImageChooserBlock(label="Illustration")), + ( + "alt", + wagtail.blocks.CharBlock( + label="Texte alternatif (description textuelle de l’image)", required=False + ), + ), + ("caption", wagtail.blocks.CharBlock(label="Légende", required=False)), + ("url", wagtail.blocks.URLBlock(label="Lien", required=False)), + ] + ), + ), + ( + "imageandtext", + wagtail.blocks.StructBlock( + [ + ("image", wagtail.images.blocks.ImageChooserBlock(label="Illustration (à gauche)")), + ( + "image_ratio", + wagtail.blocks.ChoiceBlock( + choices=[("3", "3/12"), ("5", "5/12"), ("6", "6/12")], + label="Largeur de l’image", + ), + ), + ("text", wagtail.blocks.RichTextBlock(label="Texte avec mise en forme (à droite)")), + ( + "link_label", + wagtail.blocks.CharBlock( + help_text="Le lien apparait en bas du bloc de droite, avec une flèche", + label="Titre du lien", + required=False, + ), + ), + ("link_url", wagtail.blocks.URLBlock(label="Lien", required=False)), + ], + label="Bloc image à gauche et texte à droite", + ), + ), + ( + "alert", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre du message", required=False)), + ("description", wagtail.blocks.TextBlock(label="Texte du message", required=False)), + ( + "level", + wagtail.blocks.ChoiceBlock( + choices=[ + ("error", "Erreur"), + ("success", "Succès"), + ("info", "Information"), + ("warning", "Attention"), + ], + label="Type de message", + ), + ), + ( + "heading_tag", + wagtail.blocks.ChoiceBlock( + choices=[ + ("h2", "En-tête 2"), + ("h3", "En-tête 3"), + ("h4", "En-tête 4"), + ("h5", "En-tête 5"), + ("h6", "En-tête 6"), + ("p", "Paragraphe"), + ], + help_text="À adapter à la structure de la page. Par défaut en-tête 3.", + label="Niveau de titre", + ), + ), + ], + label="Message d'alerte", + ), + ), + ( + "callout", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre de la mise en vant", required=False)), + ("text", wagtail.blocks.TextBlock(label="Texte mis en avant", required=False)), + ( + "heading_tag", + wagtail.blocks.ChoiceBlock( + choices=[ + ("h2", "En-tête 2"), + ("h3", "En-tête 3"), + ("h4", "En-tête 4"), + ("h5", "En-tête 5"), + ("h6", "En-tête 6"), + ("p", "Paragraphe"), + ], + help_text="À adapter à la structure de la page. Par défaut en-tête 3.", + label="Niveau de titre", + ), + ), + ], + label="Texte mise en avant", + ), + ), + ( + "quote", + wagtail.blocks.StructBlock( + [ + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + label="Illustration (à gauche)", required=False + ), + ), + ("quote", wagtail.blocks.CharBlock(label="Citation")), + ("author_name", wagtail.blocks.CharBlock(label="Nom de l’auteur")), + ("author_title", wagtail.blocks.CharBlock(label="Titre de l’auteur")), + ], + label="Citation", + ), + ), + ( + "video", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre", required=False)), + ("caption", wagtail.blocks.CharBlock(label="Légende")), + ( + "url", + wagtail.blocks.URLBlock( + help_text="URL au format «\xa0embed\xa0» (Ex. : https://www.youtube.com/embed/gLzXOViPX-0)", + label="Lien de la vidéo", + ), + ), + ], + label="Vidéo", + ), + ), + ( + "multicolumns", + wagtail.blocks.StructBlock( + [ + ( + "bg_image", + wagtail.images.blocks.ImageChooserBlock( + label="Image d’arrière plan", required=False + ), + ), + ( + "bg_color", + wagtail.blocks.RegexBlock( + error_messages={ + "invalid": "La couleur n’est pas correcte, le format doit être #fff ou #f5f5fe" + }, + label="Couleur d’arrière plan au format hexa (Ex: #f5f5fe)", + regex="^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", + required=False, + ), + ), + ("title", wagtail.blocks.CharBlock(label="Titre", required=False)), + ( + "columns", + wagtail.blocks.StreamBlock( + [ + ("text", wagtail.blocks.RichTextBlock(label="Texte avec mise en forme")), + ( + "image", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock(label="Titre", required=False), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + label="Illustration" + ), + ), + ( + "alt", + wagtail.blocks.CharBlock( + label="Texte alternatif (description textuelle de l’image)", + required=False, + ), + ), + ( + "caption", + wagtail.blocks.CharBlock(label="Légende", required=False), + ), + ("url", wagtail.blocks.URLBlock(label="Lien", required=False)), + ], + label="Image", + ), + ), + ( + "video", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock(label="Titre", required=False), + ), + ("caption", wagtail.blocks.CharBlock(label="Légende")), + ( + "url", + wagtail.blocks.URLBlock( + help_text="URL au format «\xa0embed\xa0» (Ex. : https://www.youtube.com/embed/gLzXOViPX-0)", + label="Lien de la vidéo", + ), + ), + ], + label="Vidéo", + ), + ), + ( + "card", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre")), + ("description", wagtail.blocks.TextBlock(label="Texte")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock(label="Image"), + ), + ("url", wagtail.blocks.URLBlock(label="Lien", required=False)), + ( + "document", + wagtail.documents.blocks.DocumentChooserBlock( + help_text="Sélectionnez un document pour rendre la carte cliquable vers celui ci (si le champ «\xa0Lien\xa0» n’est pas renseigné).", + label="ou Document", + required=False, + ), + ), + ], + label="Carte", + ), + ), + ( + "quote", + wagtail.blocks.StructBlock( + [ + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + label="Illustration (à gauche)", required=False + ), + ), + ("quote", wagtail.blocks.CharBlock(label="Citation")), + ( + "author_name", + wagtail.blocks.CharBlock(label="Nom de l’auteur"), + ), + ( + "author_title", + wagtail.blocks.CharBlock(label="Titre de l’auteur"), + ), + ], + label="Citation", + ), + ), + ( + "text_cta", + wagtail.blocks.StructBlock( + [ + ( + "text", + wagtail.blocks.RichTextBlock( + label="Texte avec mise en forme", required=False + ), + ), + ( + "cta_label", + wagtail.blocks.CharBlock( + help_text="Le lien apparait comme un bouton sous le bloc de texte", + label="Titre de l’appel à l’action", + required=False, + ), + ), + ( + "cta_url", + wagtail.blocks.CharBlock(label="Lien", required=False), + ), + ], + label="Texte et appel à l’action", + ), + ), + ( + "iframe", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock( + help_text="Accessibilité : Le titre doit décrire, de façon claire et concise, le contenu embarqué.", + label="Titre", + ), + ), + ( + "url", + wagtail.blocks.URLBlock( + help_text="Exemple pour Tally : https://tally.so/embed/w2jMRa", + label="Lien du cadre intégré", + ), + ), + ( + "height", + wagtail.blocks.IntegerBlock(label="Hauteur en pixels"), + ), + ], + label="Cadre intégré", + ), + ), + ], + label="Multi-colonnes", + ), + ), + ], + label="Multi-colonnes", + ), + ), + ( + "accordions", + wagtail.blocks.StreamBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre")), + ( + "accordion", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre")), + ("content", wagtail.blocks.RichTextBlock(label="Contenu")), + ], + label="Accordéon", + max_num=15, + min_num=1, + ), + ), + ], + label="Accordéons", + ), + ), + ( + "stepper", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre")), + ("total", wagtail.blocks.IntegerBlock(label="Nombre d’étapes")), + ("current", wagtail.blocks.IntegerBlock(label="Étape en cours")), + ( + "steps", + wagtail.blocks.StreamBlock( + [ + ( + "step", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock(label="Titre de l’étape")), + ("detail", wagtail.blocks.TextBlock(label="Détail")), + ], + label="Étape", + ), + ) + ], + label="Les étapes", + ), + ), + ], + label="Étapes", + ), + ), + ( + "separator", + wagtail.blocks.StructBlock( + [ + ( + "top_margin", + wagtail.blocks.IntegerBlock( + default=3, label="Espacement au dessus", max_value=15, min_value=0 + ), + ), + ( + "bottom_margin", + wagtail.blocks.IntegerBlock( + default=3, label="Espacement en dessous", max_value=15, min_value=0 + ), + ), + ], + label="Séparateur", + ), + ), + ], + blank=True, + use_json_field=True, + ), + ), + ] diff --git a/content_manager/templates/content_manager/blocks/hero.html b/content_manager/templates/content_manager/blocks/hero.html index fe26200b..8d79cbca 100644 --- a/content_manager/templates/content_manager/blocks/hero.html +++ b/content_manager/templates/content_manager/blocks/hero.html @@ -1,14 +1,16 @@ {% load static wagtailimages_tags %} {% image block.value.bg_image original as bg_img %} -
+
-
-

{{ block.value.title }}

+
+ {{ block.value.title }} {% if block.value.text %}

{{ block.value.text }}

{% endif %} {% if block.value.cta_link and block.value.cta_label %} - {{ block.value.cta_label }} {% endif %}
diff --git a/poetry.lock b/poetry.lock index 17be70a9..2138d04b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -386,6 +386,20 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""} argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] +[[package]] +name = "django-appconf" +version = "1.0.6" +description = "A helper class for handling configuration defaults of packaged apps gracefully." +optional = false +python-versions = ">=3.7" +files = [ + {file = "django-appconf-1.0.6.tar.gz", hash = "sha256:cfe87ea827c4ee04b9a70fab90b86d704cb02f2981f89da8423cb0fabf88efbf"}, + {file = "django_appconf-1.0.6-py3-none-any.whl", hash = "sha256:c3ae442fba1ff7ec830412c5184b17169a7a1e71cf0864a4c3f93cf4c98a1993"}, +] + +[package.dependencies] +django = "*" + [[package]] name = "django-cogwheels" version = "0.3" @@ -402,6 +416,22 @@ development = ["Django (>=2.0,<2.1)", "django-extensions", "ipdb", "werkzeug"] docs = ["Sphinx (>=1.7.4)", "pyenchant (>=2.0)", "sphinx-rtd-theme (>=0.3)", "sphinxcontrib-spelling (>=1.4)"] testing = ["coverage"] +[[package]] +name = "django-compressor" +version = "4.4" +description = "('Compresses linked and inline JavaScript or CSS into single cached files.',)" +optional = false +python-versions = "*" +files = [ + {file = "django_compressor-4.4-py2.py3-none-any.whl", hash = "sha256:6e2b0c0becb9607f5099c2546a824c5b84a6918a34bc37a8a622ffa250313596"}, + {file = "django_compressor-4.4.tar.gz", hash = "sha256:1b0acc9cfba9f69bc38e7c41da9b0d70a20bc95587b643ffef9609cf46064f67"}, +] + +[package.dependencies] +django-appconf = ">=1.0.3" +rcssmin = "1.1.1" +rjsmin = "1.2.1" + [[package]] name = "django-crispy-forms" version = "2.1" @@ -479,6 +509,23 @@ pytz = ">=2022.4" [package.extras] taggit = ["django-taggit (>=2.0)"] +[[package]] +name = "django-otp" +version = "1.3.0" +description = "A pluggable framework for adding two-factor authentication to Django using one-time passwords." +optional = false +python-versions = ">=3.7" +files = [ + {file = "django_otp-1.3.0-py3-none-any.whl", hash = "sha256:5277731bc05b6cdbf96aa84ac46018e30ed5fb248086053b0146f925de059060"}, + {file = "django_otp-1.3.0.tar.gz", hash = "sha256:8f4156a3c14ce2aaa31379385eadf388925cd50fc4b5d20a3b944f454c98ff7c"}, +] + +[package.dependencies] +django = ">=3.2" + +[package.extras] +qrcode = ["qrcode"] + [[package]] name = "django-permissionedforms" version = "0.1" @@ -1390,6 +1437,17 @@ files = [ plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pypng" +version = "0.20220715.0" +description = "Pure Python library for saving and loading PNG images" +optional = false +python-versions = "*" +files = [ + {file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"}, + {file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"}, +] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -1478,6 +1536,61 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "qrcode" +version = "7.4.2" +description = "QR Code image generator" +optional = false +python-versions = ">=3.7" +files = [ + {file = "qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a"}, + {file = "qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +pypng = "*" +typing-extensions = "*" + +[package.extras] +all = ["pillow (>=9.1.0)", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"] +dev = ["pytest", "pytest-cov", "tox"] +maintainer = ["zest.releaser[recommended]"] +pil = ["pillow (>=9.1.0)"] +test = ["coverage", "pytest"] + +[[package]] +name = "rcssmin" +version = "1.1.1" +description = "CSS Minifier" +optional = false +python-versions = "*" +files = [ + {file = "rcssmin-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d4e263fa9428704fd94c2cb565c7519ca1d225217943f71caffe6741ab5b9df1"}, + {file = "rcssmin-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c7278c1c25bb90d8e554df92cfb3b6a1195004ead50f764653d3093933ee0877"}, + {file = "rcssmin-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f15673e97f0a68b4c378c4d15b088fe96d60bc106d278c88829923118833c20f"}, + {file = "rcssmin-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d0afc6e7b64ef30d6dcde88830ec1a237b9f16a39f920a8fd159928684ccf8db"}, + {file = "rcssmin-1.1.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:705c9112d0ed54ea40aecf97e7fd29bdf0f1c46d278a32d8f957f31dde90778a"}, + {file = "rcssmin-1.1.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:f7a1fcdbafaacac0530da04edca4a44303baab430ea42e7d59aece4b3f3e9a51"}, + {file = "rcssmin-1.1.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:cf74d7ea5e191f0f344b354eed8b7c83eeafbd9a97bec3a579c3d26edf11b005"}, + {file = "rcssmin-1.1.1-cp311-cp311-manylinux1_i686.whl", hash = "sha256:908fe072efd2432fb0975a61124609a8e05021367f6a3463d45f5e3e74c4fdda"}, + {file = "rcssmin-1.1.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:35da6a6999e9e2c5b0e691b42ed56cc479373e0ecab33ef5277dfecce625e44a"}, + {file = "rcssmin-1.1.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:e923c105100ab70abde1c01d3196ddd6b07255e32073685542be4e3a60870c8e"}, + {file = "rcssmin-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:868215e1fd0e92a6122e0ed5973dfc7bb8330fe1e92274d05b2585253b38c0ca"}, + {file = "rcssmin-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c7728e3b546b1b6ea08cab721e8e21409dbcc11b881d0b87d10b0be8930af2a2"}, + {file = "rcssmin-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:271e3d2f8614a6d4637ed8fff3d90007f03e2a654cd9444f37d888797662ba72"}, + {file = "rcssmin-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:42576d95dfad53d77df2e68dfdec95b89b10fad320f241f1af3ca1438578254a"}, + {file = "rcssmin-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:79421230dd67c37ec61ed9892813d2b839b68f2f48ef55c75f976e81701d60b4"}, + {file = "rcssmin-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8fcfd10ae2a1c4ce231a33013f2539e07c3836bf17cc945cc25cc30bf8e68e45"}, + {file = "rcssmin-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c30f8bc839747b6da59274e0c6e4361915d66532e26448d589cb2b1846d7bf11"}, + {file = "rcssmin-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ee386bec6d62f8c814d65c011d604a7c82d24aa3f718facd66e850eea8d6a5a1"}, + {file = "rcssmin-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8a26fec3c1e6b7a3765ccbaccc20fbb5c0ed3422cc381e01a2607f08d7621c44"}, + {file = "rcssmin-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a04d58a2a21e9a089306d3f99c4b12bf5b656a79c198ef2321e80f8fd9afab06"}, + {file = "rcssmin-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:914e589f40573035006913861ed2adc28fbe70082a8b6bff5be7ee430b7b5c2e"}, + {file = "rcssmin-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a417735d4023d47d048a6288c88dbceadd20abaaf65a11bb4fda1e8458057019"}, + {file = "rcssmin-1.1.1.tar.gz", hash = "sha256:4f9400b4366d29f5f5446f58e78549afa8338e6a59740c73115e9f6ac413dc64"}, +] + [[package]] name = "regex" version = "2023.10.3" @@ -1596,6 +1709,38 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rjsmin" +version = "1.2.1" +description = "Javascript Minifier" +optional = false +python-versions = "*" +files = [ + {file = "rjsmin-1.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:35827844d2085bd59d34214dfba6f1fc42a215c455887437b07dbf9c73019cc1"}, + {file = "rjsmin-1.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:812af25c08d6a5ae98019a2e1b47ebb47f7469abd351670c353d619eaeae4064"}, + {file = "rjsmin-1.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:b8464629a18fe69f70677854c93a3707976024b226a0ce62707c618f923e1346"}, + {file = "rjsmin-1.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bd1faedc425006d9e86b23837d164f01d105b7a8b66b767a9766d0014773db2a"}, + {file = "rjsmin-1.2.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:99c074cd6a8302ff47118a9c3d086f89328dc8e5c4b105aa1f348fb85c765a30"}, + {file = "rjsmin-1.2.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:bc5bc2f94e59bc81562c572b7f1bdd6bcec4f61168dc68a2993bad2d355b6e19"}, + {file = "rjsmin-1.2.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:35f21046504544e2941e04190ce24161255479133751550e36ddb3f4af0ecdca"}, + {file = "rjsmin-1.2.1-cp311-cp311-manylinux1_i686.whl", hash = "sha256:ca90630b84fe94bb07739c3e3793e87d30c6ee450dde08653121f0d9153c8d0d"}, + {file = "rjsmin-1.2.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:7dd58b5ed88233bc61dc80b0ed87b93a1786031d9977c70d335221ef1ac5581a"}, + {file = "rjsmin-1.2.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:f0895b360dccf7e2d6af8762a52985e3fbaa56778de1bf6b20dbc96134253807"}, + {file = "rjsmin-1.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:747bc9d3bc8a220f40858e6aad50b2ae2eb7f69c924d4fa3803b81be1c1ddd02"}, + {file = "rjsmin-1.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f7cd33602ec0f393a0058e883284496bb4dbbdd34e0bbe23b594c8933ddf9b65"}, + {file = "rjsmin-1.2.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:3453ee6d5e7a2723ec45c2909e2382371783400e8d51952b692884c6d850a3d0"}, + {file = "rjsmin-1.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8c340e251619c97571a5ade20f147f1f7e8664f66a2d6d7319e05e3ef6a4423c"}, + {file = "rjsmin-1.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:145c6af8df42d8af102d0d39a6de2e5fa66aef9e38947cfb9d65377d1b9940b2"}, + {file = "rjsmin-1.2.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:bbd7a0abaa394afd951f5d4e05249d306fec1c9674bfee179787674dddd0bdb7"}, + {file = "rjsmin-1.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:eb770aaf637919b0011c4eb87b9ac6317079fb9800eb17c90dda05fc9de4ebc3"}, + {file = "rjsmin-1.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5d67ec09da46a492186e35cabca02a0d092eda5ef5b408a419b99ee4acf28d5c"}, + {file = "rjsmin-1.2.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d332e44a1b21ad63401cc7eebc81157e3d982d5fb503bb4faaea5028068d71e9"}, + {file = "rjsmin-1.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:113132a40ce7d03b2ced4fac215f0297338ed1c207394b739266efab7831988b"}, + {file = "rjsmin-1.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:122aa52bcf7ad9f12728d309012d1308c6ecfe4d6b09ea867a110dcad7b7728c"}, + {file = "rjsmin-1.2.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8a6710e358c661dcdcfd027e67de3afd72a6af4c88101dcf110de39e9bbded39"}, + {file = "rjsmin-1.2.1.tar.gz", hash = "sha256:1f982be8e011438777a94307279b40134a3935fc0f079312ee299725b8af5411"}, +] + [[package]] name = "s3transfer" version = "0.7.0" @@ -1853,6 +1998,28 @@ Willow = {version = ">=1.6.2,<1.7", extras = ["heif"]} docs = ["Sphinx (>=1.5.2)", "myst-parser (==0.18.1)", "pyenchant (>=3.1.1,<4)", "sphinx-autobuild (>=0.6.0)", "sphinx-copybutton (>=0.5,<1.0)", "sphinx-wagtail-theme (==6.1.1)", "sphinxcontrib-spelling (>=5.4.0,<6)"] testing = ["Jinja2 (>=3.0,<3.2)", "azure-mgmt-cdn (>=12.0,<13.0)", "azure-mgmt-frontdoor (>=1.0,<1.1)", "black (==22.3.0)", "boto3 (>=1.28,<2)", "coverage (>=3.7.0)", "curlylint (==0.13.1)", "django-pattern-library (>=0.7,<0.8)", "djhtml (==1.5.2)", "doc8 (==0.8.1)", "factory-boy (>=3.2)", "freezegun (>=0.3.8)", "polib (>=1.1,<2.0)", "python-dateutil (>=2.7)", "pytz (>=2014.7)", "ruff (==0.0.290)", "semgrep (==1.40.0)", "tblib (>=2.0,<3.0)"] +[[package]] +name = "wagtail-2fa" +version = "1.6.6" +description = "Two factor authentication for Wagtail" +optional = false +python-versions = ">=3.8" +files = [ + {file = "wagtail-2fa-1.6.6.tar.gz", hash = "sha256:179612a602b7dc5d8901f8e675197aba21c1ceef3f3f8bb61803f907f87ef8d1"}, + {file = "wagtail_2fa-1.6.6-py3-none-any.whl", hash = "sha256:b4c70658b616a616bce03174eda711f91717fcd66ec8cdb6558f8c6f6d327546"}, +] + +[package.dependencies] +Django = ">=3.2" +django-otp = ">=0.8.1" +qrcode = ">=6.1" +six = ">=1.14.0" +Wagtail = ">=4.1" + +[package.extras] +docs = ["sphinx (>=1.4.1)", "sphinx-rtd-theme (>=0.4.3)"] +test = ["coverage (==5.5)", "flake8 (==3.9.2)", "flake8-blind-except (==0.2.0)", "flake8-debugger (==4.0.0)", "isort (==5.9.3)", "pytest (==6.2.4)", "pytest-cov (==2.12.1)", "pytest-django (==4.4.0)"] + [[package]] name = "wagtail-modeladmin" version = "1.0.0" @@ -1936,4 +2103,4 @@ testing = ["Pillow (>=9.1.0,<11.0.0)", "Wand (>=0.6,<1.0)", "black (==22.3.0)", [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "9f76ff0202e291971fb5243813de065dd4a7fe381c0be53e3b26e2dcb046f671" +content-hash = "ee30a9b6340c3758e6c24332a4b9d7c49777c7563bf5a7e64908854b866ad24a" diff --git a/pyproject.toml b/pyproject.toml index 4bfeae5b..16c288bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ wagtailmenus = "^3.1.9" boto3 = "^1.29.1" django-storages = "^1.14.2" wagtail-modeladmin = "^1.0.0" +wagtail-2fa = "^1.6.6" [tool.poetry.group.dev.dependencies] @@ -37,6 +38,8 @@ pre-commit = "^3.5.0" djlint = "^1.34.0" django-extensions = "^3.2.3" ipython = "^8.18.1" +libsass = "^0.22.0" +django-compressor = "^4.4" [build-system] requires = ["poetry-core"] diff --git a/static/css/style.css b/static/css/style.css index d2424f70..20242210 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,2 +1,14 @@ +/* Prefix the classes defined here with cmsfr- */ +.cmsfr-hero { + background-size: cover; + background-position: center; + background-repeat: no-repeat; } -/*# sourceMappingURL=style.css.map */ \ No newline at end of file +.cmsfr-hero-large { + display: flex; + justify-content: center; + align-items: center; + text-align: center; } + +.cmsfr-hero-dark h1, .cmsfr-hero-dark p { + color: white; } diff --git a/static/css/style.sass b/static/css/style.sass index e69de29b..3ecd1381 100644 --- a/static/css/style.sass +++ b/static/css/style.sass @@ -0,0 +1,16 @@ +/* Prefix the classes defined here with cmsfr- */ + +.cmsfr-hero + background-size: cover + background-position: center + background-repeat: no-repeat + +.cmsfr-hero-large + display: flex + justify-content: center + align-items: center + text-align: center + +.cmsfr-hero-dark + & h1, p + color: white diff --git a/templates/base.html b/templates/base.html index b2a13755..99e343f0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,4 +1,4 @@ -{% load static dsfr_tags wagtailuserbar wagtailsettings_tags %} +{% load static dsfr_tags wagtailuserbar wagtailsettings_tags sass_tags %} {% get_settings %} @@ -6,12 +6,15 @@ + {% block title %}{% if title %}{{ title }} — {% endif %}{{ settings.content_manager.CmsDsfrConfig.site_title }}{% endblock title %} + {% dsfr_favicon %} {% dsfr_css %} - {% dsfr_favicon %} + + + {% block extra_css %}{% endblock extra_css %} - {% block title %}{% if title %}{{ title }} — {% endif %}{{ settings.content_manager.CmsDsfrConfig.site_title }}{% endblock title %} {% block tracking %} {% if settings.content_manager.AnalyticsSettings.head_scripts %} {{settings.content_manager.AnalyticsSettings.head_scripts|safe}}