2009-05-11 27 views
109

Django tiene varios campos numéricos disponibles para su uso en modelos, p. DecimalField y PositiveIntegerField. Aunque el primero se puede restringir al número de decimales almacenados y al número total de caracteres almacenados, ¿hay alguna manera de restringirlo a almacenar solo números dentro de un cierto rango, p. 0.0-5.0?¿Cómo limitar el valor máximo de un campo numérico en un modelo de Django?

En su defecto, ¿hay alguna forma de restringir un PositiveIntegerField para almacenar solo, por ejemplo, números de hasta 50?

Actualización: ahora que Bug 6845 has been closed, esta pregunta de StackOverflow puede ser discutible. - sampablokuper

+0

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: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#adding-custom-validation-to-the-admin – sampablokuper

+0

En realidad, pre -1.0 Django parece haber tenido una solución realmente elegante: http://www.cotellese.net/2007/12/11/adding-model-field-validation-to-the-django-admin-page/. Me pregunto si hay una forma igualmente elegante de hacerlo en el lanzamiento de svn de Django. – sampablokuper

+0

Me decepciona saber que * no * parece ser una forma elegante de hacer esto con el svn actual de Django. Consulte este hilo de discusión para obtener más detalles: http://groups.google.com/group/django-users/browse_thread/thread/7f23e90d9d3aedd4 – sampablokuper

Respuesta

111

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

En este caso, se podría 'hereda' de la validación de su lógica incorporada IntegerField y anular.

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

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 la clase del modelo, que podría utilizar de esta manera (siendo campo del módulo donde se coloca el código anterior):

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

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

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

Lo que sería genial si c Ould ser llamada con el operador de rango como esto:

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

embargo, eso requeriría un código mucho más desde ya que se puede especificar un parámetro 'saltar' - rango (1, 50, 2) - Una idea interesante sin embargo. ..

+4

Creo que siempre y cuando como el ticket # 6845 permanece abierto, esta es la mejor solución. http://code.djangoproject.com/ticket/6845 – sampablokuper

+0

Esto funciona pero cuando en el método de limpieza del modelo, el valor del entero es siempre Ninguno, lo que hace que no pueda proporcionarle ninguna limpieza adicional. ¿Alguna idea de por qué es esto y cómo solucionarlo? – KrisF

10

Hay dos formas de hacerlo. Una es usar la validación de formularios para que un usuario no ingrese ningún número mayor de 50. Form validation docs.

Si no hay un usuario involucrado en el proceso, o si no está usando un formulario para ingresar datos, entonces deberá anular el método save del modelo para lanzar una excepción o limitar la entrada de datos al campo.

+2

Puede usar un formulario para validando la entrada no humana, también. Funciona muy bien llenar el formulario como una técnica de validación completa. –

+0

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 una parte tan importante del modelo como la cuestión de qué tipo de número es aceptable. No quiero tener que decir a cada formulario a través del 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 buscar sobre redefinir el método de guardado del modelo, o quizás crear un tipo de campo de modelo personalizado, a menos que pueda encontrar una * incluso * mejor forma :) – sampablokuper

+0

tghw, dijiste que podía "anular el método guardar del modelo lanzar una excepción o limitar la entrada de datos al campo ". ¿Cómo podría - desde dentro del método save() modificado de la definición del modelo - hacerlo de modo que si el número ingresado está fuera de un rango determinado, el usuario recibe un error de validación como si hubiera ingresado datos de caracteres en un campo numérico? Es decir. ¿Hay alguna forma en que pueda hacer esto que funcione independientemente de si el usuario está editando a través del administrador o a través de alguna otra forma? No quiero limitar los datos al campo sin decirle al usuario lo que está sucediendo :) ¡Gracias! – sampablokuper

48

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

SCORE_CHOICES = zip(range(1,n), range(1,n)) 
score = models.IntegerField(choices=SCORE_CHOICES, blank=True) 
+9

Usando una lista de comprensión: 'models.IntegerField (choices = [(i, i) for i in range (1, n)], blank = True)' –

+1

Esto no funciona en el nivel de la base de datos. – YPCrumble

209

Usted puede utilizar Django's built-in validators -

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: Aunque estos only work when you're using the model in a ModelForm, no si está usando el modelo "por sí solo", suspiro.

+3

Supongo que tiene que escribir su propio validador si quiere que el campo_integrado_como sea opcional también? (Solo validar rango si no está en blanco) null = True, blank = True no lo hizo ... – radtek

+0

En Django 1.7 la configuración 'null = True' y' blank = True' funciona como se esperaba. El campo es opcional y si se deja en blanco se almacena como nulo. –

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

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

Esto no funciona bien si desea que el campo sea opcional. Creé un validador que hace las dos cosas y le permite pasar si el campo se deja en blanco. – radtek

4

Aquí está la mejor solución si desea flexibilidad adicional y no desea cambiar el campo de su modelo. Sólo tiene que añadir 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 puede ser utilizado 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 máximos y mínimos, y que permite nulos. Puede personalizar el validador si lo desea, deshágase de la declaración if marcada o cambie 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, sino que crea una smallint (en postgres) para este campo y se obtiene un error de DB si el número especificado está fuera de rango.

Espero que esto ayude :) Más sobre Validators in Django.

PS. Basé mi respuesta en BaseValidator en django.core.validators, pero todo es diferente a excepción del código.

Cuestiones relacionadas