Skip to main content

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

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tbxforms-1.0.3.tar.gz (38.6 kB view hashes)

Uploaded Source

Built Distribution

tbxforms-1.0.3-py3-none-any.whl (51.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page