How to Build Form Page in Wagtail

Table of Contents

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-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 
  5. Add Bootstrap Theme to Wagtail

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 Tailwind CSS using django-crispy-forms

tailwindcss-forms

tailwindcss/forms is a plugin that provides a basic reset for form styles that makes form elements easy to override with utilities.

$ npm install @tailwindcss/forms

In the package.json, we can see

"@tailwindcss/forms": "^0.5.3",

Update tailwind.config.js to use the plugin.

module.exports = {
  //
  plugins: [
    // ...
    require('@tailwindcss/forms'),                // new
  ],
}

Now the default form style would look better, let's keep moving.

Setup

Update requirements.txt

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

    'captcha',
    'wagtailcaptcha',
    'crispy_forms',
    'crispy_tailwind',
]

Put this at the bottom of wagtail_app/settings.py

CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind"
CRISPY_TEMPLATE_PACK = "tailwind"

Here we tell cripsy-form to render form with tailwind layout.

Add code below to wagtail_app/settings.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.

Model

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

from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
from wagtailcaptcha.models import WagtailCaptchaEmailForm
from wagtail.core.fields import StreamField, RichTextField
from django.utils.functional import cached_property
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, FieldRowPanel


class FormField(AbstractFormField):
    page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields')


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

    content_panels = AbstractEmailForm.content_panels + [
        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 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

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_app/templates/blog/form_page.html

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

{% block content %}

  <h1>Contact</h1>
  <br>

  {% comment %}
    mb-3 mb-2 appearance-none leading-normal block border py-2 focus:outline-none border-gray-300 bg-white w-full px-4 text-gray-700 rounded-lg
    For better solution, please check https://www.accordbox.com/blog/render-django-form-with-tailwind-css-style/
  {% endcomment %}

  <form action="{% pageurl page %}" method="POST" >
    {% csrf_token %}
    {{ form|crispy }}
    <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-lg" >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 tailwind layout (which we config in CRISPY_TEMPLATE_PACK)
  3. The css classes in the template comment block are to let tailwind know which css classes are used by the form, I recommend you to check https://www.accordbox.com/blog/render-django-form-with-tailwind-css-style/ to get a better solution.

Create wagtail_app/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 %}

<div class="p-4 mb-4 text-sm text-green-700 bg-green-100 rounded-lg" role="alert">
  {{ page.thank_you_text|richtext }}
</div>

{% endblock %}

Email Test Env

To make us can check email notification sent by Wagtail in local dev environment, update wagtail_app/settings.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 debug.

Manual Test

# restart just in case web dev server 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, in free way.

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

After I click the submit, I can see the success message.

web_1  | Content-Type: text/plain; charset="utf-8"
web_1  | MIME-Version: 1.0
web_1  | Content-Transfer-Encoding: 7bit
web_1  | Subject: Contact Message
web_1  | From: [email protected]
web_1  | To: [email protected]
web_1  | Date: Fri, 21 Oct 2022 14:07:18 -0000
web_1  | Message-ID: <166636123839.10.13444404769866612605@ea9465a5052c>
web_1  | Auto-Submitted: auto-generated
web_1  |
web_1  | Name: Tom
web_1  | Email: [email protected]
web_1  | Message: Hello World

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. Susan, 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 Susan reply email, she 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 powerful multi-step form, you can check wagtail-flexible-forms (This project has no good doc but you can check the source code)

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-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 
  5. Add Bootstrap Theme to Wagtail

More Wagtail articles and eBooks written by me

Launch Products Faster with Django

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

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.

Django SaaS Template

It aims to save your time and money building your product

Learn More

Build Jamstack web app with Next.js and Wagtail CMS.

Read More
© 2018 - 2024 AccordBox