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

169

Django a divers champs numériques disponibles pour une utilisation 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, y a-t-il un moyen de le restreindre à stocker uniquement des nombres dans une certaine plage, par exemple 0,0-5,0?

À défaut, existe-t-il un moyen de restreindre un PositiveIntegerField pour ne stocker, par exemple, que des nombres jusqu'à 50?

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

sampablokuper
la source
Vous pouvez créer un signal de pré-sauvegarde: http://docs.djangoproject.com/en/dev/ref/signals/#django.db.models.signals.pre_save
igorgue
J'aurais dû mentionner que je souhaite également que la restriction soit appliquée dans l'admin de Django. Pour obtenir cela, au moins, la documentation a ceci à dire: docs.djangoproject.com/en/dev/ref/contrib/admin/…
sampablokuper
En fait, Django pré-1.0 semble avoir eu une solution vraiment élégante: cotellese.net/2007/12/11/… . Je me demande s'il existe une manière tout aussi élégante de faire cela dans la version svn de Django.
sampablokuper
Je suis déçu d'apprendre qu'il ne semble pas y avoir de moyen élégant de le faire avec le svn Django actuel. Consultez ce fil de discussion pour plus de détails: groups.google.com/group/django-users/browse_thread/thread/…
sampablokuper
Utilisez des validateurs sur le modèle, et la validation fonctionnera dans l'interface d'administration et dans ModelForms: docs.djangoproject.com/en/dev/ref/validators/…
guettli

Réponses:

133

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» du champ 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. Peut-être qu'un type IntegerRangeField pourrait être soumis en tant que patch pour que les développeurs Django envisagent l'ajout au tronc.

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 mettez le code ci-dessus):

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

OU pour une plage de négatif et de positif (comme une plage d'oscillateur):

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

Ce qui serait vraiment cool, c'est s'il pouvait être appelé avec l'opérateur de plage comme ceci:

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

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

NathanD
la source
Cela fonctionne mais lorsque dans la méthode propre du modèle, la valeur de l'entier est toujours None, ce qui fait que je ne peux pas lui fournir de nettoyage supplémentaire. Une idée de pourquoi et comment y remédier?
KrisF
2
Vous pouvez améliorer votre champ personnalisé en ajoutant MinValueValidator(min_value) and MaxValueValidator(max_value)avant d'appeler super().__init__ ...(extrait: gist.github.com/madneon/147159f46ed478c71d5ee4950a9d697d )
madneon
350

Vous pouvez utiliser les 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 : Lorsque vous travaillez directement avec le modèle, assurez-vous d'appeler la méthode model full_clean avant d'enregistrer le modèle afin de déclencher les validateurs. Cela n'est pas nécessaire lors de l'utilisation ModelFormcar les formulaires le feront automatiquement.

user1569050
la source
4
Je suppose que vous devez écrire votre propre validateur si vous voulez que le champ limited_integer_field soit également facultatif? (Valider uniquement la plage si elle n'est pas vide) null = True, blank = True ne l'a pas fait ..
radtek
2
Dans Django 1.7 null=Trueet blank=Truefonctionne comme prévu. Le champ est facultatif et s'il est laissé vide, il est stocké comme nul.
Tim Tisdall
77
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

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

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)
chris.haueter
la source
10
Utilisation d'une liste de compréhension:models.IntegerField(choices=[(i, i) for i in range(1, n)], blank=True)
Razzi Abuissa
10

Il y a deux façons de faire ça. La première consiste à utiliser la validation de formulaire pour ne jamais laisser entrer un nombre supérieur à 50 par 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 saisir des données, vous devrez remplacer la saveméthode du modèle pour lever une exception ou limiter les données entrant dans le champ.

tghw
la source
2
Vous pouvez également utiliser un formulaire pour valider les entrées non humaines. Cela fonctionne très bien pour remplir le formulaire en tant que technique de validation complète.
S.Lott
1
Après avoir réfléchi à cela, je suis sûr que je ne veux pas mettre la validation dans un formulaire. La question de savoir quelle plage de nombres est acceptable fait tout autant partie du modèle que la question de savoir quel type de nombre est acceptable. Je ne veux pas avoir à dire à chaque formulaire par lequel le modèle est modifiable, juste quelle plage de nombres accepter. Cela violerait DRY, et en plus, c'est tout simplement inapproprié. Je vais donc examiner la possibilité de
remplacer la
tghw, vous avez dit que je pouvais "remplacer la méthode de sauvegarde du modèle pour lever une exception ou limiter les données entrant dans le champ". Comment pourrais-je - à partir de la méthode overriden save () de la définition de modèle - faire en sorte que si le nombre entré est en dehors d'une plage donnée, l'utilisateur reçoive une erreur de validation un peu comme si elle avait entré des données de caractères dans un champ numérique? Existe-t-il un moyen de faire cela qui fonctionnera indépendamment du fait que l'utilisateur modifie via l'administrateur ou via un autre formulaire? Je ne veux pas simplement limiter les données entrant sur le terrain sans dire à l'utilisateur ce qui se passe :) Merci!
sampablokuper
Même si vous utilisez la méthode "save", cela ne fonctionnera pas lors de la mise à jour de la table via QuerySet comme MyModel.object.filter (blabla) .update (blabla) n'appellera pas save donc aucune vérification ne sera effectuée
Olivier Pons
5

Voici la meilleure solution si vous souhaitez une flexibilité supplémentaire et ne souhaitez pas modifier 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 vous débarrassant de l'instruction if marquée ou en modifiant votre champ pour qu'il soit vide = False, null = False dans le modèle. Cela nécessitera bien sûr une migration.

Remarque: j'ai dû ajouter le validateur car Django ne valide pas la plage sur PositiveSmallIntegerField, au lieu de cela, il 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 plage.

J'espère que cela aidera :) Plus d'informations sur les validateurs dans Django .

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

Radtek
la source