Wie kann man den Maximalwert eines numerischen Feldes in einem Django-Modell begrenzen?


169

Django verfügt über verschiedene numerische Felder zur Verwendung in Modellen, z . B. DecimalField und PositiveIntegerField . Ersteres kann zwar auf die Anzahl der gespeicherten Dezimalstellen und die Gesamtzahl der gespeicherten Zeichen beschränkt werden. Gibt es jedoch eine Möglichkeit, das Speichern nur von Zahlen innerhalb eines bestimmten Bereichs zu beschränken, z. B. 0,0-5,0?

Wenn dies nicht der Fall ist, gibt es eine Möglichkeit, ein PositiveIntegerField so einzuschränken, dass beispielsweise nur Zahlen bis zu 50 gespeichert werden?

Update: Nachdem Bug 6845 geschlossen wurde , ist diese StackOverflow-Frage möglicherweise nicht mehr aktuell. - Sampablokuper



Ich hätte erwähnen sollen, dass ich möchte, dass die Einschränkung auch in Djangos Administrator angewendet wird. Zumindest dafür hat die Dokumentation Folgendes
sampablokuper

Tatsächlich scheint Django vor 1.0 eine wirklich elegante Lösung gehabt zu haben: cotellese.net/2007/12/11/… . Ich frage mich, ob es in der SVN-Veröffentlichung von Django eine ebenso elegante Möglichkeit gibt, dies zu tun.
Sampablokuper

Ich bin zu lernen , enttäuscht , dass es nicht der Fall ist eine elegante Art und Weise zu sein scheint dies mit dem aktuellen Django SVN zu tun. Weitere Informationen finden Sie in diesem Diskussionsthread: groups.google.com/group/django-users/browse_thread/thread/…
sampablokuper

Verwenden Sie Validatoren für das Modell, und die Validierung funktioniert in der Administrationsoberfläche und in ModelForms: docs.djangoproject.com/de/dev/ref/validators/…
guettli

Antworten:


133

Sie können auch einen benutzerdefinierten Modellfeldtyp erstellen - siehe http://docs.djangoproject.com/de/dev/howto/custom-model-fields/#howto-custom-model-fields

In diesem Fall können Sie vom integrierten IntegerField 'erben' und dessen Validierungslogik überschreiben.

Je mehr ich darüber nachdenke, desto nützlicher wird mir, dass dies für viele Django-Apps nützlich ist. Möglicherweise könnte ein IntegerRangeField-Typ als Patch für die Django-Entwickler eingereicht werden, um das Hinzufügen zum Trunk in Betracht zu ziehen.

Das funktioniert bei mir:

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)

Dann würden Sie es in Ihrer Modellklasse folgendermaßen verwenden (Feld ist das Modul, in das Sie den obigen Code einfügen):

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

ODER für einen Bereich von negativ und positiv (wie ein Oszillatorbereich):

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

Was wirklich cool wäre, wäre, wenn es mit dem Bereichsoperator wie folgt aufgerufen werden könnte:

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

Dies würde jedoch viel mehr Code erfordern, da Sie einen 'Überspringen'-Parameter angeben können - Bereich (1, 50, 2) - Interessante Idee ...


Dies funktioniert, aber wenn in der Bereinigungsmethode des Modells der Wert der Ganzzahl immer Keine ist, kann ich keine zusätzliche Bereinigung vornehmen. Irgendeine Idee, warum das so ist und wie man es behebt?
KrisF

2
Sie können Ihr benutzerdefiniertes Feld erweitern, indem Sie es MinValueValidator(min_value) and MaxValueValidator(max_value)vor dem Anruf hinzufügen super().__init__ ...(Snippet: gist.github.com/madneon/147159f46ed478c71d5ee4950a9d697d )
madneon

348

Sie können die in Django integrierten Validatoren verwenden -

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

Bearbeiten : Wenn Sie direkt mit dem Modell arbeiten, müssen Sie vor dem Speichern des Modells die Methode model full_clean aufrufen , um die Validatoren auszulösen. Dies ist bei der Verwendung nicht erforderlich, ModelFormda die Formulare dies automatisch tun.


4
Ich denke, Sie müssen Ihren eigenen Validator schreiben, wenn Sie möchten, dass das Feld limited_integer_field ebenfalls optional ist? (Nur Bereich validieren, wenn nicht leer) null = True, blank = True hat es nicht getan ..
radtek

