Skip to main content

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: StepValueValidator uses math.isclose with an absolute tolerance of 1e-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, and DecimalField accept min_value, max_value, and step_size arguments which automatically create the corresponding validators.
  • Offset Logic: When using StepValueValidator in a form, the offset is automatically set to the min_value if provided.