REST Framework and API Integration¶
django-pint-field provides serializer fields for Django REST Framework that handle serialization, deserialization, and validation of Pint quantities in your API endpoints.
Serializer Fields¶
Three serializer field classes are available:
IntegerPintRestField – for use with
IntegerPintField. Outputs a string like"1 gram"or, withwrap=True,"Quantity(1 gram)".DecimalPintRestField – for use with
DecimalPintField. Outputs a string like"1.5 gram"or, withwrap=True,"Quantity(1.5 gram)".PintRestField – outputs a dictionary:
{"magnitude": 1.5, "units": "gram"}.
The string-based fields give a compact, human-readable format. The dictionary-based field gives a structured format that front-end applications can parse without string manipulation.
Basic Serializer Setup¶
Given a WeightModel with weight as an IntegerPintField and precise_weight as a DecimalPintField:
from rest_framework import serializers
from django_pint_field.rest import IntegerPintRestField, DecimalPintRestField
class WeightSerializer(serializers.ModelSerializer):
weight = IntegerPintRestField()
precise_weight = DecimalPintRestField()
class Meta:
model = WeightModel
fields = ["id", "name", "weight", "precise_weight"]
By default this produces string output:
{
"id": 1,
"name": "Sample Weight",
"weight": "1000 gram",
"precise_weight": "1000.372 gram"
}
Using the Wrapped Representation¶
Set wrap=True to wrap the string output with Quantity():
from rest_framework import serializers
from django_pint_field.rest import IntegerPintRestField, DecimalPintRestField
class WeightSerializer(serializers.ModelSerializer):
weight = IntegerPintRestField(wrap=True)
precise_weight = DecimalPintRestField(wrap=True)
class Meta:
model = WeightModel
fields = ["id", "name", "weight", "precise_weight"]
Output:
{
"id": 1,
"name": "Sample Weight",
"weight": "Quantity(1000 gram)",
"precise_weight": "Quantity(1000.372 gram)"
}
Using Dictionary Format¶
PintRestField returns magnitude and units as separate keys, which can be easier to work with in client code:
from rest_framework import serializers
from django_pint_field.rest import PintRestField
class WeightSerializer(serializers.ModelSerializer):
weight = PintRestField()
class Meta:
model = WeightModel
fields = ["id", "name", "weight"]
Output:
{
"id": 1,
"name": "Sample Weight",
"weight": {
"magnitude": 1000,
"units": "gram"
}
}
ViewSet Example¶
A complete ViewSet with a custom convert action that accepts a target unit as a query parameter:
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from django_pint_field.units import ureg
class WeightViewSet(viewsets.ModelViewSet):
queryset = WeightModel.objects.all()
serializer_class = WeightSerializer
@action(detail=True, methods=["get"])
def convert(self, request, pk=None):
instance = self.get_object()
unit = request.query_params.get("unit", "gram")
try:
converted = instance.weight.quantity.to(getattr(ureg, unit))
return Response(
{"original": str(instance.weight), "converted": str(converted)}
)
except Exception as e:
return Response({"error": str(e)}, status=400)
Usage: GET /api/weights/1/convert/?unit=kilogram
Custom Validation in Serializers¶
Override validate_<field> to add business rules. This example rejects weights over 1000 kg:
from rest_framework import serializers
from django_pint_field.rest import DecimalPintRestField
from django_pint_field.units import ureg
class WeightSerializer(serializers.ModelSerializer):
weight = DecimalPintRestField()
class Meta:
model = WeightModel
fields = ["id", "name", "weight"]
def validate_weight(self, value):
kg_value = value.to(ureg.kilogram)
if kg_value.magnitude > 1000:
raise serializers.ValidationError("Weight cannot exceed 1000 kg")
return value
Unit Conversion in Serializers¶
Use SerializerMethodField to include the weight in additional units alongside the original value:
from rest_framework import serializers
from django_pint_field.rest import DecimalPintRestField
from django_pint_field.units import ureg
class WeightSerializer(serializers.ModelSerializer):
weight = DecimalPintRestField(wrap=True)
weight_in_kg = serializers.SerializerMethodField()
weight_in_lbs = serializers.SerializerMethodField()
class Meta:
model = WeightModel
fields = [
"id",
"name",
"weight",
"weight_in_kg",
"weight_in_lbs",
]
def get_weight_in_kg(self, obj):
if obj.weight:
converted = obj.weight.kilogram
return f"{converted.magnitude:.2f} kg"
return None
def get_weight_in_lbs(self, obj):
if obj.weight:
converted = obj.weight.pound
return f"{converted.magnitude:.2f} lb"
return None
Output:
{
"id": 1,
"name": "Sample Weight",
"weight": "Quantity(1000.00 gram)",
"weight_in_kg": "1.00 kg",
"weight_in_lbs": "2.20 lb"
}
Error Handling¶
The serializer fields include built-in validation. Invalid input produces clear error messages:
Invalid magnitude values raise a validation error.
Undefined or unrecognized units raise a validation error.
Incompatible unit conversions (e.g., meters to grams) raise a validation error.
Missing required fields raise a validation error.
Example error responses:
{ "weight": ["Invalid magnitude value."] }
{ "weight": ["Invalid or undefined unit."] }
{ "weight": ["Cannot convert from meters to grams"] }
Choosing a Serialization Format¶
Use PintRestField (dictionary format) when:
You need a structured, explicit format with separate magnitude and units keys.
Your API clients expect consistent JSON structures they can parse without string splitting.
You are building a front-end application that binds magnitude and unit to separate form controls.
Use IntegerPintRestField or DecimalPintRestField (string format) when:
You want a compact, human-readable representation.
String-based output is the convention for your API consumers.
You are integrating with systems that already handle string-encoded quantities.
Django Ninja¶
The example project includes Django Ninja integration. See Example Project for details on how PintFields work with Django Ninja serialization.
See also:
API Reference for the complete serializer field API