Skip to main content

PyPI npm PyPI downloads Build status Coverage Status Total alerts

Torchbox Forms

A Torchbox-flavoured template pack for django-crispy-forms, adapted from crispy-forms-gds.

Out of the box, forms created with tbxforms will look like the GOV.UK Design System, though many variables can be customised.

Contents

Installation

You must install both a Python package and an NPM package.

Install the Python package

Install using pip:

pip install tbxforms

Add django-crispy-forms and tbxforms to your installed apps:

INSTALLED_APPS = [
  ...
  'crispy_forms',  # django-crispy-forms
  'tbxforms',
]

Now add the following settings to tell django-crispy-forms to use this theme:

CRISPY_ALLOWED_TEMPLATE_PACKS = ["tbx"]
CRISPY_TEMPLATE_PACK = "tbx"

Install the NPM package

Install using NPM:

npm install tbxforms

Instantiate your forms:

import TbxForms from 'tbxforms';

document.addEventListener('DOMContentLoaded', () => {
    for (const form of document.querySelectorAll(TbxForms.selector())) {
        new TbxForms(form);
    }
});

Import the styles into your project, either as CSS:

// Either as CSS without any customisations:
@use 'node_modules/tbxforms/style.css';

Or as Sass, to customise variables:

// Or as Sass, with variables to customise:
@use 'node_modules/tbxforms/tbxforms.scss' with (
    $tbxforms-error-colour: #f00,
    $tbxforms-text-colour: #000,
);

Variables can also be defined in a centralised variables SCSS file, too.

See tbxforms/static/sass/abstracts/_variables.scss for customisable variables.

Usage

Create a Django form

from tbxforms.forms import BaseForm as TbxFormsBaseForm

class ExampleForm(TbxFormsBaseForm, forms.Form):
    # < Your field definitions >


class ExampleModelForm(TbxFormsBaseForm, forms.ModelForm):
    # < Your field definitions and ModelForm config >

Create a Wagtail form

Two parts are required for this to work:

  1. Add a helper property to the Wagtail form
  2. Instruct a Wagtail Page model to use the newly created form

Add a helper property to the Wagtail form

from wagtail.contrib.forms.forms import BaseForm as WagtailBaseForm
from tbxforms.forms import BaseForm as TbxFormsBaseForm

class ExampleWagtailForm(TbxFormsBaseForm, WagtailBaseForm):

    # Extend the `TbxFormsBaseForm.helper()` to add a submit button.
    @property
    def helper(self):
        fh = super().helper
        fh.add_input(
            Button.primary(
                name="submit",
                type="submit",
                value=_("Submit"),
            )
        )
        return fh

Instruct a Wagtail Page model to use the newly created form

# -----------------------------------------------------------------------------
# in your forms definitions (e.g. forms.py)

from tbxforms.forms import BaseWagtailFormBuilder as TbxFormsBaseWagtailFormBuilder
from path.to.your.forms import ExampleWagtailForm

class WagtailFormBuilder(TbxFormsBaseWagtailFormBuilder):
    def get_form_class(self):
        return type(str("WagtailForm"), (ExampleWagtailForm,), self.formfields)

# -----------------------------------------------------------------------------
# in your page models (e.g. models.py)

from path.to.your.forms import WagtailFormBuilder

class ExampleFormPage(...):
    ...
    form_builder = WagtailFormBuilder
    ...

Render a form

Just like Django Crispy Forms, you need to pass your form object to the {% crispy ... %} template tag, e.g.:

{% load crispy_forms_tags %}
{% crispy your_form %}

Customise a form's attributes (via the helper property)

By default, every form that inherits from TbxFormsBaseForm will have the following attributes set:

  • html5_required = True
  • label_size = Size.MEDIUM
  • legend_size = Size.MEDIUM
  • form_error_title = _("There is a problem with your submission")
  • Plus everything from django-crispy-forms' default attributes.

These can be overridden (and/or additional attributes from the above list defined) just like you would do with any other inherited class, e.g.:

class YourSexyForm(TbxFormsBaseForm, forms.Form):

    @property
    def helper(self):
        fh = super().helper
        fh.html5_required = False
        fh.label_size = Size.SMALL
        fh.form_error_title = _("Something's wrong, yo.")
        return fh

