New Wagtail Course! 🥳 The Ultimate Wagtail Developers Course

Tutorial Wagtail Version: 2.x

Custom StreamField Field Validation

Wagtail does A LOT of beautiful things for us, right out of the box. But the

Occasionally we as developers would like to add additional field validation to parts of our website. Let's talk about a common example: internal vs. external links.

When you want to create a custom link in a StreamField, a lot of times we'll give people the option select an existing Wagtail Page with a PageChooserPanel, and we'll give them a URLBlock. And then in the template, we'll check if the internal (Wagtail Page) was selected, and if it wasn't we'll use the external link (URLBlock). And both fields are optional so the user could enter both fields, one field, or no fields.

In this tutorial, let's cover just one of these situations and we want the user to enter just one field, but both fields are empty when the page is saved.

Let's take a look at our StructBlock StreamField.

The StreamField
``` from wagtail.core import blocks class CTABlock(blocks.StructBlock): """A simple call to action section.""" button_url = blocks.URLBlock(required=False) button_page = blocks.PageChooserBlock(required=False) class Meta: template = "streams/cta_block.html" icon = "placeholder" label = "Call to Action" ```
Call to Action URL Fields Call to Action URLBlock and PageChooserBlock

Ok, so this is a pretty standard Call to Action block, with the option to enter a URLBlock, and another option for a PageChooserBlock.

The problem is when you save your page, the button_url and the button_page are optional, so they can both be filled and they can both be empty. Let's make sure at least one of these fields are filled out.

The way we make this happen is by invoking the clean() method on the StreamField. Let's take a look.

``` # ... other imports from django.core.exceptions import ValidationError from django.forms.utils import ErrorList class CTABlock(blocks.StructBlock): # ... def clean(self, value): errors = {} if not value.get('button_url') and value.get('button_page') is None: errors['button_url'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these fields.']) errors['button_page'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these fields.']) if errors: raise ValidationError('Validation error in StructBlock', params=errors) return super().clean(value) ```

What we're doing here is:

  1. Defining a local clean() method and passing in the value of the SteamField.
  2. Create an empty dict with errors = {}
  3. Check if the button_url is empty, and check if the button_page is not None
  4. If there are errors, add the error (ErrorList) to your errors dict.
  5. If there are errors, raise a ValidationError.

The code snippet above will technically raise 2 errors. We're showing the same error twice, but on both of the link fields.

This takes care of both fields (being optional in nature) being left empty. We're now forcing at least one of the fields to be filled. The end result, when both fields are left empty, will look like this:

Empty Link Fields Both fields left empty raises this error on both fields.
What about when both fields are filled?

This is the next issue. As of right now, our StreamField will save perfectly fine when you select a Wagtail Page and enter a URL manually. At this point we need to create another conditional statement, where we check for both fields being filled out. Let's adjust our clean() method and add new errors to the errors dict.

``` class CTABlock(blocks.StructBlock): # ... def clean(self, value): errors = {} if not value.get('button_url') and value.get('button_page') is None: errors['button_url'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these.']) errors['button_page'] = ErrorList(['You must select a page, or enter a URL. Please fill just one of these.']) # We're adding the elif statement here. elif value.get('button_url') and value.get('button_page'): errors['button_url'] = ErrorList(['Please select a page OR enter a URL (choose one)']) errors['button_page'] = ErrorList(['Please select a page OR enter a URL (choose one)']) if errors: raise ValidationError('Validation error in StructBlock', params=errors) return super().clean(value) ```

Now we're able to check if both fields are empty, and if both fields are filled. Here's what it looks like when you have both fields filled out.

Filled Out CTA Fields When both fields are filled, it throws an error.

In summary, we've alleviated the problem of having two optionally blank fields and having our future users and client become a bit confused when they're asked to enter an external URL or select an internal Wagtail Page. And we've given them some additional guidance via ValidationError's when something goes wrong. For a full example of the code, check out the gist below.

Related tutorials

Adding Custom StreamField Logic

Posted on

There will be times when you need to provide multiple optional fields in a StreamField, and naturally we lean towards using logic in the template. In this video we're going to learn how to pull out template logic and use Python instead. In this example we'll be using a StructValue and StructBlock to return a single URL in the template even though the StreamField has a PageChooserBlock and a URLBlock (pick one and return it).

View lesson, Adding Custom StreamField Logic

Headless CMS: Image Rendition Field

Posted on

In the previous lesson we created a custom Django Rest Framework Serializer to output JSONified image data in the Wagtail v2 API, including the URL, title, height and width. In this tutorial I'll show you how to to do the same thing but in one line of code using a built in Wagtail class.

View lesson, Headless CMS: Image Rendition Field

Headless CMS: Exposing Orderable Data and StreamFields

Posted on

In this tutorial you will learn how to add Orderable model fields to your Wagtail v2 API, and how to add StreamFields to your API response. As with everything in Wagtail, this is a simple task for developers.

View lesson, Headless CMS: Exposing Orderable Data and StreamFields

Setting Up A RichText Content Area

Posted on

In Wagtail you can have two Richtext areas: a model field and a StreamField. In this article we'll cover the model field type.

View lesson, Setting Up A RichText Content Area

The Ultimate Wagtail Developers Course

This course covers everything from basic installation to advanced features like custom blocks and API integration, it's perfect for developers looking to enhance their skills with this powerful CMS.

Ultimate Wagtail Developers Course Logo