Как ограничить максимальное значение числового поля в модели Django?

169

Django имеет различные числовые поля, доступные для использования в моделях, например, DecimalField и PositiveIntegerField . Хотя первое может быть ограничено количеством сохраненных десятичных разрядов и общим количеством сохраненных символов, есть ли способ ограничить его сохранением только чисел в определенном диапазоне, например, 0,0-5,0?

В противном случае, есть ли способ ограничить PositiveIntegerField, чтобы хранить только, например, числа до 50?

Обновление: теперь, когда ошибка 6845 закрыта , этот вопрос StackOverflow может быть спорным. - сампаблокупер

sampablokuper
источник
Вы можете создать сигнал предварительного сохранения: http://docs.djangoproject.com/en/dev/ref/signals/#django.db.models.signals.pre_save
igorgue
Я должен был упомянуть, что я также хочу, чтобы ограничение применялось в админке Django. Для получения этого, по крайней мере, в документации есть следующее: docs.djangoproject.com/en/dev/ref/contrib/admin/…am
sampablokuper
На самом деле, до версии 1.0 у Django, похоже, было действительно элегантное решение: cotellese.net/2007/12/11/… . Интересно, есть ли такой же элегантный способ сделать это в svn-релизе Django?
Сампаблокупер
Я разочарован, узнав, что , кажется, нет элегантного способа сделать это с нынешним Django SVN. См. Эту ветку
sampablokuper
Используйте валидаторы на модели, и валидация будет работать в интерфейсе администратора и в ModelForms: docs.djangoproject.com/en/dev/ref/validators/…
guettli

Ответы:

133

Вы также можете создать собственный тип поля модели - см. Http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

В этом случае вы можете «унаследовать» от встроенного IntegerField и переопределить его логику проверки.

Чем больше я думаю об этом, тем больше понимаю, насколько это полезно для многих приложений Django. Возможно, тип IntegerRangeField мог бы быть представлен как патч для разработчиков Django, чтобы рассмотреть возможность добавления в trunk.

Это работает для меня:

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)

Затем в своем классе модели вы бы использовали его следующим образом (поле является модулем, в который вы поместили вышеуказанный код):

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

ИЛИ для диапазона отрицательных и положительных значений (например, диапазона осциллятора):

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

Что было бы действительно круто, так это если бы его можно было вызвать с помощью оператора диапазона, например так:

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

Но это потребовало бы намного больше кода, поскольку вы можете указать параметр 'skip' - range (1, 50, 2) - Интересная идея, хотя ...

NathanD
источник
Это работает, но когда в чистом методе модели значение целого числа всегда None, что делает его таким образом, что я не могу обеспечить дополнительную очистку для него. Есть идеи, почему это и как это исправить?
KrisF
2
Вы можете улучшить свое настраиваемое поле, добавив MinValueValidator(min_value) and MaxValueValidator(max_value)перед вызовом super().__init__ ...(фрагмент: gist.github.com/madneon/147159f46ed478c71d5ee4950a9d697d )
madneon
350

Вы можете использовать встроенные валидаторы 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)
        ]
     )

Редактирование : при работе непосредственно с моделью обязательно вызовите метод model full_clean перед сохранением модели, чтобы активировать валидаторы. Это не требуется при использовании, ModelFormтак как формы будут делать это автоматически.

user1569050
источник
4
Я полагаю, вам нужно написать свой собственный валидатор, если вы хотите, чтобы поле limited_integer_ также было необязательным? (Только проверить диапазон, если не пусто) null = True, пусто = True не делал этого ...
radtek
2
В Django 1.7 настройка null=Trueи blank=Trueработает как положено. Поле является необязательным, и если оно оставлено пустым, оно сохраняется как нулевое.
Тим
77
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

size = models.IntegerField(validators=[MinValueValidator(0),
                                       MaxValueValidator(5)])
congocongo
источник
52

У меня была такая же проблема; вот мое решение:

SCORE_CHOICES = zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
chris.haueter
источник
10
Используя понимание списка:models.IntegerField(choices=[(i, i) for i in range(1, n)], blank=True)
Razzi Abuissa
10

Есть два способа сделать это. Одним из них является использование проверки формы, чтобы никогда не позволять пользователю вводить любое число свыше 50. Форма проверки документов .

Если в процессе не участвует ни один пользователь или вы не используете форму для ввода данных, вам придется переопределить метод модели, saveчтобы сгенерировать исключение или ограничить ввод данных в поле.

tghw
источник
2
Вы также можете использовать форму для проверки нечеловеческого ввода. Это прекрасно работает, чтобы заполнить форму как всестороннюю технику проверки.
С.Лотт
1
Подумав об этом, я совершенно уверен, что не хочу помещать валидацию в форму. Вопрос о том, какой диапазон чисел является приемлемым, является такой же частью модели, как и вопрос о том, какой тип чисел является приемлемым. Я не хочу указывать каждую форму, с помощью которой модель является редактируемой, какой диапазон чисел принимать. Это нарушит СУХОЙ, и, кроме того, это просто неуместно. Итак, я собираюсь изучить переопределение метода сохранения модели или, возможно, создание собственного типа поля модели - если я не смогу найти еще лучший способ :)
sampablokuper
tghw, вы сказали, что я мог бы "переопределить метод сохранения модели, чтобы вызвать исключение или ограничить данные, поступающие в поле". Как бы я - из переопределенного метода save () определения модели - сделать так, чтобы, если введенное число находилось вне заданного диапазона, пользователь получал ошибку проверки так же, как если бы он вводил символьные данные в числовое поле? Т.е. есть ли способ, которым я могу сделать это, который будет работать независимо от того, редактирует ли пользователь через администратора или через какую-либо другую форму? Я не хочу просто ограничивать данные, поступающие в поле, не сообщая пользователю, что происходит :) Спасибо!
Сампаблокупер
Даже если вы используете метод "save", это не будет работать при обновлении таблицы через QuerySet, например, MyModel.object.filter (blabla) .update (blabla) не вызовет save, поэтому проверка не будет выполнена
Olivier Pons
5

Вот лучшее решение, если вам нужна дополнительная гибкость и вы не хотите менять поле модели. Просто добавьте этот пользовательский валидатор:

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

И это можно использовать как таковое:

class YourModel(models.Model):

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

Два параметра - это max и min, и это допускает нулевые значения. Вы можете настроить валидатор, если хотите, избавившись от отмеченного оператора if или изменив свое поле на пустое = False, null = False в модели. Это, конечно, потребует миграции.

Примечание: мне пришлось добавить валидатор, потому что Django не проверяет диапазон на PositiveSmallIntegerField, вместо этого он создает smallint (в postgres) для этого поля, и вы получаете ошибку БД, если указанное число выходит за пределы диапазона.

Надеюсь, это поможет :) Подробнее о валидаторах в Django .

PS. Я основал свой ответ на BaseValidator в django.core.validators, но все иначе, кроме кода.

radtek
источник