Отключенное поле рассматривается для проверки в 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()])
Ответы
Ответ 1
На самом деле это интересная проблема, и способ решения WTForms преднамеренно требует чего-то, что требует объяснения, поскольку оно связано с безопасностью и не позволяет пользователям подделывать ввод.
Таким образом, намерение заключается в том, что "менеджеры" не могут редактировать имя, а "admins" могут.
На первый взгляд это кажется очевидным, просто отключите поле в 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')
Как написано, это серьезный риск для безопасности, потому что отключенное свойство в 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:
Ответ 2
Вам нужно сделать поле имени необязательным при определении формы.
name = wtf.TextField("Team Name", validators=[validators.Optional()])
Затем в ваших представлениях передайте переменную с именем "role" и установите ее в качестве менеджера или администратора в зависимости от пользователя.
<form action="" method=POST>
{% if role == 'manager' % }
{{ form.name(disabled=True) }}
{% else % }
{{ form.name() }}
{{ form.title }}
-- submit button --
</form>
Ответ 3
Я определил свой собственный валидатор для этой проблемы:
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
, чтобы скопировать разрешенные данные формы в любой объект. Он будет игнорировать отключенные поля.
Ответ 4
- Создать собственный валидатор
from wtforms.validators import Optional
class DisabledValidator(Optional):
"""
do nothing
"""
pass
- Давайте создадим собственное правило на основе 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)
- Переписать написать некоторые функции 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)
- установите 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'
)
- Просто тестирую...