How to Build Form Page in Wagtail

Michael Yin

Last updated on December 10 2021

Table of Contents

Wagtail Tutorial Series:

  1. Create Wagtail Project
  2. Dockerizing Wagtail App
  3. Add Blog Models to Wagtail
  4. How to write Wagtail page template
  5. Add Bootstrap Theme to Wagtail
  6. How to use StreamField in Wagtail
  7. Wagtail Routable Page
  8. Add pagination component to Wagtail
  9. Customize Wagtail Page URL
  10. Add Full Text Search to Wagtail
  11. Add Markdown Support to Wagtail
  12. Add LaTeX Support & Code Highlight In Wagtail
  13. How to Build Form Page in Wagtail
  14. How to Create and Manage Menus in Wagtail
  15. Wagtail SEO Guide
  16. Source code: https://github.com/AccordBox/wagtail-bootstrap-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 

More Wagtail articles and eBooks written by me

Objective

By the end of this chapter, you should be able to:

  1. Understand how FormBuilder works and how to use it to build contact page in Wagtail project.
  2. Learn how to make FormBuilder work with Bootstrap theme using django-crispy-forms

Setup

Add wagtail-django-recaptcha and django-crispy-forms to the requirements.txt

Django>=3.1,<3.2
wagtail>=2.11,<2.12
psycopg2-binary
django-extensions==3.1.0

wagtail-markdown==0.6
Pygments
wagtail-django-recaptcha==1.0
django-crispy-forms==1.10.0
$ docker-compose up -d --build
$ docker-compose logs -f
  1. Add crispy_forms, captcha, and wagtailcaptcha to the INSTALLED_APPS in wagtail_bootstrap_blog/settings/base.py
  2. Please make sure wagtail.contrib.forms already exists in INSTALLED_APPS
INSTALLED_APPS = [
    # code omitted for brevity

    'modelcluster',
    'taggit',
    'django_extensions',
    'wagtailmarkdown',
    "crispy_forms",
    "captcha",
    "wagtailcaptcha",
]

Add CRISPY_TEMPLATE_PACK = "bootstrap4" to the bottom of the wagtail_bootstrap_blog/settings/base.py, which tell cripsy-form to render form with bootstrap layout. Template packs

Add code below to wagtail_bootstrap_blog/settings/dev.py

# DO NOT use on production, test key is available in the URL below
# https://developers.google.com/recaptcha/docs/faq
RECAPTCHA_PUBLIC_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
RECAPTCHA_PRIVATE_KEY = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"
NOCAPTCHA = True
SILENCED_SYSTEM_CHECKS = ["captcha.recaptcha_test_key_error"]

The config would help make recaptcha work in dev mode.

# check if config is correct
$ docker-compose run --rm web python manage.py check
System check identified no issues (1 silenced).

Model

Update blog/models.py to add new models below.

from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
from wagtailcaptcha.models import WagtailCaptchaEmailForm


class FormField(AbstractFormField):
    page = ParentalKey("FormPage", related_name="custom_form_fields")


class FormPage(WagtailCaptchaEmailForm):
    thank_you_text = RichTextField(blank=True)

    content_panels = AbstractEmailForm.content_panels + [
        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 Notification Config",
        ),
    ]

    @cached_property
    def blog_page(self):
        return self.get_parent().specific

    def get_context(self, request, *args, **kwargs):
        context = super(FormPage, self).get_context(request, *args, **kwargs)
        context["blog_page"] = self.blog_page
        return context

    def get_form_fields(self):
        return self.custom_form_fields.all()

Notes:

  1. We created FormField to store meta data of the form fields.
  2. FormPage inherit from the WagtailCaptchaEmailForm, which support email sending after submission and Google reCAPTCHA validation.
  3. from_address, to_address, and subject, these fields are used to send email notification
  4. thank_you_text here is used to store the thanks message after submission submitted.
  5. Please also remember to make blog_page available in the context, so widgets in the sidebar can work.
# migrate the db
$ docker-compose run --rm web python manage.py makemigrations
$ docker-compose run --rm web python manage.py migrate

Template

Create wagtail_bootstrap_blog/templates/blog/form_page.html

{% extends "base.html" %}
{% load static wagtailcore_tags crispy_forms_tags %}

{% block content %}

  <h1>Contact</h1>
  <br>

  <form action="{% pageurl page %}" method="POST" >
    {% csrf_token %}
    {{ form|crispy }}
    <button type="submit" class="btn btn-primary" >Submit</button>
  </form>

