diff --git a/config/settings.py b/config/settings.py index e4d40a5e..436db6bb 100644 --- a/config/settings.py +++ b/config/settings.py @@ -50,6 +50,7 @@ INSTALLED_APPS = [ "storages", "dashboard", + "forms", "wagtail.contrib.forms", "wagtail.contrib.redirects", "wagtail.contrib.settings", diff --git a/content_manager/models.py b/content_manager/models.py index 9e82eae7..e4f6585c 100644 --- a/content_manager/models.py +++ b/content_manager/models.py @@ -1,14 +1,11 @@ from django.db import models from django.forms import widgets -from django.template.response import TemplateResponse from django.utils.translation import gettext_lazy as _ from modelcluster.fields import ParentalKey from modelcluster.models import ClusterableModel from modelcluster.tags import ClusterTaggableManager from taggit.models import Tag as TaggitTag, TaggedItemBase -from wagtail.admin.panels import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel, ObjectList, TabbedInterface -from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField -from wagtail.contrib.forms.panels import FormSubmissionsPanel +from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, TabbedInterface from wagtail.contrib.settings.models import BaseSiteSetting, register_setting from wagtail.fields import RichTextField from wagtail.images import get_image_model_string @@ -335,97 +332,3 @@ def get_categories(self): class Meta: verbose_name = _("Mega menu") - - -class FormField(AbstractFormField): - FORM_FIELD_CHOICES = ( - ("singleline", _("Single line text")), - ("multiline", _("Multi-line text")), - ("email", _("Email")), - ("number", _("Number")), - ("url", _("URL")), - ("checkbox", _("Checkbox")), - ("cmsfr_checkboxes", _("Checkboxes")), - ("dropdown", _("Drop down")), - ("cmsfr_radio", _("Radio buttons")), - ("cmsfr_date", _("Date")), - ("cmsfr_datetime", _("Date/time")), - ("hidden", _("Hidden field")), - ) - - page = ParentalKey("FormPage", on_delete=models.CASCADE, related_name="form_fields") - - -class FormPage(AbstractEmailForm): - intro = RichTextField(blank=True) - thank_you_text = RichTextField(blank=True) - - content_panels = AbstractEmailForm.content_panels + [ - FormSubmissionsPanel(), - FieldPanel("intro", heading="Introduction"), - InlinePanel("form_fields", label="Champs de formulaire"), - FieldPanel("thank_you_text", heading="Texte de remerciement"), - MultiFieldPanel( - [ - FieldRowPanel( - [ - FieldPanel("from_address", classname="col6"), - FieldPanel("to_address", classname="col6"), - ] - ), - FieldPanel("subject"), - ], - "Courriel", - help_text="Facultatif", - ), - ] - - class Meta: - verbose_name = "Page de formulaire" - verbose_name_plural = "Pages de formulaire" - - def serve(self, request, *args, **kwargs): - # These input widgets don't need the fr-input class - if request.method == "POST": - form = self.get_form(request.POST, request.FILES, page=self, user=request.user) - - if form.is_valid(): - form_submission = self.process_form_submission(form) - return self.render_landing_page(request, form_submission, *args, **kwargs) - else: - form = self.get_form(page=self, user=request.user) - - WIDGETS_NO_FR_INPUT = [ - widgets.CheckboxInput, - widgets.FileInput, - widgets.ClearableFileInput, - ] - - for visible in form.visible_fields(): - """ - Depending on the widget, we have to add some classes: - - on the outer group - - on the form field itsef - If a class is already set, we don't force the DSFR-specific classes. - """ - if "class" not in visible.field.widget.attrs: - if type(visible.field.widget) in [ - widgets.Select, - widgets.SelectMultiple, - ]: - visible.field.widget.attrs["class"] = "fr-select" - visible.field.widget.group_class = "fr-select-group" - elif isinstance(visible.field.widget, widgets.DateInput): - visible.field.widget.attrs["class"] = "fr-input" - visible.field.widget.attrs["type"] = "date" - elif isinstance(visible.field.widget, widgets.RadioSelect): - visible.field.widget.attrs["dsfr"] = "dsfr" - visible.field.widget.group_class = "fr-radio-group" - elif isinstance(visible.field.widget, widgets.CheckboxSelectMultiple): - visible.field.widget.attrs["dsfr"] = "dsfr" - elif type(visible.field.widget) not in WIDGETS_NO_FR_INPUT: - visible.field.widget.attrs["class"] = "fr-input" - - context = self.get_context(request) - context["form"] = form - return TemplateResponse(request, self.get_template(request), context) diff --git a/forms/__init__.py b/forms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/forms/admin.py b/forms/admin.py new file mode 100644 index 00000000..846f6b40 --- /dev/null +++ b/forms/admin.py @@ -0,0 +1 @@ +# Register your models here. diff --git a/forms/apps.py b/forms/apps.py new file mode 100644 index 00000000..ec22dd82 --- /dev/null +++ b/forms/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FormsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "forms" diff --git a/forms/locale/fr/LC_MESSAGES/django.mo b/forms/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 00000000..07ac9ad5 Binary files /dev/null and b/forms/locale/fr/LC_MESSAGES/django.mo differ diff --git a/forms/locale/fr/LC_MESSAGES/django.po b/forms/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..78a90b32 --- /dev/null +++ b/forms/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,68 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-06-25 10:02+0200\n" +"PO-Revision-Date: 2024-06-25 10:09+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 3.4.2\n" + +#: forms/models.py:14 +msgid "Single line text" +msgstr "Texte sur une ligne" + +#: forms/models.py:15 +msgid "Multi-line text" +msgstr "Texte sur plusieurs lignes" + +#: forms/models.py:16 +msgid "Email" +msgstr "Adresse e-mail" + +#: forms/models.py:17 +msgid "Number" +msgstr "Nombre" + +#: forms/models.py:18 +msgid "URL" +msgstr "URL" + +#: forms/models.py:19 +msgid "Checkbox" +msgstr "Case à cocher" + +#: forms/models.py:20 +msgid "Checkboxes" +msgstr "Cases à cocher" + +#: forms/models.py:21 +msgid "Drop down" +msgstr "Liste déroulante" + +#: forms/models.py:22 +msgid "Radio buttons" +msgstr "Boutons radio" + +#: forms/models.py:23 +msgid "Date" +msgstr "Date" + +#: forms/models.py:24 +msgid "Date/time" +msgstr "Date et heure" + +#: forms/models.py:25 +msgid "Hidden field" +msgstr "Champ caché" diff --git a/content_manager/migrations/0035_formpage_formfield.py b/forms/migrations/0001_initial.py similarity index 95% rename from content_manager/migrations/0035_formpage_formfield.py rename to forms/migrations/0001_initial.py index eae1bb14..f60f624e 100644 --- a/content_manager/migrations/0035_formpage_formfield.py +++ b/forms/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.6 on 2024-06-24 14:34 +# Generated by Django 5.0.6 on 2024-06-25 07:10 import django.db.models.deletion import modelcluster.fields @@ -8,8 +8,9 @@ class Migration(migrations.Migration): + initial = True + dependencies = [ - ("content_manager", "0034_alter_contentpage_body"), ("wagtailcore", "0093_uploadedfile"), ] @@ -111,9 +112,7 @@ class Migration(migrations.Migration): ( "page", modelcluster.fields.ParentalKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="form_fields", - to="content_manager.formpage", + on_delete=django.db.models.deletion.CASCADE, related_name="form_fields", to="forms.formpage" ), ), ], diff --git a/forms/migrations/__init__.py b/forms/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/forms/models.py b/forms/models.py new file mode 100644 index 00000000..b3ddc790 --- /dev/null +++ b/forms/models.py @@ -0,0 +1,103 @@ +from django.db import models +from django.forms import widgets +from django.template.response import TemplateResponse +from django.utils.translation import gettext_lazy as _ +from modelcluster.fields import ParentalKey +from wagtail.admin.panels import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel +from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField +from wagtail.contrib.forms.panels import FormSubmissionsPanel +from wagtail.fields import RichTextField + + +class FormField(AbstractFormField): + FORM_FIELD_CHOICES = ( + ("singleline", _("Single line text")), + ("multiline", _("Multi-line text")), + ("email", _("Email")), + ("number", _("Number")), + ("url", _("URL")), + ("checkbox", _("Checkbox")), + ("cmsfr_checkboxes", _("Checkboxes")), + ("dropdown", _("Drop down")), + ("cmsfr_radio", _("Radio buttons")), + ("cmsfr_date", _("Date")), + ("cmsfr_datetime", _("Date/time")), + ("hidden", _("Hidden field")), + ) + + page = ParentalKey("FormPage", on_delete=models.CASCADE, related_name="form_fields") + + +class FormPage(AbstractEmailForm): + intro = RichTextField(blank=True) + thank_you_text = RichTextField(blank=True) + + content_panels = AbstractEmailForm.content_panels + [ + FormSubmissionsPanel(), + FieldPanel("intro", heading="Introduction"), + InlinePanel("form_fields", label="Champs de formulaire"), + FieldPanel("thank_you_text", heading="Texte de remerciement"), + MultiFieldPanel( + [ + FieldRowPanel( + [ + FieldPanel("from_address", classname="col6"), + FieldPanel("to_address", classname="col6"), + ] + ), + FieldPanel("subject"), + ], + "Courriel", + help_text="Facultatif", + ), + ] + + class Meta: + verbose_name = "Page de formulaire" + verbose_name_plural = "Pages de formulaire" + + def serve(self, request, *args, **kwargs): + # These input widgets don't need the fr-input class + if request.method == "POST": + form = self.get_form(request.POST, request.FILES, page=self, user=request.user) + + if form.is_valid(): + form_submission = self.process_form_submission(form) + return self.render_landing_page(request, form_submission, *args, **kwargs) + else: + form = self.get_form(page=self, user=request.user) + + WIDGETS_NO_FR_INPUT = [ + widgets.CheckboxInput, + widgets.FileInput, + widgets.ClearableFileInput, + ] + + for visible in form.visible_fields(): + """ + Depending on the widget, we have to add some classes: + - on the outer group + - on the form field itsef + If a class is already set, we don't force the DSFR-specific classes. + """ + if "class" not in visible.field.widget.attrs: + if type(visible.field.widget) in [ + widgets.Select, + widgets.SelectMultiple, + ]: + visible.field.widget.attrs["class"] = "fr-select" + visible.field.widget.group_class = "fr-select-group" + elif isinstance(visible.field.widget, widgets.DateInput): + visible.field.widget.attrs["class"] = "fr-input" + visible.field.widget.attrs["type"] = "date" + elif isinstance(visible.field.widget, widgets.RadioSelect): + visible.field.widget.attrs["dsfr"] = "dsfr" + visible.field.widget.group_class = "fr-radio-group" + elif isinstance(visible.field.widget, widgets.CheckboxSelectMultiple): + visible.field.widget.attrs["dsfr"] = "dsfr" + elif type(visible.field.widget) not in WIDGETS_NO_FR_INPUT: + visible.field.widget.attrs["class"] = "fr-input" + + context = self.get_context(request) + context["form"] = form + return TemplateResponse(request, self.get_template(request), context) diff --git a/content_manager/templates/content_manager/form_page.html b/forms/templates/forms/form_page.html similarity index 82% rename from content_manager/templates/content_manager/form_page.html rename to forms/templates/forms/form_page.html index ba9fe79f..beba909e 100644 --- a/content_manager/templates/content_manager/form_page.html +++ b/forms/templates/forms/form_page.html @@ -12,11 +12,14 @@

{{ page.title }}

{% csrf_token %} {% dsfr_form %} -