Wagtail Streamforms

Fields

Form fields are added to the form by the means of a StreamField. There are various default fields already defined as listed below:

  • singleline
  • multiline
  • date
  • datetime
  • email
  • url
  • number
  • dropdown
  • multiselect
  • radio
  • checkboxes
  • checkbox
  • hidden
  • singlefile
  • multifile

The various default options for the fields are set when choosing that type of field within the StreamField. For example a dropdown includes options to set the choices and an additional empty_label as the first choice.

Adding new fields

You can also register your own fields which will be added to the form builders StreamField. First you need to create the file wagtailstreamforms_fields.py in the root of an app in your project and add the following as an example:

from django import forms
from wagtailstreamforms.fields import BaseField, register

@register('mytext')
class CustomTextField(BaseField):
    field_class = forms.CharField

This will add a simple single line charfield to the list of available fields with the type mytext.

The BaseField class also has some additional properties you can set as follows:

@register('mytextarea')
class CustomTextAreaField(BaseField):
    # the form field class
    field_class = forms.CharField
    # the widget for the form field
    widget = forms.widgets.Textarea
    # the icon in the streamfield
    icon = 'placeholder'
    # the label to show in the streamfield
    label = 'My text area'

Setting widget attributes

Setting widget attributes can be done on the BaseField class as follows:

@register('mytextarea')
class CustomTextAreaField(BaseField):
    field_class = forms.CharField
    widget = forms.widgets.Textarea(attrs={'rows': 10})

Setting field options

The BaseField class has a default dict of options set from the StreamField’s StructValue:

class BaseField:
    def get_options(self, block_value):
        return {
            'label': block_value.get('label'),
            'help_text': block_value.get('help_text'),
            'required': block_value.get('required'),
            'initial': block_value.get('default_value')
        }

You can use this to provide additional options set either by passing them from the StreamField or manually setting them. The below adds django’s slug validator to create a slug field:

from django.core import validators

@register('slug')
class SlugField(BaseField):
    field_class = forms.CharField

    def get_options(self, block_value):
        options = super().get_options(block_value)
        options.update({'validators': [validators.validate_slug]})
        return options

Editable field options

To be able to make the field options editable from within the StreamField you must override the BaseField.get_form_block() method with the additonal options you will require.

Consider that you need a max length on a CharField but want the length to be configurable on every instance of that field. Firstly you need to setup the field’s StructBlock so that the additional options are available within the StreamField:

@register('maxlength')
class MaxLengthField(BaseField):
    field_class = forms.CharField
    label = 'Text field (max length)'

    def get_form_block(self):
        return blocks.StructBlock([
            ('label', blocks.CharBlock()),
            ('help_text', blocks.CharBlock(required=False)),
            ('required', blocks.BooleanBlock(required=False)),
            ('max_length', blocks.IntegerBlock(required=True)),
            ('default_value', blocks.CharBlock(required=False)),
        ], icon=self.icon, label=self.label)

and then pull that value into the fields options:

@register('maxlength')
class MaxLengthField(BaseField):
    field_class = forms.CharField
    label = 'Text field (max length)'

    def get_options(self, block_value):
        options = super().get_options(block_value)
        options.update({'max_length': block_value.get('max_length')})
        return options

    def get_form_block(self):
        return blocks.StructBlock([
            ('label', blocks.CharBlock()),
            ('help_text', blocks.CharBlock(required=False)),
            ('required', blocks.BooleanBlock(required=False)),
            ('max_length', blocks.IntegerBlock(required=True)),
            ('default_value', blocks.CharBlock(required=False)),
        ], icon=self.icon, label=self.label)

Overriding an existing field

Important

When overriding an existing field make sure the app that has the wagtailstreamforms_fields.py file appears after wagtailstreamforms in your INSTALLED_APPS or the field will not be overridden.

You can replace one of the form fields by simply using an existing name in the @register decorator. Suppose you want to add a rows attribute to the textarea widget of the multiline field.

In your wagtailstreamforms_fields.py file:

@register('multiline')
class MultiLineTextField(BaseField):
    field_class = forms.CharField
    widget = forms.widgets.Textarea(attrs={'rows': 10})

Using file fields

To handle file fields correctly you must ensure the your form template has the correct enctype. you can automatically add this with a simple if statement to detect if the form is a multipart type form.

