我正在尝试在python / Flask中创建一个表单,它将向一组标准字段添加一些动态滑块输入。不过,我正努力让它正常工作。
我的应用中的大多数网络表单都是静态的,通过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.rating
- 在CritiqueForm
中明确定义的整数字段 - 没有问题,并且滑块是使用标签生成的,如预期的那样。但是,对于动态整数字段,我无法在整数字段中引用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'
通过一些调试,我确认没有出现任何预期的字段属性(例如,name,short_name,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
,然后在那里完成动态追加:< / p>
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)
我不知道这是否解决了评论中提到的预期性能和数据完整性问题,但它至少似乎是朝着正确方向迈出的一步。
答案 0 :(得分:1)
setattr(obj, name, value)
是obj.name = value
的完全等价物 - 两者都是obj.__setattr__(name, value)
的语法糖 - 所以你的问题不在于&#34;某些限制&#34; setattr()
的{{1}},但首先是wtform.Form
的工作原理。如果你看一下source code,你可以看到,除了将字段声明为类属性(涉及元类魔法......)之外,还可以使字段和表单一起工作。现在,您必须通过源代码了解如何动态地向表单添加字段。
此外,您的代码尝试在类本身上设置新字段。 在具有并发访问权限的多进程/多线程/长时间运行的进程环境中,这是一个很大的NO NO - 每个请求将修改(在进程级别共享)表单类,同时添加或覆盖字段。它似乎可以在具有单个并发用户的单进程单线程开发服务器上工作,但会因最不可预测的错误或(更糟糕的)错误结果而中断生产。
所以你想知道的是如何动态地将字段添加到表单 instance - 或者作为替代方法,如何动态构建一个新的临时表单类(这远非困难)真的 - 记住Python类也是对象。)