14

我在页面中禁用了一些字段,例如:(使用 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()])
4

4 回答 4

26

这实际上是一个有趣的问题,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')

如前所述,这是一个重大的安全风险,因为HTML 表单中的 disabled 属性仅适用于客户端。任何拥有 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 最佳实践的一些参考资料:

于 2013-05-15T22:51:39.307 回答
2

在定义表单时,您需要将名称字段设为可选。

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

然后在您的视图中,传递一个名为“role”的变量,并根据用户将其设置为 manager 或 admin。

<form action="" method=POST>
{% if role == 'manager' % }
    {{ form.name(disabled=True) }}
{% else % }
    {{ form.name() }}
{{ form.title }}
-- submit button --
</form>
于 2013-05-10T14:04:02.917 回答
1

我为这个问题定义了自己的验证器:

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在任何对象中复制启用的表单数据。它将忽略禁用的字段。

于 2016-08-12T10:46:51.360 回答
1
  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. 在 ModelView 中设置 form_base_class,并设置 form_edit_rules 或 form_create_rulesEasyCustomFieldRule
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. 只是测试...
于 2019-04-28T16:54:45.363 回答