【问题标题】:How to use a WTForms FieldList of FormFields?如何使用 FormFields 的 WTForms FieldList?
【发布时间】: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. 为什么位置列表只有一个,而不是两个字典?
  2. 为什么位置字典中的值是空的?

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

【问题讨论】:

    标签: python forms flask wtforms fieldlist


    【解决方案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 是默认值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-15
      • 2013-03-16
      • 2018-03-21
      • 2014-07-17
      • 2016-06-13
      • 1970-01-01
      • 2018-10-19
      相关资源
      最近更新 更多