wagtail/docs/reference/contrib/forms/customisation.rst

795 wiersze
28 KiB
ReStructuredText
Czysty Zwykły widok Historia

Form builder customisation
==========================
2016-12-23 09:36:32 +00:00
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
Rename wagtail.wagtailadmin to wagtail.admin Conflicts: docs/advanced_topics/customisation/admin_templates.rst docs/advanced_topics/customisation/page_editing_interface.rst docs/advanced_topics/i18n/duplicate_tree.rst docs/advanced_topics/jinja2.rst docs/advanced_topics/settings.rst docs/getting_started/integrating_into_django.rst docs/getting_started/tutorial.rst docs/reference/hooks.rst docs/reference/pages/panels.rst docs/topics/pages.rst docs/topics/streamfield.rst wagtail/admin/blocks.py wagtail/admin/checks.py wagtail/admin/edit_handlers.py wagtail/admin/forms.py wagtail/admin/rich_text.py wagtail/admin/search.py wagtail/admin/site_summary.py wagtail/admin/templatetags/wagtailadmin_tags.py wagtail/admin/tests/test_admin_search.py wagtail/admin/tests/test_buttons_hooks.py wagtail/admin/tests/test_compare.py wagtail/admin/tests/test_edit_handlers.py wagtail/admin/tests/test_page_chooser.py wagtail/admin/tests/test_pages_views.py wagtail/admin/tests/test_rich_text.py wagtail/admin/tests/test_widgets.py wagtail/admin/tests/tests.py wagtail/admin/urls/__init__.py wagtail/admin/views/account.py wagtail/admin/views/chooser.py wagtail/admin/views/collection_privacy.py wagtail/admin/views/collections.py wagtail/admin/views/home.py wagtail/admin/views/page_privacy.py wagtail/admin/viewsets/model.py wagtail/admin/wagtail_hooks.py wagtail/admin/widgets.py wagtail/contrib/settings/registry.py wagtail/contrib/wagtailsearchpromotions/wagtail_hooks.py wagtail/contrib/wagtailstyleguide/wagtail_hooks.py wagtail/project_template/project_name/settings/base.py wagtail/project_template/project_name/urls.py wagtail/tests/non_root_urls.py wagtail/tests/settings.py wagtail/tests/snippets/models.py wagtail/tests/testapp/models.py wagtail/tests/testapp/wagtail_hooks.py wagtail/tests/urls.py wagtail/wagtaildocs/models.py wagtail/wagtaildocs/views/chooser.py wagtail/wagtaildocs/wagtail_hooks.py wagtail/wagtailembeds/wagtail_hooks.py wagtail/wagtailforms/models.py wagtail/wagtailforms/tests/test_views.py wagtail/wagtailforms/views.py wagtail/wagtailforms/wagtail_hooks.py wagtail/wagtailimages/models.py wagtail/wagtailimages/views/chooser.py wagtail/wagtailimages/wagtail_hooks.py wagtail/wagtailredirects/forms.py wagtail/wagtailredirects/wagtail_hooks.py wagtail/wagtailsites/forms.py wagtail/wagtailsites/views.py wagtail/wagtailsites/wagtail_hooks.py wagtail/wagtailsnippets/tests.py wagtail/wagtailsnippets/wagtail_hooks.py wagtail/wagtailusers/forms.py wagtail/wagtailusers/views/groups.py wagtail/wagtailusers/wagtail_hooks.py
2017-11-17 10:44:34 +00:00
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
Rename wagtail.wagtailadmin to wagtail.admin Conflicts: docs/advanced_topics/customisation/admin_templates.rst docs/advanced_topics/customisation/page_editing_interface.rst docs/advanced_topics/i18n/duplicate_tree.rst docs/advanced_topics/jinja2.rst docs/advanced_topics/settings.rst docs/getting_started/integrating_into_django.rst docs/getting_started/tutorial.rst docs/reference/hooks.rst docs/reference/pages/panels.rst docs/topics/pages.rst docs/topics/streamfield.rst wagtail/admin/blocks.py wagtail/admin/checks.py wagtail/admin/edit_handlers.py wagtail/admin/forms.py wagtail/admin/rich_text.py wagtail/admin/search.py wagtail/admin/site_summary.py wagtail/admin/templatetags/wagtailadmin_tags.py wagtail/admin/tests/test_admin_search.py wagtail/admin/tests/test_buttons_hooks.py wagtail/admin/tests/test_compare.py wagtail/admin/tests/test_edit_handlers.py wagtail/admin/tests/test_page_chooser.py wagtail/admin/tests/test_pages_views.py wagtail/admin/tests/test_rich_text.py wagtail/admin/tests/test_widgets.py wagtail/admin/tests/tests.py wagtail/admin/urls/__init__.py wagtail/admin/views/account.py wagtail/admin/views/chooser.py wagtail/admin/views/collection_privacy.py wagtail/admin/views/collections.py wagtail/admin/views/home.py wagtail/admin/views/page_privacy.py wagtail/admin/viewsets/model.py wagtail/admin/wagtail_hooks.py wagtail/admin/widgets.py wagtail/contrib/settings/registry.py wagtail/contrib/wagtailsearchpromotions/wagtail_hooks.py wagtail/contrib/wagtailstyleguide/wagtail_hooks.py wagtail/project_template/project_name/settings/base.py wagtail/project_template/project_name/urls.py wagtail/tests/non_root_urls.py wagtail/tests/settings.py wagtail/tests/snippets/models.py wagtail/tests/testapp/models.py wagtail/tests/testapp/wagtail_hooks.py wagtail/tests/urls.py wagtail/wagtaildocs/models.py wagtail/wagtaildocs/views/chooser.py wagtail/wagtaildocs/wagtail_hooks.py wagtail/wagtailembeds/wagtail_hooks.py wagtail/wagtailforms/models.py wagtail/wagtailforms/tests/test_views.py wagtail/wagtailforms/views.py wagtail/wagtailforms/wagtail_hooks.py wagtail/wagtailimages/models.py wagtail/wagtailimages/views/chooser.py wagtail/wagtailimages/wagtail_hooks.py wagtail/wagtailredirects/forms.py wagtail/wagtailredirects/wagtail_hooks.py wagtail/wagtailsites/forms.py wagtail/wagtailsites/views.py wagtail/wagtailsites/wagtail_hooks.py wagtail/wagtailsnippets/tests.py wagtail/wagtailsnippets/wagtail_hooks.py wagtail/wagtailusers/forms.py wagtail/wagtailusers/views/groups.py wagtail/wagtailusers/wagtail_hooks.py
2017-11-17 10:44:34 +00:00
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
Rename wagtail.wagtailadmin to wagtail.admin Conflicts: docs/advanced_topics/customisation/admin_templates.rst docs/advanced_topics/customisation/page_editing_interface.rst docs/advanced_topics/i18n/duplicate_tree.rst docs/advanced_topics/jinja2.rst docs/advanced_topics/settings.rst docs/getting_started/integrating_into_django.rst docs/getting_started/tutorial.rst docs/reference/hooks.rst docs/reference/pages/panels.rst docs/topics/pages.rst docs/topics/streamfield.rst wagtail/admin/blocks.py wagtail/admin/checks.py wagtail/admin/edit_handlers.py wagtail/admin/forms.py wagtail/admin/rich_text.py wagtail/admin/search.py wagtail/admin/site_summary.py wagtail/admin/templatetags/wagtailadmin_tags.py wagtail/admin/tests/test_admin_search.py wagtail/admin/tests/test_buttons_hooks.py wagtail/admin/tests/test_compare.py wagtail/admin/tests/test_edit_handlers.py wagtail/admin/tests/test_page_chooser.py wagtail/admin/tests/test_pages_views.py wagtail/admin/tests/test_rich_text.py wagtail/admin/tests/test_widgets.py wagtail/admin/tests/tests.py wagtail/admin/urls/__init__.py wagtail/admin/views/account.py wagtail/admin/views/chooser.py wagtail/admin/views/collection_privacy.py wagtail/admin/views/collections.py wagtail/admin/views/home.py wagtail/admin/views/page_privacy.py wagtail/admin/viewsets/model.py wagtail/admin/wagtail_hooks.py wagtail/admin/widgets.py wagtail/contrib/settings/registry.py wagtail/contrib/wagtailsearchpromotions/wagtail_hooks.py wagtail/contrib/wagtailstyleguide/wagtail_hooks.py wagtail/project_template/project_name/settings/base.py wagtail/project_template/project_name/urls.py wagtail/tests/non_root_urls.py wagtail/tests/settings.py wagtail/tests/snippets/models.py wagtail/tests/testapp/models.py wagtail/tests/testapp/wagtail_hooks.py wagtail/tests/urls.py wagtail/wagtaildocs/models.py wagtail/wagtaildocs/views/chooser.py wagtail/wagtaildocs/wagtail_hooks.py wagtail/wagtailembeds/wagtail_hooks.py wagtail/wagtailforms/models.py wagtail/wagtailforms/tests/test_views.py wagtail/wagtailforms/views.py wagtail/wagtailforms/wagtail_hooks.py wagtail/wagtailimages/models.py wagtail/wagtailimages/views/chooser.py wagtail/wagtailimages/wagtail_hooks.py wagtail/wagtailredirects/forms.py wagtail/wagtailredirects/wagtail_hooks.py wagtail/wagtailsites/forms.py wagtail/wagtailsites/views.py wagtail/wagtailsites/wagtail_hooks.py wagtail/wagtailsnippets/tests.py wagtail/wagtailsnippets/wagtail_hooks.py wagtail/wagtailusers/forms.py wagtail/wagtailusers/views/groups.py wagtail/wagtailusers/wagtail_hooks.py
2017-11-17 10:44:34 +00:00
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
Rename wagtail.wagtailadmin to wagtail.admin Conflicts: docs/advanced_topics/customisation/admin_templates.rst docs/advanced_topics/customisation/page_editing_interface.rst docs/advanced_topics/i18n/duplicate_tree.rst docs/advanced_topics/jinja2.rst docs/advanced_topics/settings.rst docs/getting_started/integrating_into_django.rst docs/getting_started/tutorial.rst docs/reference/hooks.rst docs/reference/pages/panels.rst docs/topics/pages.rst docs/topics/streamfield.rst wagtail/admin/blocks.py wagtail/admin/checks.py wagtail/admin/edit_handlers.py wagtail/admin/forms.py wagtail/admin/rich_text.py wagtail/admin/search.py wagtail/admin/site_summary.py wagtail/admin/templatetags/wagtailadmin_tags.py wagtail/admin/tests/test_admin_search.py wagtail/admin/tests/test_buttons_hooks.py wagtail/admin/tests/test_compare.py wagtail/admin/tests/test_edit_handlers.py wagtail/admin/tests/test_page_chooser.py wagtail/admin/tests/test_pages_views.py wagtail/admin/tests/test_rich_text.py wagtail/admin/tests/test_widgets.py wagtail/admin/tests/tests.py wagtail/admin/urls/__init__.py wagtail/admin/views/account.py wagtail/admin/views/chooser.py wagtail/admin/views/collection_privacy.py wagtail/admin/views/collections.py wagtail/admin/views/home.py wagtail/admin/views/page_privacy.py wagtail/admin/viewsets/model.py wagtail/admin/wagtail_hooks.py wagtail/admin/widgets.py wagtail/contrib/settings/registry.py wagtail/contrib/wagtailsearchpromotions/wagtail_hooks.py wagtail/contrib/wagtailstyleguide/wagtail_hooks.py wagtail/project_template/project_name/settings/base.py wagtail/project_template/project_name/urls.py wagtail/tests/non_root_urls.py wagtail/tests/settings.py wagtail/tests/snippets/models.py wagtail/tests/testapp/models.py wagtail/tests/testapp/wagtail_hooks.py wagtail/tests/urls.py wagtail/wagtaildocs/models.py wagtail/wagtaildocs/views/chooser.py wagtail/wagtaildocs/wagtail_hooks.py wagtail/wagtailembeds/wagtail_hooks.py wagtail/wagtailforms/models.py wagtail/wagtailforms/tests/test_views.py wagtail/wagtailforms/views.py wagtail/wagtailforms/wagtail_hooks.py wagtail/wagtailimages/models.py wagtail/wagtailimages/views/chooser.py wagtail/wagtailimages/wagtail_hooks.py wagtail/wagtailredirects/forms.py wagtail/wagtailredirects/wagtail_hooks.py wagtail/wagtailsites/forms.py wagtail/wagtailsites/views.py wagtail/wagtailsites/wagtail_hooks.py wagtail/wagtailsnippets/tests.py wagtail/wagtailsnippets/wagtail_hooks.py wagtail/wagtailusers/forms.py wagtail/wagtailusers/views/groups.py wagtail/wagtailusers/wagtail_hooks.py
2017-11-17 10:44:34 +00:00
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
Rename wagtail.wagtailadmin to wagtail.admin Conflicts: docs/advanced_topics/customisation/admin_templates.rst docs/advanced_topics/customisation/page_editing_interface.rst docs/advanced_topics/i18n/duplicate_tree.rst docs/advanced_topics/jinja2.rst docs/advanced_topics/settings.rst docs/getting_started/integrating_into_django.rst docs/getting_started/tutorial.rst docs/reference/hooks.rst docs/reference/pages/panels.rst docs/topics/pages.rst docs/topics/streamfield.rst wagtail/admin/blocks.py wagtail/admin/checks.py wagtail/admin/edit_handlers.py wagtail/admin/forms.py wagtail/admin/rich_text.py wagtail/admin/search.py wagtail/admin/site_summary.py wagtail/admin/templatetags/wagtailadmin_tags.py wagtail/admin/tests/test_admin_search.py wagtail/admin/tests/test_buttons_hooks.py wagtail/admin/tests/test_compare.py wagtail/admin/tests/test_edit_handlers.py wagtail/admin/tests/test_page_chooser.py wagtail/admin/tests/test_pages_views.py wagtail/admin/tests/test_rich_text.py wagtail/admin/tests/test_widgets.py wagtail/admin/tests/tests.py wagtail/admin/urls/__init__.py wagtail/admin/views/account.py wagtail/admin/views/chooser.py wagtail/admin/views/collection_privacy.py wagtail/admin/views/collections.py wagtail/admin/views/home.py wagtail/admin/views/page_privacy.py wagtail/admin/viewsets/model.py wagtail/admin/wagtail_hooks.py wagtail/admin/widgets.py wagtail/contrib/settings/registry.py wagtail/contrib/wagtailsearchpromotions/wagtail_hooks.py wagtail/contrib/wagtailstyleguide/wagtail_hooks.py wagtail/project_template/project_name/settings/base.py wagtail/project_template/project_name/urls.py wagtail/tests/non_root_urls.py wagtail/tests/settings.py wagtail/tests/snippets/models.py wagtail/tests/testapp/models.py wagtail/tests/testapp/wagtail_hooks.py wagtail/tests/urls.py wagtail/wagtaildocs/models.py wagtail/wagtaildocs/views/chooser.py wagtail/wagtaildocs/wagtail_hooks.py wagtail/wagtailembeds/wagtail_hooks.py wagtail/wagtailforms/models.py wagtail/wagtailforms/tests/test_views.py wagtail/wagtailforms/views.py wagtail/wagtailforms/wagtail_hooks.py wagtail/wagtailimages/models.py wagtail/wagtailimages/views/chooser.py wagtail/wagtailimages/wagtail_hooks.py wagtail/wagtailredirects/forms.py wagtail/wagtailredirects/wagtail_hooks.py wagtail/wagtailsites/forms.py wagtail/wagtailsites/views.py wagtail/wagtailsites/wagtail_hooks.py wagtail/wagtailsnippets/tests.py wagtail/wagtailsnippets/wagtail_hooks.py wagtail/wagtailusers/forms.py wagtail/wagtailusers/views/groups.py wagtail/wagtailusers/wagtail_hooks.py
2017-11-17 10:44:34 +00:00
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):
"""
2017-01-22 08:36:38 +00:00
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,
2016-12-23 09:36:32 +00:00
# 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:
2016-12-23 09:36:32 +00:00
# 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
------------
2016-12-23 09:36:32 +00:00
If you are implementing polls or surveys, you may want to show results after submission.
The following example demonstrates how to do this.
2016-12-23 09:36:32 +00:00
First, you need to collect results as shown below:
.. code-block:: python
from modelcluster.fields import ParentalKey
Rename wagtail.wagtailadmin to wagtail.admin Conflicts: docs/advanced_topics/customisation/admin_templates.rst docs/advanced_topics/customisation/page_editing_interface.rst docs/advanced_topics/i18n/duplicate_tree.rst docs/advanced_topics/jinja2.rst docs/advanced_topics/settings.rst docs/getting_started/integrating_into_django.rst docs/getting_started/tutorial.rst docs/reference/hooks.rst docs/reference/pages/panels.rst docs/topics/pages.rst docs/topics/streamfield.rst wagtail/admin/blocks.py wagtail/admin/checks.py wagtail/admin/edit_handlers.py wagtail/admin/forms.py wagtail/admin/rich_text.py wagtail/admin/search.py wagtail/admin/site_summary.py wagtail/admin/templatetags/wagtailadmin_tags.py wagtail/admin/tests/test_admin_search.py wagtail/admin/tests/test_buttons_hooks.py wagtail/admin/tests/test_compare.py wagtail/admin/tests/test_edit_handlers.py wagtail/admin/tests/test_page_chooser.py wagtail/admin/tests/test_pages_views.py wagtail/admin/tests/test_rich_text.py wagtail/admin/tests/test_widgets.py wagtail/admin/tests/tests.py wagtail/admin/urls/__init__.py wagtail/admin/views/account.py wagtail/admin/views/chooser.py wagtail/admin/views/collection_privacy.py wagtail/admin/views/collections.py wagtail/admin/views/home.py wagtail/admin/views/page_privacy.py wagtail/admin/viewsets/model.py wagtail/admin/wagtail_hooks.py wagtail/admin/widgets.py wagtail/contrib/settings/registry.py wagtail/contrib/wagtailsearchpromotions/wagtail_hooks.py wagtail/contrib/wagtailstyleguide/wagtail_hooks.py wagtail/project_template/project_name/settings/base.py wagtail/project_template/project_name/urls.py wagtail/tests/non_root_urls.py wagtail/tests/settings.py wagtail/tests/snippets/models.py wagtail/tests/testapp/models.py wagtail/tests/testapp/wagtail_hooks.py wagtail/tests/urls.py wagtail/wagtaildocs/models.py wagtail/wagtaildocs/views/chooser.py wagtail/wagtaildocs/wagtail_hooks.py wagtail/wagtailembeds/wagtail_hooks.py wagtail/wagtailforms/models.py wagtail/wagtailforms/tests/test_views.py wagtail/wagtailforms/views.py wagtail/wagtailforms/wagtail_hooks.py wagtail/wagtailimages/models.py wagtail/wagtailimages/views/chooser.py wagtail/wagtailimages/wagtail_hooks.py wagtail/wagtailredirects/forms.py wagtail/wagtailredirects/wagtail_hooks.py wagtail/wagtailsites/forms.py wagtail/wagtailsites/views.py wagtail/wagtailsites/wagtail_hooks.py wagtail/wagtailsnippets/tests.py wagtail/wagtailsnippets/wagtail_hooks.py wagtail/wagtailusers/forms.py wagtail/wagtailusers/views/groups.py wagtail/wagtailusers/wagtail_hooks.py
2017-11-17 10:44:34 +00:00
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
2018-02-23 05:52:22 +00:00
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):
2017-11-29 09:27:26 +00:00
""" 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 = ...
2017-11-29 09:27:26 +00:00
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,)