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


169

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

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

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


Вы можете создать сигнал предварительного сохранения: 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) - Интересная идея, хотя ...


Это работает, но когда в чистом методе модели значение целого числа всегда 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так как формы будут делать это автоматически.


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

52

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

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

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

10

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

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


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, но все иначе, кроме кода.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.