Tutorial: Your First PintField¶
This tutorial walks through defining a model with a PintField, creating instances, retrieving values, converting units, and running basic queries. By the end you will have a working product catalog that stores weights with full unit support.
Prerequisites: a Django project with PostgreSQL, django-pint-field installed and
added to INSTALLED_APPS, and the django-pint-field migrations applied. See the
quickstart guide if you have not done that yet.
Defining the Model¶
django-pint-field provides two main field types. IntegerPintField stores
magnitudes as whole numbers and is a good fit when fractional values do not make
sense (item counts, for example). DecimalPintField stores magnitudes as
decimals and gives you control over display precision and rounding.
For a product weight, decimals are the natural choice:
# catalog/models.py
from django.db import models
from django_pint_field.models import DecimalPintField
class Product(models.Model):
name = models.CharField(max_length=100)
weight = DecimalPintField(
"gram",
unit_choices=["gram", "kilogram", "pound"],
display_decimal_places=2,
)
def __str__(self):
return f"{self.name} ({self.weight})"
The first positional argument, "gram", is the default unit. unit_choices
limits which units are available in forms and the admin. display_decimal_places
controls how many decimal places appear when the value is rendered as a string.
After saving the model, generate and apply migrations:
python manage.py makemigrations catalog
python manage.py migrate
Creating Instances¶
You can assign a PintField value using a Quantity object or a plain string.
from decimal import Decimal
from django_pint_field.units import ureg
# Using a Quantity object
Product.objects.create(
name="Flour",
weight=ureg.Quantity(Decimal("500.00"), "gram"),
)
# Using string notation
Product.objects.create(
name="Sugar",
weight="750 gram",
)
Updating a value works the same way. Assign a new quantity and call save():
product = Product.objects.get(name="Flour")
product.weight = ureg.Quantity(Decimal("1.5"), "kilogram")
product.save()
The field accepts any unit compatible with the default unit’s dimensionality. Here we stored the flour weight in kilograms even though the default unit is grams. Internally, the field converts the value to a base-unit comparator so that cross-unit lookups work correctly.
Retrieving and Inspecting Values¶
When you read a PintField from the database, you get a PintFieldProxy object.
It behaves like a quantity but adds some convenient extras.
product = Product.objects.get(name="Flour")
print(product.weight) # 1.5 kilogram
print(product.weight.magnitude) # Decimal('1.5')
print(product.weight.units) # kilogram
The underlying Pint Quantity is available as product.weight.quantity if you
need to pass it to code that expects a raw Pint object.
Converting Between Units¶
There are two ways to convert. You can call .to() on the underlying quantity,
or you can use the proxy’s attribute-access shortcut.
# Using .to() on the quantity
in_grams = product.weight.quantity.to("gram")
print(in_grams) # 1500.00 gram
# Using attribute access on the proxy
print(product.weight.gram) # 1500.0 gram
print(product.weight.pound) # ~3.31 pound
The attribute-access style also supports a double-underscore suffix to control decimal places:
print(product.weight.gram__2) # 1500.00 gram
print(product.weight.pound__3) # 3.307 pound
You can format the value in its current units the same way, using digits__N:
print(product.weight.digits__4) # 1.5000 kilogram
Basic Queries¶
django-pint-field supports exact, gt, gte, lt, lte, range, and
isnull lookups. Provide a Quantity as the lookup value.
from django_pint_field.units import ureg
# Exact match
Product.objects.filter(weight=ureg.Quantity("500 gram"))
# Greater than
Product.objects.filter(weight__gt=ureg.Quantity("1 kilogram"))
# Less than
Product.objects.filter(weight__lt=ureg.Quantity("100 gram"))
# Range
min_w = ureg.Quantity("100 gram")
max_w = ureg.Quantity("1 kilogram")
Product.objects.filter(weight__range=(min_w, max_w))
Comparisons work across units automatically. A filter for
weight__gt=ureg.Quantity("1 kilogram") will match a product stored as
"1500 gram" because the field stores a base-unit comparator internally and all
lookups operate against that comparator.
Next Steps¶
You now have a model that stores, retrieves, converts, and queries physical quantities. From here you can explore:
Configuring and Working with PintFields for custom units, decimal precision, and validation
Querying, Filtering, and Aggregating for aggregations and advanced queries
Forms, Widgets, and Templates for using PintFields in forms
Cheatsheet for a quick syntax reference