Come limitare il valore massimo di un campo numerico in un modello Django?


169

Django ha vari campi numerici disponibili per l'uso nei modelli, ad esempio DecimalField e PositiveIntegerField . Sebbene il primo possa essere limitato al numero di cifre decimali memorizzate e al numero complessivo di caratteri memorizzati, esiste un modo per limitarlo a memorizzare solo numeri entro un certo intervallo, ad esempio 0,0-5,0?

In caso contrario, c'è un modo per limitare un PositiveIntegerField per memorizzare solo, ad esempio, numeri fino a 50?

Aggiornamento: ora che Bug 6845 è stato chiuso , questa domanda StackOverflow potrebbe essere discutibile. - sampablokuper



Avrei dovuto menzionare che voglio anche applicare la limitazione nell'amministratore di Django. Per ottenere ciò, almeno, la documentazione ha questo da dire: docs.djangoproject.com/en/dev/ref/contrib/admin/…
sampablokuper

In realtà, pre-1.0 Django sembra aver avuto una soluzione davvero elegante: cotellese.net/2007/12/11/… . Mi chiedo se ci sia un modo altrettanto elegante di farlo nella versione svn di Django.
sampablokuper,

Sono deluso nell'apprendere che non sembra esserci un modo elegante per farlo con l'attuale Django svn. Vedi questo thread di discussione per maggiori dettagli: groups.google.com/group/django-users/browse_thread/thread/…
sampablokuper

Utilizza i validatori sul modello e la convalida funzionerà nell'interfaccia di amministrazione e in ModelForms: docs.djangoproject.com/en/dev/ref/validators/…
guettli

Risposte:


133

È inoltre possibile creare un tipo di campo del modello personalizzato: consultare http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

In questo caso, è possibile "ereditare" da IntegerField integrato e sovrascriverne la logica di convalida.

Più ci penso, mi rendo conto di quanto sarebbe utile per molte app Django. Forse un tipo IntegerRangeField potrebbe essere inviato come patch per gli sviluppatori di Django per prendere in considerazione l'aggiunta al trunk.

Questo funziona per me:

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)

Quindi nella tua classe di modello, lo useresti in questo modo (campo essendo il modulo in cui hai inserito il codice sopra):

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

OPPURE per un intervallo di negativo e positivo (come un intervallo di oscillatori):

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

Ciò che sarebbe davvero bello sarebbe se potesse essere chiamato con l'operatore di gamma in questo modo:

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

Ma ciò richiederebbe molto più codice dato che è possibile specificare un parametro 'skip' - range (1, 50, 2) - Idea interessante però ...


Questo funziona, ma quando nel metodo clean del modello, il valore dell'intero è sempre None, il che lo rende quindi non posso fornire alcuna pulizia aggiuntiva ad esso. Qualche idea sul perché questo sia e come risolverlo?
KrisF,

2
Puoi migliorare il tuo campo personalizzato aggiungendo MinValueValidator(min_value) and MaxValueValidator(max_value)prima di chiamare super().__init__ ...(snippet: gist.github.com/madneon/147159f46ed478c71d5ee4950a9d697d )
madneon

349

Puoi usare i validatori integrati di 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)
        ]
     )

Modifica : quando si lavora direttamente con il modello, assicurarsi di chiamare il metodo full_clean del modello prima di salvare il modello per attivare i validatori. Questo non è necessario quando si utilizza ModelFormpoiché i moduli lo faranno automaticamente.


4
Immagino che tu debba scrivere il tuo validatore se vuoi che anche il campo limitato sia facoltativo? (Convalida solo intervallo se non vuoto) null = Vero, vuoto = Vero non ha funzionato ..
Radtek,

2
In Django 1.7 impostazione null=Truee blank=Truefunziona come previsto. Il campo è facoltativo e se viene lasciato vuoto viene archiviato come null.
Tim Tisdall,

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

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

52

Ho avuto questo stesso problema; ecco la mia soluzione:

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

10
Usando una comprensione dell'elenco:models.IntegerField(choices=[(i, i) for i in range(1, n)], blank=True)
Razzi Abuissa,

10

Ci sono due modi per farlo. Uno è utilizzare la validazione del modulo per non consentire a nessun utente di inserire un numero superiore a 50. Documenti di convalida del modulo .

Se non vi è alcun utente coinvolto nel processo o non si utilizza un modulo per immettere dati, è necessario sostituire il savemetodo del modello per generare un'eccezione o limitare i dati che entrano nel campo.


2
Puoi anche usare un modulo per validare input non umani. Funziona benissimo per popolare il modulo come tecnica di convalida completa.
S.Lott

1
Dopo aver riflettuto su questo, sono abbastanza sicuro di non voler mettere la convalida in un modulo. La domanda su quale intervallo di numeri è accettabile è tanto una parte del modello quanto la domanda su quale tipo di numero è accettabile. Non voglio dover dire ogni forma attraverso la quale il modello è modificabile, solo quale intervallo di numeri accettare. Ciò violerebbe il DRY e, inoltre, è semplicemente inappropriato. Quindi esaminerò l'override del metodo di salvataggio del modello o forse la creazione di un tipo di campo del modello personalizzato, a meno che non riesca a trovare un modo ancora migliore :)
sampablokuper,

tu, hai detto che potrei "scavalcare il metodo di salvataggio del modello per lanciare un'eccezione o limitare i dati che vanno nel campo". In che modo, all'interno del metodo save () overriden della definizione del modello, farei in modo che se il numero inserito è al di fuori di un determinato intervallo, l'utente riceva un errore di convalida come se avesse inserito i dati dei caratteri in un campo numerico? Cioè c'è un modo in cui posso farlo che funzionerà indipendentemente dal fatto che l'utente stia modificando tramite l'amministratore o tramite qualche altro modulo? Non voglio solo limitare i dati che entrano nel campo senza dire all'utente cosa sta succedendo :) Grazie!
sampablokuper,

Anche se usi il metodo "save", questo non funzionerà durante l'aggiornamento della tabella tramite QuerySet come MyModel.object.filter (blabla) .update (blabla) non chiamerà save, quindi non verrà effettuato alcun controllo
Olivier Pons

5

Ecco la soluzione migliore se vuoi maggiore flessibilità e non vuoi cambiare il campo del tuo modello. Aggiungi questo validatore personalizzato:

#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 può essere usato come tale:

class YourModel(models.Model):

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

I due parametri sono max e min e consente valori null. Se lo desideri, puoi personalizzare il validatore eliminando l'istruzione if contrassegnata o modificare il campo in modo che sia vuoto = Falso, null = Falso nel modello. Ciò richiederà ovviamente una migrazione.

Nota: ho dovuto aggiungere il validatore perché Django non convalida l'intervallo su PositiveSmallIntegerField, invece crea un smallint (in postgres) per questo campo e si ottiene un errore DB se il valore numerico specificato non è compreso nell'intervallo.

Spero che questo aiuti :) Altre informazioni sui validatori in Django .

PS. Ho basato la mia risposta su BaseValidator in django.core.validators, ma tutto è diverso tranne il codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.