From 134bd19bef529f0c205a48cedb8574ee0c52d436 Mon Sep 17 00:00:00 2001 From: Abdul Date: Sun, 20 Mar 2022 19:57:59 +0100 Subject: [PATCH] add ability for form builder to split choices by newline - fixes #3001 - keep support for comma separated lists if supplied --- CHANGELOG.txt | 1 + docs/releases/3.0.md | 2 + wagtail/contrib/forms/forms.py | 52 ++++++-- wagtail/contrib/forms/models.py | 9 +- wagtail/contrib/forms/tests/test_forms.py | 83 ++++++++++++ ...lter_extendedformfield_choices_and_more.py | 121 ++++++++++++++++++ 6 files changed, 253 insertions(+), 15 deletions(-) create mode 100644 wagtail/test/testapp/migrations/0065_alter_extendedformfield_choices_and_more.py diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f101802ff3..6f036000f4 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -46,6 +46,7 @@ Changelog * Fully remove the legacy sidebar, with slim sidebar replacing it for all users (Thibaud Colas) * Add support for adding custom attributes for link menu items in the slim sidebar (Thibaud Colas) * Implement new slim page editor header with breadcrumb (Steven Steinwand, Karl Hobley) + * Add the ability for choices to be separated by new lines instead of just commas within the form builder, commas will still be supported if used (Abdulmajeed Isa) * Fix: When using `simple_translations` ensure that the user is redirected to the page edit view when submitting for a single locale (Mitchel Cabuloy) * Fix: When previewing unsaved changes to `Form` pages, ensure that all added fields are correctly shown in the preview (Joshua Munn) * Fix: When Documents (e.g. PDFs) have been configured to be served inline via `WAGTAILDOCS_CONTENT_TYPES` & `WAGTAILDOCS_INLINE_CONTENT_TYPES` ensure that the filename is correctly set in the `Content-Disposition` header so that saving the files will use the correct filename (John-Scott Atlakson) diff --git a/docs/releases/3.0.md b/docs/releases/3.0.md index 4b5504c5bb..982ea3d982 100644 --- a/docs/releases/3.0.md +++ b/docs/releases/3.0.md @@ -75,6 +75,7 @@ class LandingPage(Page): * Validate to and from email addresses within form builder pages when using `AbstractEmailForm` (Jake Howard) * Add [`WAGTAILIMAGES_RENDITION_STORAGE`](wagtailimages_rendition_storage) setting to allow an alternative image rendition storage (Heather White) * Add [`wagtail_update_image_renditions` management command](wagtail_update_image_renditions) to regenerate image renditions or purge all existing renditions (Hitansh Shah, Onno Timmerman, Damian Moore) + * Add the ability for choices to be separated by new lines instead of just commas within the form builder, commas will still be supported if used (Abdulmajeed Isa) ### Bug fixes @@ -214,3 +215,4 @@ After setting the keyword argument, make sure to generate and run the migrations ### Removed support for Jinja2 2.x Jinja2 2.x is no longer supported as of this release; if you are using Jinja2 templating on your project, please upgrade to Jinja2 3.0 or above. + diff --git a/wagtail/contrib/forms/forms.py b/wagtail/contrib/forms/forms.py index 489f46c1f6..eefc28e140 100644 --- a/wagtail/contrib/forms/forms.py +++ b/wagtail/contrib/forms/forms.py @@ -47,26 +47,20 @@ class FormBuilder: return django.forms.DecimalField(**options) def create_dropdown_field(self, field, options): - options["choices"] = map( - lambda x: (x.strip(), x.strip()), field.choices.split(",") - ) + options["choices"] = self.get_formatted_field_choices(field) return django.forms.ChoiceField(**options) def create_multiselect_field(self, field, options): - options["choices"] = map( - lambda x: (x.strip(), x.strip()), field.choices.split(",") - ) + options["choices"] = self.get_formatted_field_choices(field) return django.forms.MultipleChoiceField(**options) def create_radio_field(self, field, options): - options["choices"] = map( - lambda x: (x.strip(), x.strip()), field.choices.split(",") - ) + options["choices"] = self.get_formatted_field_choices(field) return django.forms.ChoiceField(widget=django.forms.RadioSelect, **options) def create_checkboxes_field(self, field, options): - options["choices"] = [(x.strip(), x.strip()) for x in field.choices.split(",")] - options["initial"] = [x.strip() for x in field.default_value.split(",")] + options["choices"] = self.get_formatted_field_choices(field) + options["initial"] = self.get_formatted_field_initial(field) return django.forms.MultipleChoiceField( widget=django.forms.CheckboxSelectMultiple, **options ) @@ -101,6 +95,42 @@ class FormBuilder: "Must be one of: " + ", ".join(method_list), ) + def get_formatted_field_choices(self, field): + """ + Returns a list of choices [(string, string),] for the field. + Split the provided choices into a list, separated by new lines. + If no new lines in the provided choices, split by commas. + """ + + if "\n" in field.choices: + choices = map( + lambda x: ( + x.strip().rstrip(",").strip(), + x.strip().rstrip(",").strip(), + ), + field.choices.split("\r\n"), + ) + else: + choices = map(lambda x: (x.strip(), x.strip()), field.choices.split(",")) + + return choices + + def get_formatted_field_initial(self, field): + """ + Returns a list of initial values [string,] for the field. + Split the supplied default values into a list, separated by new lines. + If no new lines in the provided default values, split by commas. + """ + + if "\n" in field.default_value: + values = [ + x.strip().rstrip(",").strip() for x in field.default_value.split("\r\n") + ] + else: + values = [x.strip() for x in field.default_value.split(",")] + + return values + @property def formfields(self): formfields = OrderedDict() diff --git a/wagtail/contrib/forms/models.py b/wagtail/contrib/forms/models.py index 6abe4f5d96..4b72bb6844 100644 --- a/wagtail/contrib/forms/models.py +++ b/wagtail/contrib/forms/models.py @@ -102,14 +102,15 @@ class AbstractFormField(Orderable): verbose_name=_("choices"), blank=True, help_text=_( - "Comma separated list of choices. Only applicable in checkboxes, radio and dropdown." + "Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown." ), ) - default_value = models.CharField( + default_value = models.TextField( verbose_name=_("default value"), - max_length=255, blank=True, - help_text=_("Default value. Comma separated values supported for checkboxes."), + help_text=_( + "Default value. Comma or new line separated values supported for checkboxes." + ), ) help_text = models.CharField( verbose_name=_("help text"), max_length=255, blank=True diff --git a/wagtail/contrib/forms/tests/test_forms.py b/wagtail/contrib/forms/tests/test_forms.py index a71f57b014..59c3de6c57 100644 --- a/wagtail/contrib/forms/tests/test_forms.py +++ b/wagtail/contrib/forms/tests/test_forms.py @@ -222,6 +222,89 @@ class TestFormBuilder(TestCase): self.assertIn(get_field_clean_name(unsaved_field_1.label), fb.formfields) self.assertIn(get_field_clean_name(unsaved_field_2.label), fb.formfields) + def test_newline_value_separation_in_choices_and_default_value_fields(self): + """Ensure that the new line present between input choices or values gets formatted into choices or value list + respectively as an alternative to commas. + """ + multiselect_field = FormField.objects.create( + page=self.form_page, + sort_order=2, + label="Your favorite colors", + field_type="multiselect", + required=True, + choices="red\r\nblue\r\ngreen", + ) + self.form_page.form_fields.add(multiselect_field) + + dropdown_field = FormField.objects.create( + page=self.form_page, + sort_order=2, + label="Pick your next destination", + field_type="dropdown", + required=True, + choices="hawaii\r\nparis\r\nabuja", + ) + self.form_page.form_fields.add(dropdown_field) + + checkboxes_field = FormField.objects.create( + page=self.form_page, + sort_order=3, + label="Do you possess these attributes", + field_type="checkboxes", + required=False, + choices="good, kind and gentle.\r\nstrong, bold and brave.", + ) + self.form_page.form_fields.add(checkboxes_field) + + radio_field = FormField.objects.create( + page=self.form_page, + sort_order=2, + label="Your favorite animal", + help_text="Choose one", + field_type="radio", + required=True, + choices="cat\r\ndog\r\nbird", + ) + self.form_page.form_fields.add(radio_field) + + checkboxes_field_with_default_value = FormField.objects.create( + page=self.form_page, + sort_order=3, + label="Choose the correct answer", + field_type="checkboxes", + required=False, + choices="a\r\nb\r\nc", + default_value="a\r\nc", + ) + self.form_page.form_fields.add(checkboxes_field_with_default_value) + + fb = FormBuilder(self.form_page.get_form_fields()) + form_class = fb.get_form_class() + + self.assertEqual( + [("red", "red"), ("blue", "blue"), ("green", "green")], + form_class.base_fields["your_favorite_colors"].choices, + ) + self.assertEqual( + [("cat", "cat"), ("dog", "dog"), ("bird", "bird")], + form_class.base_fields["your_favorite_animal"].choices, + ) + self.assertEqual( + [ + ("good, kind and gentle.", "good, kind and gentle."), + ("strong, bold and brave.", "strong, bold and brave."), + ], + form_class.base_fields["do_you_possess_these_attributes"].choices, + ) + self.assertEqual( + [("hawaii", "hawaii"), ("paris", "paris"), ("abuja", "abuja")], + form_class.base_fields["pick_your_next_destination"].choices, + ) + self.assertEqual( + ["a", "c"], + form_class.base_fields["choose_the_correct_answer"].initial, + ) + class TestCustomFormBuilder(TestCase): def setUp(self): diff --git a/wagtail/test/testapp/migrations/0065_alter_extendedformfield_choices_and_more.py b/wagtail/test/testapp/migrations/0065_alter_extendedformfield_choices_and_more.py new file mode 100644 index 0000000000..228746a73d --- /dev/null +++ b/wagtail/test/testapp/migrations/0065_alter_extendedformfield_choices_and_more.py @@ -0,0 +1,121 @@ +# Generated by Django 4.0.3 on 2022-03-22 13:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tests", "0064_alter_formpage_from_address_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="extendedformfield", + name="choices", + field=models.TextField( + blank=True, + help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", + verbose_name="choices", + ), + ), + migrations.AlterField( + model_name="extendedformfield", + name="default_value", + field=models.TextField( + blank=True, + help_text="Default value. Comma or new line separated values supported for checkboxes.", + verbose_name="default value", + ), + ), + migrations.AlterField( + model_name="formfield", + name="choices", + field=models.TextField( + blank=True, + help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", + verbose_name="choices", + ), + ), + migrations.AlterField( + model_name="formfield", + name="default_value", + field=models.TextField( + blank=True, + help_text="Default value. Comma or new line separated values supported for checkboxes.", + verbose_name="default value", + ), + ), + migrations.AlterField( + model_name="formfieldforcustomlistviewpage", + name="choices", + field=models.TextField( + blank=True, + help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", + verbose_name="choices", + ), + ), + migrations.AlterField( + model_name="formfieldforcustomlistviewpage", + name="default_value", + field=models.TextField( + blank=True, + help_text="Default value. Comma or new line separated values supported for checkboxes.", + verbose_name="default value", + ), + ), + migrations.AlterField( + model_name="formfieldwithcustomsubmission", + name="choices", + field=models.TextField( + blank=True, + help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", + verbose_name="choices", + ), + ), + migrations.AlterField( + model_name="formfieldwithcustomsubmission", + name="default_value", + field=models.TextField( + blank=True, + help_text="Default value. Comma or new line separated values supported for checkboxes.", + verbose_name="default value", + ), + ), + migrations.AlterField( + model_name="jadeformfield", + name="choices", + field=models.TextField( + blank=True, + help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", + verbose_name="choices", + ), + ), + migrations.AlterField( + model_name="jadeformfield", + name="default_value", + field=models.TextField( + blank=True, + help_text="Default value. Comma or new line separated values supported for checkboxes.", + verbose_name="default value", + ), + ), + migrations.AlterField( + model_name="redirectformfield", + name="choices", + field=models.TextField( + blank=True, + help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", + verbose_name="choices", + ), + ), + migrations.AlterField( + model_name="redirectformfield", + name="default_value", + field=models.TextField( + blank=True, + help_text="Default value. Comma or new line separated values supported for checkboxes.", + verbose_name="default value", + ), + ), + ]