Wagtail Streamforms

Wagtail Streamforms

Allows you to build forms in the CMS admin area and add them to any StreamField in your site. You can add your own fields along with the vast array of default fields which include the likes of file fields. Form submissions are controlled by hooks that you can add that process the forms cleaned data. Templates can be created which will then appear as choices when you build your form, allowing you to display and submit a form however you want.

Backwards Compatibility

Important

Please note that due to this package being virtually re-written for version 3, you cannot upgrade any existing older version of this package to version 3 and onwards. If you have an existing version installed less than 3 then you will need to completely remove it including tables and any migrations that were applied in the databases django_migrations table.

Older versions:

If you are using a version of wagtail 1.x, then the latest compatible version of this package is 1.6.3:

$ pip install wagtailstreamforms<2

Other wise you must install a version of this package from 2 onwards:

$ pip install wagtailstreamforms>=2

What else is included?

  • Each form is built using a StreamField.
  • Customise things like success and error messages, post submit redirects and more.
  • Forms are processed via a before_page_serve hook. Meaning there is no fuss like remembering to include a page mixin.
  • The hook can easily be disabled to provide the ability to create your own.
  • Form submissions are controlled via hooks meaning you can easily create things like emailing the submission which you can turn on and off on each form.
  • Fields can easily be added to the form from your own code such as Recaptcha or a Regex Field.
  • The default set of fields can easily be replaced to add things like widget attributes.
  • Form submissions are also listed by their form which you can filter by date and are ordered by newest first.
  • Files can also be submitted to the forms that are shown with the form submissions.
  • A form and its fields can easily be copied to a new form.
  • There is a template tag that can be used to render a form, in case you want it to appear outside a StreamField.

Installation

Wagtail Streamform is available on PyPI - to install it, just run:

pip install wagtailstreamforms

Once thats done you need to add the following to your INSTALLED_APPS settings:

INSTALLED_APPS = [
    ...
    'wagtail.contrib.modeladmin',
    'wagtailstreamforms'
    ...
]

Run migrations:

python manage.py migrate

Go to your cms admin area and you will see the Streamforms section.

Basic Usage

Just add the wagtailstreamforms.blocks.WagtailFormBlock() in any of your streamfields:

body = StreamField([
    ...
    ('form', WagtailFormBlock())
    ...
])

And you are ready to go.

Using the template tag

There is also a template tag you can use outside of a streamfield, within a page. All this is doing is rendering the form using the same block as in the streamfield.

The tag takes three parameters:

  • slug (string) - The slug of the form instance.
  • reference (string) - This should be a unique string and needs to be persistent on refresh/reload. See note below.
  • action (string) optional - The form action url.

Note

The reference is used when the form is being validated.

Because you can have any number of the same form on a page there needs to be a way of uniquely identifying the form beyond its PK. This is so that when the form has validation errors and it is passed back through the pages context, We know what form it is.

This reference MUST be persistent on page refresh or you will never see the errors.

Usage:

{% load streamforms_tags %}
{% streamforms_form slug="form-slug" reference="some-very-unique-reference" action="." %}

Templates

You can create your own form templates to use against any form in the system, providing a vast array of ways to create, style and submit your forms.

The default template located at streamforms/form_block.html can be seen below:

<h2>{{ value.form.title }}</h2>
<form{% if form.is_multipart %} enctype="multipart/form-data"{% endif %} action="{{ value.form_action }}" method="post" novalidate>
    {{ form.media }}
    {% csrf_token %}
    {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
    {% for field in form.visible_fields %}
        {% include 'streamforms/partials/form_field.html' %}
    {% endfor %}
    <input type="submit" value="{{ value.form.submit_button_text }}">
</form>

Note

It is important here to keep the hidden fields as the form will have some in order to process correctly.

Once you have created you own you will need to add it to the list of available templates within the form builder. This is as simple as adding it to the WAGTAILSTREAMFORMS_FORM_TEMPLATES in settings as below.

# this includes the default template in the package and an additional custom template.

WAGTAILSTREAMFORMS_FORM_TEMPLATES = (
    ('streamforms/form_block.html', 'Default Form Template'),  # default
    ('app/custom_form_template.html', 'Custom Form Template'),
)

You are not required to use the default template, its only there as a guideline to what is required and provide a fully working package out of the box. If you dont want it just remove it from the WAGTAILSTREAMFORMS_FORM_TEMPLATES setting.

Rendering your StreamField

It is important to ensure the request is in the context of your page to do this iterate over your StreamField block using wagtails include_block template tag.

{% load wagtailcore_tags %}

{% for block in page.body %}
    {% include_block block %}
{% endfor %}

DO NOT use the short form method of {{ block }} as described here as you will get CSRF verification failures.

Deleted forms

In the event of a form being deleted which is still in use in a streamfield the following template will be rendered in its place:

streamforms/non_existent_form.html

{% load i18n %}
<p>{% trans 'Sorry, this form has been deleted.' %}</p>

You can override this by putting a copy of the template in you own project using the same path under a templates directory ie app/templates/streamforms/non_existent_form.html. As long as the app is before wagtailstreamforms in INSTALLED_APPS it will use your template instead.

Messaging

When the success or error message options are completed in the form builder and upon submission of the form a message is sent to django’s messaging framework.

You will need to add django.contrib.messages to your INSTALLED_APPS setting:

INSTALLED_APPS = [
    ...
    'django.contrib.messages'
    ...
]

To display these in your site you will need to include somewhere in your page’s markup a snippet similar to the following:

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

Any message from the form will then be displayed.

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)

