Skip to main content
Posts by LB

Image Uploads in Wagtail Forms (old)

Original Post — Wagtail 1.12

The Problem  — Your team are loving the custom form builder in Wagtail CMS and want to let people submit an image along with the form.

The Solution  — Define a new form field type that is selectable when editing fields in the CMS Admin, this field type will be called ‘Upload Image’. This field should show up in the view as a normal upload field with restrictions on file type and size, just like the Wagtail Images system.

Note the Field Type: ‘Upload Image’ — that is what we want to build.
Goal: When you add an ‘Upload Image’ field, it will show up on the form view for you.

Wagtail, Images and Forms?

Skip ahead if you know the basics here.

Wagtail is a Content Management System CMS that is built on top of the Django Web Framework . What I love about Wagtail is that it embraces the Django ecosystem and way of doing things. It also has a really nice admin interface that makes it easy for users to interact with the content.

Wagtail has a built in interface and framework for uploading, storing and serving images. This is aptly named Wagtail Images, you can review the docs about Using Images in Templates or Advanced Image Usage for more information.

Wagtail comes with a great Form Builder module, it lets users build their own forms in the admin interface. These forms can have a series of fields such as Text, Multi-line Text, Email, URL, Checkbox, and others that build up a form page that can be viewed on the front end of the website. Users can customise the default value, whether the field is required and also some help text that relates to the field.

Before We Start

Before we start changing (breaking) things, it is important that you have the following items completed.

  1. Wagtail v1.12.x up and running as per the main documentation .
  2. Wagtailforms module is installed, running and you have forms working.

Adding Image Upload Fields to Forms in Wagtail

Planning our Changes

We want to enable the following user interaction:

  1. The admin interface should provide the ability to edit an existing form and create a new form as normal.
  2. When editing a form page, there should be a new dropdown option on the ‘Field Type’ field called ‘Upload Image’.
  3. The form page view should have one file upload field for every ‘Upload Image’ field that was defined in the admin.
  4. The form page view should accept images with the same restrictions as Wagtail Images (< 10mb, only PNG/JPG/GIF*).
  5. The form page view should require the image if the field is defined as ‘required’ in admin.
  6. When an image is valid, it should save this image into the Wagtail Images area.
  7. A link to the the image should be saved to the form submission (aka form response), this will ensure it appears on emails or reports.

* Default GIF support is quite basic in Wagtail, if you want to support animated GIFs you should read these docs regarding Animated GIFs .

1. Extend the AbstractFormField Class

In your models file that contains your FormPage class definition, you should also have a definition for a FormField class. In the original definition, the AbstractFormField class uses a fixed tuple of FORM_FIELD_CHOICES . We need to override the field_type with an appended set of choices.

In the above code you can see that we imported the original FORM_FIELD_CHOICES from wagtail.wagtailforms.models. We then converted it to a list, added our new field type and then this is used in the choices argument of the field_type field.

When you do this, you will need to make a migration, and run that migration. Test it out, the form in admin will now let you select this type, but it will not do much else yet.

2. Extend the FormBuilder Class

In your models file you will now need to create an extended form builder class. In the original definition the FormBuilder class builds a form based on the field_type list that is stored in each FormPage instance. This building process generates a set of Django field classes. This is done by taking each field type and using a dictionary to find a dedicated function that returns a Django field, these functions are stored in a dictionary called FIELD_TYPES .

In the above code, we have imported FormBuilder and WagtailImageField, then created our own extended FormBuilder with a new class. The first thing we do is define a function that returns a created WagtailImageField. Then we update the dictionary of FIELD_TYPES with our new function, mapped to the ‘image’ field type. Remember above we added the choice (‘image’, ‘Upload Image’) where the key is image .

3. Set our FormPage class to use ExtendedFormBuilder

This step is pretty easy, we want to override the form_builder definition in our FormPage model. This is a very nifty way that Wagtail enables you to override the form_builder you use.

4. FormPage serve method to accept uploaded files

Uploaded files in Django are located in the request.FILES object, not in the request.POST object. In the original definition of the serve method inside AbstractForm the request.FILES object is not give to the get_form method. This means we will have to override the whole serve method. This step is also small but requires copying and pasting a lot of code from the serve method on FormPage just to change one small part.

The only difference between this serve method and the one we are extending is in the line form = self.getform(…  . We are adding request.FILES to the arguments between request.POST and page=self  .

The detailed reason for this is that Django handles files sent with the request differently, see the Django documentation about file uploads for more information. We need to ensure that our form instance gets any request.FILES along with the data stored in request.POST.

Important: In future versions of Wagtail you should not have to do this, I put through a pull request that has been approved which stops the need for this whole step.

5. Ensure our form view can accept File Data

The form page view should have a <form> tag in it, the the implementation suggested by Wagtail does not allow files data to be submitted in the form.

The only difference to the basic form is that we have added enctype=”multipart/form-data” to our form attributes. If you do not do this you will never get any files sent through the request and no errors to advise you why. For more information about why we need to do this, you can view the Django Docs File Uploads page.

6. Process the Image (file) Data after Validation

We will now override the process_form_submission on our FormPage class. The original definition of the process_form_submission method has no notion of processing anything other than the request.POST data. It will simply convert the cleaned data to JSON for storing on the form submission instance. We will iterate through each field and find any instances of WagtailImageField then get the data, create a new Wagtail Image with that file data, finally we will store a link to the image in the response.

A few items of note here:

  • cleaned_data contains the File Data (for any files), the Django form module does this for us. File Data cannot be parsed by the JSON parser, hence us having to process into a URL for these cases.
  • filename_to_title can look like whatever you want, I stripped out dashes and made the file title case. You do not have to do this but you do have to ensure there is some title when inserting a WagtailImage.
def filename_to_title(filename):
from os.path import splitext
if filename:
result = splitext(filename)[0]
result = result.replace('-', ' ').replace('_', ' ')
return result.title()
  • image.get_rendition is a very useful function detailed in the Wagtail Documentation , it mimics the template helper but can be used in Python. By default the URL will be relative (it will not contain the http/https, or the domain), this will mean links sent to email will not work. It is up to you to work out how to best solve this if it is an issue.
  • You must use cleaned_data.update to save a json seralizable reference to your image, hence the file data will not work. You could save the image ID or any other type of string, an Image URL is just what works for this use case.
  • The images will be added to the default collection, which should be the root collection, you can also customise this if you want.
  • Images require uploaded_by_user to be defined, if the form is public (ie. on a website that does not require being signed in) you will need to work around this. Maybe by just saying it was uploaded by the user that created the form page or the first user.
  • Using get_image_model is the best practice way to get the Image Model that Wagtail is using.

Finishing Up

Your Form models.py file will now look something like the following:

Forms can now have one or more Image Upload fields that are defined by the CMS editors. These images will be available in Admin in the Images section and can be used throughout the rest of Wagtail. You also get all the benefits that come with Wagtail Images like search indexing, usage in templates and URLS for images of various compressed sizes.

The Admin view of form responses will now show whatever you store from the clean_data.

Let me know if you run into issues or find some typos/bugs in this article. Thank you to the amazing team at Torchbox and all the developers of Wagtail for making this amazing tool. Show your support of Wagtail by starring the Wagtail repo on Github .

Thanks to my friend Adam for helping me proof this.