How to Enforce Numeric Ranges and Steps
To enforce numeric constraints such as minimum/maximum values, specific step increments, or decimal precision in your models and forms, use the built-in validators in django.core.validators.
Enforcing Constraints in Models
The most common way to apply these constraints is by adding them to the validators list of a model field.
from django.db import models
from django.core.validators import (
MinValueValidator,
MaxValueValidator,
StepValueValidator,
DecimalValidator
)
from decimal import Decimal
class InventoryItem(models.Model):
# Price must be between 0.01 and 9999.99
price = models.DecimalField(
max_digits=6,
decimal_places=2,
validators=[
MinValueValidator(Decimal("0.01")),
MaxValueValidator(Decimal("9999.99"))
]
)
# Quantity must be a multiple of 5 (e.g., 0, 5, 10...)
quantity = models.IntegerField(
validators=[StepValueValidator(5)]
)
# Rating must be between 1 and 5, in increments of 0.5
# Valid values: 1.0, 1.5, 2.0, 2.5 ... 5.0
rating = models.FloatField(
validators=[
MinValueValidator(1.0),
MaxValueValidator(5.0),
StepValueValidator(0.5, offset=1.0)
]
)
Validating Numeric Ranges
Use MinValueValidator and MaxValueValidator to set inclusive boundaries. These validators compare the input against a limit_value.
from django.core.validators import MinValueValidator, MaxValueValidator
# Direct usage
min_val = MinValueValidator(10)
min_val(15) # Passes
min_val(5) # Raises ValidationError: "Ensure this value is greater than or equal to 10."
max_val = MaxValueValidator(100)
max_val(50) # Passes
max_val(150) # Raises ValidationError: "Ensure this value is less than or equal to 100."
Enforcing Step Increments
StepValueValidator ensures a value is a multiple of a specific step size. By default, it calculates multiples starting from 0.
Basic Step
from django.core.validators import StepValueValidator
# Only multiples of 3 are allowed (0, 3, 6, 9...)
validator = StepValueValidator(3)
validator(9) # Passes
validator(10) # Raises ValidationError
Step with Offset
Use the offset parameter to start the step sequence from a non-zero value. This is useful for ranges like "starting at 1, increment by 2" (1, 3, 5...).
# Valid values: 1.4, 4.4, 7.4, 10.4...
validator = StepValueValidator(3, offset=1.4)
validator(4.4) # Passes
validator(5.4) # Raises ValidationError
Validating Decimal Precision
DecimalValidator enforces limits on the total number of digits and decimal places. It is automatically used by DecimalField, but can be used manually for custom validation.
from django.core.validators import DecimalValidator
from decimal import Decimal
# Max 5 digits total, max 2 decimal places
# This implies max 3 digits before the decimal point (5 - 2 = 3)
v = DecimalValidator(max_digits=5, decimal_places=2)
v(Decimal("123.45")) # Passes
v(Decimal("12.345")) # Raises ValidationError (too many decimal places)
v(Decimal("1234.5")) # Raises ValidationError (too many digits before decimal point)
Using Dynamic Limits
Both MinValueValidator and MaxValueValidator support callables for the limit_value. This allows you to enforce ranges that change based on the current state (e.g., current date or time).
from datetime import date
from django.db import models
from django.core.validators import MaxValueValidator
def current_year():
return date.today().year
class Vehicle(models.Model):
# Year cannot be in the future
year = models.IntegerField(
validators=[MaxValueValidator(current_year)]
)
Troubleshooting and Precision
- Floating Point Precision:
StepValueValidatorusesmath.isclosewith an absolute tolerance of1e-9. If you are working with extremely small numbers or high-precision scientific data, ensure this tolerance is appropriate for your use case. - Form Field Shorthand: In Django forms, you don't always need to instantiate these classes manually.
IntegerField,FloatField, andDecimalFieldacceptmin_value,max_value, andstep_sizearguments which automatically create the corresponding validators. - Offset Logic: When using
StepValueValidatorin a form, theoffsetis automatically set to themin_valueif provided.