{% endblock %}

Notes:

  1. Here we use Django Template inheritance {% extends "base.html" %}
  2. {{ form|crispy }} would call cripsy form to render the form with bootstrap layout (which we config in CRISPY_TEMPLATE_PACK)

Create wagtail_bootstrap_blog/templates/blog/form_page_landing.html, this template would be used to render success message after form submission.

{% extends "base.html" %}

{% load static wagtailcore_tags %}

{% block content %}

<h2>{{ page.thank_you_text|richtext }}</h2>

{% endblock %}

Email Test Env

To make us can check email notification sent by Wagtail in local dev environment, update wagtail_bootstrap_blog/settings/dev.py

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

The code above make Django print the Email message to our console, which make us easy to check.

Manual Test

# restart just in case web dever has exit
$ docker-compose up -d
$ docker-compose logs -f

Create a FormPage which has contact slug as child of the BlogPage, and add some form fields.

As you can see, it is very flexible, People who have no coking skill can also create form page!

Next, let's test the form on http://127.0.0.1:8000/contact/

After I click the submit, I can see this

web_1  | Content-Type: text/plain; charset="utf-8"
web_1  | MIME-Version: 1.0
web_1  | Content-Transfer-Encoding: 7bit
web_1  | Subject: AccordBox Contact Message
web_1  | From: [email protected]
web_1  | To: [email protected]
web_1  | Date: Tue, 22 Dec 2020 02:08:26 -0000
web_1  | Message-ID: <[email protected]>
web_1  | Auto-Submitted: auto-generated
web_1  |
web_1  | Name: MichaelYin
web_1  | Email: [email protected]
web_1  | Message: This is a test message from Michael.

You can also check the form submissions in Wagtail admin/forms and export it as CSV.

Email Reply

Some people like to reply the email notification in Gmail or some 3-party customer service app such as Zendesk.

How to make it work with Wagtail form page.

Let's take a look at this example

  1. Our Wagtail project send email from [email protected], which is the from_address
  2. Tom, the admin of the Wagtail project has personal email [email protected], which is the to_address
  3. Now a visitor submit submission using the email [email protected].

The email headers would seem like this

| Subject: Contact Message
| From: [email protected]
| To: [email protected]

If Tom reply email, he will notice the email will sent back to [email protected], however, this is not the correct and Rebecca will never receive the reply.

Reply-To can help us here.

A Reply-To address is identified by inserting the Reply-To header in your email. It is the email address that the reply message is sent when you want the reply to go to an email address that is different than the From: address.

We should make the email headers like this

| Subject: Contact Message
| From: [email protected]
| To: [email protected]
| Reply-To: [email protected]

So other email services can understand it and the email reply can work as expected.

We can override the send_mail in FormPage to make this work, code below can help you.

from django.core.mail import EmailMessage

email = EmailMessage(
    subject,
    content,
    from_address,
    to_address,
    reply_to=[form['email'].data],
)

Notes:

  1. If you want to build Multi-step form, you can check snippet in Wagtail doc
  2. If you want the form work with StreamField, you can cehck wagtailstreamforms
  3. If you want a more powerfule multi-step form, you can check wagtail-flexible-forms (This project now has not detaild doc but you can check the source code)

Wagtail Tutorial Series:

  1. Create Wagtail Project
  2. Dockerizing Wagtail App
  3. Add Blog Models to Wagtail
  4. How to write Wagtail page template
  5. Add Bootstrap Theme to Wagtail
  6. How to use StreamField in Wagtail
  7. Wagtail Routable Page
  8. Add pagination component to Wagtail
  9. Customize Wagtail Page URL
  10. Add Full Text Search to Wagtail
  11. Add Markdown Support to Wagtail
  12. Add LaTeX Support & Code Highlight In Wagtail
  13. How to Build Form Page in Wagtail
  14. How to Create and Manage Menus in Wagtail
  15. Wagtail SEO Guide
  16. Source code: https://github.com/AccordBox/wagtail-bootstrap-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 

More Wagtail articles and eBooks written by me


Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

He has published some ebooks on leanpub and tech course on testdriven.io.

He is also the founder of the AccordBox which provides the web development services.


Table of Contents

Django SaaS Template

It aims to save your time and money building your product, developed by Michael Yin

Learn More

Hotwire is the default frontend solution shipped in Rails, this book will teach you how to make it work with Django, you will learn building modern web applications without using much JavaScript.

Read More

Subscribe

Get notified about new great Web Development Tutorial