django/django · error · ValidationError

step_size

step_size

Error message

Ensure this value is a multiple of step size %(limit_value)s, starting from %(offset)s, e.g. %(offset)s, %(valid_value1)s, %(valid_value2)s, and so on.

What it means

Raised by StepValueValidator.__call__ (django/core/validators.py:463) when offset is set and compare returns True. compare uses math.isclose(math.remainder(value - offset, step), 0, abs_tol=1e-9): the value must be an exact multiple of step measured from offset. The message lists example valid values (offset, offset+step, offset+2*step). Used by IntegerField/DecimalField when the form/widget declares a step_size.

Source

Thrown at django/core/validators.py:463

        self.offset = offset

    def __call__(self, value):
        if self.offset is None:
            super().__call__(value)
        else:
            cleaned = self.clean(value)
            limit_value = (
                self.limit_value() if callable(self.limit_value) else self.limit_value
            )
            if self.compare(cleaned, limit_value):
                offset = cleaned.__class__(self.offset)
                params = {
                    "limit_value": limit_value,
                    "offset": offset,
                    "valid_value1": offset + limit_value,
                    "valid_value2": offset + 2 * limit_value,
                }
                raise ValidationError(self.message, code=self.code, params=params)

    def compare(self, a, b):
        offset = 0 if self.offset is None else self.offset
        return not math.isclose(math.remainder(a - offset, b), 0, abs_tol=1e-9)


@deconstructible
class MinLengthValidator(BaseValidator):
    message = ngettext_lazy(
        "Ensure this value has at least %(limit_value)d character (it has "
        "%(show_value)d).",
        "Ensure this value has at least %(limit_value)d characters (it has "
        "%(show_value)d).",
        "limit_value",
    )
    code = "min_length"

    def compare(self, a, b):

View on GitHub (pinned to ae25a40be0)

Solutions

  1. Submit a value equal to offset + n*step (n integer).
  2. Snap the input to the nearest valid step before submission: rounded = offset + round((value-offset)/step)*step.
  3. Remove or widen step_size on the field/widget if arbitrary values are acceptable.

Example fix

// before
step = 5; offset = 0
qty = 7                          # rejected (7 % 5 != 0)
// after
qty = offset + round((7 - offset)/step) * step   # -> 5
Defensive patterns

Strategy: validation

Validate before calling

import math
def is_on_step(value, step, offset=0) -> bool:
    if step in (0, None):
        return True
    return math.isclose(math.remainder(value - offset, step), 0, abs_tol=1e-9)

Type guard

import math
def is_step_aligned(value: float, step: float, offset: float = 0) -> bool:
    return step not in (0, None) and math.isclose(math.remainder(value - offset, step), 0, abs_tol=1e-9)

Try / catch

from django.core.exceptions import ValidationError
try:
    validator(value)
except ValidationError as e:
    if e.code == 'step_size':
        value = offset + round((value - offset) / step) * step
        validator(value)

Prevention

When it happens

Trigger: A field with step_size=5 and offset=0 given value 7 (valid: 0,5,10,15); step_size=0.25, offset=0, value 0.30. Both trip the remainder check.

Common situations: Quantity/pricing tier fields; time-interval pickers; range inputs that snap to a step; HTML <input type=number step=...> wired to a Django form.

Related errors


AI-assisted analysis of django/django@ae25a40be0 (2026-08-06). Data as JSON: /api/errors/222baef740df9415. Report an issue: GitHub.