Como limitar o valor máximo de um campo numérico em um modelo do Django?

169

O Django possui vários campos numéricos disponíveis para uso em modelos, por exemplo, DecimalField e PositiveIntegerField . Embora o primeiro possa ser restrito ao número de casas decimais armazenadas e ao número geral de caracteres armazenados, existe alguma maneira de restringi-lo a armazenar apenas números dentro de um determinado intervalo, por exemplo, 0,0-5,0?

Caso contrário, existe alguma maneira de restringir um PositiveIntegerField para armazenar apenas, por exemplo, números de até 50?

Atualização: agora que o Bug 6845 foi fechado , esta questão do StackOverflow pode ser discutida. - sampablokuper

sampablokuper
fonte
Eu deveria ter mencionado que também quero que a restrição seja aplicada no admin do Django. Para conseguir que, pelo menos, a documentação tem a dizer: docs.djangoproject.com/en/dev/ref/contrib/admin/...
sampablokuper
Na verdade, o Django anterior à 1.0 parece ter uma solução realmente elegante: cotellese.net/2007/12/11/… . Gostaria de saber se existe uma maneira igualmente elegante de fazer isso no lançamento svn do Django.
Sampablokuper 17/05/09
Estou desapontado ao saber que não parece haver uma maneira elegante de fazer isso com o svn Django atual. Veja este tópico de discussão para obter mais detalhes: groups.google.com/group/django-users/browse_thread/thread/…
sampablokuper
Use validadores no modelo, e a validação funcionará na interface do administrador e no ModelForms: docs.djangoproject.com/en/dev/ref/validators/…
guettli

Respostas:

133

Você também pode criar um tipo de campo de modelo personalizado - consulte http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

Nesse caso, você pode 'herdar' do IntegerField interno e substituir sua lógica de validação.

Quanto mais penso nisso, percebo o quanto isso seria útil para muitos aplicativos Django. Talvez um tipo IntegerRangeField possa ser enviado como um patch para os desenvolvedores do Django considerarem adicionar ao tronco.

Isso está funcionando para mim:

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)

Em seguida, na sua classe de modelo, você a usaria assim (o campo é o módulo onde você coloca o código acima):

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

OU para uma faixa de negativo e positivo (como uma faixa do oscilador):

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

O que seria muito legal é se ele pudesse ser chamado com o operador de intervalo assim:

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

Mas, isso exigiria muito mais código, já que você pode especificar um parâmetro 'skip' - range (1, 50, 2) - embora seja interessante ...

NathanD
fonte
Isso funciona, mas quando no método de limpeza do modelo, o valor do número inteiro é sempre None, o que faz com que eu não possa fornecer nenhuma limpeza adicional a ele. Alguma idéia do porquê disso e como corrigi-lo?
KrisF
2
Você pode aprimorar seu campo personalizado adicionando MinValueValidator(min_value) and MaxValueValidator(max_value)antes de chamar super().__init__ ...(snippet: gist.github.com/madneon/147159f46ed478c71d5ee4950a9d697d )
madneon
349

Você pode usar os validadores embutidos do 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 : Ao trabalhar diretamente com o modelo, chame o método full_clean do modelo antes de salvar o modelo para acionar os validadores. Isso não é necessário ao usar, ModelFormpois os formulários farão isso automaticamente.

user1569050
fonte
4
Eu acho que você precisa escrever seu próprio validador se quiser que o limited_integer_field seja opcional também? (Só gama Validar se não em branco) null = True, em branco = True não fazê-lo ..
radtek
2
Na configuração do Django 1.7 null=Truee blank=Truefunciona conforme o esperado. O campo é opcional e, se for deixado em branco, será armazenado 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
fonte
52

Eu tive esse mesmo problema; aqui estava a minha solução:

SCORE_CHOICES = zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
chris.haueter
fonte
10
Usando uma compreensão de lista:models.IntegerField(choices=[(i, i) for i in range(1, n)], blank=True)
Razzi Abuissa
10

Existem duas maneiras de fazer isso. Uma é usar a validação de formulário para nunca permitir que um número acima de 50 seja inserido por um usuário. Documentos de validação de formulário .

Se não houver usuário envolvido no processo ou você não estiver usando um formulário para inserir dados, será necessário substituir o savemétodo do modelo para lançar uma exceção ou limitar os dados que entram no campo.

tghw
fonte
2
Você também pode usar um formulário para validar entradas não humanas. Ele funciona muito bem para preencher o formulário como uma técnica de validação geral.
1189 de S.Lott
1
Depois de pensar nisso, tenho certeza de que não quero colocar a validação em um formulário. A questão de qual intervalo de números é aceitável faz parte do modelo e a questão de qual tipo de número é aceitável. Eu não quero ter que dizer todas as formas pelas quais o modelo é editável, apenas qual intervalo de números aceitar. Isso violaria o DRY e, além disso, é simplesmente inapropriado. Então, eu vou olhar para substituir o modelo é salvar método, ou talvez criar um tipo personalizado campo modelo - a menos que eu posso encontrar um mesmo caminho melhor :)
sampablokuper
então, você disse que eu poderia "substituir o método de salvamento do modelo para lançar uma exceção ou limitar os dados que entram no campo". Como eu - a partir do método save () anulado da definição do modelo - faria com que, se o número digitado estivesse fora de um determinado intervalo, o usuário recebesse um erro de validação como se tivesse inserido dados de caracteres em um campo numérico? Ou seja, existe alguma maneira de fazer isso que funcione independentemente de o usuário estar editando pelo administrador ou por algum outro formulário? Não quero limitar os dados que entram no campo sem dizer ao usuário o que está acontecendo :) Obrigado!
Sampablokuper 17/05/09
Mesmo se você usar o método "save", isso não funcionará ao atualizar a tabela através do QuerySet, como MyModel.object.filter (blabla) .update (blabla) não chamará save, portanto nenhuma verificação será feita
Olivier Pons
5

Aqui está a melhor solução, se você deseja alguma flexibilidade extra e não deseja alterar seu campo de modelo. Basta adicionar 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)

E pode ser usado 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)])

Os dois parâmetros são max e min, e permite nulos. Você pode personalizar o validador, se quiser, se livrando da instrução if marcada ou alterar seu campo para ficar em branco = Falso, nulo = Falso no modelo. É claro que isso exigirá uma migração.

Nota: Eu tive que adicionar o validador porque o Django não valida o intervalo no PositiveSmallIntegerField; em vez disso, ele cria um smallint (no postgres) para esse campo e você recebe um erro de banco de dados se o numérico especificado estiver fora do intervalo.

Espero que isso ajude :) Mais sobre Validadores no Django .

PS. Baseei minha resposta no BaseValidator em django.core.validators, mas tudo é diferente, exceto o código.

Radtek
fonte