Jak ograniczyć maksymalną wartość pola numerycznego w modelu Django?

169

Django ma różne pola numeryczne dostępne do użycia w modelach, np. DecimalField i PositiveIntegerField . Chociaż to pierwsze można ograniczyć do liczby przechowywanych miejsc dziesiętnych i ogólnej liczby przechowywanych znaków, czy istnieje sposób, aby ograniczyć je do przechowywania tylko liczb z określonego zakresu, np. 0,0–5,0?

Jeśli to się nie uda, czy istnieje sposób, aby ograniczyć PositiveIntegerField do przechowywania, na przykład, tylko liczb do 50?

Aktualizacja: teraz, gdy Bug 6845 został zamknięty , to pytanie StackOverflow może być dyskusyjne. - sampablokuper

sampablokuper
źródło
Powinienem był wspomnieć, że chcę również, aby ograniczenie zostało zastosowane w panelu administracyjnym Django. Aby to osiągnąć
sampablokuper
Właściwie wydaje się, że Django sprzed wersji 1.0 miało naprawdę eleganckie rozwiązanie: cotellese.net/2007/12/11/… . Zastanawiam się, czy istnieje równie elegancki sposób zrobienia tego w wydaniu svn Django.
sampablokuper
Jestem rozczarowany, gdy dowiedziałem się, że nie ma eleganckiego sposobu na zrobienie tego z obecnym svn Django. Więcej informacji można znaleźć w tym wątku dyskusji: groups.google.com/group/django-users/browse_thread/thread/…
sampablokuper
Użyj walidatorów na modelu, a walidacja będzie działać w interfejsie administratora oraz w ModelForms
guettli

Odpowiedzi:

133

Możesz także utworzyć niestandardowy typ pola modelu - zobacz http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

W takim przypadku można „dziedziczyć” z wbudowanej wartości IntegerField i przesłonić jej logikę walidacji.

Im więcej o tym myślę, tym zdaję sobie sprawę, jak przydatne byłoby to dla wielu aplikacji Django. Być może typ IntegerRangeField mógłby zostać przesłany jako łatka dla programistów Django, aby rozważyli dodanie do linii głównej.

To działa dla mnie:

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)

Następnie w swojej klasie modelowej użyłbyś tego w następujący sposób (pole jest modułem, w którym umieściłeś powyższy kod):

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

LUB dla zakresu ujemnego i dodatniego (jak zakres oscylatora):

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

Byłoby naprawdę fajne, gdyby można go było wywołać za pomocą operatora zasięgu w ten sposób:

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

Ale wymagałoby to znacznie więcej kodu, ponieważ można określić parametr `` pomiń '' - zakres (1, 50, 2) - ciekawy pomysł ...

NathanD
źródło
To działa, ale gdy w metodzie czyszczenia modelu wartość liczby całkowitej jest zawsze None, co sprawia, że ​​nie mogę zapewnić jej dodatkowego czyszczenia. Masz jakiś pomysł, dlaczego tak jest i jak to naprawić?
KrisF
2
Możesz ulepszyć swoje pole niestandardowe, dodając je MinValueValidator(min_value) and MaxValueValidator(max_value)przed wywołaniem super().__init__ ...(snippet: gist.github.com/madneon/147159f46ed478c71d5ee4950a9d697d )
madneon
349

Możesz użyć wbudowanych walidatorów 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)
        ]
     )

Edycja : Pracując bezpośrednio z modelem, przed zapisaniem modelu należy wywołać metodę full_clean , aby uruchomić walidatory. Nie jest to wymagane podczas używania, ModelFormponieważ formularze zrobią to automatycznie.

user1569050
źródło
4
Myślę, że musisz napisać własny walidator, jeśli chcesz, aby pole limited_integer_field było również opcjonalne? (Tylko Zatwierdź zakres, jeśli nie jest puste) null = True, blank = True nie zrobiłem tego ..
radtek
2
W Django 1.7 ustawienie null=Truei blank=Truedziała zgodnie z oczekiwaniami. Pole jest opcjonalne i jeśli pozostanie puste, zostanie zapisane jako null.
Tim Tisdall
77
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

