django-pint-field¶
Store, validate, and convert physical quantities in Django using Pint.
Why Django Pint Field?¶
Django Pint Field enables you to:
Store quantities (like 1 gram, 3 miles, 8.120391 angstroms) in your Django models
Edit quantities in forms with automatic unit conversion
Compare quantities in different units (e.g., compare weights in pounds vs. kilograms)
Display quantities in user-preferred units while maintaining accurate comparisons
Perform aggregations and lookups across different units of measurement
The package uses a PostgreSQL composite field to store both the magnitude and units, along with a base-unit comparator value for accurate comparisons. This approach ensures that users can work with their preferred units while maintaining data integrity and comparability. For this reason, the project only works with PostgreSQL databases.
Requirements¶
Python 3.11+
Django 4.2+
PostgreSQL database
Pint 0.24+
Installation¶
pip install django-pint-field
Add to your INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"django_pint_field",
# ...
]
Run migrations:
python manage.py migrate django_pint_field
Caution
Failure to run django-pint-field migrations before running migrations for models using PintFields will result in errors. The migration creates a required composite type in your PostgreSQL database.
Previous versions of the package added three composite types to the database. The newest migration modifies the columns with these types to use a single composite type.
Tips for Upgrading from Legacy django-pint-field¶
Warning
If using django-pgtrigger or other packages that depend on it (e.g.: django-pghistory), we highly recommend that you temporarily uninstall all triggers before running the django-pint-field migrations. It is also a good practice to make a backup of your database before running the migration. Users freshly installing django-pint-field do not need to worry about this warning.
python manage.py pgtrigger uninstall
Then run the migrations:
python manage.py migrate django_pint_field
Reinstall the triggers after the migrations are complete:
python manage.py pgtrigger install
Quick Start¶
Define your model:
from decimal import Decimal
from django.db import models
from django_pint_field.models import DecimalPintField
class Product(models.Model):
name = models.CharField(max_length=100)
weight = DecimalPintField(
default_unit="gram",
unit_choices=["gram", "kilogram", "pound", "ounce"],
)
Use it in your code:
from django_pint_field.units import ureg
# Create objects
product = Product.objects.create(
name="Coffee Bag",
weight=ureg.Quantity(Decimal("340"), "gram"),
)
# Query using different units
products = Product.objects.filter(
weight__gte=ureg.Quantity(Decimal("0.5"), "kilogram"),
)
# Access values
print(product.weight) # 340 gram
print(product.weight.quantity) # 340 gram (accessing the Pint Quantity object)
# Convert to different units
print(product.weight.quantity.to("kilogram")) # 0.340 kilogram
print(product.weight.kilogram) # 0.340 kilogram
print(product.weight.kilogram__2) # 0.34 kilogram (rounded to 2 decimal places)
Features¶
Field Types¶
IntegerPintField: For whole number quantities
DecimalPintField: For precise decimal quantities
Form Fields and Widgets¶
Built-in form fields with unit conversion
TabledPintFieldWidget for displaying unit conversion tables
Customizable validation and unit choices
Form Fields¶
IntegerPintFormField: Used in forms with IntegerPintField.
DecimalPintFormField: Used in forms with DecimalPintField.
Widgets¶
PintFieldWidget: Default widget for all django pint field types.
TabledPintFieldWidget: Provides a table showing conversion to each of the
unit_choices.

Django REST Framework Integration¶
from django_pint_field.rest import DecimalPintRestField
class ProductSerializer(serializers.ModelSerializer):
weight = DecimalPintRestField()
class Meta:
model = Product
fields = ["name", "weight"]
Note
The package is tested to work with both Django REST Framework and Django Ninja.
Supported Lookups¶
exactgt,gtelt,lterangeisnull
Aggregation Support¶
from django_pint_field.aggregates import PintAvg, PintSum
Product.objects.aggregate(
avg_weight=PintAvg("weight"),
total_weight=PintSum("weight"),
)
Supported Aggregates¶
PintAvgPintCountPintMaxPintMinPintSumPintStdDevPintVariancePintPercentile- continuous percentile (PERCENTILE_CONT)PintMedian- 50th percentilepint_histogram()- equi-width histogram helper
Running Totals and Partitioned Aggregates¶
Use PintWindow for unit-aware window functions; a plain django.db.models.Window around a Pint aggregate is rejected (it cannot carry the unit conversion):
from django.db.models import F
from django_pint_field.aggregates import PintSum, PintWindow
Product.objects.annotate(
running_total=PintWindow(PintSum("weight"), order_by=F("pk").asc()),
)
The result is a PintFieldProxy in the field’s base unit; pass output_unit= on the wrapped aggregate (or call .quantity.to(...)) to convert. Ordered-set aggregates (PintPercentile, PintMedian) cannot be used in a window.
Advanced Usage¶
Custom Units¶
Create your own unit registry:
from pint import UnitRegistry
custom_ureg = UnitRegistry(non_int_type=Decimal)
custom_ureg.define("custom_unit = [custom]")
# In settings.py
DJANGO_PINT_FIELD_UNIT_REGISTER = custom_ureg
Indexing¶
Django Pint Field supports creating indexes on the comparator components of Pint fields. Indexes can improve query performance when filtering, ordering, or joining on Pint field values.
Single Field Index¶
from django_pint_field.indexes import PintFieldComparatorIndex
class Package(models.Model):
weight = DecimalPintField("gram")
class Meta:
indexes = [PintFieldComparatorIndex(fields=["weight"])]
Multi-Field Index¶
from django_pint_field.indexes import PintFieldComparatorIndex
class Package(models.Model):
weight = DecimalPintField("gram")
volume = DecimalPintField("liter")
class Meta:
indexes = [PintFieldComparatorIndex(fields=["weight", "volume"])]
You can also use additional index options, as usual. e.g.:
name: Custom index namecondition: Partial index conditioninclude: Additional columns to include in the indexdb_tablespace: Custom tablespace for the index
Settings¶
# settings.py
# Set decimal precision for the entire project
DJANGO_PINT_FIELD_DECIMAL_PRECISION = 40
# Configure custom unit registry
DJANGO_PINT_FIELD_UNIT_REGISTER = custom_ureg
# Set default format for quantity display
DJANGO_PINT_FIELD_DEFAULT_FORMAT = "D" # Options: D, P, ~P, etc.
Credits¶
Modified from django-pint with a focus on composite field storage and enhanced comparison capabilities.
Getting Started
Tutorials
How-To Guides
- Configuring and Working with PintFields
- Choosing Between IntegerPintField and DecimalPintField
- Configuring unit_choices
- Defining and Using Custom Units
- Controlling Decimal Precision and Rounding
- Working with Derived Units
- Validating PintField Values
- Building Reusable Conversion Utilities
- Handling Conversion Errors Safely
- Handling Edge Cases
- Converting Between Unit Systems
- Cross-References
- Querying, Filtering, and Aggregating
- Filtering by Exact Value
- Filtering by Comparison
- Filtering by Range
- Checking for Null Values
- Combining Filters
- Using Aggregations
- Combining Aggregates with Unit Conversion
- Advanced Aggregation Patterns
- Unsupported Lookups
- Best Practices
- Converting and comparing in the database
- Analytics aggregates
- Running totals and partitioned aggregates (window functions)
- Bulk and expression-based updates
- Filtering with django-filter
- Forms, Widgets, and Templates
- Django Admin Integration
- REST Framework and API Integration
- Performance, Deployment, and Troubleshooting
Understanding
Reference
Project