Отключенное поле рассматривается для проверки в WTForms и Flask

У меня отключены некоторые поля на странице, например: (используя систему шаблонов jinja2)

<html>
<body>
<form action="" method=POST>
    {{ form.name(disabled=True) }}
    {{ form.title }}
    -- submit button --
</form>
</body>
</html>

Поле отключено в форме, как и ожидалось.

В моих views.py: при выполнении validate_on_submit() при отправке формы происходит сбой с ошибкой проверки в поле «имя», которое отключено. Я надеялся, что проверка игнорирует отключенное поле. Это правильное поведение? Если да, то не могли бы вы сообщить, как поступить в таком случае?

Обновлено:

class TeamForm(wtf.Form):
    name = wtf.TextField("Team Name", validators=[validators.Required()])
    title = wtf.TextField("Title", validators=[validators.Required()])

person rajpy    schedule 10.05.2013    source источник
comment
не могли бы вы поделиться кодом формы, пожалуйста?   -  person codegeek    schedule 10.05.2013
comment
@codegeek: обновлен код форм.   -  person rajpy    schedule 10.05.2013
comment
Можете ли вы предоставить некоторые сведения о вашей цели? name является обязательным полем, но вы не разрешаете указывать значение. Планируете ли вы внедрить его программно?   -  person dirn    schedule 10.05.2013
comment
У меня есть два типа пользователей, администратор и менеджер. Только администратор может изменить поле «имя». Для менеджера я просто хочу показать «имя» как отключенное, но менеджер может редактировать/изменять другие поля. Я хочу, чтобы validate_on_submit() игнорировал отключенные поля, чтобы обновление из менеджера в других полях должно работать,   -  person rajpy    schedule 10.05.2013


Ответы (4)


На самом деле это интересная проблема, и способ, которым WTForms решает ее, преднамеренно требует явного описания, потому что он связан с безопасностью и не позволяет пользователям подделывать ввод.

Таким образом, намерение состоит в том, что «менеджеры» не могут редактировать имя, а «администраторы» могут.

На первый взгляд это кажется очевидным, просто отключите поле в HTML и напишите свое представление так:

def edit_team():
    form = TeamForm(request.POST, obj=team)
    if request.POST and form.validate():
        form.populate_obj(team) # <-- This is the dangerous part here
        return redirect('/teams')
    return render('edit_team.html')

Как написано, это серьезная угроза безопасности, поскольку свойство disabled в HTML-формах доступно только на стороне клиента. Любой, у кого есть инспектор HTML (например, FireBug, инспектор документов webkit и т. д.), может удалить это свойство, или кто-то может просто сделать такой запрос:

POST /edit_team/7 HTTP/1.0
Content-Type: application/x-urlencoded

team=EVILTEAMNAME&title=foo

Вопрос тогда, конечно, в том, как мы правильно заблокируем это на стороне сервера, в соответствии с подходящим способом сделать это? Правильный подход к WTForms заключается в отсутствии поля. Есть несколько способов сделать это, один из них - использовать композицию формы и, например, ManagerTeamForm и AdminTeamForm (иногда это лучше), но в других случаях проще используйте del для удаления определенных полей.

Итак, вот как вы могли бы написать свое представление и не иметь проблем с проверкой:

def edit_team():
    form = TeamForm(request.POST, obj=team)
    if user.role == 'manager':
        del form.name
    if request.POST and form.validate():
        form.populate_obj(team)
        return redirect('/teams')
    return render('edit_team.html')

И быстрая модификация шаблона:

<html>
<body>
<form action="" method=POST>
    {% if 'name' in form %}
        {{ form.name() }}
    {% else %}
        {{ team.name|e }}
    {% endif %}
    {{ form.title }}
    -- submit button --
</form>
</body>
</html>

Некоторые ссылки на лучшие практики wtforms:

person Crast    schedule 15.05.2013
comment
Спасибо за подробное объяснение. В итоге я удалил поля формы, как вы сказали. Но в идеале «отключенное» поле не будет отправлено на обработку формы. Разве это не правильно? - person rajpy; 16.05.2013
comment
WTForms не интерпретирует атрибуты отображения в вашем шаблоне, такие как отключено, отображение, видимость, только для чтения и т. д. (их так много, и они такие разные), также учтите, что рендеринг происходит после проверки (и рендеринг обычно не происходит, если форма проверяет), поэтому единственный способ сообщить wtforms, что его не следует просматривать, — это удалить это поле. - person Crast; 21.05.2013
comment
@rajpy, возможно, инвалид - это неправильное название. Атрибут определяет, разрешено ли пользователю изменять значение, а не то, действительно ли это влияет на поведение на сервере. Иногда полезно показать пользователю, какие данные отправляются на сервер, но не позволять им изменять значение. - person MageWind; 22.07.2014