Advanced Settings

Some times there is a requirement to save additional data for each form. Such as details of where to email the form submission. When this is needed we have provided the means to define your own model.

To enable this you need to declare a model that inherits from wagtailstreamforms.models.AbstractFormSetting:

from wagtailstreamforms.models.abstract import AbstractFormSetting

class AdvancedFormSetting(AbstractFormSetting):
    to_address = models.EmailField()

Once that’s done you need to add a setting to point to that model:

# the model defined to save advanced form settings
# in the format of 'app_label.model_class'.
# Model must inherit from 'wagtailstreamforms.models.AbstractFormSetting'.
WAGTAILSTREAMFORMS_ADVANCED_SETTINGS_MODEL = 'myapp.AdvancedFormSetting'

A button will appear on the Streamforms listing view Advanced which will allow you to edit that model.

Usage

The data saved can be used in Submission Hooks on the instance.advanced_settings property.

@register('process_form_submission')
def email_submission(instance, form):
    send_mail(
        ..
        recipient_list=[instance.advanced_settings.to_address]
    )

Submission Methods

Form submissions are handled by the means of a wagtail before_serve_page hook. The built in hook at wagtailstreamforms.wagtail_hooks.process_form looks for a form in the post request, and either:

  • processes it redirecting back to the current page or defined page in the form setup.
  • or renders the current page with any validation error.

If no form was posted then the page serves in the usual manner.

Note

Currently the hook expects the form to be posting to the same page it exists on.

Providing your own submission method

If you do not want the current hook to be used you need to disable it by setting the WAGTAILSTREAMFORMS_ENABLE_FORM_PROCESSING to False in your settings:

WAGTAILSTREAMFORMS_ENABLE_FORM_PROCESSING = False

With this set no forms will be processed of any kind and you are free to process them how you feel fit.

A basic hook example
@hooks.register('before_serve_page')
def process_form(page, request, *args, **kwargs):
    """ Process the form if there is one, if not just continue. """

    # only process if settings.WAGTAILSTREAMFORMS_ENABLE_FORM_PROCESSING is True
    if not get_setting('ENABLE_FORM_PROCESSING'):
        return

    if request.method == 'POST':
        form_def = get_form_instance_from_request(request)

        if form_def:
            form = form_def.get_form(request.POST, request.FILES, page=page, user=request.user)
            context = page.get_context(request, *args, **kwargs)

            if form.is_valid():
                # process the form submission
                form_def.process_form_submission(form)

                # create success message
                if form_def.success_message:
                    messages.success(request, form_def.success_message, fail_silently=True)

                # redirect to the page defined in the form
                # or the current page as a fallback - this will avoid refreshing and submitting again
                redirect_page = form_def.post_redirect_page or page

                return redirect(redirect_page.get_url(request), context=context)

            else:
                # update the context with the invalid form and serve the page
                context.update({
                    'invalid_stream_form_reference': form.data.get('form_reference'),
                    'invalid_stream_form': form
                })

                # create error message
                if form_def.error_message:
                    messages.error(request, form_def.error_message, fail_silently=True)

                return TemplateResponse(
                    request,
                    page.get_template(request, *args, **kwargs),
                    context
                )
Supporting ajax requests

The only addition here from the basic example is just the if request.is_ajax: and the JsonResponse parts.

We are just making it respond with this if the request was ajax.

