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: