¿Cómo limitar el valor máximo de un campo numérico en un modelo Django?

169

Django tiene varios campos numéricos disponibles para su uso en modelos, por ejemplo, DecimalField y PositiveIntegerField . Aunque el primero puede restringirse al número de lugares decimales almacenados y al número total de caracteres almacenados, ¿hay alguna forma de restringirlo a almacenar solo números dentro de un cierto rango, por ejemplo, 0.0-5.0?

De lo contrario, ¿hay alguna forma de restringir un PositiveIntegerField para almacenar solo, por ejemplo, números hasta 50?

Actualización: ahora que el Bug 6845 se ha cerrado , esta pregunta de StackOverflow puede ser discutible. - sampablokuper

sampablokuper
fuente
Debería haber mencionado que también quiero que la restricción se aplique en el administrador de Django. Para obtener eso, al menos, la documentación tiene esto que decir: docs.djangoproject.com/en/dev/ref/contrib/admin/…
sampablokuper
En realidad, Django anterior a 1.0 parece haber tenido una solución muy elegante: cotellese.net/2007/12/11/… . Me pregunto si hay una forma igualmente elegante de hacer esto en la versión svn de Django.
sampablokuper
Me decepciona saber que no parece haber una manera elegante de hacer esto con el Django svn actual. Vea este hilo de discusión para más detalles: groups.google.com/group/django-users/browse_thread/thread/…
sampablokuper
Utilice validadores en el modelo, y la validación funcionará en la interfaz de administración y en ModelForms: docs.djangoproject.com/en/dev/ref/validators/…
guettli

Respuestas:

133

También puede crear un tipo de campo de modelo personalizado: consulte http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

En este caso, podría 'heredar' del IntegerField incorporado y anular su lógica de validación.

Cuanto más pienso en esto, me doy cuenta de lo útil que sería para muchas aplicaciones de Django. Quizás un tipo IntegerRangeField podría enviarse como un parche para que los desarrolladores de Django consideren agregarlo a la troncal.

Esto es trabajo para mí:

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)

Luego, en su clase de modelo, lo usaría así (el campo es el módulo donde coloca el código anterior):

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

O para un rango negativo y positivo (como un rango de oscilador):

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

Lo que sería realmente genial es si se pudiera llamar con el operador de rango de esta manera:

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

Pero, eso requeriría mucho más código ya que puede especificar un parámetro 'omitir' - rango (1, 50, 2) - Aunque es una idea interesante ...

NathanD
fuente
Esto funciona pero cuando en el método de limpieza del modelo, el valor del entero siempre es Ninguno, lo que lo hace así que no puedo proporcionarle ninguna limpieza adicional. ¿Alguna idea de por qué es esto y cómo solucionarlo?
KrisF
2
Puede mejorar su campo personalizado agregando MinValueValidator(min_value) and MaxValueValidator(max_value)antes de llamar super().__init__ ...(fragmento: gist.github.com/madneon/147159f46ed478c71d5ee4950a9d697d )
madneon
350

Puede usar los validadores integrados 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)
        ]
     )

Editar : cuando trabaje directamente con el modelo, asegúrese de llamar al método full_clean del modelo antes de guardar el modelo para activar los validadores. Esto no es necesario cuando se usa, ModelFormya que los formularios lo harán automáticamente.

usuario1569050
fuente
44
Supongo que tienes que escribir tu propio validador si quieres que el campo_interte_limitado también sea opcional. (Solo validar el rango si no está en blanco) nulo = Verdadero, en blanco = Verdadero no lo hizo ..
radtek
2
En Django 1.7 configura null=Truey blank=Truefunciona como se esperaba. El campo es opcional y si se deja en blanco se almacena como nulo.
Tim Tisdall
77
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

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

Tuve este mismo problema; aquí estaba mi solución:

SCORE_CHOICES = zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
chris.haueter
fuente
10
Usando una lista de comprensión:models.IntegerField(choices=[(i, i) for i in range(1, n)], blank=True)
Razzi Abuissa
10

Hay dos maneras de hacer esto. Una es utilizar la validación de formularios para que un usuario nunca ingrese ningún número superior a 50. Documentos de validación de formularios .

Si no hay ningún usuario involucrado en el proceso, o si no está utilizando un formulario para ingresar datos, deberá anular el savemétodo del modelo para lanzar una excepción o limitar los datos que ingresan al campo.

tghw
fuente
2
También puede usar un formulario para validar la entrada no humana. Funciona muy bien para llenar el formulario como una técnica de validación completa.
S.Lott
1
Después de pensar en esto, estoy bastante seguro de que no quiero poner la validación en un formulario. La cuestión de qué rango de números es aceptable es tan parte del modelo como lo es qué tipo de número es aceptable. No quiero tener que decir cada forma a través de la cual el modelo es editable, solo qué rango de números aceptar. Esto violaría DRY, y además, es simplemente inapropiado. Así que voy a ver a anular el modelo a guardar método, o tal vez la creación de un tipo de campo modelo personalizado - a menos que pueda encontrar un aun mejor manera :)
sampablokuper
tghw, dijiste que podía "anular el método de guardar del modelo para lanzar una excepción o limitar los datos que ingresan al campo". ¿Cómo podría, desde el método save () sobrescrito de la definición del modelo, hacer que si el número ingresado está fuera de un rango dado, el usuario recibe un error de validación como si hubiera ingresado datos de caracteres en un campo numérico? Es decir, ¿hay alguna manera de hacer esto que funcione independientemente de si el usuario está editando a través del administrador o de alguna otra forma? No quiero limitar los datos que entran al campo sin decirle al usuario lo que está pasando :) ¡Gracias!
sampablokuper
Incluso si usa el método "guardar", esto no funcionará cuando actualice la tabla a través de QuerySet como MyModel.object.filter (blabla) .update (blabla) no llamará a guardar, por lo que no se realizará ninguna comprobación
Olivier Pons,
5

Aquí está la mejor solución si desea algo de flexibilidad adicional y no desea cambiar el campo de su modelo. Simplemente agregue este validador personalizado:

#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)

Y se puede usar como tal:

class YourModel(models.Model):

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

Los dos parámetros son max y min, y permite valores nulos. Si lo desea, puede personalizar el validador eliminando la instrucción if marcada o cambiar su campo para que esté en blanco = False, null = False en el modelo. Eso, por supuesto, requerirá una migración.

Nota: Tuve que agregar el validador porque Django no valida el rango en PositiveSmallIntegerField, en su lugar, crea una letra pequeña (en postgres) para este campo y obtiene un error de DB si el valor numérico especificado está fuera de rango.

Espero que esto ayude :) Más sobre validadores en Django .

PD. Basé mi respuesta en BaseValidator en django.core.validators, pero todo es diferente excepto el código.

radtek
fuente