2
In Django 1.7 Einstellung null=Trueund blank=Truefunktioniert wie erwartet. Das Feld ist optional und wird leer gelassen, wenn es leer gelassen wird.
Tim Tisdall

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

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

52

Ich hatte genau das gleiche Problem; Hier war meine Lösung:

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

10
Verwenden eines Listenverständnisses:models.IntegerField(choices=[(i, i) for i in range(1, n)], blank=True)
Razzi Abuissa

10

Es gibt zwei Möglichkeiten, dies zu tun. Eine besteht darin, die Formularvalidierung zu verwenden, um niemals zuzulassen, dass ein Benutzer eine Zahl über 50 eingibt. Formularvalidierungsdokumente .

Wenn kein Benutzer an dem Prozess beteiligt ist oder Sie kein Formular zur Dateneingabe verwenden, müssen Sie die saveMethode des Modells überschreiben , um eine Ausnahme auszulösen oder die in das Feld eingehenden Daten einzuschränken .


2
Sie können auch ein Formular zur Validierung nicht menschlicher Eingaben verwenden. Es funktioniert hervorragend, das Formular als Allround-Validierungstechnik auszufüllen.
S.Lott

1
Nachdem ich darüber nachgedacht habe, bin ich mir ziemlich sicher, dass ich die Validierung nicht in eine Form bringen möchte. Die Frage, welcher Zahlenbereich akzeptabel ist, ist ebenso Teil des Modells wie die Frage, welche Art von Zahl akzeptabel ist. Ich möchte nicht jedem Formular mitteilen müssen, über das das Modell bearbeitet werden kann, sondern nur, welche Zahlenbereiche akzeptiert werden sollen. Dies würde gegen DRY verstoßen, und außerdem ist es einfach unangemessen. Also werde ich versuchen, die Speichermethode des Modells zu überschreiben oder vielleicht einen benutzerdefinierten
Modellfeldtyp zu

tghw, Sie sagten, ich könnte "die Speichermethode des Modells überschreiben, um eine Ausnahme auszulösen oder die Daten zu begrenzen, die in das Feld gelangen". Wie würde ich - innerhalb der überschriebenen save () -Methode der Modelldefinition - sicherstellen, dass der Benutzer einen Validierungsfehler erhält, wenn die eingegebene Zahl außerhalb eines bestimmten Bereichs liegt, als hätte er Zeichendaten in ein numerisches Feld eingegeben? Dh gibt es eine Möglichkeit, dies zu tun, die unabhängig davon funktioniert, ob der Benutzer über den Administrator oder über ein anderes Formular bearbeitet? Ich möchte nicht nur die Daten einschränken, die ins Feld gehen, ohne dem Benutzer zu sagen, was los ist :) Danke!
Sampablokuper

Selbst wenn Sie die "save" -Methode verwenden, funktioniert dies nicht, wenn Sie eine Tabelle über QuerySet wie MyModel.object.filter (blabla) aktualisieren. Update (blabla) ruft save nicht auf, sodass keine Überprüfung durchgeführt wird
Olivier Pons,

5

Hier ist die beste Lösung, wenn Sie zusätzliche Flexibilität wünschen und Ihr Modellfeld nicht ändern möchten. Fügen Sie einfach diesen benutzerdefinierten Validator hinzu:

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

Und es kann als solches verwendet werden:

class YourModel(models.Model):

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

Die beiden Parameter sind max und min und erlauben Nullen. Sie können den Validator anpassen, indem Sie die markierte if-Anweisung entfernen oder Ihr Feld so ändern, dass es leer = False, null = False im Modell ist. Das erfordert natürlich eine Migration.

Hinweis: Ich musste den Validator hinzufügen, da Django den Bereich in PositiveSmallIntegerField nicht validiert, sondern eine kleine Intint (in Postgres) für dieses Feld erstellt und Sie einen DB-Fehler erhalten, wenn die angegebene Zahl außerhalb des Bereichs liegt.

Hoffe das hilft :) Mehr zu Validators in Django .

PS. Ich habe meine Antwort auf BaseValidator in django.core.validators basiert, aber bis auf den Code ist alles anders.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.