We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
__str__ method doesn't return a string.
This code sample:

    >>> from wagtail.contrib.forms.models import FormSubmission

    >>> FormSubmission.objects.count()
    1
    >>> FormSubmission.objects.first()
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "lib64/python3.10/site-packages/django/db/models/base.py", line 580, in __repr__
        return "<%s: %s>" % (self.__class__.__name__, self)
      TypeError: __str__ returned non-string (type dict)

This method:

https://github.com/wagtail/wagtail/blob/18ad15a18f8e533b858ccde7d060b9d4e85dcfd4/wagtail/contrib/forms/models.py#L61-L62

should be:

    def __str__(self):
        return f"{self.form_data}"

 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wagtail/contrib/forms/models.py`
Content:
```
1 import datetime
2 import os
3 
4 from django.conf import settings
5 from django.core.serializers.json import DjangoJSONEncoder
6 from django.core.validators import validate_email
7 from django.db import models
8 from django.template.response import TemplateResponse
9 from django.utils.formats import date_format
10 from django.utils.translation import gettext_lazy as _
11 
12 from wagtail.admin.mail import send_mail
13 from wagtail.admin.panels import FieldPanel
14 from wagtail.contrib.forms.utils import get_field_clean_name
15 from wagtail.models import Orderable, Page
16 
17 from .forms import FormBuilder, WagtailAdminFormPageForm
18 
19 FORM_FIELD_CHOICES = (
20     ("singleline", _("Single line text")),
21     ("multiline", _("Multi-line text")),
22     ("email", _("Email")),
23     ("number", _("Number")),
24     ("url", _("URL")),
25     ("checkbox", _("Checkbox")),
26     ("checkboxes", _("Checkboxes")),
27     ("dropdown", _("Drop down")),
28     ("multiselect", _("Multiple select")),
29     ("radio", _("Radio buttons")),
30     ("date", _("Date")),
31     ("datetime", _("Date/time")),
32     ("hidden", _("Hidden field")),
33 )
34 
35 
36 class AbstractFormSubmission(models.Model):
37     """
38     Data for a form submission.
39 
40     You can create custom submission model based on this abstract model.
41     For example, if you need to save additional data or a reference to a user.
42     """
43 
44     form_data = models.JSONField(encoder=DjangoJSONEncoder)
45     page = models.ForeignKey(Page, on_delete=models.CASCADE)
46 
47     submit_time = models.DateTimeField(verbose_name=_("submit time"), auto_now_add=True)
48 
49     def get_data(self):
50         """
51         Returns dict with form data.
52 
53         You can override this method to add additional data.
54         """
55 
56         return {
57             **self.form_data,
58             "submit_time": self.submit_time,
59         }
60 
61     def __str__(self):
62         return self.form_data
63 
64     class Meta:
65         abstract = True
66         verbose_name = _("form submission")
67         verbose_name_plural = _("form submissions")
68 
69 
70 class FormSubmission(AbstractFormSubmission):
71     """Data for a Form submission."""
72 
73 
74 class AbstractFormField(Orderable):
75     """
76     Database Fields required for building a Django Form field.
77     """
78 
79     clean_name = models.CharField(
80         verbose_name=_("name"),
81         max_length=255,
82         blank=True,
83         default="",
84         help_text=_(
85             "Safe name of the form field, the label converted to ascii_snake_case"
86         ),
87     )
88     label = models.CharField(
89         verbose_name=_("label"),
90         max_length=255,
91         help_text=_("The label of the form field"),
92     )
93     field_type = models.CharField(
94         verbose_name=_("field type"), max_length=16, choices=FORM_FIELD_CHOICES
95     )
96     required = models.BooleanField(verbose_name=_("required"), default=True)
97     choices = models.TextField(
98         verbose_name=_("choices"),
99         blank=True,
100         help_text=_(
101             "Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown."
102         ),
103     )
104     default_value = models.TextField(
105         verbose_name=_("default value"),
106         blank=True,
107         help_text=_(
108             "Default value. Comma or new line separated values supported for checkboxes."
109         ),
110     )
111     help_text = models.CharField(
112         verbose_name=_("help text"), max_length=255, blank=True
113     )
114 
115     panels = [
116         FieldPanel("label"),
117         FieldPanel("help_text"),
118         FieldPanel("required"),
119         FieldPanel("field_type", classname="formbuilder-type"),
120         FieldPanel("choices", classname="formbuilder-choices"),
121         FieldPanel("default_value", classname="formbuilder-default"),
122     ]
123 
124     def get_field_clean_name(self):
125         """
126         Prepare an ascii safe lower_snake_case variant of the field name to use as the field key.
127         This key is used to reference the field responses in the JSON store and as the field name in forms.
128         Called for new field creation, validation of duplicate labels and form previews.
129         When called, does not have access to the Page, nor its own id as the record is not yet created.
130         """
131 
132         return get_field_clean_name(self.label)
133 
134     def save(self, *args, **kwargs):
135         """
136         When new fields are created, generate a template safe ascii name to use as the
137         JSON storage reference for this field. Previously created fields will be updated
138         to use the legacy unidecode method via checks & _migrate_legacy_clean_name.
139         We do not want to update the clean name on any subsequent changes to the label
140         as this would invalidate any previously submitted data.
141         """
142 
143         is_new = self.pk is None
144         if is_new:
145             clean_name = self.get_field_clean_name()
146             self.clean_name = clean_name
147 
148         super().save(*args, **kwargs)
149 
150     class Meta:
151         abstract = True
152         ordering = ["sort_order"]
153 
154 
155 class AbstractForm(Page):
156     """
157     A Form Page. Pages implementing a form should inherit from it
158     """
159 
160     base_form_class = WagtailAdminFormPageForm
161 
162     form_builder = FormBuilder
163 
164     submissions_list_view_class = None
165 
166     def __init__(self, *args, **kwargs):
167         super().__init__(*args, **kwargs)
168         if not hasattr(self, "landing_page_template"):
169             name, ext = os.path.splitext(self.template)
170             self.landing_page_template = name + "_landing" + ext
171 
172     class Meta:
173         abstract = True
174 
175     def get_form_fields(self):
176         """
177         Form page expects `form_fields` to be declared.
178         If you want to change backwards relation name,
179         you need to override this method.
180         """
181 
182         return self.form_fields.all()
183 
184     def get_data_fields(self):
185         """
186         Returns a list of tuples with (field_name, field_label).
187         """
188 
189         data_fields = [
190             ("submit_time", _("Submission date")),
191         ]
192         data_fields += [
193             (field.clean_name, field.label) for field in self.get_form_fields()
194         ]
195 
196         return data_fields
197 
198     def get_form_class(self):
199         fb = self.form_builder(self.get_form_fields())
200         return fb.get_form_class()
201 
202     def get_form_parameters(self):
203         return {}
204 
205     def get_form(self, *args, **kwargs):
206         form_class = self.get_form_class()
207         form_params = self.get_form_parameters()
208         form_params.update(kwargs)
209 
210         return form_class(*args, **form_params)
211 
212     def get_landing_page_template(self, request, *args, **kwargs):
213         return self.landing_page_template
214 
215     def get_submission_class(self):
216         """
217         Returns submission class.
218 
219         You can override this method to provide custom submission class.
220         Your class must be inherited from AbstractFormSubmission.
221         """
222 
223         return FormSubmission
224 
225     def get_submissions_list_view_class(self):
226         from .views import SubmissionsListView
227 
228         return self.submissions_list_view_class or SubmissionsListView
229 
230     def process_form_submission(self, form):
231         """
232         Accepts form instance with submitted data, user and page.
233         Creates submission instance.
234 
235         You can override this method if you want to have custom creation logic.
236         For example, if you want to save reference to a user.
237         """
238 
239         return self.get_submission_class().objects.create(
240             form_data=form.cleaned_data,
241             page=self,
242         )
243 
244     def render_landing_page(self, request, form_submission=None, *args, **kwargs):
245         """
246         Renders the landing page.
247 
248         You can override this method to return a different HttpResponse as
249         landing page. E.g. you could return a redirect to a separate page.
250         """
251         context = self.get_context(request)
252         context["form_submission"] = form_submission
253         return TemplateResponse(
254             request, self.get_landing_page_template(request), context
255         )
256 
257     def serve_submissions_list_view(self, request, *args, **kwargs):
258         """
259         Returns list submissions view for admin.
260 
261         `list_submissions_view_class` can bse set to provide custom view class.
262         Your class must be inherited from SubmissionsListView.
263         """
264         view = self.get_submissions_list_view_class().as_view()
265         return view(request, form_page=self, *args, **kwargs)
266 
267     def serve(self, request, *args, **kwargs):
268         if request.method == "POST":
269             form = self.get_form(
270                 request.POST, request.FILES, page=self, user=request.user
271             )
272 
273             if form.is_valid():
274                 form_submission = self.process_form_submission(form)
275                 return self.render_landing_page(
276                     request, form_submission, *args, **kwargs
277                 )
278         else:
279             form = self.get_form(page=self, user=request.user)
280 
281         context = self.get_context(request)
282         context["form"] = form
283         return TemplateResponse(request, self.get_template(request), context)
284 
285     preview_modes = [
286         ("form", _("Form")),
287         ("landing", _("Landing page")),
288     ]
289 
290     def serve_preview(self, request, mode_name):
291         if mode_name == "landing":
292             return self.render_landing_page(request)
293         else:
294             return super().serve_preview(request, mode_name)
295 
296 
297 def validate_to_address(value):
298     for address in value.split(","):
299         validate_email(address.strip())
300 
301 
302 class AbstractEmailForm(AbstractForm):
303     """
304     A Form Page that sends email. Pages implementing a form to be send to an email should inherit from it
305     """
306 
307     to_address = models.CharField(
308         verbose_name=_("to address"),
309         max_length=255,
310         blank=True,
311         help_text=_(
312             "Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma."
313         ),
314         validators=[validate_to_address],
315     )
316     from_address = models.EmailField(
317         verbose_name=_("from address"), max_length=255, blank=True
318     )
319     subject = models.CharField(verbose_name=_("subject"), max_length=255, blank=True)
320 
321     def process_form_submission(self, form):
322         submission = super().process_form_submission(form)
323         if self.to_address:
324             self.send_mail(form)
325         return submission
326 
327     def send_mail(self, form):
328         addresses = [x.strip() for x in self.to_address.split(",")]
329         send_mail(
330             self.subject,
331             self.render_email(form),
332             addresses,
333             self.from_address,
334         )
335 
336     def render_email(self, form):
337         content = []
338 
339         cleaned_data = form.cleaned_data
340         for field in form:
341             if field.name not in cleaned_data:
342                 continue
343 
344             value = cleaned_data.get(field.name)
345 
346             if isinstance(value, list):
347                 value = ", ".join(value)
348 
349             # Format dates and datetimes with SHORT_DATE(TIME)_FORMAT
350             if isinstance(value, datetime.datetime):
351                 value = date_format(value, settings.SHORT_DATETIME_FORMAT)
352             elif isinstance(value, datetime.date):
353                 value = date_format(value, settings.SHORT_DATE_FORMAT)
354 
355             content.append("{}: {}".format(field.label, value))
356 
357         return "\n".join(content)
358 
359     class Meta:
360         abstract = True
361
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -59,7 +59,7 @@
         }
 
     def __str__(self):
