# Forms, Widgets, and Templates
This guide covers how to build forms with PintFields, configure the built-in widgets, customize their appearance, and display quantity values in templates.
## Using PintFields in Forms
django-pint-field ships with two form field types that correspond to the model fields. Both support unit selection dropdowns and value validation out of the box.
### IntegerPintFormField
Use `IntegerPintFormField` for whole-number quantities. It pairs with `IntegerPintField` on the model side.
```python
from django import forms
from django_pint_field.forms import IntegerPintFormField
class ProductForm(forms.Form):
weight = IntegerPintFormField(
default_unit="gram",
unit_choices=[
("Gram", "gram"),
("Kilogram", "kilogram"),
("Pound", "pound"),
],
required=True,
help_text="Enter the product weight in whole numbers",
min_value=0, # Minimum allowed value
max_value=1000000, # Maximum allowed value
)
```
The `unit_choices` parameter controls which units appear in the dropdown. Each entry can be a plain string (`"gram"`) or a two-element tuple with a display label and the unit string (`("Gram", "gram")`).
### DecimalPintFormField
Use `DecimalPintFormField` for precise measurements that need decimal places. It pairs with `DecimalPintField` on the model side.
```python
from decimal import Decimal
from django_pint_field.forms import DecimalPintFormField
class PreciseProductForm(forms.Form):
weight = DecimalPintFormField(
default_unit="gram",
unit_choices=[
("Gram", "gram"),
("Kilogram", "kilogram"),
("Pound", "pound"),
],
display_decimal_places=3, # Display precision
rounding_method="ROUND_HALF_UP", # Rounding strategy
required=True,
help_text="Enter the precise weight",
min_value=Decimal("0.001"), # Minimum allowed value
max_value=Decimal("1000.000"), # Maximum allowed value
)
```
The `display_decimal_places` parameter controls how many decimal digits are shown. The `rounding_method` accepts any Python `decimal` module rounding constant as a string, such as `"ROUND_HALF_UP"`, `"ROUND_CEILING"`, or `"ROUND_DOWN"`.
## Configuring the Default Widget
`PintFieldWidget` is the default widget for all PintFields. It renders a numeric input alongside a unit selection dropdown. You can customize it in a `ModelForm` by overriding the `widgets` dictionary in `Meta`.
```python
from django_pint_field.widgets import PintFieldWidget
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ["weight"]
widgets = {
"weight": PintFieldWidget(
default_unit="gram",
unit_choices=[
("Gram", "gram"),
("Kilogram", "kilogram"),
("Pound", "pound"),
],
attrs={"step": "0.01"},
)
}
```
The `attrs` dictionary passes HTML attributes directly to the numeric input element. Setting `step` to `"0.01"` allows two-decimal-place input in browsers that enforce step validation.
## Using the Tabled Widget
`TabledPintFieldWidget` extends the default widget by adding a conversion table below the input. When a user enters a value, the table displays that value converted to every unit in `unit_choices`.
```python
from django_pint_field.widgets import TabledPintFieldWidget
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ["weight"]
widgets = {
"weight": TabledPintFieldWidget(
default_unit="gram",
unit_choices=[
("Gram", "gram"),
("Kilogram", "kilogram"),
("Pound", "pound"),
],
floatformat=2, # Number of decimal places in table
table_class="conversion-table", # CSS class for the
td_class="text-end", # CSS class for elements
show_units_in_values=True, # Show unit labels in value cells
)
}
```
Configuration options at a glance:
- `floatformat` -- number of decimal places shown in the conversion table cells
- `table_class` -- CSS class applied to the `` element
- `td_class` -- CSS class applied to each `| ` element
- `show_units_in_values` -- when `True`, the unit name appears next to each converted value
## Customizing Widget Styling
For richer styling, configure the tabled widget with CSS framework classes (Bootstrap, Tailwind, etc.) and add custom CSS for fine-grained control.
```python
from decimal import Decimal
from django_pint_field.forms import DecimalPintFormField
from django_pint_field.widgets import TabledPintFieldWidget
class WeightForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["weight"].widget = TabledPintFieldWidget(
default_unit="gram",
unit_choices=[
("Gram", "gram"),
("Kilogram", "kilogram"),
("Pound", "pound"),
],
floatformat=2,
table_class="table table-striped table-hover",
td_class="text-end",
show_units_in_values=True,
attrs={
"step": "0.01",
},
)
class Meta:
model = Product
fields = ["name", "weight"]
class Media:
css = {"all": ["css/weight-form.css"]}
```
A companion CSS file for the conversion table:
```css
/* weight-form.css */
.conversion-table {
margin-top: 1rem;
width: 100%;
border-collapse: collapse;
}
.conversion-table th,
.conversion-table td {
padding: 0.5rem;
border: 1px solid #dee2e6;
}
.conversion-table th {
background-color: #f8f9fa;
font-weight: bold;
}
.text-end {
text-align: right;
}
.conversion-table tr:hover {
background-color: #f5f5f5;
}
/* Responsive table */
@media (max-width: 768px) {
.conversion-table {
display: block;
overflow-x: auto;
}
}
```
## Implementing Custom Form Cleaning
Override `clean_()` to add validation logic that goes beyond simple min/max bounds. The cleaned value is a Pint `Quantity` object, so you can compare it against other quantities directly, even across different units.
```python
from decimal import Decimal
from django import forms
from django_pint_field.widgets import PintFieldWidget
from django_pint_field.units import ureg
class ProductForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["weight"].widget = PintFieldWidget(
default_unit="gram",
unit_choices=[
("Gram", "gram"),
("Kilogram", "kilogram"),
("Pound", "pound"),
],
attrs={
"class": "weight-input",
"step": "0.01",
"data-toggle": "tooltip",
"title": "Enter product weight",
},
)
def clean_weight(self):
"""Custom validation for weight field with decimal precision."""
weight = self.cleaned_data.get("weight")
if weight:
min_weight = ureg.Quantity(Decimal("0.01"), "gram")
max_weight = ureg.Quantity(Decimal("1000.00"), "kilogram")
if weight < min_weight:
raise forms.ValidationError(f"Weight must be at least {min_weight}")
if weight > max_weight:
raise forms.ValidationError(f"Weight cannot exceed {max_weight}")
return weight
class Meta:
model = Product
fields = ["name", "weight"]
```
Because `Quantity` objects handle unit conversion automatically, comparing a value in grams against a limit in kilograms works without any manual conversion.
## Customizing the Widget Template
The tabled widget renders through a Django template that you can override in your project. Place your custom version at:
```
templates/django_pint_field/tabled_django_pint_field_widget.html
```
Here is an example custom template:
```html
{% spaceless %}
{% for widget in widget.subwidgets %} {% include widget.template_name %} {%
endfor %}
{% if values_list %}
| Unit |
Value |
{% for value_item in values_list %}
| {{ value_item.units }} |
{{ value_item.magnitude|floatformat:floatformat }} {% if
show_units_in_values %} {{ value_item.units }} {% endif %}
|
{% endfor %}
{% endif %} {% endspaceless %}
```
The template context provides:
- `widget.subwidgets` -- the numeric input and unit dropdown
- `values_list` -- a list of converted values, each with `.magnitude` and `.units`
- `table_class`, `td_class`, `floatformat`, `show_units_in_values` -- the options passed to the widget constructor
## Displaying PintField Values in Templates
PintField values are accessible in templates just like any other model attribute. The value exposes `.magnitude` and `.units` for granular control.
```html
{{ product.weight }}
{{ product.weight.magnitude }}
{{ product.weight.units }}
```
You can also use the proxy's unit conversion properties directly in templates:
```html
{{ product.weight.kilogram }}
{{ product.weight.pound }}
```
See the [Cheatsheet](cheatsheet) for comprehensive template filter examples and formatting patterns.
---
**Related pages:**
- [API Reference](reference) -- complete list of widget and form field parameters
- [Cheatsheet](cheatsheet) -- template filter examples and syntax quick reference
| |