web-dev-qa-db-fra.com

Comment limiter la valeur maximale d'un champ numérique dans un modèle Django?

Django propose divers champs numériques pouvant être utilisés dans les modèles, par exemple. DecimalField et PositiveIntegerField . Bien que le premier puisse être limité au nombre de décimales stockées et au nombre total de caractères stockés, existe-t-il un moyen de le limiter à l'enregistrement de seulement nombres dans une certaine plage, par ex. 0,0-5,0?

Sinon, y a-t-il un moyen de restreindre un champ PositiveIntegerField pour ne stocker que, par exemple, des nombres allant jusqu'à 50?

Mise à jour: maintenant que le bogue 6845 a été fermé , cette question de StackOverflow peut être sans objet. - sampablokuper

138
sampablokuper

Vous pouvez également créer un type de champ de modèle personnalisé - voir http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

Dans ce cas, vous pouvez "hériter" d'IntegerField intégré et remplacer sa logique de validation.

Plus j'y pense, plus je réalise à quel point cela serait utile pour de nombreuses applications Django. Un type IntegerRangeField pourrait peut-être être soumis en tant que correctif pour le Django devs à envisager d'ajouter au coffre.

Cela fonctionne pour moi:

from Django.db import models

class IntegerRangeField(models.IntegerField):
    def __init__(self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs):
        self.min_value, self.max_value = min_value, max_value
        models.IntegerField.__init__(self, verbose_name, name, **kwargs)
    def formfield(self, **kwargs):
        defaults = {'min_value': self.min_value, 'max_value':self.max_value}
        defaults.update(kwargs)
        return super(IntegerRangeField, self).formfield(**defaults)

Ensuite, dans votre classe de modèle, vous l'utiliseriez comme ceci (le champ étant le module où vous avez mis le code ci-dessus):

size = fields.IntegerRangeField(min_value=1, max_value=50)

OU pour une plage de valeurs négative et positive (comme une plage d'oscillateur):

size = fields.IntegerRangeField(min_value=-100, max_value=100)

Ce qui serait vraiment génial, c’est qu’il puisse être appelé avec l’opérateur de gamme comme ceci:

size = fields.IntegerRangeField(range(1, 50))

Mais, cela demanderait beaucoup plus de code puisque vous pouvez spécifier un paramètre 'skip' - intervalle (1, 50, 2) - Idée intéressante cependant ...

125
NathanD

Vous pouvez utiliser validateurs intégrés de Django -

from Django.db.models import IntegerField, Model
from Django.core.validators import MaxValueValidator, MinValueValidator

class CoolModelBro(Model):
    limited_integer_field = IntegerField(
        default=1,
        validators=[
            MaxValueValidator(100),
            MinValueValidator(1)
        ]
     )

Edit : Bien que ces ne fonctionnent que lorsque vous utilisez le modèle dans un ModelForm , pas si vous utilisez le modèle "tout seul", soupir.

278
user1569050
from Django.db import models
from Django.core.validators import MinValueValidator, MaxValueValidator

size = models.IntegerField(validators=[MinValueValidator(0),
                                       MaxValueValidator(5)])
69
congocongo

J'ai eu ce même problème; voici ma solution:

SCORE_CHOICES = Zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
50
chris.haueter

Il y a deux façons de faire ça. L’une consiste à utiliser la validation de formulaire pour ne jamais laisser un nombre supérieur à 50 à un utilisateur. Documents de validation de formulaire .

Si aucun utilisateur n'est impliqué dans le processus ou si vous n'utilisez pas de formulaire pour entrer des données, vous devrez alors remplacer la méthode save du modèle pour générer une exception ou limiter le nombre de données à afficher dans le champ. .

10
tghw

Voici la meilleure solution si vous voulez plus de flexibilité et que vous ne voulez pas changer votre champ de modèle. Ajoutez simplement ce validateur personnalisé:

#Imports
from Django.core.exceptions import ValidationError      

class validate_range_or_null(object):
    compare = lambda self, a, b, c: a > c or a < b
    clean = lambda self, x: x
    message = ('Ensure this value is between %(limit_min)s and %(limit_max)s (it is %(show_value)s).')
    code = 'limit_value'

    def __init__(self, limit_min, limit_max):
        self.limit_min = limit_min
        self.limit_max = limit_max

    def __call__(self, value):
        cleaned = self.clean(value)
        params = {'limit_min': self.limit_min, 'limit_max': self.limit_max, 'show_value': cleaned}
        if value:  # make it optional, remove it to make required, or make required on the model
            if self.compare(cleaned, self.limit_min, self.limit_max):
                raise ValidationError(self.message, code=self.code, params=params)

Et il peut être utilisé comme tel:

class YourModel(models.Model):

    ....
    no_dependents = models.PositiveSmallIntegerField("How many dependants?", blank=True, null=True, default=0, validators=[validate_range_or_null(1,100)])

Les deux paramètres sont max et min et autorise les valeurs nulles. Vous pouvez personnaliser le validateur si vous le souhaitez en supprimant l'instruction if marquée ou en changeant votre champ en blanc = False, null = False dans le modèle. Cela nécessitera bien sûr une migration.

Note: J'ai dû ajouter le validateur car Django ne valide pas la plage sur PositiveSmallIntegerField, mais crée un smallint (dans postgres) pour ce champ et vous obtenez une erreur de base de données si le numérique spécifié est hors de portée.

J'espère que cela vous aidera :) En savoir plus sur Validators in Django .

PS J'ai basé ma réponse sur BaseValidator dans Django.core.validators, mais tout est différent sauf le code.

5
radtek