Repository: wagtail/wagtail

Issue:
Using commas in Checkbox option text
When building a form with form-builder, whats the workaround for using a comma inside one option's text, lets say, for a "Accept conditions" checkbox, there is usually a long text which may include commas. But if commas are used, they are taken as a new options, which is wrong.



Discussion:
As part of the Wagtail’s first Kickstarter campaign, I’m working on this!
https://www.kickstarter.com/projects/noripyt/wagtails-first-hatch
This is the workaround I came up with for dealing with commas in choices. By subclassing `FormBuilder` and splitting on newlines instead of commas. You can also separate value and label with a pipe character ( | ). If no newline is detected it falls back to the original behavior of splitting on comma.

``` python
class CustomFormBuilder(FormBuilder):

    def format_choices(self, field):
        if '\n' in field.choices:
            choice_list = field.choices.split('\r\n')
            choices = []
            for x in choice_list:
                if '|' in x:
                    value,label = x.split('|')
                    choices.append((value,label))
                else:
                    choices = map(
                        lambda x: (x.strip(), x.strip()),
                        choice_list
                    )
        else:
            # fallback on default wagtail way
            choices = map(
                lambda x: (x.strip(), x.strip()),
                field.choices.split(',')
            )

        return choices

    def create_checkboxes_field(self, field, options):
        options['choices'] = self.format_choices(field)
        options['initial'] = [x.strip() for x in field.default_value.split(',')]
        return forms.MultipleChoiceField(
            widget=forms.CheckboxSelectMultiple, **options
        )

    def create_dropdown_field(self, field, options):
        options['choices'] = self.format_choices(field)
        return forms.ChoiceField(**options)

    def create_multiselect_field(self, field, options):
        options['choices'] = self.format_choices(field)
        return forms.MultipleChoiceField(**options)

    def create_radio_field(self, field, options):
        options['choices'] = self.format_choices(field)
        return forms.ChoiceField(widget=forms.RadioSelect, **options)

```
I think the newline approach could be good, means this field is a text area instead of a single line.
However, we need to think of existing implementations that use commas.

The backwards compatible approach would be to allow both to exist (as per the implementation above).

Marking this as a good first issue.
@lb- Started working on it. Should I include the feature of separating the value and label with a pipe character?
Nevermind. I'll just focus on the newline approach only.

Resolving patch:
diff --git a/wagtail/contrib/forms/forms.py b/wagtail/contrib/forms/forms.py
--- a/wagtail/contrib/forms/forms.py
+++ b/wagtail/contrib/forms/forms.py
@@ -47,26 +47,20 @@ def create_number_field(self, field, options):
         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 @@ def get_create_field_function(self, type):
                 "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
--- 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


Test patch:
diff --git a/wagtail/contrib/forms/tests/test_forms.py b/wagtail/contrib/forms/tests/test_forms.py
--- a/wagtail/contrib/forms/tests/test_forms.py
+++ b/wagtail/contrib/forms/tests/test_forms.py
@@ -222,6 +222,89 @@ def test_unsaved_fields_in_form_builder_formfields(self):
         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
--- /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",
+            ),
+        ),
+    ]