@hooks.register('before_serve_page')
def process_form(page, request, *args, **kwargs):
    """ Process the form if there is one, if not just continue. """

    if request.method == 'POST':
        form_def = get_form_instance_from_request(request)

        if form_def:
            form = form_def.get_form(request.POST, request.FILES, page=page, user=request.user)
            context = page.get_context(request, *args, **kwargs)

            if form.is_valid():
                # process the form submission
                form_def.process_form_submission(form)

                # if the request is_ajax then just return a success message
                if request.is_ajax():
                    return JsonResponse({'message': form_def.success_message or 'success'})

                # insert code to serve page if not ajax (as original)

            else:
                # if the request is_ajax then return an error message and the form errors
                if request.is_ajax():
                    return JsonResponse({
                        'message': form_def.error_message or 'error',
                        'errors': form.errors
                    })

                # insert code to serve page if not ajax (as original)

Add some javascript somewhere to process the form via ajax:

<form id="id_streamforms_{{ form.initial.form_reference }}">...</form>

<script>
    $("#id_streamforms_{{ form.initial.form_reference }}").submit(function(e) {
      e.preventDefault();
      var data = new FormData($(this).get(0));
      $.ajax({
          type: "POST",
          url: ".",
          data: data,
          processData: false,
          contentType: false,
          success: function(data) {
              // do something with data
              console.log(data);
          },
          error: function(data) {
              // do something with data
              console.log(data);
          }
      });
    });
</script>

Submission Hooks

Form submission hooks are used to process the cleaned_data of the form after a successful post. The only defined one is that to save the form submission data.

@register('process_form_submission')
def save_form_submission_data(instance, form):
    """ saves the form submission data """

    # copy the cleaned_data so we dont mess with the original
    submission_data = form.cleaned_data.copy()

    # change the submission data to a count of the files
    for field in form.files.keys():
        count = len(form.files.getlist(field))
        submission_data[field] = '{} file{}'.format(count, pluralize(count))

    # save the submission data
    submission = instance.get_submission_class().objects.create(
        form_data=json.dumps(submission_data, cls=FormSubmissionSerializer),
        form=instance
    )

    # save the form files
    for field in form.files:
        for file in form.files.getlist(field):
            FormSubmissionFile.objects.create(
                submission=submission,
                field=field,
                file=file
            )

You can disable this by setting WAGTAILSTREAMFORMS_ENABLE_BUILTIN_HOOKS=False in your settings.py

Create your own hook

You can easily define additional hooks to perform a vast array of actions like

  • send a mail
  • save the data to a db
  • reply to the sender
  • etc

Here is a simple example to send an email with the submission data.

Create a wagtailstreamforms_hooks.py in the root of one of your apps and add the following.

from django.conf import settings
from django.core.mail import EmailMessage
from django.template.defaultfilters import pluralize

from wagtailstreamforms.hooks import register

@register('process_form_submission')
def email_submission(instance, form):
    """ Send an email with the submission. """

    addresses = ['to@example.com']
    content = ['Please see below submission\n', ]
    from_address = settings.DEFAULT_FROM_EMAIL
    subject = 'New Form Submission : %s' % instance.title

    # build up the email content
    for field, value in form.cleaned_data.items():
        if field in form.files:
            count = len(form.files.getlist(field))
            value = '{} file{}'.format(count, pluralize(count))
        elif isinstance(value, list):
            value = ', '.join(value)
        content.append('{}: {}'.format(field, value))
    content = '\n'.join(content)

    # create the email message
    email = EmailMessage(
        subject=subject,
        body=content,
        from_email=from_address,
        to=addresses
    )

    # attach any files submitted
    for field in form.files:
        for file in form.files.getlist(field):
            file.seek(0)
            email.attach(file.name, file.read(), file.content_type)

    # finally send the email
    email.send(fail_silently=True)

A new option will appear in the setup of the forms to run the above hook. The name of the option is taken from the function name so keep them unique to avoid confusion. The instance is the form class instance, the form is the processed valid form in the request.

Permissions

Setting the level of access to administer your forms is the same as it is for any page. The permissions will appear in the groups section of the wagtail admin > settings area.

Here you can assign the usual add, change and delete permissions.

Note

Its worth noting here that if you do delete a form it will also delete all submissions for that form.

Form submission permissions

Because the form submission model is not listed in the admin area the following statement applies.

Important

If you can either add, change or delete a form then you can view all of its submissions. However to be able to delete the submissions, it requires that you can delete the form.

Housekeeping

Removing old form submissions

There is a management command you can use to remove submissions that are older than the supplied number of days to keep.

python manage.py prunesubmissions 30