Possible values for the label_size and legend_size:

  1. SMALL
  2. MEDIUM (default)
  3. LARGE
  4. EXTRA_LARGE

Conditionally-required fields

tbxforms supports hiding/showing of fields and/or div/fieldset elements based on the values of a given input field.

Field example:

class ExampleForm(TbxFormsBaseForm, forms.Form):
    NEWSLETTER_CHOICES = (
        Choice("yes", "Yes please", hint="Receive occasional email newsletters."),
        Choice("no", "No thanks"),
    )

    newsletter_signup = forms.ChoiceField(
        choices=NEWSLETTER_CHOICES
    )

    email = forms.EmailField(
        widget=forms.EmailInput(required=False)
    )

    @staticmethod
    def conditional_fields_to_show_as_required() -> [str]:
        return [
            "email", # Include any fields that should show as required to the user.
        ]

    @property
    def helper(self):
        fh = super().helper

        # Override what is rendered for this form.
        fh.layout = Layout(

            # Add our newsletter sign-up field.
            Field("newsletter_signup"),

            # Add our email field, and define the conditional 'show' logic.
            Field(
                "email",
                data_conditional={
                    "field_name": "newsletter_signup", # Field to inspect.
                    "values": ["yes"], # Value(s) to cause this field to show.
                },
            ),

        )

        return fh


    def clean(self):
        cleaned_data = super().clean()
        newsletter_signup = cleaned_data.get("newsletter_signup")
        email = cleaned_data.get("email")

        # Assuming `form.helper.html5_required == True`, tbxforms will toggle the
        # html5 'required' attribute when a conditionally-required field is shown,
        # though it is recommended to also check the value in your clean() method.
        if newsletter_signup == "yes" and not email:
            raise ValidationError(
                {
                    "email": _("This field is required."),
                }
            )
        # The tbxforms JS will attempt to clear any redundant data upon submission,
        # though it is recommended to also handle this in your clean() method.
        elif newsletter_signup == "no" and email:
            del cleaned_data['email']

        return cleaned_data

Container example:

When you have multiple fields/elements that you want to show/hide together, you can use the exact same data_conditional definition as above but on a div or fieldset element, e.g.:

Div(
    HTML("<p>Some relevant text.</p>"),
    Field("some_other_field"),
    Field("email"),
    data_conditional={
        "field_name": "newsletter_signup",
        "values": ["yes"],
    },
),

Further reading

Metadata

Release files for tbxforms 1.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tbxforms 1.0.3
File Size Uploaded
tbxforms-1.0.3.tar.gz 38.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tbxforms 1.0.3
File Interpreter ABI Platform
tbxforms-1.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 89.6 kB

Release files / tbxforms-1.0.3.tar.gz

Download URL tbxforms-1.0.3.tar.gz
Size 38.6 kB
Tags Source
SHA-256 checksum
How to use checksums
960a91ebd6b15e6d711b2bd8da9486a71ad6e77337ca3ed1c6951806a63a2c9f
BLAKE2b-256 checksum
How to use checksums
98d927c320e888009efba13919c9bb7923d7011aecdc74b8e7decf9fc4ce9997
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.1.5 CPython/3.7.9 Darwin/20.6.0

Release files / tbxforms-1.0.3-py3-none-any.whl

Download URL tbxforms-1.0.3-py3-none-any.whl
Size 51.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
92372aaf6710140b606f27d0602d76a9d1cdb88f53ff48adb883608785264142
BLAKE2b-256 checksum
How to use checksums
ec79310238fc073dca45ec6dc525fa70641c9cd9a0f9212d706b3892e22594c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.1.5 CPython/3.7.9 Darwin/20.6.0

Release history Release notifications | RSS feed

4.3.1

2 release files

4.3.0

2 release files

4.2.0

2 release files

4.1.0

2 release files

4.0.0

2 release files

3.0.0

2 release files

2.1.0

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.1.0

2 release files

1.0.16

2 release files

1.0.15

2 release files

1.0.14

2 release files

1.0.13

2 release files

1.0.12

2 release files

1.0.10

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

This release

1.0.3 This release

2 release files

1.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page