如何使用 FormFields 的 WTForms FieldList?

Posted

技术标签:

【中文标题】如何使用 FormFields 的 WTForms FieldList?【英文标题】:How to use a WTForms FieldList of FormFields? 【发布时间】:2015-07-19 05:56:39 【问题描述】:

我正在使用Flask 构建一个网站,其中我使用WTForms。在表单中,我现在想使用 FormFields 的 FieldList,如下所示:

class LocationForm(Form):
    location_id = StringField('location_id')
    city = StringField('city')

class CompanyForm(Form):
    company_name = StringField('company_name')
    locations = FieldList(FormField(LocationForm))

所以为了让人们能够进入一家有两个地点的公司(稍后会动态添加地点),我在前台这样做:

<form action="" method="post" role="form">
     companyForm.hidden_tag() 
     companyForm.company_name() 
     locationForm.location_id() 
     locationForm.city() 
     locationForm.location_id() 
     locationForm.city() 
    <input type="submit" value="Submit!" />
</form>

所以在提交时我打印位置:

print companyForm.locations.data

但我明白了

['location_id': u'', 'city': u'']

我可以使用 locationForm 打印第一个位置的值(见下文),但我仍然不知道如何获取第二个位置的数据。

print locationForm.location_id.data
print locationForm.city.data

所以位置列表确实有一个空值字典,但是:

    为什么位置列表只有一个,而不是两个字典? 为什么位置字典中的值是空的?

有人知道我在这里做错了什么吗?欢迎所有提示!

【问题讨论】:

【参考方案1】:

这是一个老问题,但仍然是个好问题。

我想添加一个基于 Flask 的玩具数据库示例(只是一个字符串列表),重点放在 Python 部分 - 如何使用可变数量的子表单初始化表单以及如何处理发布的数据。

这是example.py 文件:

import flask
import wtforms
import flask_wtf

app = flask.Flask(__name__)
app.secret_key = 'fixme!'

# not subclassing from flask_wtf.FlaskForm
# in order to avoid CSRF on subforms
class EntryForm(wtforms.Form):
    city = wtforms.fields.StringField('city name:')
    delete = wtforms.fields.BooleanField('delete?')

class MainForm(flask_wtf.FlaskForm):
    entries = wtforms.fields.FieldList(wtforms.fields.FormField(EntryForm))
    submit = wtforms.fields.SubmitField('SUBMIT')

city_db = "Graz Poprad Brno Basel Rosenheim Torino".split() # initial value

@app.route("/", methods=['POST'])
def demo_view_function_post():
    global city_db

    form = MainForm()
    if form.validate_on_submit():
        city_db = [
            entry['city'] for entry in form.entries.data
            if entry['city'] and not entry['delete']]
        return flask.redirect(flask.url_for('demo_view_function_get'))

    # handle the validation error, i.e. flash a warning
    return flask.render_template('demo.html', form=form)

@app.route("/")
def demo_view_function_get():
    form = MainForm()
    entries_data = ['city': city, 'delete': False for city in city_db]
    entries_data.append('city': '', 'delete': False)  # "add new" placeholder
    form.process(data='entries': entries_data)
    return flask.render_template('demo.html', form=form)

这是demo.html 文件:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Demo</title>
</head>
<body>
  <h1>Subform demo</h1>
  <p>Edit names / mark for deletion / add new</p>
  <form method="post">
     form.csrf_token() 
    % for entry in form.entries %
      % if loop.last %
        <div>Add new:</div>
      % endif %  
      <div>
         entry.city.label   entry.city() 
         entry.delete()   entry.delete.label 
      </div>
    % endfor %
     form.submit() 
  </form>
</body>

运行:FLASK_APP=example flask run

【讨论】:

【参考方案2】:

对于初学者,FieldList 有一个名为 min_entries 的参数,它将为您的数据腾出空间:

class CompanyForm(Form):
    company_name = StringField('company_name')
    locations = FieldList(FormField(LocationForm), min_entries=2)

这将以您需要的方式设置列表。接下来,您应该直接从 locations 属性呈现字段,以便正确生成名称:

<form action="" method="post" role="form">
     companyForm.hidden_tag() 
     companyForm.company_name() 
     companyForm.locations() 
    <input type="submit" value="Submit!" />
</form>

查看呈现的 html,输入的名称应该类似于 locations-0-city,这样 WTForms 就会知道哪个是哪个。

或者,对于元素的自定义渲染,可以这样做

% for l in companyForms.locations %
 l.form.city 
% endfor %

(仅在 wtforms 中,l.cityl.form.city 的简写。但是,该语法似乎与 Jinja 冲突,因此有必要在模板中使用显式的 l.form.city。)

现在准备提交的数据,只需创建 CompanyForm 并遍历位置:

for entry in form.locations.entries:
    print entry.data['location_id']
    print entry.data['city']

【讨论】:

感谢您的回答。这确实使我能够在 html 中创建字段。还有一个问题:companyForm.locations() 创建了一个 &lt;ul&gt;,其中包含一个包含每个位置输入的表。但我显然想自己创建格式,以便它融入我的设计。您知道如何在没有围绕它的表格的情况下创建单独的输入,以便我可以自己进行样式设置吗? 嗯...如果您需要控制体验,总有一个地方您必须手动接管,在这种情况下,您真正​​需要的是输入正确的名称,所以去吧,只要你有&lt;input name=locations-0-city&gt;,一切都会好起来的:) 啊,我已经这么想了。至少现在我知道输入需要有什么name。感谢您的帮助! @kramer65 如果您还没有在其他地方找到答案,您可以在模板中使用 Jinja 迭代 companyForm.locations() - 各个项目将只是相关的 HTML 输入,没有任何额外的列表或表格标记。 如何更改locations-0-city1 开头的数字,所以我希望locations-1-city 是默认值。

以上是关于如何使用 FormFields 的 WTForms FieldList?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 iText 中为 FormFields 设置行距?

Wtforms:如何使用具有动态选择值的选择字段生成空白值

使用 formFields 导致 UnsupportedRequestContentTypeRejection 的 Akka Http 路由测试

使用 Flask-WTForms,如何设置 html 表单部分的样式?

如何通过 AJAX 使用 Flask-WTForms CSRF 保护?

WTForms-如何预填充文本区域字段?