diff --git a/wagtail/api/v2/tests/test_pages.py b/wagtail/api/v2/tests/test_pages.py index e9436db366..9ca9f8a313 100644 --- a/wagtail/api/v2/tests/test_pages.py +++ b/wagtail/api/v2/tests/test_pages.py @@ -1554,6 +1554,60 @@ class TestPageDetail(TestCase): self.assertEqual(response.status_code, 400) self.assertEqual(content, {"message": "'title' does not support nested fields"}) + def test_form_fields_on_form_page(self): + """ + Check that adding form_fields will correctly return then in the API response when declared + """ + + home_page = Page.objects.get(slug="home-page") + form_page = home_page.add_child(instance=models.FormPage(title="Contact us")) + + field_1 = models.FormField.objects.create( + page=form_page, + sort_order=1, + label="email", + field_type="email", + ) + field_2 = models.FormField.objects.create( + page=form_page, + sort_order=2, + label="message", + field_type="multiline", + required=True, + help_text="please be polite", + ) + + response = self.get_response(form_page.pk, fields="form_fields") + content = json.loads(response.content.decode("UTF-8")) + + self.assertEqual( + content["form_fields"], + [ + { + "id": field_1.pk, + "clean_name": "email", + "meta": {"type": "demosite.FormField"}, + "label": "email", + "help_text": "", + "required": True, + "field_type": "email", + "choices": "", + "default_value": "", + }, + { + "id": field_2.pk, + "clean_name": "message", + "meta": {"type": "demosite.FormField"}, + "label": "message", + "help_text": "please be polite", + "required": True, + "field_type": "multiline", + "choices": "", + "default_value": "", + }, + ], + ) + class TestPageFind(TestCase): fixtures = ["demosite.json"] diff --git a/wagtail/test/demosite/migrations/0001_initial.py b/wagtail/test/demosite/migrations/0001_initial.py index 7e58e57fab..def1985497 100644 --- a/wagtail/test/demosite/migrations/0001_initial.py +++ b/wagtail/test/demosite/migrations/0001_initial.py @@ -1204,4 +1204,120 @@ class Migration(migrations.Migration): ), preserve_default=True, ), + migrations.CreateModel( + name="FormPage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + related_name="+", + serialize=False, + to="wagtailcore.page", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + migrations.CreateModel( + name="FormField", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "sort_order", + models.IntegerField(blank=True, editable=False, null=True), + ), + ( + "clean_name", + models.CharField( + blank=True, + default="", + help_text="Safe name of the form field, the label converted to ascii_snake_case", + max_length=255, + verbose_name="name", + ), + ), + ( + "label", + models.CharField( + help_text="The label of the form field", + max_length=255, + verbose_name="label", + ), + ), + ( + "field_type", + models.CharField( + choices=[ + ("singleline", "Single line text"), + ("multiline", "Multi-line text"), + ("email", "Email"), + ("number", "Number"), + ("url", "URL"), + ("checkbox", "Checkbox"), + ("checkboxes", "Checkboxes"), + ("dropdown", "Drop down"), + ("multiselect", "Multiple select"), + ("radio", "Radio buttons"), + ("date", "Date"), + ("datetime", "Date/time"), + ("hidden", "Hidden field"), + ], + max_length=16, + verbose_name="field type", + ), + ), + ( + "required", + models.BooleanField(default=True, verbose_name="required"), + ), + ( + "choices", + models.TextField( + blank=True, + help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", + verbose_name="choices", + ), + ), + ( + "default_value", + models.TextField( + blank=True, + help_text="Default value. Comma or new line separated values supported for checkboxes.", + verbose_name="default value", + ), + ), + ( + "help_text", + models.CharField( + blank=True, max_length=255, verbose_name="help text" + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="form_fields", + to="demosite.formpage", + ), + ), + ], + options={ + "ordering": ["sort_order"], + "abstract": False, + }, + ), ] diff --git a/wagtail/test/demosite/models.py b/wagtail/test/demosite/models.py index c923174c3f..3e590a0e02 100644 --- a/wagtail/test/demosite/models.py +++ b/wagtail/test/demosite/models.py @@ -9,6 +9,7 @@ from taggit.models import TaggedItemBase from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel from wagtail.api import APIField +from wagtail.contrib.forms.models import AbstractForm, AbstractFormField from wagtail.fields import RichTextField from wagtail.images.api.fields import ImageRenditionField from wagtail.models import Orderable, Page @@ -677,3 +678,18 @@ ContactPage.promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), FieldPanel("feed_image"), ] + + +class FormField(AbstractFormField): + page = ParentalKey("FormPage", related_name="form_fields", on_delete=models.CASCADE) + + +class FormPage(AbstractForm): + + page_ptr = models.OneToOneField( + Page, parent_link=True, related_name="+", on_delete=models.CASCADE + ) + api_fields = [APIField("form_fields")] + content_panels = AbstractForm.content_panels + [ + InlinePanel("form_fields", label="Form fields") + ]