Вы должны сделать поле имени необязательным при определении формы.

name = wtf.TextField("Team Name", validators=[validators.Optional()])

Затем в ваших представлениях передайте переменную с именем «роль» и установите для нее значение «менеджер» или «администратор» в зависимости от пользователя.

<form action="" method=POST>
{% if role == 'manager' % }
    {{ form.name(disabled=True) }}
{% else % }
    {{ form.name() }}
{{ form.title }}
-- submit button --
</form>
person codegeek    schedule 10.05.2013
comment
В этом случае он будет работать. Но я хочу, чтобы имя было предоставлено администратором при создании команды, и оно не должно быть необязательным. - person rajpy; 10.05.2013
comment
Это также представляет собой угрозу безопасности, потому что вредоносный «менеджер» все равно может отправить данные, даже если поле отключено на стороне клиента, и любой может удалить данные. - person Crast; 16.05.2013
comment
Это работает, но перед сохранением значений выполните проверку безопасности. - person Thiago; 25.08.2014

Я определил свой собственный валидатор для этой проблемы:

from wtforms.validators import Optional

class OptionalIfDisabled(Optional):

    def __call__(self, form, field):
        if field.render_kw is not None and field.render_kw.get('disabled', False):
            field.flags.disabled = True
            super(OptionalIfDisabled, self).__call__(form, field)

И затем я определил новую базу для своих форм:

from wtforms.form import Form

class BaseForm(Form):

    def populate_obj(self, obj):
        for name, field in self._fields.items():
            if not field.flags.disabled:
                field.populate_obj(obj, name)

Теперь каждая форма может расширять BaseForm и отключать такие поля:

from wtforms.fields import StringField, SubmitField

class TeamForm(BaseForm):
    team = StringField(label='Team Name', 
                       validators=[OptionalIfDisabled(), InputRequired()]
    submit = SubmitField(label='Submit')

    def __init__(self, *args, **kwargs):
        super(TeamForm, self).__init__(*args, **kwargs)
        # disable the fields if you want to
        if some_condition:
            self.team.render_kw = {'disabled': True}

После проверки TeamForm вы можете использовать populate_obj для копирования включенных данных формы в любой объект. Он будет игнорировать отключенные поля.

person Y4sper    schedule 12.08.2016

  1. Создайте собственный валидатор
from wtforms.validators import Optional
class DisabledValidator(Optional):
    """
    do nothing
    """
    pass
  1. Давайте создадим пользовательское правило на основе form.rule
from flask_admin.form.rules import Field
class EasyCustomFieldRule(Field):
    def __init__(self, field_name, render_field='lib.render_field', field_args={}):
        super(self.__class__, self).__init__(field_name, render_field)
        self.extra_field_args = field_args

    def __call__(self, form, form_opts=None, field_args={}):
        field = getattr(form, self.field_name)
        if self.extra_field_args.get('disabled'):
            field.validators.append(DisabledValidator())

        field_args.update(self.extra_field_args)
        return super(self.__class__, self).__call__(form, form_opts, field_args)
  1. Переопределить запись некоторых функций wtforms.form
from wtforms.form import Form
from wtforms.compat import iteritems
class BaseForm(Form):
    """
    重写部分方法,以适应disabled的Field
    """

    def validate(self):
        """
        Validates the form by calling `validate` on each field, passing any
        extra `Form.validate_<fieldname>` validators to the field validator.
        """
        extra = {}
        for name in self._fields:
            inline = getattr(self.__class__, 'validate_%s' % name, None)
            if inline is not None:
                extra[name] = [inline]

        return self.validate_(extra)

    def validate_(self, extra_validators=None):
        self._errors = None
        success = True
        for name, field in iteritems(self._fields):
            is_disabled = False
            for v in field.validators:
                if isinstance(v, DisabledValidator):
                    field.flags.disabled = True
                    is_disabled = True
                    break
            if is_disabled:
                continue

            if extra_validators is not None and name in extra_validators:
                extra = extra_validators[name]
            else:
                extra = tuple()
            if not field.validate(self, extra):
                success = False
        return success

    def populate_obj(self, obj):
        for name, field in self._fields.items():
            if not field.flags.disabled:
                field.populate_obj(obj, name)
  1. установите form_base_class в вашем ModelView и установите form_edit_rules или form_create_rules с помощью EasyCustomFieldRule
from flask_admin.contrib.sqla import ModelView
class MyTestModelView(ModelView):
    ...
    form_base_class = BaseForm
    ...

    form_edit_rules = (
        EasyCustomFieldRule('column0', field_args={'disabled': True}),
        'column1', 'column2'
    )
  1. Просто тестирование...
person Nicholas Chen    schedule 28.04.2019