Where 30 is the number of days to keep before today. Passing 0 will keep today’s submissions only.

Or to run the command from code:

from django.core.management import call_command

call_command('prunesubmissions', 30)

Settings

Any settings with their defaults are listed below for quick reference.

# the label of the forms area in the admin sidebar
WAGTAILSTREAMFORMS_ADMIN_MENU_LABEL = 'Streamforms'

# the order of the forms area in the admin sidebar
WAGTAILSTREAMFORMS_ADMIN_MENU_ORDER = None

# the model defined to save advanced form settings
# in the format of 'app_label.model_class'.
# Model must inherit from 'wagtailstreamforms.models.AbstractFormSetting'.
WAGTAILSTREAMFORMS_ADVANCED_SETTINGS_MODEL = None

# enable the built in hook to process form submissions
WAGTAILSTREAMFORMS_ENABLE_FORM_PROCESSING = True

# enable the built in hooks defined in wagtailstreamforms
# currently (save_form_submission_data)
WAGTAILSTREAMFORMS_ENABLE_BUILTIN_HOOKS = True

# the default form template choices
WAGTAILSTREAMFORMS_FORM_TEMPLATES = (
    ('streamforms/form_block.html', 'Default Form Template'),
)

Contributors

People that have helped in any way shape or form to get to where we are, many thanks.

Changelog

Next Release

  • Wagtail 2.4 Support
  • Tweak docs to ensure files work in js example (Thanks Aimee Hendrycks)

3.4.0

  • Support for Wagtail 2.3

3.3.0

  • fix issue with saving a submission with a file attached on disk.
  • added new setting WAGTAILSTREAMFORM_ENABLE_BUILTIN_HOOKS default True to allow the inbuilt form processing hooks to be disabled.

3.2.0

  • fix template that inherited from wagtailforms to wagtailadmin

3.1.0

  • Support for Wagtail 2.2

3.0.0

Version 3 is a major re-write and direction change and therefor any version prior to this needs to be removed in its entirety first.

Whats New:

  • Update to Wagtail 2.1
  • The concept of creating a custom form class to add functionality has been removed.
  • Along with the concept of custom form submission classes.
  • Fields are now added via a StreamField and you can define your own like ReCAPTCHA or RegexFields.
  • You can easily overwrite fields to add things like widget attributes.
  • You can define a model that will allow you to save additional settings for each form.
  • The form submission is processed via hooks instead of baked into the models.
  • You can create as many form submission hooks as you like to process, email etc the data as you wish. These will be available to all forms that you can enable/disable at will.
  • Files can now be uploaded and are stored along with the submission using the default storage.
  • There is a management command to easily remove old submission data.

2.0.1

  • Fixed migration issue #70

2.0.0

  • Added support for wagtail 2.

1.6.3

  • Fix issue where js was not in final package

1.6.2

  • Added javascript to auto populate the form slug from the name

1.6.1

  • Small tidy up in form code

1.6.0

  • Stable Release

1.5.2

  • Added AbstractEmailForm to more easily allow creating additional form types.

1.5.1

  • Fix migrations being regenerated when template choices change

1.5.0

  • Removed all project dependencies except wagtail and recapcha
  • The urls no longer need to be specified in your urls.py and can be removed.

1.4.4

  • The template tag now has the full page context incase u need a reference to the user or page

1.4.3

  • Fixed bug where messages are not available in the template tags context

1.4.2

  • Removed label value from recapcha field
  • Added setting to set order of menu item in cms admin

1.4.1

  • Added an optional error message to display if the forms have errors

1.4.0

  • Added a template tag that can be used to render a form. Incase you want it to appear outside a streamfield

1.3.0

  • A form and it’s fields can easily be copied to a new form from within the admin area

1.2.3

  • Fix paginator on submission list not remembering date filters

1.2.2

  • Form submission viewing and deleting permissions have been implemented

1.2.1

  • On the event that a form is deleted that is still referenced in a streamfield, we are rendering a generic template that can be overridden to warn the end user

1.2.0

  • In the form builder you can now specify a page to redirect to upon successful submission of the form
  • The page mixin StreamFormPageMixin that needed to be included in every page has now been replaced by a wagtail before_serve_page hook so you will need to remove this mixin

1.1.1

  • Fixed bug where multiple forms of same type in a streamfield were both showing validation errors when one submitted

Screenshots

_images/screen_1.png

Example Front End

_images/screen_2.png

Form Listing

_images/screen_3.png

Form Fields Selection

_images/screen_4.png

Submission Listing