Skip to main content
Posts by LB

Extending The Functionality of Email Forms in Wagtail

For developers using the Wagtail CMS, intending to add more functionality to their email forms.

Tip: The ‘Forms’ sidebar item will only show up after you have your first Form created. Nifty!

The problem — you want to extend what shows up in the email from Wagtail’s form pages.

The solution — extend the ‘send_mail’ function on your form, this is not in the docs but it is available.

What is Wagtail?

Wagtail is a Content Management System (CMS) built on top of the Django Web Framework . It is written in Python and wherever possible does not reinvent the wheel so you can use a lot of the Django world really easily. The thing I love about it is that the admin user interface is really well thought out, beautiful and easy to use.

Wagtail CMS admin — helpful and clean.

I have been using Wagtail actively for the last four months after reviewing a cross-section of Python and PHP CMS options for a company I am working for. We have been building an internal wiki/intranet style application and starting with a CMS as the backbone has helped us get up and running much faster.

Is There A Way To….

It did not take long for our team to ask questions about submitting forms and requests (maintenance requests, design requests, etc) as part of the same system. After investigating other options for ad-hoc forms such as Google Forms, we elected to dig deeper into the world of Wagtail. This meant we can leverage the same codebase and keep data in the same database.

Wagtail has a basic form builder build in, which lets the users edit their own field types. Also, who the email gets sent to and custom content for the response page and form page itself.

We quickly ran into a few minor issues with the form system, the forms are quite basic and do not let you fully customise things or add new non-basic form fields. However, some of these limitations can be solved by extending the form model, here is how we solved a few of our problems.

Extending Wagtail Form Processing and Emails

Build Your Form Model

You will need to have Wagtail up and running, this post won’t cover that for you, the Wagtail Docs are great and should be enough to get you started. You need to have a form page type available in Wagtail before you do anything else. The Form section on Wagtail Docs will be sufficient to get you up and running with your first form page model.

Planning The Changes

We want to enable the following items on our form model, this means that all form pages that use this model will have these features.

  1. Email subject and body should contain the date the form was submitted .
  2. Email body should contain a link to where the form was submitted .

Tip: The benefit of unique email subjects is that they will not get threaded together in email clients.

Understanding the send_mail Function

In Wagtail version 1.7 the email functionality was extracted out into a send_mail function, you can see the pull request here . The function we will be overriding can be seen in wagtail/wagtail/wagtailforms/models.py .

send_mail(self, form)

Essentially this goes through all the fields in the form, note that the form object in this case is the POST request to the form page, not the actual database submission row. The final call to another send_mail is from wagtail.wagtailadmin.utils and this does the actual sending using Django’s built in emailing.

def send_mail(self, form):
addresses = [x.strip() for x in self.to_address.split(',')]
content = []
for field in form:
value = field.value()
if isinstance(value, list):
value = ', '.join(value)
content.append('{}: {}'.format(field.label, value))
content = '\n'.join(content)
send_mail(
self.subject, content, addresses, self.from_address)

Adding Submission Date

I will be forking the wagtaildemo for this code and this will be available on Github. You will need to go to where your Form Page class is modelled, in the demo it is in demo/models.py — something like class FormPage(AbstractEmailForm).

To get started we will simply copy and paste the send_mail function described above into our model and then test that emails still work as intended.

You will need to also import the final send_mail function by adding it to the top of your models.py

from wagtail.wagtailadmin.utils import send_mail

Your class definition should look like this:

class FormPage(AbstractEmailForm):
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
    content_panels = [
FieldPanel('title', classname="full title"),
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 send_mail(self, form):
addresses = [x.strip() for x in self.to_address.split(',')]
content = []
for field in form:
value = field.value()
if isinstance(value, list):
value = ', '.join(value)
content.append('{}: {}'.format(field.label, value))
content = '\n'.join(content)
send_mail(
self.subject, content, addresses, self.from_address)

Test that emails send as intended before moving forward, one step at a time.

In the final send_mail call it uses self.subject, this refers to the subject defined by the user when they are editing the form. We are going to flesh this out and add our date. Our last two lines of our override send_mail function will look like below:

subject = self.subject + " - " + date.today().strftime('%x')
send_mail(subject, content, addresses, self.from_address)

I used date.today() because date was already imported in the demo model, and formatted as the locale’s date representation with strftime and %x.

Our email subjects will now look like Maintenance Request Submitted — 02/15/17 (depending on your location and the subject entered).

Now we can add the date to the body of the email, using the same format as the fields with their labels.

submitted_date_str = date.today().strftime('%x')
content.append('{}: {}'.format('Submitted', submitted_date_str))
content = '\n'.join(content)
subject = self.subject + " - " + submitted_date_str
send_mail(subject, content, addresses, self.from_address)

This means that at the bottom of the email there will be a ‘Submitted: 02/15/17’ line.

Adding a Link to The Form

Now that we have done all the work above, this next step becomes a lot easier. Simply add the following line (before the content items are joined).

content.append('{}: {}'.format('Submitted Via', self.full_url))

This will create a line in the email body that looks like:

Submitted Via: http://my-domain.com/maintenance-request/

Remember that self refers to the page class, which extends the original page model, this model has some great helper functions like full_url which will give you a URL that will work in emails. Remember that relative URLs will not work in email.

Finishing Up

Our final FormPage class now looks like the following:

class FormPage(AbstractEmailForm):
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
    content_panels = [
FieldPanel('title', classname="full title"),
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 send_mail(self, form):
addresses = [x.strip() for x in self.to_address.split(',')]
content = []
for field in form:
value = field.value()
if isinstance(value, list):
value = ', '.join(value)
content.append('{}: {}'.format(field.label, value))
submitted_date_str = date.today().strftime('%x')
content.append('{}: {}'.format(
'Submitted', submitted_date_str))
content.append('{}: {}'.format(
'Submitted Via', self.full_url))
content = '\n'.join(content)
subject = self.subject + " - " + submitted_date_str
send_mail(subject, content, addresses, self.from_address)

This will generate an email with the subject containing the submission date, and the body of the email containing the date and a helpful link to where the form was submitted.

Once you have gotten this far you can start to think about better ways to enhance the content of the email. For example we could add the submitter’s IP address and even explore adding the Submission ID to the email content.

I hope this was helpful, this is my first ‘code tutorial’ and I would love any feedback and want to say thanks to the Wagtail community and Torchbox for the incredible CMS platform they have built.

Code available online on Github