【问题标题】:wtforms field created through setattr() loses properties通过 setattr() 创建的 wtforms 字段丢失属性
【发布时间】:2015-07-16 10:44:35
【问题描述】:

我正在尝试在 python / Flask 中创建一个表单,它将一些动态滑块输入添加到一组标准字段中。不过,我正在努力让它正常工作。

我的应用程序中的大多数 Web 表单都是静态的,通过 wtforms 创建,如下所示:

    class CritiqueForm(Form):

        rating = IntegerField('Rating')
        comment = TextAreaField('Comments')
        submit = SubmitField('Save Critique')

当我这样明确时,我可以通过在视图中使用CritiqueForm() 并传递表单对象以在模板中呈现来获得预期的结果。

但是,我有一个评论表单,它需要动态包含一些滑块,用于特定记录的评级标准。滑块的数量可以从一条记录到另一条记录有所不同,来自记录相关条件的文本和 ID 也是如此。

当我寻找一些方法来处理这个问题时,我从 dezza (Dynamic forms from variable length elements: wtforms) 中找到了一个可能的解决方案,方法是在表单中创建一个类方法,然后我可以在实例化我想要呈现的表单之前调用它。如:

    class CritiqueForm(Form):

        rating = IntegerField('Rating')
        comment = TextAreaField('Comments')
        submit = SubmitField('Save Critique')

        @classmethod
        def append_slider(cls, name, label):
            setattr(cls, name, IntegerField(label))
            return cls

其中 'append_slider' 始终是带有我提供的标签的 IntegerField。这足以让我在视图中填充标准滑块,如下所示:

    @app.route('/critique/<url_id>/edit', methods=['GET', 'POST'])
    def edit_critique(url_id):
        from app.models import RecordModel
        from app.models.forms import CritiqueForm

        record = RecordModel.get_object_by_url_id(url_id)
        if not record: abort(404)

        # build editing form
        ratings = list()
        for i, criterium in enumerate(record.criteria):
            CritiqueForm.append_slider('rating_' + str(i+1),criterium.name)
            ratings.append('form.rating_' + str(i+1))
        form = CritiqueForm(request.form)

        # Process valid POST
        if request.method=='POST' and form.validate():
           # Process the submitted form and show updated read-only record        
            return render_template('critique.html')

        # Display edit form
        return render_template('edit_critique.html',
            form=form,
            ratings=ratings,
            )

ratings 列表旨在为模板提供一种简单的方法来引用动态字段:

    {% for rating_field in ratings %}
        {{ render_slider_field(rating_field, label_visible=True, default_value=0) }} 
    {% endfor %}

其中render_slider_field 是将 IntegerField 转换为滑块的宏。

使用form.ratingCritiqueForm 中明确定义的整数字段)没有问题,并且按预期生成带有标签的滑块。但是,对于动态整数字段,我无法在整数字段中引用 label 值。堆栈跟踪的最后一部分如下所示:

    File "/home/vagrant/msp/app/templates/edit_critique.html", line 41, in block "content"
    {{ render_slider_field(rating_field, label_visible=True, default_value=0) }}

    File "/home/vagrant/msp/app/templates/common/form_macros.html", line 49, in template
    {% set label = kwargs.pop('label', field.label.text) %}

    File "/home/vagrant/.virtualenvs/msp/lib/python2.7/site-packages/jinja2/environment.py", line 397, in getattr
    return getattr(obj, attribute)

    UndefinedError: 'str object' has no attribute 'label'

通过一些调试,我确认没有出现任何预期的字段属性(例如,名称、短名称、id ...)。当尘埃落定,我只想要这个:

        CritiqueForm.append_slider('rating', 'Rating')

相当于这个:

        rating = IntegerField('Rating')

setattr() 技术是否固有地限制了表单中可以包含的信息,还是我只是错误地初始化或引用了字段属性?

编辑: 两项更改允许删除我的直接阻止程序。

1) 我不正确地引用了模板中的表单域。字段参数(例如标签)出现在此更改的预期位置:

    {% for rating_field in ratings %}
            {{ render_slider_field(form[rating_field], label_visible=True, default_value=0) }} 
    {% endfor %}

我将字符串 rating_field 替换为 form[rating_field]

2)为了解决从视图中动态改变基类的问题,创建了一个新的表单类ThisForm()来扩展我的基类CritiqueForm,然后在那里进行动态追加:

    class ThisForm(CritiqueForm):
        pass

    # build criteria form fields
    ratings = list()
    for i, criterium in enumerate(record.criteria):
        setattr(ThisForm, 'rating_' + str(i+1), IntegerField(criterium.name))
        ratings.append('rating_' + str(i+1))

    form = ThisForm(request.form)

我不知道这是否解决了 cmets 中提到的预期性能和数据完整性问题,但至少看起来是朝着正确方向迈出的一步。

【问题讨论】:

    标签: python class wtforms setattr


    【解决方案1】:

    setattr(obj, name, value)obj.name = value 完全等价——两者都是obj.__setattr__(name, value) 的语法糖——所以你的问题不在于setattr() 的“某些限制”,而是首先在于wtform.Form 的工作方式。如果您查看source code,您会发现除了将字段声明为类属性(涉及元类魔法......)之外,还有更多内容可以使字段和表单一起工作。 IOW,您必须通过源代码来了解如何将字段动态添加到表单。

    此外,您的代码会尝试在类本身上设置新字段。 这是一个很大的 NO NO,在具有并发访问的多进程/多线程/长时间运行的进程环境中 - 每个请求都会修改(在进程级别共享)表单类,随意添加或覆盖字段。它似乎可以在具有单个并发用户的单进程单线程开发服务器上工作,但会因最不可预测的错误或(更糟糕的)错误结果而中断生产。

    所以你真正想知道的是如何动态地将字段添加到表单instance - 或者,作为替代方案,如何动态构建一个新的临时表单类(这远非困难真的 - 请记住 Python 类也是对象)。

    【讨论】:

    • 谢谢。我对edit_critique 视图进行了调整,以避免对主 CritiqueForm() 进行更改,而是将其用作在视图中创建和更改的表单类的基础。我不知道这是否解决了您提到的 NO-NO 问题,但事实证明这不是我的阻塞问题。尝试呈现滑块时,我引用的是名称,而不是表单字段。我将 rating_field 更改为 form[rating_field],这给了我我的字段参数。
    • setattr 也不适用于 wtforms 实例
    • @TomSawyer 如果重点是向表单动态添加字段,那么由于我的回答中提到的相同原因,它确实不会以这种方式工作 - 添加字段比将其设置为属性-,它仍然与setattr()本身无关。
    猜你喜欢
    • 2022-01-16
    • 2014-06-21
    • 2012-07-22
    • 2015-03-02
    • 2017-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多