size = models.IntegerField(validators=[MinValueValidator(0),
                                       MaxValueValidator(5)])
congocongo
źródło
52

Miałem ten sam problem; oto moje rozwiązanie:

SCORE_CHOICES = zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
chris.haueter
źródło
10
Korzystanie ze zrozumienia listy:models.IntegerField(choices=[(i, i) for i in range(1, n)], blank=True)
Razzi Abuissa
10

Można to zrobić na dwa sposoby. Jednym z nich jest użycie walidacji formularza, aby nigdy nie pozwolić użytkownikowi wprowadzić żadnej liczby powyżej 50. Dokumenty walidacji formularzy .

Jeśli w proces nie jest zaangażowany żaden użytkownik lub nie używasz formularza do wprowadzania danych, musisz zastąpić savemetodę modelu, aby zgłosić wyjątek lub ograniczyć dane trafiające do pola.

tghw
źródło
2
Możesz również użyć formularza do weryfikacji danych wejściowych innych niż człowiek. Doskonale sprawdza się wypełnianie formularza jako wszechstronnej techniki walidacji.
S.Lott
1
Po przemyśleniu tego jestem pewien, że nie chcę umieszczać walidacji w formularzu. Kwestia, który zakres liczb jest akceptowalny, jest w takim samym stopniu częścią modelu, jak kwestia tego, jaki rodzaj liczby jest akceptowalny. Nie chcę mówić do każdego formularza, za pomocą którego model jest edytowalny, tylko jaki zakres liczb zaakceptować. To naruszyłoby DRY, a poza tym jest to po prostu niewłaściwe. Więc mam zamiar przyjrzeć się przesłanianie modelu Zapisz metodę, czy może tworzenie niestandardowego typu pola model - chyba mogę znaleźć nawet lepszy sposób :)
sampablokuper
tghw, powiedziałeś, że mogę „zastąpić metodę zapisu modelu, aby zgłosić wyjątek lub ograniczyć dane trafiające do pola”. Jak bym - z nadpisanej metody save () definicji modelu - sprawić, że jeśli wprowadzona liczba jest poza podanym zakresem, użytkownik otrzyma błąd walidacji, tak jakby wprowadził dane znakowe do pola liczbowego? Czy jest jakiś sposób, w jaki mogę to zrobić, który będzie działał niezależnie od tego, czy użytkownik edytuje przez administratora, czy przez inny formularz? Nie chcę po prostu ograniczać danych wchodzących w teren bez mówienia użytkownikowi, co się dzieje :) Dzięki!
sampablokuper
Nawet jeśli użyjesz metody "save", to nie zadziała podczas aktualizowania tabeli przez QuerySet, jak MyModel.object.filter (blabla) .update (blabla) nie wywoła save, więc żadne sprawdzenie nie zostanie wykonane
Olivier Pons
5

Oto najlepsze rozwiązanie, jeśli potrzebujesz dodatkowej elastyczności i nie chcesz zmieniać pola modelu. Po prostu dodaj ten niestandardowy walidator:

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

I może być używany jako taki:

class YourModel(models.Model):

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

Te dwa parametry to max i min i dopuszczają wartości null. Możesz dostosować walidator, jeśli chcesz, usuwając zaznaczoną instrukcję if lub zmieniając pole tak, aby było puste = False, null = False w modelu. Będzie to oczywiście wymagało migracji.

Uwaga: Musiałem dodać walidator, ponieważ Django nie sprawdza zakresu na PositiveSmallIntegerField, zamiast tego tworzy smallint (w postgres) dla tego pola i otrzymujesz błąd DB, jeśli podana liczba jest poza zakresem.

Mam nadzieję, że to pomoże :) Więcej o walidatorach w Django .

PS. Swoją odpowiedź oparłem na BaseValidator w django.core.validators, ale wszystko jest inne poza kodem.

radtek
źródło