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.
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.
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.
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.
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 <table>
td_class="text-end", # CSS class for <td> 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 cellstable_class– CSS class applied to the<table>elementtd_class– CSS class applied to each<td>elementshow_units_in_values– whenTrue, 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.
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:
/* 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_<fieldname>() 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.
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:
{% spaceless %}
<div class="pint-field-inputs">
{% for widget in widget.subwidgets %} {% include widget.template_name %} {%
endfor %}
</div>
{% if values_list %}
<div class="pint-field-table">
<table class="{{ table_class }}">
<thead>
<tr>
<th>Unit</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for value_item in values_list %}
<tr>
<td>{{ value_item.units }}</td>
<td class="{{ td_class }}">
{{ value_item.magnitude|floatformat:floatformat }} {% if
show_units_in_values %} {{ value_item.units }} {% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %} {% endspaceless %}
The template context provides:
widget.subwidgets– the numeric input and unit dropdownvalues_list– a list of converted values, each with.magnitudeand.unitstable_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.
<!-- Full quantity string (e.g., "500.00 gram") -->
{{ product.weight }}
<!-- Magnitude only (e.g., "500.00") -->
{{ product.weight.magnitude }}
<!-- Units only (e.g., "gram") -->
{{ product.weight.units }}
You can also use the proxy’s unit conversion properties directly in templates:
<!-- Convert to kilograms -->
{{ product.weight.kilogram }}
<!-- Convert to pounds -->
{{ product.weight.pound }}
See the Cheatsheet for comprehensive template filter examples and formatting patterns.
Related pages:
API Reference – complete list of widget and form field parameters
Cheatsheet – template filter examples and syntax quick reference