Валидаторы Django model.field не выполняются для обязательных логических полей - PullRequest
0 голосов
/ 20 июня 2019

Мой вариант использования:

У меня есть вопрос «Да / Нет», на который должен ответить пользователь, но он может ответить «да» или «нет». Я не хочу указывать значение по умолчанию, потому что мне нужно, чтобы пользователь активно выбирал свой ответ. Если они не выбирают ответ, я хочу получить ошибку проверки формы, например «Это поле обязательно для заполнения».

Я знаю, что может хранить это в БД как CharField, но я бы предпочел сохранить его как необходимый BooleanField. Похоже, проблема заключается в том, что логика проверки формы НЕ обеспечивает применения обязательного = True для логических полей и не вызывает пользовательских валидаторов поля модели, когда в данных POST возвращается значение ''.

Моя настройка:

boolean_choices = (
  (True, 'Yes'),
  (False, 'No'),
)

def boolean_validator(value):
  if value is not True and value is not False:
    raise ValidationError("This field is required.")

class Item(models.Model):
  accept = models.BooleanField(
    choices=boolean_choices,
    validators=[boolean_validator]
  )

class ItemForm(ModelForm):
  class Meta:
      model = Item
      fields = ['accept']

Полная, рабочая демонстрация проблемы здесь: https://repl.it/@powderflask/django-model-form-validators

Воспроизведение номера

  • создать или отредактировать элемент и установить для значения 'accept' значение None ("-------") -> сохранить будет сбой - уведомление form.is_valid () прошло, сбой находится в form.save () -> обратите внимание, что boolean_validator НЕ вызывается.

  • создайте или отредактируйте элемент и выберите «Да» или «Нет» -> save будет работать нормально, и вы можете видеть в терминале, что DID boolean_validator вызывается.

Мои подозрения

Я подозреваю, что где-то глубоко в логике проверки формы есть особый случай для логических полей, которые чаще всего являются флажками (которые имеют неприятную привычку ничего не возвращать в данных POST, когда они не проверены). Я подозреваю, что трудно различить «флажок не отмечен, поэтому False» и «значение не возвращено, поэтому вызовите RequiredField ValidationError), и проверка формы неправильно обрабатывает мой сценарий использования.

Мой вопрос Я делаю здесь что-то безнадежно глупое? Я что-то пропустил? Должен ли я сдаться и пойти принять горячую ванну? с благодарностью.

1 Ответ

1 голос
/ 20 июня 2019

Просмотр проверочной документации и исходного кода классов полей формы выявляет проблему и предлагает решение.

Поле формы обрабатывает свой ввод в этомпорядок в его clean методе:

  1. вызов to_python для преобразования необработанного значения в правильный тип
  2. вызов validate для преобразованного значения для проверки условий типа required
  3. вызов run_validators для преобразованного значения для проверки нарушений любого зарегистрированного валидатора

Метод to_python A BooleanField превращает любой ввод в True или False.Он не может вернуть None или любое другое значение:

class BooleanField(Field):
    widget = CheckboxInput

    def to_python(self, value):
        """Return a Python boolean object."""
        # Explicitly check for the string 'False', which is what a hidden field
        # will submit for False. Also check for '0', since this is what
        # RadioSelect will provide. Because bool("True") == bool('1') == True,
        # we don't need to handle that explicitly.
        if isinstance(value, str) and value.lower() in ('false', '0'):
            value = False
        else:
            value = bool(value)
        return super().to_python(value)

Это означает, что ни один валидатор не может отличить ситуацию, в которой поле было присвоено None, как значение от другой ситуации, которая привела к тому, что полевернуть False как проанализированное значение Python.Вы можете использовать required=True или явный валидатор, чтобы принудительно установить, что значение должно быть True, если вы этого хотите, но вы не можете сказать, по каким разным причинам это значение может быть False отдельно.

Комудля этого вам нужен другой класс - тот, который либо обрабатывает None, и все, что вы не хотите принимать как недопустимый ввод перед преобразованием его в Python, или тот, который преобразует такие значения в None и затем может использоватьвалидатор для отклонения None значений.

Для первого решения вы можете создать подкласс и переопределить to_python.Класс NullBooleanField предоставляет хороший пример, но мы адаптируем его для обработки нулевого ввода как недопустимого, вместо того, чтобы позволять ему возвращать None:

class StrictBooleanField(Field):

    def to_python(self, value):
        """
        Explicitly check for the string 'True' and 'False', which is what a
        hidden field will submit for True and False, for 'true' and 'false',
        which are likely to be returned by JavaScript serializations of forms,
        and for '1' and '0', which is what a RadioField will submit. Unlike
        the Booleanfield, this field must check for True because it doesn't
        use the bool() function.
        """
        if value in (True, 'True', 'true', '1'):
            return True
        elif value in (False, 'False', 'false', '0'):
            return False
        else:
            raise ValidationError(_('Invalid boolean value %(value)s'), params={'value': value},', code='invalid')

для второго варианта (привлекательный, потому что он не'не требует специального подкласса поля) - это принять конфликт между именем поля и намерением и использовать NullBooleanField напрямую, регистрируя валидатор, который обнаружит и отклонит нулевое значение.Что-то вроде:

def validate_non_null(value):
    if value is None:
        raise ValidationError(_('Value must not be None', code='invalid')

class ItemForm(ModelForm):
    accept = NullBooleanField(validators=[validate_non_null])      

    class Meta:
        model = Item
        fields = ['accept']

Здесь мы используем NullBooleanField, чтобы понимать ввод как True, False или None, а не просто True или False, а затем с помощью стандартной проверки для обработки None как распознанного, но неверного ввода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...