-        return self.form_data
+        return f"{self.form_data}"
 
     class Meta:
         abstract = True

{"golden_diff": "diff --git a/wagtail/contrib/forms/models.py b/wagtail/contrib/forms/models.py\n--- a/wagtail/contrib/forms/models.py\n+++ b/wagtail/contrib/forms/models.py\n@@ -59,7 +59,7 @@\n         }\n \n     def __str__(self):\n-        return self.form_data\n+        return f\"{self.form_data}\"\n \n     class Meta:\n         abstract = True\n", "issue": "__str__ method doesn't return a string.\nThis code sample:\r\n\r\n    >>> from wagtail.contrib.forms.models import FormSubmission\r\n\r\n    >>> FormSubmission.objects.count()\r\n    1\r\n    >>> FormSubmission.objects.first()\r\n    Traceback (most recent call last):\r\n      File \"<console>\", line 1, in <module>\r\n      File \"lib64/python3.10/site-packages/django/db/models/base.py\", line 580, in __repr__\r\n        return \"<%s: %s>\" % (self.__class__.__name__, self)\r\n      TypeError: __str__ returned non-string (type dict)\r\n\r\nThis method:\r\n\r\nhttps://github.com/wagtail/wagtail/blob/18ad15a18f8e533b858ccde7d060b9d4e85dcfd4/wagtail/contrib/forms/models.py#L61-L62\r\n\r\nshould be:\r\n\r\n    def __str__(self):\r\n        return f\"{self.form_data}\"\r\n\r\n \n", "before_files": [{"content": "import datetime\nimport os\n\nfrom django.conf import settings\nfrom django.core.serializers.json import DjangoJSONEncoder\nfrom django.core.validators import validate_email\nfrom django.db import models\nfrom django.template.response import TemplateResponse\nfrom django.utils.formats import date_format\nfrom django.utils.translation import gettext_lazy as _\n\nfrom wagtail.admin.mail import send_mail\nfrom wagtail.admin.panels import FieldPanel\nfrom wagtail.contrib.forms.utils import get_field_clean_name\nfrom wagtail.models import Orderable, Page\n\nfrom .forms import FormBuilder, WagtailAdminFormPageForm\n\nFORM_FIELD_CHOICES = (\n    (\"singleline\", _(\"Single line text\")),\n    (\"multiline\", _(\"Multi-line text\")),\n    (\"email\", _(\"Email\")),\n    (\"number\", _(\"Number\")),\n    (\"url\", _(\"URL\")),\n    (\"checkbox\", _(\"Checkbox\")),\n    (\"checkboxes\", _(\"Checkboxes\")),\n    (\"dropdown\", _(\"Drop down\")),\n    (\"multiselect\", _(\"Multiple select\")),\n    (\"radio\", _(\"Radio buttons\")),\n    (\"date\", _(\"Date\")),\n    (\"datetime\", _(\"Date/time\")),\n    (\"hidden\", _(\"Hidden field\")),\n)\n\n\nclass AbstractFormSubmission(models.Model):\n    \"\"\"\n    Data for a form submission.\n\n    You can create custom submission model based on this abstract model.\n    For example, if you need to save additional data or a reference to a user.\n    \"\"\"\n\n    form_data = models.JSONField(encoder=DjangoJSONEncoder)\n    page = models.ForeignKey(Page, on_delete=models.CASCADE)\n\n    submit_time = models.DateTimeField(verbose_name=_(\"submit time\"), auto_now_add=True)\n\n    def get_data(self):\n        \"\"\"\n        Returns dict with form data.\n\n        You can override this method to add additional data.\n        \"\"\"\n\n        return {\n            **self.form_data,\n            \"submit_time\": self.submit_time,\n        }\n\n    def __str__(self):\n        return self.form_data\n\n    class Meta:\n        abstract = True\n        verbose_name = _(\"form submission\")\n        verbose_name_plural = _(\"form submissions\")\n\n\nclass FormSubmission(AbstractFormSubmission):\n    \"\"\"Data for a Form submission.\"\"\"\n\n\nclass AbstractFormField(Orderable):\n    \"\"\"\n    Database Fields required for building a Django Form field.\n    \"\"\"\n\n    clean_name = models.CharField(\n        verbose_name=_(\"name\"),\n        max_length=255,\n        blank=True,\n        default=\"\",\n        help_text=_(\n            \"Safe name of the form field, the label converted to ascii_snake_case\"\n        ),\n    )\n    label = models.CharField(\n        verbose_name=_(\"label\"),\n        max_length=255,\n        help_text=_(\"The label of the form field\"),\n    )\n    field_type = models.CharField(\n        verbose_name=_(\"field type\"), max_length=16, choices=FORM_FIELD_CHOICES\n    )\n    required = models.BooleanField(verbose_name=_(\"required\"), default=True)\n    choices = models.TextField(\n        verbose_name=_(\"choices\"),\n        blank=True,\n        help_text=_(\n            \"Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.\"\n        ),\n    )\n    default_value = models.TextField(\n        verbose_name=_(\"default value\"),\n        blank=True,\n        help_text=_(\n            \"Default value. Comma or new line separated values supported for checkboxes.\"\n        ),\n    )\n    help_text = models.CharField(\n        verbose_name=_(\"help text\"), max_length=255, blank=True\n    )\n\n    panels = [\n        FieldPanel(\"label\"),\n        FieldPanel(\"help_text\"),\n        FieldPanel(\"required\"),\n        FieldPanel(\"field_type\", classname=\"formbuilder-type\"),\n        FieldPanel(\"choices\", classname=\"formbuilder-choices\"),\n        FieldPanel(\"default_value\", classname=\"formbuilder-default\"),\n    ]\n\n    def get_field_clean_name(self):\n        \"\"\"\n        Prepare an ascii safe lower_snake_case variant of the field name to use as the field key.\n        This key is used to reference the field responses in the JSON store and as the field name in forms.\n        Called for new field creation, validation of duplicate labels and form previews.\n        When called, does not have access to the Page, nor its own id as the record is not yet created.\n        \"\"\"\n\n        return get_field_clean_name(self.label)\n\n    def save(self, *args, **kwargs):\n        \"\"\"\n        When new fields are created, generate a template safe ascii name to use as the\n        JSON storage reference for this field. Previously created fields will be updated\n        to use the legacy unidecode method via checks & _migrate_legacy_clean_name.\n        We do not want to update the clean name on any subsequent changes to the label\n        as this would invalidate any previously submitted data.\n        \"\"\"\n\n        is_new = self.pk is None\n        if is_new:\n            clean_name = self.get_field_clean_name()\n            self.clean_name = clean_name\n\n        super().save(*args, **kwargs)\n\n    class Meta:\n        abstract = True\n        ordering = [\"sort_order\"]\n\n\nclass AbstractForm(Page):\n    \"\"\"\n    A Form Page. Pages implementing a form should inherit from it\n    \"\"\"\n\n    base_form_class = WagtailAdminFormPageForm\n\n    form_builder = FormBuilder\n\n    submissions_list_view_class = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if not hasattr(self, \"landing_page_template\"):\n            name, ext = os.path.splitext(self.template)\n            self.landing_page_template = name + \"_landing\" + ext\n\n    class Meta:\n        abstract = True\n\n    def get_form_fields(self):\n        \"\"\"\n        Form page expects `form_fields` to be declared.\n        If you want to change backwards relation name,\n        you need to override this method.\n        \"\"\"\n\n        return self.form_fields.all()\n\n    def get_data_fields(self):\n        \"\"\"\n        Returns a list of tuples with (field_name, field_label).\n        \"\"\"\n\n        data_fields = [\n            (\"submit_time\", _(\"Submission date\")),\n        ]\n        data_fields += [\n            (field.clean_name, field.label) for field in self.get_form_fields()\n        ]\n\n        return data_fields\n\n    def get_form_class(self):\n        fb = self.form_builder(self.get_form_fields())\n        return fb.get_form_class()\n\n    def get_form_parameters(self):\n        return {}\n\n    def get_form(self, *args, **kwargs):\n        form_class = self.get_form_class()\n        form_params = self.get_form_parameters()\n        form_params.update(kwargs)\n\n        return form_class(*args, **form_params)\n\n    def get_landing_page_template(self, request, *args, **kwargs):\n        return self.landing_page_template\n\n    def get_submission_class(self):\n        \"\"\"\n        Returns submission class.\n\n        You can override this method to provide custom submission class.\n        Your class must be inherited from AbstractFormSubmission.\n        \"\"\"\n\n        return FormSubmission\n\n    def get_submissions_list_view_class(self):\n        from .views import SubmissionsListView\n\n        return self.submissions_list_view_class or SubmissionsListView\n\n    def process_form_submission(self, form):\n        \"\"\"\n        Accepts form instance with submitted data, user and page.\n        Creates submission instance.\n\n        You can override this method if you want to have custom creation logic.\n        For example, if you want to save reference to a user.\n        \"\"\"\n\n        return self.get_submission_class().objects.create(\n            form_data=form.cleaned_data,\n            page=self,\n        )\n\n    def render_landing_page(self, request, form_submission=None, *args, **kwargs):\n        \"\"\"\n        Renders the landing page.\n\n        You can override this method to return a different HttpResponse as\n        landing page. E.g. you could return a redirect to a separate page.\n        \"\"\"\n        context = self.get_context(request)\n        context[\"form_submission\"] = form_submission\n        return TemplateResponse(\n            request, self.get_landing_page_template(request), context\n        )\n\n    def serve_submissions_list_view(self, request, *args, **kwargs):\n        \"\"\"\n        Returns list submissions view for admin.\n\n        `list_submissions_view_class` can bse set to provide custom view class.\n        Your class must be inherited from SubmissionsListView.\n        \"\"\"\n        view = self.get_submissions_list_view_class().as_view()\n        return view(request, form_page=self, *args, **kwargs)\n\n    def serve(self, request, *args, **kwargs):\n        if request.method == \"POST\":\n            form = self.get_form(\n                request.POST, request.FILES, page=self, user=request.user\n            )\n\n            if form.is_valid():\n                form_submission = self.process_form_submission(form)\n                return self.render_landing_page(\n                    request, form_submission, *args, **kwargs\n                )\n        else:\n            form = self.get_form(page=self, user=request.user)\n\n        context = self.get_context(request)\n        context[\"form\"] = form\n        return TemplateResponse(request, self.get_template(request), context)\n\n    preview_modes = [\n        (\"form\", _(\"Form\")),\n        (\"landing\", _(\"Landing page\")),\n    ]\n\n    def serve_preview(self, request, mode_name):\n        if mode_name == \"landing\":\n            return self.render_landing_page(request)\n        else:\n            return super().serve_preview(request, mode_name)\n\n\ndef validate_to_address(value):\n    for address in value.split(\",\"):\n        validate_email(address.strip())\n\n\nclass AbstractEmailForm(AbstractForm):\n    \"\"\"\n    A Form Page that sends email. Pages implementing a form to be send to an email should inherit from it\n    \"\"\"\n\n    to_address = models.CharField(\n        verbose_name=_(\"to address\"),\n        max_length=255,\n        blank=True,\n        help_text=_(\n            \"Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.\"\n        ),\n        validators=[validate_to_address],\n    )\n    from_address = models.EmailField(\n        verbose_name=_(\"from address\"), max_length=255, blank=True\n    )\n    subject = models.CharField(verbose_name=_(\"subject\"), max_length=255, blank=True)\n\n    def process_form_submission(self, form):\n        submission = super().process_form_submission(form)\n        if self.to_address:\n            self.send_mail(form)\n        return submission\n\n    def send_mail(self, form):\n        addresses = [x.strip() for x in self.to_address.split(\",\")]\n        send_mail(\n            self.subject,\n            self.render_email(form),\n            addresses,\n            self.from_address,\n        )\n\n    def render_email(self, form):\n        content = []\n\n        cleaned_data = form.cleaned_data\n        for field in form:\n            if field.name not in cleaned_data:\n                continue\n\n            value = cleaned_data.get(field.name)\n\n            if isinstance(value, list):\n                value = \", \".join(value)\n\n            # Format dates and datetimes with SHORT_DATE(TIME)_FORMAT\n            if isinstance(value, datetime.datetime):\n                value = date_format(value, settings.SHORT_DATETIME_FORMAT)\n            elif isinstance(value, datetime.date):\n                value = date_format(value, settings.SHORT_DATE_FORMAT)\n\n            content.append(\"{}: {}\".format(field.label, value))\n\n        return \"\\n\".join(content)\n\n    class Meta:\n        abstract = True\n", "path": "wagtail/contrib/forms/models.py"}], "after_files": [{"content": "import datetime\nimport os\n\nfrom django.conf import settings\nfrom django.core.serializers.json import DjangoJSONEncoder\nfrom django.core.validators import validate_email\nfrom django.db import models\nfrom django.template.response import TemplateResponse\nfrom django.utils.formats import date_format\nfrom django.utils.translation import gettext_lazy as _\n\nfrom wagtail.admin.mail import send_mail\nfrom wagtail.admin.panels import FieldPanel\nfrom wagtail.contrib.forms.utils import get_field_clean_name\nfrom wagtail.models import Orderable, Page\n\nfrom .forms import FormBuilder, WagtailAdminFormPageForm\n\nFORM_FIELD_CHOICES = (\n    (\"singleline\", _(\"Single line text\")),\n    (\"multiline\", _(\"Multi-line text\")),\n    (\"email\", _(\"Email\")),\n    (\"number\", _(\"Number\")),\n    (\"url\", _(\"URL\")),\n    (\"checkbox\", _(\"Checkbox\")),\n    (\"checkboxes\", _(\"Checkboxes\")),\n    (\"dropdown\", _(\"Drop down\")),\n    (\"multiselect\", _(\"Multiple select\")),\n    (\"radio\", _(\"Radio buttons\")),\n    (\"date\", _(\"Date\")),\n    (\"datetime\", _(\"Date/time\")),\n    (\"hidden\", _(\"Hidden field\")),\n)\n\n\nclass AbstractFormSubmission(models.Model):\n    \"\"\"\n    Data for a form submission.\n\n    You can create custom submission model based on this abstract model.\n    For example, if you need to save additional data or a reference to a user.\n    \"\"\"\n\n    form_data = models.JSONField(encoder=DjangoJSONEncoder)\n    page = models.ForeignKey(Page, on_delete=models.CASCADE)\n\n    submit_time = models.DateTimeField(verbose_name=_(\"submit time\"), auto_now_add=True)\n\n    def get_data(self):\n        \"\"\"\n        Returns dict with form data.\n\n        You can override this method to add additional data.\n        \"\"\"\n\n        return {\n            **self.form_data,\n            \"submit_time\": self.submit_time,\n        }\n\n    def __str__(self):\n        return f\"{self.form_data}\"\n\n    class Meta:\n        abstract = True\n        verbose_name = _(\"form submission\")\n        verbose_name_plural = _(\"form submissions\")\n\n\nclass FormSubmission(AbstractFormSubmission):\n    \"\"\"Data for a Form submission.\"\"\"\n\n\nclass AbstractFormField(Orderable):\n    \"\"\"\n    Database Fields required for building a Django Form field.\n    \"\"\"\n\n    clean_name = models.CharField(\n        verbose_name=_(\"name\"),\n        max_length=255,\n        blank=True,\n        default=\"\",\n        help_text=_(\n            \"Safe name of the form field, the label converted to ascii_snake_case\"\n        ),\n    )\n    label = models.CharField(\n        verbose_name=_(\"label\"),\n        max_length=255,\n        help_text=_(\"The label of the form field\"),\n    )\n    field_type = models.CharField(\n        verbose_name=_(\"field type\"), max_length=16, choices=FORM_FIELD_CHOICES\n    )\n    required = models.BooleanField(verbose_name=_(\"required\"), default=True)\n    choices = models.TextField(\n        verbose_name=_(\"choices\"),\n        blank=True,\n        help_text=_(\n            \"Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.\"\n        ),\n    )\n    default_value = models.TextField(\n        verbose_name=_(\"default value\"),\n        blank=True,\n        help_text=_(\n            \"Default value. Comma or new line separated values supported for checkboxes.\"\n        ),\n    )\n    help_text = models.CharField(\n        verbose_name=_(\"help text\"), max_length=255, blank=True\n    )\n\n    panels = [\n        FieldPanel(\"label\"),\n        FieldPanel(\"help_text\"),\n        FieldPanel(\"required\"),\n        FieldPanel(\"field_type\", classname=\"formbuilder-type\"),\n        FieldPanel(\"choices\", classname=\"formbuilder-choices\"),\n        FieldPanel(\"default_value\", classname=\"formbuilder-default\"),\n    ]\n\n    def get_field_clean_name(self):\n        \"\"\"\n        Prepare an ascii safe lower_snake_case variant of the field name to use as the field key.\n        This key is used to reference the field responses in the JSON store and as the field name in forms.\n        Called for new field creation, validation of duplicate labels and form previews.\n        When called, does not have access to the Page, nor its own id as the record is not yet created.\n        \"\"\"\n\n        return get_field_clean_name(self.label)\n\n    def save(self, *args, **kwargs):\n        \"\"\"\n        When new fields are created, generate a template safe ascii name to use as the\n        JSON storage reference for this field. Previously created fields will be updated\n        to use the legacy unidecode method via checks & _migrate_legacy_clean_name.\n        We do not want to update the clean name on any subsequent changes to the label\n        as this would invalidate any previously submitted data.\n        \"\"\"\n\n        is_new = self.pk is None\n        if is_new:\n            clean_name = self.get_field_clean_name()\n            self.clean_name = clean_name\n\n        super().save(*args, **kwargs)\n\n    class Meta:\n        abstract = True\n        ordering = [\"sort_order\"]\n\n\nclass AbstractForm(Page):\n    \"\"\"\n    A Form Page. Pages implementing a form should inherit from it\n    \"\"\"\n\n    base_form_class = WagtailAdminFormPageForm\n\n    form_builder = FormBuilder\n\n    submissions_list_view_class = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if not hasattr(self, \"landing_page_template\"):\n            name, ext = os.path.splitext(self.template)\n            self.landing_page_template = name + \"_landing\" + ext\n\n    class Meta:\n        abstract = True\n\n    def get_form_fields(self):\n        \"\"\"\n        Form page expects `form_fields` to be declared.\n        If you want to change backwards relation name,\n        you need to override this method.\n        \"\"\"\n\n        return self.form_fields.all()\n\n    def get_data_fields(self):\n        \"\"\"\n        Returns a list of tuples with (field_name, field_label).\n        \"\"\"\n\n        data_fields = [\n            (\"submit_time\", _(\"Submission date\")),\n        ]\n        data_fields += [\n            (field.clean_name, field.label) for field in self.get_form_fields()\n        ]\n\n        return data_fields\n\n    def get_form_class(self):\n        fb = self.form_builder(self.get_form_fields())\n        return fb.get_form_class()\n\n    def get_form_parameters(self):\n        return {}\n\n    def get_form(self, *args, **kwargs):\n        form_class = self.get_form_class()\n        form_params = self.get_form_parameters()\n        form_params.update(kwargs)\n\n        return form_class(*args, **form_params)\n\n    def get_landing_page_template(self, request, *args, **kwargs):\n        return self.landing_page_template\n\n    def get_submission_class(self):\n        \"\"\"\n        Returns submission class.\n\n        You can override this method to provide custom submission class.\n        Your class must be inherited from AbstractFormSubmission.\n        \"\"\"\n\n        return FormSubmission\n\n    def get_submissions_list_view_class(self):\n        from .views import SubmissionsListView\n\n        return self.submissions_list_view_class or SubmissionsListView\n\n    def process_form_submission(self, form):\n        \"\"\"\n        Accepts form instance with submitted data, user and page.\n        Creates submission instance.\n\n        You can override this method if you want to have custom creation logic.\n        For example, if you want to save reference to a user.\n        \"\"\"\n\n        return self.get_submission_class().objects.create(\n            form_data=form.cleaned_data,\n            page=self,\n        )\n\n    def render_landing_page(self, request, form_submission=None, *args, **kwargs):\n        \"\"\"\n        Renders the landing page.\n\n        You can override this method to return a different HttpResponse as\n        landing page. E.g. you could return a redirect to a separate page.\n        \"\"\"\n        context = self.get_context(request)\n        context[\"form_submission\"] = form_submission\n        return TemplateResponse(\n            request, self.get_landing_page_template(request), context\n        )\n\n    def serve_submissions_list_view(self, request, *args, **kwargs):\n        \"\"\"\n        Returns list submissions view for admin.\n\n        `list_submissions_view_class` can bse set to provide custom view class.\n        Your class must be inherited from SubmissionsListView.\n        \"\"\"\n        view = self.get_submissions_list_view_class().as_view()\n        return view(request, form_page=self, *args, **kwargs)\n\n    def serve(self, request, *args, **kwargs):\n        if request.method == \"POST\":\n            form = self.get_form(\n                request.POST, request.FILES, page=self, user=request.user\n            )\n\n            if form.is_valid():\n                form_submission = self.process_form_submission(form)\n                return self.render_landing_page(\n                    request, form_submission, *args, **kwargs\n                )\n        else:\n            form = self.get_form(page=self, user=request.user)\n\n        context = self.get_context(request)\n        context[\"form\"] = form\n        return TemplateResponse(request, self.get_template(request), context)\n\n    preview_modes = [\n        (\"form\", _(\"Form\")),\n        (\"landing\", _(\"Landing page\")),\n    ]\n\n    def serve_preview(self, request, mode_name):\n        if mode_name == \"landing\":\n            return self.render_landing_page(request)\n        else:\n            return super().serve_preview(request, mode_name)\n\n\ndef validate_to_address(value):\n    for address in value.split(\",\"):\n        validate_email(address.strip())\n\n\nclass AbstractEmailForm(AbstractForm):\n    \"\"\"\n    A Form Page that sends email. Pages implementing a form to be send to an email should inherit from it\n    \"\"\"\n\n    to_address = models.CharField(\n        verbose_name=_(\"to address\"),\n        max_length=255,\n        blank=True,\n        help_text=_(\n            \"Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.\"\n        ),\n        validators=[validate_to_address],\n    )\n    from_address = models.EmailField(\n        verbose_name=_(\"from address\"), max_length=255, blank=True\n    )\n    subject = models.CharField(verbose_name=_(\"subject\"), max_length=255, blank=True)\n\n    def process_form_submission(self, form):\n        submission = super().process_form_submission(form)\n        if self.to_address:\n            self.send_mail(form)\n        return submission\n\n    def send_mail(self, form):\n        addresses = [x.strip() for x in self.to_address.split(\",\")]\n        send_mail(\n            self.subject,\n            self.render_email(form),\n            addresses,\n            self.from_address,\n        )\n\n    def render_email(self, form):\n        content = []\n\n        cleaned_data = form.cleaned_data\n        for field in form:\n            if field.name not in cleaned_data:\n                continue\n\n            value = cleaned_data.get(field.name)\n\n            if isinstance(value, list):\n                value = \", \".join(value)\n\n            # Format dates and datetimes with SHORT_DATE(TIME)_FORMAT\n            if isinstance(value, datetime.datetime):\n                value = date_format(value, settings.SHORT_DATETIME_FORMAT)\n            elif isinstance(value, datetime.date):\n                value = date_format(value, settings.SHORT_DATE_FORMAT)\n\n            content.append(\"{}: {}\".format(field.label, value))\n\n        return \"\\n\".join(content)\n\n    class Meta:\n        abstract = True\n", "path": "wagtail/contrib/forms/models.py"}]}