A Torchbox-flavoured template pack for django-crispy-forms, adapted from crispy-forms-gds
Project description
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.
Requirements
- python
>=3.8.1,<4.0 - Django
>=3.2 - django-crispy-forms
>=2.1,<3.0 - wagtail
>=2.15if usingWagtailBaseForm - sass
>=1.33.0if building the sass yourself
[!NOTE] govuk-frontend will not, and does not need to, be installed to use this package.
All form-related styles from
govuk-frontend==5.4.1have been copied into this project with the prepended "govuk-" replaced with "tbxforms-", e.g..govuk-buttonto.tbxforms-buttonand@mixin govuk-clearfixto@mixin tbxforms-clearfix.
For non-government projects, installing the complete GOV.UK Frontend package unnecessarily increases the bundle size as we only need form-related styles.
For government projects, this increases the bundle size as both tbxforms and
govuk-frontend must be installed. However, these projects are less common, so
they are not prioritised.
Installation
You must install both the Python package and the 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 tbxforms:
CRISPY_ALLOWED_TEMPLATE_PACKS = ["tbxforms"]
CRISPY_TEMPLATE_PACK = "tbxforms"
Install the NPM package
Install using NPM:
npm install tbxforms
Note: This package uses the Element.closest, NodeList.forEach, and
Array.includes APIs. You will need to install and configure polyfills for
legacy browser support if you need to.
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 without any customisations:
@use 'node_modules/tbxforms/dist/style.css';
...Or as Sass to customise variables:
@use 'node_modules/tbxforms/tbxforms.scss' with (
$tbxforms-text-colour: #000,
$tbxforms-error-colour: #f00,
);
Add button styles
tbxforms provides out-of-the-box GOV.UK Design System styles for everything
except buttons, as styles for these probably exist within your project.
You will need to write button styles for the following classes:
.tbxforms-button.tbxforms-button.tbxforms-button--primary.tbxforms-button.tbxforms-button--secondary.tbxforms-button.tbxforms-button--warning
Usage
tbxforms can be used for coded Django forms and editor-controlled Wagtail forms.
Django forms
All forms must inherit the TbxFormsMixin mixin, as well as specifying a Django base form class (e.g. forms.Form or forms.ModelForm)
from django import forms
from tbxforms.forms import TbxFormsMixin
class ExampleForm(TbxFormsMixin, forms.Form):
...
class ExampleModelForm(TbxFormsMixin, forms.ModelForm):
...
Wagtail forms
Create or update a Wagtail form
Wagtail forms must inherit from TbxFormsMixin and WagtailBaseForm.
from wagtail.contrib.forms.forms import BaseForm as WagtailBaseForm
from tbxforms.forms import TbxFormsMixin
class ExampleWagtailForm(TbxFormsMixin, WagtailBaseForm):
...
Instruct a Wagtail Page model to use your form
In your form 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)
And in your form 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 %}
<html>
<body>
{% crispy your_sexy_form %}
</body>
</html>
FormHelpers
A FormHelper allows you to alter the rendering behaviour of forms.
Every form that inherits from TbxFormsMixin (i.e. every form within tbxforms)
will have a FormHelper with the following default attributes:
highlight_required_fields: see later section on highlighting required fieldshtml5_required = Truelabel_size = Size.MEDIUMlegend_size = Size.MEDIUMform_error_title = _("There is a problem with your submission")- Plus everything from django-crispy-forms' default attributes.
These can be changed during instantiation or on the go - examples below.
Add a submit button
Submit buttons are not automatically added to forms. To add one, you can extend
the form.helper.layout (examples below).
Extend during instantiation:
from django import forms
from tbxforms.forms import TbxFormsMixin
from tbxforms.layout import Button
class YourSexyForm(TbxFormsMixin, forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper.layout.extend([
Button.primary(
name="submit",
type="submit",
value="Submit",
)
])
Or afterwards:
from tbxforms.layout import Button
form = YourSexyForm()
form.helper.layout.extend([
Button.primary(
name="submit",
type="submit",
value="Submit",
)
])
Conditionally show/hide fields
tbxforms can show/hide parts of the layout depending on a given value. For
example, you could show an email address field only when the user
chooses to sign up to a newsletter (examples below).
You can apply this logic to field, div, and fieldset elements.
Field example:
from django import forms
from django.core.exceptions import ValidationError
from tbxforms.choices import Choice
from tbxforms.forms import TbxFormsMixin
from tbxforms.layout import Field, Layout
class ExampleForm(TbxFormsMixin, 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)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper.layout = Layout(
# Add our newsletter sign-up field.
Field.text("newsletter_signup"),
# Add our email field and define the conditional logic.
Field.text(
"email",
data_conditional={
"field_name": "newsletter_signup", # Field to inspect.
"values": ["yes"], # Value(s) to cause this field to show.
},
),
)
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.:
from tbxforms.layout import HTML, Div, Field, Layout
Layout(
Div(
HTML("<p>Some relevant text.</p>"),
Field.text("some_other_field"),
Field.text("email"),
data_conditional={
"field_name": "newsletter_signup",
"values": ["yes"],
},
),
)
Show conditional fields as required
Conditional fields must be optional (required=False) as they are not always
visible, but it can be useful to show them as required to the user.
To do this, use the conditional_fields_to_show_as_required() method:
from django import forms
from django.core.exceptions import ValidationError
from tbxforms.choices import Choice
from tbxforms.forms import TbxFormsMixin
from tbxforms.layout import Field, Layout
class ExampleForm(TbxFormsMixin, 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)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper.layout = Layout(
# Add our newsletter sign-up field.
Field.text("newsletter_signup"),
# Add our email field and define the conditional logic.
Field.text(
"email",
data_conditional={
"field_name": "newsletter_signup", # Field to inspect.
"values": ["yes"], # Value(s) to cause this field to show.
},
),
)
@staticmethod
def conditional_fields_to_show_as_required() -> [str]:
# Non-required fields that should show as required to the user.
return [
"email",
]
def clean(self):
cleaned_data = super().clean()
newsletter_signup = cleaned_data.get("newsletter_signup")
email = cleaned_data.get("email")
# Fields included within `conditional_fields_to_show_as_required()` will
# be shown as required but not marked as required. Therefore, we need to
# write our own check to enforce the value exists.
if newsletter_signup == "yes" and not email:
raise ValidationError(
{
"email": "This field is required.",
}
)
return cleaned_data
Customising behaviour
Highlight required fields instead of optional ones
If TBXFORMS_HIGHLIGHT_REQUIRED_FIELDS=False (or unset), optional fields will
have "(optional)" appended to their labels. This is the default behaviour and
recommended by GDS.
If TBXFORMS_HIGHLIGHT_REQUIRED_FIELDS=True, required fields will have an
asterisk appended to their labels and optional fields will not be highlighted.
This setting can be changed on a per-form basis by setting the form helper's
highlight_required_fields attribute:
from django import forms
from tbxforms.forms import TbxFormsMixin
class ExampleForm(TbxFormsMixin, forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Highlight required fields regardless of settings.TBXFORMS_HIGHLIGHT_REQUIRED_FIELDS
self.helper.highlight_required_fields = True
You can also style these markers by targeting these CSS classes:
.tbxforms-field_marker--required.tbxforms-field_marker--optional
Change the default label and legend classes
Label and legend sizes can be changed through the form's helper, e.g.:
from tbxforms.layout import Size
class ExampleForm(...):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper.label_size = Size.LARGE
self.helper.legend_size = Size.LARGE
Possible values for the label_size and legend_size:
SMALLMEDIUM(default)LARGEEXTRA_LARGE
Disable error summary
You can disable the error summary
by setting show_error_summary=False in the form's helper, e.g.:
class ExampleForm(...):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper.show_error_summary = False
This is useful if your form has a complex setup and you need complete control over the error summary - e.g. if the form also contains a formset. In this instance, you would want to create your own error summary template and include it in your template.
Further reading
- Download the PyPI package
- Download the NPM package
- Learn more about Django Crispy Forms
- Learn more about Crispy Forms GDS
- Learn more about GOV.UK Design System
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tbxforms-4.3.1.tar.gz.
File metadata
- Download URL: tbxforms-4.3.1.tar.gz
- Upload date:
- Size: 63.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.11.10 Darwin/24.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14cd3a1eacd70232ccffb0403fa21bf579f30f266242f96afa674849e8298954
|
|
| MD5 |
0ba9de8ed69e31adfa6f31d193562a2f
|
|
| BLAKE2b-256 |
1c5dd0bc4decf57487fffd380f595009365747eeaaee901b1bb9d4528b7812b4
|
File details
Details for the file tbxforms-4.3.1-py3-none-any.whl.
File metadata
- Download URL: tbxforms-4.3.1-py3-none-any.whl
- Upload date:
- Size: 90.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.11.10 Darwin/24.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ced9b2526ee1a0dccbba92ffe90eeb98717616309bc695c5cf5ca5b23e96d27
|
|
| MD5 |
c771a9f9a9f6332618c9cbc03e95de68
|
|
| BLAKE2b-256 |
78c3ffcb3690661b47c02a9bd3ffa81464ea2daabf9bc1f7904b3804756408bf
|