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.2

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.2
File Size Uploaded
tbxforms-1.0.2.tar.gz 38.6 kB Details

Built distribution (wheel)

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

Total release size: 89.6 kB

Release files / tbxforms-1.0.2.tar.gz

Download URL tbxforms-1.0.2.tar.gz
Size 38.6 kB
Tags Source
SHA-256 checksum
How to use checksums
0afe6fc033b01d768d4f60d195971d850647bc86112f1186b5dfba0ffd279975
BLAKE2b-256 checksum
How to use checksums
ed4c40b123d86a50ad5ddef50c4ae700ef3dad7c9d8ef580a0889047053a9a91
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.2-py3-none-any.whl

Download URL tbxforms-1.0.2-py3-none-any.whl
Size 51.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
89f8a4145326bf6eb871e96760cc8905b9324f4b0e60904f784241ac529ec00c
BLAKE2b-256 checksum
How to use checksums
b7addafac10824ab3804ade177f5ea8a9921df32a293e855d1244fa96852a612
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

1.0.3

2 release files

This release

1.0.2 This release

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