53

I have a django model,

class MyModel(models.Model)
    qty = model.IntegerField()

where I want to set constraint for qty something like this, >0 or <0,i.e the qty can be negative or positive but can not be 0.

Is there any straight forward way to do this in Django?

Louis Barranqueiro
  • 10,058
  • 6
  • 42
  • 52
Md. Tanvir Raihan
  • 4,075
  • 9
  • 37
  • 70

5 Answers5

112

You can use Django's built-in validators -

from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator

class MyModel(models.Model):
    qty = models.IntegerField(
        default=1,
        validators=[MaxValueValidator(100), MinValueValidator(1)]
     )

NOTE:

  1. The validators will not run automatically when you save a model, but if you are using a ModelForm, it will run your validators on the fields that are included in the form. Check this link for more info.
  2. To enforce constraints on a database level refer to maertejin's answer below.
JRodDynamite
  • 12,325
  • 5
  • 43
  • 63
6

You will have to create a custom validator

from django.core.exceptions import ValidationError

def validate_number(value):
    if something :  # Your conditions here
        raise ValidationError('%s some error message' % value)

And then use this validator in your model

from django.db import models

class MyModel(models.Model):
    field = models.IntegerField(validators=[validate_number])
utkbansal
  • 2,787
  • 2
  • 26
  • 47
6

Since Django 2.2 you can enforce the constraints on a database level with CheckConstraint:

class MyModel(models.Model):
    qty = models.IntegerField()

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=models.Q(qty__gte=1) & models.Q(qty__lt=10),
                name="A qty value is valid between 1 and 10",
            )
        ]
maerteijn
  • 489
  • 6
  • 9
  • I get the following error: "IndentationError: unindent does not match any outer indentation level" Should there be an indentation for class Meta? – Bhavesh Jun 09 '22 at 12:28
  • 1
    @Bhavesh There was no `:` anfter (models.Model) and I used `model` instead of `models` so I updated the example. Indentation should be ok, but try to copy/paste it again. – maerteijn Jun 09 '22 at 13:51
1

For better code reusability you can create custom RangeIntegerField

from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models


class RangeIntegerField(models.IntegerField):
    def __init__(self, *args, **kwargs):
        validators = kwargs.pop("validators", [])
        
        # turn min_value and max_value params into validators
        min_value = kwargs.pop("min_value", None)
        if min_value is not None:
            validators.append(MinValueValidator(min_value))
        max_value = kwargs.pop("max_value", None)
        if max_value is not None:
            validators.append(MaxValueValidator(max_value))

        kwargs["validators"] = validators

        super().__init__(*args, **kwargs)

You can use this Field in your models

class SomeModel(models.Model):
    some_value = RangeIntegerField(min_value=42, max_value=451)

It plays well with both django-forms and DRF's serializers

Yevhen Bondar
  • 4,357
  • 1
  • 11
  • 31
-2

If you are using postgres, you can use range fields to specify the range. Check this: Range Fields in django

cezar
  • 11,616
  • 6
  • 48
  • 84
Kishan Mehta
  • 2,598
  • 5
  • 39
  • 61
  • 1
    you are referring the fields it for storing ranges, but the question was about constraints, which limit field value inside the range – ramusus Feb 10 '18 at 21:10