<form{% if form.is_multipart %} enctype="multipart/form-data"{% endif %} action="...

Files will be uploaded using your default storage class to the path streamforms/ and are listed along with the form submissions. When a submission is deleted all files are also deleted from the storage.

Examples

Below are some examples of useful fields.

Model choice

An example model choice field of users.

from django import forms
from django.contrib.auth.models import User

from wagtail.core import blocks
from wagtailstreamforms.fields import BaseField, register


@register('user')
class UserChoiceField(BaseField):
    field_class = forms.ModelChoiceField
    icon = 'arrow-down-big'
    label = 'User dropdown field'

    @staticmethod
    def get_queryset():
        return User.objects.all()

    def get_options(self, block_value):
        options = super().get_options(block_value)
        options.update({'queryset': self.get_queryset()})
        return options

    def get_form_block(self):
        return blocks.StructBlock([
            ('label', blocks.CharBlock()),
            ('help_text', blocks.CharBlock(required=False)),
            ('required', blocks.BooleanBlock(required=False)),
        ], icon=self.icon, label=self.label)

Regex validated

An example field that allows a selection of regex patterns with an option to set the invalid error message.

Taking this further you could provide the invalid error messages from code if they were always the same for any given regex pattern.

from django import forms

from wagtail.core import blocks
from wagtailstreamforms.fields import BaseField, register

@register('regex_validated')
class RegexValidatedField(BaseField):
    field_class = forms.RegexField
    label = 'Regex field'

    def get_options(self, block_value):
        options = super().get_options(block_value)
        options.update({
            'regex': block_value.get('regex'),
            'error_messages': {'invalid': block_value.get('error_message')}
        })
        return options

    def get_regex_choices(self):
        return (
            ('(.*?)', 'Any'),
            ('^[a-zA-Z0-9]+$', 'Letters and numbers only'),
        )

    def get_form_block(self):
        return blocks.StructBlock([
            ('label', blocks.CharBlock()),
            ('help_text', blocks.CharBlock(required=False)),
            ('required', blocks.BooleanBlock(required=False)),
            ('regex', blocks.ChoiceBlock(choices=self.get_regex_choices())),
            ('error_message', blocks.CharBlock()),
            ('default_value', blocks.CharBlock(required=False)),
        ], icon=self.icon, label=self.label)

ReCAPTCHA

Adding a ReCAPTCHA field is as simple as follows.

Installing django-recaptcha:

pip install django-recaptcha

Django settings.py file:

INSTALLED_APPS = [
    ...
    'captcha'
    ...
]

# developer keys
RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
# enable no captcha
NOCAPTCHA = True

wagtailstreamforms_fields.py file:

from captcha.fields import ReCaptchaField
from wagtail.core import blocks
from wagtailstreamforms.fields import BaseField, register

@register('recaptcha')
class ReCaptchaField(BaseField):
    field_class = ReCaptchaField
    icon = 'success'
    label = 'ReCAPTCHA field'

    def get_options(self, block_value):
         options = super().get_options(block_value)
         options.update({
             'required': True
         })
         return options

    def get_form_block(self):
        return blocks.StructBlock([
            ('label', blocks.CharBlock()),
            ('help_text', blocks.CharBlock(required=False)),
        ], icon=self.icon, label=self.label)

Reference

class wagtailstreamforms.fields.BaseField

A base form field class, all form fields must inherit this class.

Usage:

@register('multiline')
class MultiLineTextField(BaseField):
    field_class = forms.CharField
    widget = forms.widgets.Textarea
    icon = 'placeholder'
    label = 'Text (multi line)'
get_form_block()

The StreamField StructBlock.

Override this to provide additional fields in the StreamField.

Returns:The wagtail.core.blocks.StructBlock to be used in the StreamField
get_formfield(block_value)

Get the form field. Its unlikely you will need to override this.

Parameters:block_value – The StreamValue for this field from the StreamField
Returns:An instance of a form field class, ie django.forms.CharField(**options)
get_options(block_value)

The field options.

Override this to provide additional options such as choices for a dropdown.

Parameters:block_value – The StreamValue for this field from the StreamField
Returns:The options to be passed into the field, ie django.forms.CharField(**options)