Django
Python
numeric fields
model validation
data constraints

How to limit the maximum value of a numeric field in a Django model?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Django, limiting a numeric field’s maximum value usually involves two separate concerns: application-level validation and database-level enforcement. If you only add one of them, invalid data can still slip through through a different code path.

Use MaxValueValidator for Model and Form Validation

The most direct Django-level solution is MaxValueValidator:

python
1from django.core.validators import MaxValueValidator
2from django.db import models
3
4
5class Product(models.Model):
6    rating = models.IntegerField(validators=[MaxValueValidator(5)])

With this validator in place, Django forms, model forms, and serializer-like validation flows that call model validation can reject values above 5 with a clear error message.

For decimal values, the pattern is the same:

python
1from decimal import Decimal
2from django.core.validators import MaxValueValidator
3from django.db import models
4
5
6class Invoice(models.Model):
7    tax_rate = models.DecimalField(
8        max_digits=5,
9        decimal_places=2,
10        validators=[MaxValueValidator(Decimal("99.99"))],
11    )

This is the right starting point when you want user-facing validation feedback.

Add a Database Constraint for Strong Enforcement

Validators are good, but they do not protect every write path. Bulk updates, direct SQL, and some internal save flows can bypass model validation. If the limit is a real business rule, add a database constraint too:

python
1from django.db import models
2from django.db.models import Q
3
4
5class Product(models.Model):
6    rating = models.IntegerField()
7
8    class Meta:
9        constraints = [
10            models.CheckConstraint(
11                check=Q(rating__lte=5),
12                name="product_rating_lte_5",
13            )
14        ]

This creates a database-level guardrail so the data store itself refuses out-of-range values.

The best production answer is often both:

  • validator for user-friendly errors
  • check constraint for hard integrity enforcement

Remember That save() Does Not Call full_clean()

This is one of the most important Django details in the whole topic. Model validators are not automatically run just because you call model.save().

If you create a model instance directly in Python code and save it, validation is skipped unless you call full_clean() yourself:

python
product = Product(rating=10)
product.full_clean()  # raises ValidationError
product.save()

That is why database constraints matter so much. They catch invalid writes even when application-level validation was skipped.

max_digits Is Not a Maximum Value

Developers sometimes assume max_digits on a DecimalField limits the numeric magnitude in the same way a business rule does. It does not. max_digits controls storage precision, not a business maximum like “must be 100 or less.”

For example, a field with max_digits=5 and decimal_places=2 controls how many total digits can be stored, but you still need a validator if the rule is “value must not exceed 99.99.”

Custom Validation for Multi-Field Rules

If the maximum depends on other fields, add custom model validation in clean():

python
1from django.core.exceptions import ValidationError
2from django.db import models
3
4
5class Discount(models.Model):
6    percentage = models.IntegerField()
7    is_premium = models.BooleanField(default=False)
8
9    def clean(self):
10        if self.is_premium and self.percentage > 80:
11            raise ValidationError({"percentage": "Premium discounts cannot exceed 80."})

This is useful when the rule is dynamic rather than a fixed numeric cap.

Common Pitfalls

The most common mistake is adding only MaxValueValidator and assuming the database is now protected. Validation helps, but it is not the same as a hard database constraint.

Another pitfall is forgetting that save() does not automatically call full_clean(). Direct model saves can bypass your validators unless your code enforces validation explicitly.

It is also easy to misuse max_digits and decimal_places as though they were business-rule limits. They are field-shape constraints, not maximum-value semantics.

Finally, if you use bulk operations or raw SQL, remember that model-level validators are not in the path. That is exactly why important invariants should also be represented as database constraints.

Summary

  • Use MaxValueValidator for Django-level validation and clear user-facing errors.
  • Add a CheckConstraint when the maximum value is a true data-integrity rule.
  • Do not assume save() automatically runs validators.
  • Do not confuse max_digits with a business maximum.
  • Use clean() when the allowed maximum depends on other model fields.

Course illustration
Course illustration

All Rights Reserved.