96

I'm storing some floating-point data in my Django models, and only a certain range of values are meaningful. Therefore, I'd like to impose these limits at both the model and SQL constraint level.

For example, I'd like to do something like this:

class Foo(Model):
   myfloat = FloatField(min=0.0, max=1.0)

I want to do this at the model level, not the form level. In fact, I might like the form level to have a different range; e.g., use percentages [0,100] at the form level but translate to [0,1] in the model.

Is this possible, and if so, how would I go about doing it?

3 Answers 3

178

The answers so far describe how to make forms validate. You can also put validators in the model. Use MinValueValidator and MaxValueValidator.

For example:

from django.core.validators import MaxValueValidator, MinValueValidator

...
weight = models.FloatField(
    validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],
)

EDIT:

However, that does not add a SQL constraint.

You can add SQL constraints as described here as CheckConstraints in Meta.constraints.

Combined example:

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

class Foo(Model):
    myfloat = FloatField(min=0.0, max=1.0,
        # for checking in forms
        validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],)

    class Meta:
        constraints = (
            # for checking in the DB
            CheckConstraint(
                check=Q(myfloat__gte=0.0) & Q(myfloat__lte=1.0),
                name='foo_myfloat_range'),
            )
Sign up to request clarification or add additional context in comments.

2 Comments

However these validators don't work at the model level, at least not automatically. You can save a model with weight exceeding these limits alright. It's just that form validation will automatically pick up these validators.
@AntonisChristofides Over a year later, but I added CheckConstraint for the DB constraint.
22

If you need constraint on form level you can pass min_value and max_value to form field:

myfloat = forms.FloatField(min_value=0.0, max_value=1.0)

But if you need to move it up to model level you have to extend base models.FloatField class

class MinMaxFloat(models.FloatField):
    def __init__(self, min_value=None, max_value=None, *args, **kwargs):
        self.min_value, self.max_value = min_value, max_value
        super(MinMaxFloat, self).__init__(*args, **kwargs)

    def formfield(self, **kwargs):
        defaults = {'min_value': self.min_value, 'max_value' : self.max_value}
        defaults.update(kwargs)
        return super(MinMaxFloat, self).formfield(**defaults)

Then you can use it in models

class Foo(models.Model):
    myfloat = MinMaxFloat(min_value=0.0, max_value=1.0)

2 Comments

OK. And then extending the MinMaxFloat to actually validate against those values, and create database constraints, would be up to me?
It creates constraint on django model level, i.e. you can insert record manually like Foo(myfloat=-1.0).save(), but you will get ValidationError when try to send ModelForm based on your Foo model
3

It's been quite some time and the community voted answer is good enough, based on Django built-ins, but here is another approach, problem specific, following the Django Project's documentation.

Create a custom validator

# app/models.py or create another file for custom validators, i.e. app/custom_validators.py
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _  # use if you support internationalization

def validate_interval(value):
    if value < 0.0 or value > 1.0:
        raise ValidationError(_('%(value)s must be in the range [0.0, 1.0]'), params={'value': value},)

Use your validator in your models

# app/models.py

class Foo(models.Model):
    myfloat = models.FloatField(validators=[validate_interval])

You can even make the validator parametric to be more generic.

EDIT - IMPORTANT

Bare in mind that any validator is invoked during the form's clean() method.

I.E. if you try to directly create an object without using a form, your validator will NOT be invoked.

There are those who don't recommend it but if you want to invoke validators while creating an object override the save method.

# app/models.py

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _  # use if you support internationalization


class Foo(models.Model):
    def save(self, *args, **kwargs):
        self.full_clean()  # or clean
        super().save(*args, **kwargs)

    def validate_interval(value):
        if value < 0.0 or value > 1.0:
            raise ValidationError(_('%(value)s must be in the range [0.0, 1.0]'), params={'value': value},)

    myfloat = models.FloatField(validators=[validate_interval])

3 Comments

i put a logger inside the validation function, and it doesn't run
Please read carefully the provided resources. If you read in the link provided in my original answer it is clarified that the validator is called in the ModelForm's clean() method. Quoting from the link : Note that validators will not be run automatically when you save a model. I assume that you try to directly create an object, i.e. using save() method and thus the validator is never to be called. If you create an object using a form the validator will be utilized. Otherwise you can override the save() method.
I understand why someone downvoted (as the question is quite specific), but really this answer is a helpful addition, and gives perspective on why you might want to use different approaches to validation

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.