mirror of
https://github.com/wagtail/wagtail.git
synced 2024-12-01 11:41:20 +01:00
165c5c0ce5
* Rewrite AbstractEmailForm render to use cleaned_data * Add date time formatting to AbstractEmailForm's render method according to Django settings SHORT_DATE_FORMAT and SHORT_DATETIME_FORMAT * Previously it was iterating over `form` which left no place to remove data from the submission without removing the field from the form (which is inadvisable, as discussed on #4313) * Preserve order of fields in form emails using cleaned data * Add a test for consistent date rendering in email forms * update form customisation examples and add note about date / time formatting in email form usage (docs) * resolves #3733 * resolves #4313
795 lines
28 KiB
ReStructuredText
795 lines
28 KiB
ReStructuredText
Form builder customisation
|
|
==========================
|
|
|
|
For a basic usage example see :ref:`form_builder_usage`.
|
|
|
|
Custom ``related_name`` for form fields
|
|
---------------------------------------
|
|
|
|
If you want to change ``related_name`` for form fields
|
|
(by default ``AbstractForm`` and ``AbstractEmailForm`` expect ``form_fields`` to be defined),
|
|
you will need to override the ``get_form_fields`` method.
|
|
You can do this as shown below.
|
|
|
|
.. code-block:: python
|
|
|
|
from modelcluster.fields import ParentalKey
|
|
from wagtail.admin.edit_handlers import (
|
|
FieldPanel, FieldRowPanel,
|
|
InlinePanel, MultiFieldPanel
|
|
)
|
|
from wagtail.core.fields import RichTextField
|
|
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
|
|
|
|
|
|
class FormField(AbstractFormField):
|
|
page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='custom_form_fields')
|
|
|
|
|
|
class FormPage(AbstractEmailForm):
|
|
intro = RichTextField(blank=True)
|
|
thank_you_text = RichTextField(blank=True)
|
|
|
|
content_panels = AbstractEmailForm.content_panels + [
|
|
FieldPanel('intro', classname="full"),
|
|
InlinePanel('custom_form_fields', label="Form fields"),
|
|
FieldPanel('thank_you_text', classname="full"),
|
|
MultiFieldPanel([
|
|
FieldRowPanel([
|
|
FieldPanel('from_address', classname="col6"),
|
|
FieldPanel('to_address', classname="col6"),
|
|
]),
|
|
FieldPanel('subject'),
|
|
], "Email"),
|
|
]
|
|
|
|
def get_form_fields(self):
|
|
return self.custom_form_fields.all()
|
|
|
|
Custom form submission model
|
|
----------------------------
|
|
|
|
If you need to save additional data, you can use a custom form submission model.
|
|
To do this, you need to:
|
|
|
|
* Define a model that extends ``wagtail.contrib.forms.models.AbstractFormSubmission``.
|
|
* Override the ``get_submission_class`` and ``process_form_submission`` methods in your page model.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
import json
|
|
|
|
from django.conf import settings
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
from django.db import models
|
|
from modelcluster.fields import ParentalKey
|
|
from wagtail.admin.edit_handlers import (
|
|
FieldPanel, FieldRowPanel,
|
|
InlinePanel, MultiFieldPanel
|
|
)
|
|
from wagtail.core.fields import RichTextField
|
|
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField, AbstractFormSubmission
|
|
|
|
|
|
class FormField(AbstractFormField):
|
|
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 + [
|
|
FieldPanel('intro', classname="full"),
|
|
InlinePanel('form_fields', label="Form fields"),
|
|
FieldPanel('thank_you_text', classname="full"),
|
|
MultiFieldPanel([
|
|
FieldRowPanel([
|
|
FieldPanel('from_address', classname="col6"),
|
|
FieldPanel('to_address', classname="col6"),
|
|
]),
|
|
FieldPanel('subject'),
|
|
], "Email"),
|
|
]
|
|
|
|
def get_submission_class(self):
|
|
return CustomFormSubmission
|
|
|
|
def process_form_submission(self, form):
|
|
self.get_submission_class().objects.create(
|
|
form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
|
|
page=self, user=form.user
|
|
)
|
|
|
|
|
|
class CustomFormSubmission(AbstractFormSubmission):
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
|
|
|
|
Add custom data to CSV export
|
|
-----------------------------
|
|
|
|
If you want to add custom data to the CSV export, you will need to:
|
|
|
|
* Override the ``get_data_fields`` method in page model.
|
|
* Override ``get_data`` in the submission model.
|
|
|
|
The following example shows how to add a username to the CSV export:
|
|
|
|
.. code-block:: python
|
|
|
|
import json
|
|
|
|
from django.conf import settings
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
from django.db import models
|
|
from modelcluster.fields import ParentalKey
|
|
from wagtail.admin.edit_handlers import (
|
|
FieldPanel, FieldRowPanel,
|
|
InlinePanel, MultiFieldPanel
|
|
)
|
|
from wagtail.core.fields import RichTextField
|
|
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField, AbstractFormSubmission
|
|
|
|
|
|
class FormField(AbstractFormField):
|
|
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 + [
|
|
FieldPanel('intro', classname="full"),
|
|
InlinePanel('form_fields', label="Form fields"),
|
|
FieldPanel('thank_you_text', classname="full"),
|
|
MultiFieldPanel([
|
|
FieldRowPanel([
|
|
FieldPanel('from_address', classname="col6"),
|
|
FieldPanel('to_address', classname="col6"),
|
|
]),
|
|
FieldPanel('subject'),
|
|
], "Email"),
|
|
]
|
|
|
|
def get_data_fields(self):
|
|
data_fields = [
|
|
('username', 'Username'),
|
|
]
|
|
data_fields += super().get_data_fields()
|
|
|
|
return data_fields
|
|
|
|
def get_submission_class(self):
|
|
return CustomFormSubmission
|
|
|
|
def process_form_submission(self, form):
|
|
self.get_submission_class().objects.create(
|
|
form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
|
|
page=self, user=form.user
|
|
)
|
|
|
|
|
|
class CustomFormSubmission(AbstractFormSubmission):
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
|
|
def get_data(self):
|
|
form_data = super().get_data()
|
|
form_data.update({
|
|
'username': self.user.username,
|
|
})
|
|
|
|
return form_data
|
|
|
|
|
|
Note that this code also changes the submissions list view.
|
|
|
|
Check that a submission already exists for a user
|
|
-------------------------------------------------
|
|
|
|
If you want to prevent users from filling in a form more than once,
|
|
you need to override the ``serve`` method in your page model.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
import json
|
|
|
|
from django.conf import settings
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
from django.db import models
|
|
from django.shortcuts import render
|
|
from modelcluster.fields import ParentalKey
|
|
from wagtail.admin.edit_handlers import (
|
|
FieldPanel, FieldRowPanel,
|
|
InlinePanel, MultiFieldPanel
|
|
)
|
|
from wagtail.core.fields import RichTextField
|
|
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField, AbstractFormSubmission
|
|
|
|
|
|
class FormField(AbstractFormField):
|
|
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 + [
|
|
FieldPanel('intro', classname="full"),
|
|
InlinePanel('form_fields', label="Form fields"),
|
|
FieldPanel('thank_you_text', classname="full"),
|
|
MultiFieldPanel([
|
|
FieldRowPanel([
|
|
FieldPanel('from_address', classname="col6"),
|
|
FieldPanel('to_address', classname="col6"),
|
|
]),
|
|
FieldPanel('subject'),
|
|
], "Email"),
|
|
]
|
|
|
|
def serve(self, request, *args, **kwargs):
|
|
if self.get_submission_class().objects.filter(page=self, user__pk=request.user.pk).exists():
|
|
return render(
|
|
request,
|
|
self.template,
|
|
self.get_context(request)
|
|
)
|
|
|
|
return super().serve(request, *args, **kwargs)
|
|
|
|
def get_submission_class(self):
|
|
return CustomFormSubmission
|
|
|
|
def process_form_submission(self, form):
|
|
self.get_submission_class().objects.create(
|
|
form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
|
|
page=self, user=form.user
|
|
)
|
|
|
|
|
|
class CustomFormSubmission(AbstractFormSubmission):
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
|
|
class Meta:
|
|
unique_together = ('page', 'user')
|
|
|
|
|
|
Your template should look like this:
|
|
|
|
.. code-block:: django
|
|
|
|
{% load wagtailcore_tags %}
|
|
<html>
|
|
<head>
|
|
<title>{{ page.title }}</title>
|
|
</head>
|
|
<body>
|
|
<h1>{{ page.title }}</h1>
|
|
|
|
{% if user.is_authenticated and user.is_active or request.is_preview %}
|
|
{% if form %}
|
|
<div>{{ page.intro|richtext }}</div>
|
|
<form action="{% pageurl page %}" method="POST">
|
|
{% csrf_token %}
|
|
{{ form.as_p }}
|
|
<input type="submit">
|
|
</form>
|
|
{% else %}
|
|
<div>You can fill in the from only one time.</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div>To fill in the form, you must to log in.</div>
|
|
{% endif %}
|
|
</body>
|
|
</html>
|
|
|
|
|
|
Multi-step form
|
|
---------------
|
|
|
|
The following example shows how to create a multi-step form.
|
|
|
|
.. code-block:: python
|
|
|
|
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
|
|
from django.shortcuts import render
|
|
from modelcluster.fields import ParentalKey
|
|
from wagtail.admin.edit_handlers import (
|
|
FieldPanel, FieldRowPanel,
|
|
InlinePanel, MultiFieldPanel
|
|
)
|
|
from wagtail.core.fields import RichTextField
|
|
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
|
|
|
|
|
|
class FormField(AbstractFormField):
|
|
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 + [
|
|
FieldPanel('intro', classname="full"),
|
|
InlinePanel('form_fields', label="Form fields"),
|
|
FieldPanel('thank_you_text', classname="full"),
|
|
MultiFieldPanel([
|
|
FieldRowPanel([
|
|
FieldPanel('from_address', classname="col6"),
|
|
FieldPanel('to_address', classname="col6"),
|
|
]),
|
|
FieldPanel('subject'),
|
|
], "Email"),
|
|
]
|
|
|
|
def get_form_class_for_step(self, step):
|
|
return self.form_builder(step.object_list).get_form_class()
|
|
|
|
def serve(self, request, *args, **kwargs):
|
|
"""
|
|
Implements a simple multi-step form.
|
|
|
|
Stores each step into a session.
|
|
When the last step was submitted correctly, saves whole form into a DB.
|
|
"""
|
|
|
|
session_key_data = 'form_data-%s' % self.pk
|
|
is_last_step = False
|
|
step_number = request.GET.get('p', 1)
|
|
|
|
paginator = Paginator(self.get_form_fields(), per_page=1)
|
|
try:
|
|
step = paginator.page(step_number)
|
|
except PageNotAnInteger:
|
|
step = paginator.page(1)
|
|
except EmptyPage:
|
|
step = paginator.page(paginator.num_pages)
|
|
is_last_step = True
|
|
|
|
if request.method == 'POST':
|
|
# The first step will be submitted with step_number == 2,
|
|
# so we need to get a form from previous step
|
|
# Edge case - submission of the last step
|
|
prev_step = step if is_last_step else paginator.page(step.previous_page_number())
|
|
|
|
# Create a form only for submitted step
|
|
prev_form_class = self.get_form_class_for_step(prev_step)
|
|
prev_form = prev_form_class(request.POST, page=self, user=request.user)
|
|
if prev_form.is_valid():
|
|
# If data for step is valid, update the session
|
|
form_data = request.session.get(session_key_data, {})
|
|
form_data.update(prev_form.cleaned_data)
|
|
request.session[session_key_data] = form_data
|
|
|
|
if prev_step.has_next():
|
|
# Create a new form for a following step, if the following step is present
|
|
form_class = self.get_form_class_for_step(step)
|
|
form = form_class(page=self, user=request.user)
|
|
else:
|
|
# If there is no next step, create form for all fields
|
|
form = self.get_form(
|
|
request.session[session_key_data],
|
|
page=self, user=request.user
|
|
)
|
|
|
|
if form.is_valid():
|
|
# Perform validation again for whole form.
|
|
# After successful validation, save data into DB,
|
|
# and remove from the session.
|
|
form_submission = self.process_form_submission(form)
|
|
del request.session[session_key_data]
|
|
# render the landing page
|
|
return self.render_landing_page(request, form_submission, *args, **kwargs)
|
|
else:
|
|
# If data for step is invalid
|
|
# we will need to display form again with errors,
|
|
# so restore previous state.
|
|
form = prev_form
|
|
step = prev_step
|
|
else:
|
|
# Create empty form for non-POST requests
|
|
form_class = self.get_form_class_for_step(step)
|
|
form = form_class(page=self, user=request.user)
|
|
|
|
context = self.get_context(request)
|
|
context['form'] = form
|
|
context['fields_step'] = step
|
|
return render(
|
|
request,
|
|
self.template,
|
|
context
|
|
)
|
|
|
|
|
|
|
|
Your template for this form page should look like this:
|
|
|
|
.. code-block:: django
|
|
|
|
{% load wagtailcore_tags %}
|
|
<html>
|
|
<head>
|
|
<title>{{ page.title }}</title>
|
|
</head>
|
|
<body>
|
|
<h1>{{ page.title }}</h1>
|
|
|
|
<div>{{ page.intro|richtext }}</div>
|
|
<form action="{% pageurl page %}?p={{ fields_step.number|add:"1" }}" method="POST">
|
|
{% csrf_token %}
|
|
{{ form.as_p }}
|
|
<input type="submit">
|
|
</form>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
Note that the example shown before allows the user to return to a previous step,
|
|
or to open a second step without submitting the first step.
|
|
Depending on your requirements, you may need to add extra checks.
|
|
|
|
Show results
|
|
------------
|
|
|
|
If you are implementing polls or surveys, you may want to show results after submission.
|
|
The following example demonstrates how to do this.
|
|
|
|
First, you need to collect results as shown below:
|
|
|
|
.. code-block:: python
|
|
|
|
from modelcluster.fields import ParentalKey
|
|
from wagtail.admin.edit_handlers import (
|
|
FieldPanel, FieldRowPanel,
|
|
InlinePanel, MultiFieldPanel
|
|
)
|
|
from wagtail.core.fields import RichTextField
|
|
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
|
|
|
|
|
|
class FormField(AbstractFormField):
|
|
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 + [
|
|
FieldPanel('intro', classname="full"),
|
|
InlinePanel('form_fields', label="Form fields"),
|
|
FieldPanel('thank_you_text', classname="full"),
|
|
MultiFieldPanel([
|
|
FieldRowPanel([
|
|
FieldPanel('from_address', classname="col6"),
|
|
FieldPanel('to_address', classname="col6"),
|
|
]),
|
|
FieldPanel('subject'),
|
|
], "Email"),
|
|
]
|
|
|
|
def get_context(self, request, *args, **kwargs):
|
|
context = super().get_context(request, *args, **kwargs)
|
|
|
|
# If you need to show results only on landing page,
|
|
# you may need check request.method
|
|
|
|
results = dict()
|
|
# Get information about form fields
|
|
data_fields = [
|
|
(field.clean_name, field.label)
|
|
for field in self.get_form_fields()
|
|
]
|
|
|
|
# Get all submissions for current page
|
|
submissions = self.get_submission_class().objects.filter(page=self)
|
|
for submission in submissions:
|
|
data = submission.get_data()
|
|
|
|
# Count results for each question
|
|
for name, label in data_fields:
|
|
answer = data.get(name)
|
|
if answer is None:
|
|
# Something wrong with data.
|
|
# Probably you have changed questions
|
|
# and now we are receiving answers for old questions.
|
|
# Just skip them.
|
|
continue
|
|
|
|
if type(answer) is list:
|
|
# Answer is a list if the field type is 'Checkboxes'
|
|
answer = u', '.join(answer)
|
|
|
|
question_stats = results.get(label, {})
|
|
question_stats[answer] = question_stats.get(answer, 0) + 1
|
|
results[label] = question_stats
|
|
|
|
context.update({
|
|
'results': results,
|
|
})
|
|
return context
|
|
|
|
|
|
Next, you need to transform your template to display the results:
|
|
|
|
.. code-block:: django
|
|
|
|
{% load wagtailcore_tags %}
|
|
<html>
|
|
<head>
|
|
<title>{{ page.title }}</title>
|
|
</head>
|
|
<body>
|
|
<h1>{{ page.title }}</h1>
|
|
|
|
<h2>Results</h2>
|
|
{% for question, answers in results.items %}
|
|
<h3>{{ question }}</h3>
|
|
{% for answer, count in answers.items %}
|
|
<div>{{ answer }}: {{ count }}</div>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
|
|
<div>{{ page.intro|richtext }}</div>
|
|
<form action="{% pageurl page %}" method="POST">
|
|
{% csrf_token %}
|
|
{{ form.as_p }}
|
|
<input type="submit">
|
|
</form>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
You can also show the results on the landing page.
|
|
|
|
Custom landing page redirect
|
|
----------------------------
|
|
|
|
You can override the ``render_landing_page`` method on your `FormPage` to change what is rendered when a form submits.
|
|
|
|
In this example below we have added a `thank_you_page` field that enables custom redirects after a form submits to the selected page.
|
|
|
|
When overriding the ``render_landing_page`` method, we check if there is a linked `thank_you_page` and then redirect to it if it exists.
|
|
|
|
Finally, we add a URL param of `id` based on the ``form_submission`` if it exists.
|
|
|
|
.. code-block:: python
|
|
|
|
from django.shortcuts import redirect
|
|
from wagtail.admin.edit_handlers import (
|
|
FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel, PageChooserPanel)
|
|
from wagtail.contrib.forms.models import AbstractEmailForm
|
|
|
|
class FormPage(AbstractEmailForm):
|
|
|
|
# intro, thank_you_text, ...
|
|
|
|
thank_you_page = models.ForeignKey(
|
|
'wagtailcore.Page',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name='+',
|
|
)
|
|
|
|
def render_landing_page(self, request, form_submission=None, *args, **kwargs):
|
|
if self.thank_you_page:
|
|
url = self.thank_you_page.url
|
|
# if a form_submission instance is available, append the id to URL
|
|
# when previewing landing page, there will not be a form_submission instance
|
|
if form_submission:
|
|
url += '?id=%s' % form_submission.id
|
|
return redirect(url, permanent=False)
|
|
# if no thank_you_page is set, render default landing page
|
|
return super().render_landing_page(request, form_submission, *args, **kwargs)
|
|
|
|
content_panels = AbstractEmailForm.content_panels + [
|
|
FieldPanel('intro', classname='full'),
|
|
InlinePanel('form_fields'),
|
|
FieldPanel('thank_you_text', classname='full'),
|
|
PageChooserPanel('thank_you_page'),
|
|
MultiFieldPanel([
|
|
FieldRowPanel([
|
|
FieldPanel('from_address', classname='col6'),
|
|
FieldPanel('to_address', classname='col6'),
|
|
]),
|
|
FieldPanel('subject'),
|
|
], 'Email'),
|
|
]
|
|
|
|
Customise form submissions listing in Wagtail Admin
|
|
---------------------------------------------------
|
|
|
|
The Admin listing of form submissions can be customised by setting the attribute ``submissions_list_view_class`` on your FormPage model.
|
|
|
|
The list view class must be a subclass of ``SubmissionsListView`` from ``wagtail.contrib.forms.views``, which is a child class of Django's class based :class:`~django.views.generic.list.ListView`.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
|
|
from wagtail.contrib.forms.views import SubmissionsListView
|
|
|
|
|
|
class CustomSubmissionsListView(SubmissionsListView):
|
|
paginate_by = 50 # show more submissions per page, default is 20
|
|
ordering = ('submit_time',) # order submissions by oldest first, normally newest first
|
|
ordering_csv = ('-submit_time',) # order csv export by newest first, normally oldest first
|
|
|
|
# override the method to generate csv filename
|
|
def get_csv_filename(self):
|
|
""" Returns the filename for CSV file with page slug at start"""
|
|
filename = super().get_csv_filename()
|
|
return self.form_page.slug + '-' + filename
|
|
|
|
|
|
class FormField(AbstractFormField):
|
|
page = ParentalKey('FormPage', related_name='form_fields')
|
|
|
|
|
|
class FormPage(AbstractEmailForm):
|
|
"""Form Page with customised submissions listing view"""
|
|
|
|
# set custom view class as class attribute
|
|
submissions_list_view_class = CustomSubmissionsListView
|
|
|
|
intro = RichTextField(blank=True)
|
|
thank_you_text = RichTextField(blank=True)
|
|
|
|
# content_panels = ...
|
|
|
|
Adding a custom field type
|
|
--------------------------
|
|
|
|
First, make the new field type available in the page editor by changing your ``FormField`` model.
|
|
|
|
* Create a new set of choices which includes the original ``FORM_FIELD_CHOICES`` along with new field types you want to make available.
|
|
* Each choice must contain a unique key and a human readable name of the field, e.g. ``('slug', 'URL Slug')``
|
|
* Override the ``field_type`` field in your ``FormField`` model with ``choices`` attribute using these choices.
|
|
* You will need to run ``./manage.py makemigrations`` and ``./manage.py migrate`` after this step.
|
|
|
|
|
|
Then, create and use a new form builder class.
|
|
|
|
* Define a new form builder class that extends the ``FormBuilder`` class.
|
|
* Add a method that will return a created Django form field for the new field type.
|
|
* Its name must be in the format: ``create_<field_type_key>_field``, e.g. ``create_slug_field``
|
|
* Override the ``form_builder`` attribute in your form page model to use your new form builder class.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
from django import forms
|
|
from django.db import models
|
|
from modelcluster.fields import ParentalKey
|
|
from wagtail.contrib.forms.forms import FormBuilder
|
|
from wagtail.contrib.forms.models import (
|
|
AbstractEmailForm, AbstractFormField, FORM_FIELD_CHOICES)
|
|
|
|
|
|
class FormField(AbstractFormField):
|
|
# extend the built in field type choices
|
|
# our field type key will be 'ipaddress'
|
|
CHOICES = FORM_FIELD_CHOICES + (('ipaddress', 'IP Address'),)
|
|
|
|
page = ParentalKey('FormPage', related_name='form_fields')
|
|
# override the field_type field with extended choices
|
|
field_type = models.CharField(
|
|
verbose_name='field type',
|
|
max_length=16,
|
|
# use the choices tuple defined above
|
|
choices=CHOICES
|
|
)
|
|
|
|
|
|
class CustomFormBuilder(FormBuilder):
|
|
# create a function that returns an instanced Django form field
|
|
# function name must match create_<field_type_key>_field
|
|
def create_ipaddress_field(self, field, options):
|
|
# return `forms.GenericIPAddressField(**options)` not `forms.SlugField`
|
|
# returns created a form field with the options passed in
|
|
return forms.GenericIPAddressField(**options)
|
|
|
|
|
|
class FormPage(AbstractEmailForm):
|
|
# intro, thank_you_text, edit_handlers, etc...
|
|
|
|
# use custom form builder defined above
|
|
form_builder = CustomFormBuilder
|
|
|
|
|
|
.. _form_builder_render_email:
|
|
|
|
Custom ``render_email`` method
|
|
------------------------------
|
|
|
|
If you want to change the content of the email that is sent when a form submits you can override the ``render_email`` method.
|
|
|
|
|
|
To do this, you need to:
|
|
|
|
* Ensure you have your form model defined that extends ``wagtail.contrib.forms.models.AbstractEmailForm``.
|
|
* Override the ``render_email`` method in your page model.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
from datetime import date
|
|
# ... additional wagtail imports
|
|
from wagtail.contrib.forms.models import AbstractEmailForm
|
|
|
|
|
|
class FormPage(AbstractEmailForm):
|
|
# ... fields, content_panels, etc
|
|
|
|
def render_email(self, form):
|
|
# Get the original content (string)
|
|
email_content = super().render_email(form)
|
|
|
|
# Add a title (not part of original method)
|
|
title = '{}: {}'.format('Form', self.title)
|
|
|
|
content = [title, '', email_content, '']
|
|
|
|
# Add a link to the form page
|
|
content.append('{}: {}'.format('Submitted Via', self.full_url))
|
|
|
|
# Add the date the form was submitted
|
|
submitted_date_str = date.today().strftime('%x')
|
|
content.append('{}: {}'.format('Submitted on', submitted_date_str))
|
|
|
|
# Content is joined with a new line to separate each text line
|
|
content = '\n'.join(content)
|
|
|
|
return content
|
|
|
|
|
|
Custom ``send_mail`` method
|
|
---------------------------
|
|
|
|
If you want to change the subject or some other part of how an email is sent when a form submits you can override the ``send_mail`` method.
|
|
|
|
|
|
To do this, you need to:
|
|
|
|
* Ensure you have your form model defined that extends ``wagtail.contrib.forms.models.AbstractEmailForm``.
|
|
* In your models.py file, import the ``wagtail.admin.mail.send_mail`` function.
|
|
* Override the ``send_mail`` method in your page model.
|
|
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
from datetime import date
|
|
# ... additional wagtail imports
|
|
from wagtail.admin.mail import send_mail
|
|
from wagtail.contrib.forms.models import AbstractEmailForm
|
|
|
|
|
|
class FormPage(AbstractEmailForm):
|
|
# ... fields, content_panels, etc
|
|
|
|
def send_mail(self, form):
|
|
# `self` is the FormPage, `form` is the form's POST data on submit
|
|
|
|
# Email addresses are parsed from the FormPage's addresses field
|
|
addresses = [x.strip() for x in self.to_address.split(',')]
|
|
|
|
# Subject can be adjusted (adding submitted date), be sure to include the form's defined subject field
|
|
submitted_date_str = date.today().strftime('%x')
|
|
subject = f"{self.subject} - {submitted_date_str}"
|
|
|
|
send_mail(subject, self.render_email(form), addresses, self.from_address,)
|
|
|