使用Flask、Jinja2模板呈现一个可编辑的表,然后处理返回的表单数据

2024-05-19 00:43:15 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在使用Flask和Jinja2,我需要创建一个包含多行的可编辑表。

这就是桌子的样子:

img

下面是HTML:

<form action="/support/team-members-update" method="post">
<table>
  <tbody><tr>
    <th>Name</th>
    <th>Id</th>
    <th>Inbox Share</th>
  </tr>
  <tr>
    <td>Ben</td><td>55555</td><td><input type="text" name="share_55555" value="0"></td></tr>  <tr>
    <td>Steve</td><td>66666</td><td><input type="text" name="share_66666" value="1"></td></tr> <tr>
    <td>Harry</td><td>77777</td><td><input type="text" name="share_77777" value="1"></td></tr>  <tr>
    <td>Sally</td><td>88888</td><td><input type="text" name="share_88888" value="1"></td></tr></tbody></table>
  <button type="submit">Send</button>
</form>

我目前的实现是在Lua中,我在那里硬编码一堆字符串,并手动将post数据连接到本地Lua类型(有趣!)。如果必须这样做,我也可以用Python手工处理表单数据,但我想可能有更好的解决方案。


我对WTForms进行了一些探索,但是没有太多的运气让它正常工作。

我确实找到了FieldList,但这似乎处理的是同一字段的列表,而不是同一字段的多行。

我也找到了TableWidget,但是文档很稀疏,我不知道如何实现它来知道这是否能实现我所期望的功能。


Tags: textnameformshareinputvaluetypetable
3条回答

FieldList将起作用,您需要创建一个FormField列表。像这样指定FormField:

class MemberForm(Form):
    name = StringField('name')
    member_id = StringField('member_id')
    inbox_share = IntegerField('inbox_share')
    # etc.

class TeamForm(Form):
    title = StringField('title')
    teammembers = FieldList(FormField(MemberForm))

然后可以在视图函数中从数据库创建表单,如下所示:

@app.route('/support/team-members-update', methods=['GET','POST'])
def update_team_members():
    teamform = TeamForm()
    teamform.title.data = "My Team" # change the field's data
    for member in DB.get('teammembers') # some database function to get a list of team members
        member_form = MemberForm()
        member_form.name = member.name # These fields don't use 'data'
        member_form.member_id = member.id
        member_form.inbox_share = member.share

        teamform.teammembers.append_entry(member_form)

    return render_template('edit-team.html', teamform = teamform)

然后在模板中,您可以在创建表行时对teammembers中的每个项进行迭代:

<html>
    <head>
        <title>Edit Team Members</title>
    </head>
    <body>
        <h1>Edit Team</h1>
        <div>
            <form action="" method="post" name="teamform">
                {{ teamform.hidden_tag() }}
                Team Title: {{ teamform.title }}<br>
                <div>
                    <table>
                        <tr>
                            <th> Name </th>
                            <th> ID </th>
                            <th> Inbox Share </th>
                        </tr>
                        {% for member in teamform.teammembers %}
                        <tr>
                            <td>{{ member.name }}</td>
                            <td>{{ member.member_id }}</td>
                            <td>{{ member.inbox_share }}</td>
                        </tr>
                        {% endfor %}
                    </table>
                </div>
                <p><input type="submit" name="edit" value="Send"></p>
            </form>
        </div>
    </body>
</html>

我从来没能让WTForms按照我想要的方式工作。我认为这对我的需求来说有点太重了,所以我最终使用了自己的Jinja2模板来构建表单,然后使用formencode库将post变量解析为dict。这对我来说足够好了。(感谢this question将我指向formencode库)。

我将粗略地介绍一下我正在使用的各种文件,然后在底部解释重要的部分:

应用程序py:

from flask import Flask, render_template, request
from formencode import variabledecode
import pickledb

app = Flask(__name__)   

DB = pickledb.load('data/data.db', False)

@app.route('/team-members', methods=['GET', 'POST'])
def team_members():
  global DB
  teammembers = DB.get('teammembers')
  # teammembers looks like this, roughly:
  # [{"id": 55555, "name": "Ben", "share": 0},
  #  {"id": 66666, "name": "Amy", "share": 1},
  #  {"id": 77777, "name": "Ted", "share": 1}] 

  if request.method == 'POST':    
    postvars = variabledecode.variable_decode(request.form, dict_char='_')
    for k, v in postvars.iteritems():
      member = [m for m in teammembers if m["id"] == int(k)][0]
      member['share'] = v["share"]
    DB.set('teammembers', teammembers)
    DB.dump()
  return render_template('team-members.html', teammembers=teammembers) 

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--debug', '-d', action='store_true')
    parser.add_argument('--port', '-p', default=5000, type=int)
    parser.add_argument('--host', default='0.0.0.0')

    args = parser.parse_args()
    app.run(args.host, args.port, debug=args.debug)

我有三个模板文件,但你当然不需要这么多。team-members.html包含与此问题相关的代码。

_formhelpers.html:

{% macro render_input(id, fieldname, value) %}<input type="text" name="{{ id  }}_{{ fieldname }}" value="{{ value }}" />{% endmacro %}

layout.html格式:

<!doctype html>
<html>
<head>
  <title>Support Team Site</title>
</head>
<body>
<div class="page">
  <h1>Support Team Site</h1>
  {% for message in get_flashed_messages() %}
    <div class=flash>{{ message }}</div>
  {% endfor %}
  {% block body %}{% endblock %}
</div>
</body>
</html>

组员.html:

{% from "_formhelpers.html" import render_input %}

{% extends "layout.html" %}
{% block body %}

  <form action="/team-members" method="post">
    <table>
      <tr>
        <th>Name</th>
        <th>ID</th>
        <th>Inbox Share</th>
      </tr>
      {% for member in teammembers %}
      <tr>
        <td>{{member['name']}}</td>
        <td>{{member['id']}}</td>
        <td>{{ render_input(member['id'], 'share', member['share']) }}</td>
      </tr>
      {% endfor %}
    </table>
    <button type="submit">Send</button>
  </form>

{% endblock %}

这将呈现以下HTML:

<!doctype html>
<html>
<head>
  <title>Support Team Site</title>
</head>
<body>
<div class="page">
  <h1>Support Team Site</h1>



  <form action="/team-members" method="post">
    <table>
      <tr>
        <th>Name</th>
        <th>ID</th>
        <th>Inbox Share</th>
      </tr>

      <tr>
        <td>Ben</td>
        <td>55555</td>
        <td><input type="text" name="55555_share" value="0" /></td>
      </tr>

      <tr>
        <td>Amy</td>
        <td>66666</td>
        <td><input type="text" name="66666_share" value="1" /></td>
      </tr>

      <tr>
        <td>Ted</td>
        <td>77777</td>
        <td><input type="text" name="77777_share" value="1" /></td>
      </tr>

    </table>
    <button type="submit">Send</button>
  </form>


</div>
</body>
</html>

值得一提的是app.py的if request.method == 'POST':部分发生了什么。request.form变量将是ImmutableMultiDict类型,打印出来时类似于这样:

ImmutableMultiDict([('55555_share', u'0'), ('66666_share', u'1'), ('77777_share', u'1')])

这有点有用,但是我们仍然需要手工解析它来处理它。注意键的格式,格式为id_fieldname(例如55555_share)。这要感谢我们在formhelpers.html模板文件中放入的render_input宏。当我们处理post表单输入时,我们使用variabledecode.variable_decode(request.form, dict_char='_'),它解析表单数据并根据我们用于表单输入的name值的命名约定将其转换为字典。下面是它的样子:

{
    "55555": {
        "share": "0"
    },
    "66666": {
        "share": "1"
    },
    "77777": {
        "share": "1"
    }
}

这使得我们很容易映射回原始数据并更新它。

我从来没能让WTForms按照我想要的方式工作。我认为这对我的需求来说有点太重了,所以我最终使用了自己的Jinja2模板来构建表单,然后使用formencode库将post变量解析为dict。这对我来说足够好了。(感谢this question将我指向formencode库)。

我将粗略地介绍一下我正在使用的各种文件,然后在底部解释重要的部分:

应用程序py:

from flask import Flask, render_template, request
from formencode import variabledecode
import pickledb

app = Flask(__name__)   

DB = pickledb.load('data/data.db', False)

@app.route('/team-members', methods=['GET', 'POST'])
def team_members():
  global DB
  teammembers = DB.get('teammembers')
  # teammembers looks like this, roughly:
  # [{"id": 55555, "name": "Ben", "share": 0},
  #  {"id": 66666, "name": "Amy", "share": 1},
  #  {"id": 77777, "name": "Ted", "share": 1}] 

  if request.method == 'POST':    
    postvars = variabledecode.variable_decode(request.form, dict_char='_')
    for k, v in postvars.iteritems():
      member = [m for m in teammembers if m["id"] == int(k)][0]
      member['share'] = v["share"]
    DB.set('teammembers', teammembers)
    DB.dump()
  return render_template('team-members.html', teammembers=teammembers) 

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--debug', '-d', action='store_true')
    parser.add_argument('--port', '-p', default=5000, type=int)
    parser.add_argument('--host', default='0.0.0.0')

    args = parser.parse_args()
    app.run(args.host, args.port, debug=args.debug)

我有三个模板文件,但你当然不需要这么多。team-members.html包含与此问题相关的代码。

_formhelpers.html:

{% macro render_input(id, fieldname, value) %}<input type="text" name="{{ id  }}_{{ fieldname }}" value="{{ value }}" />{% endmacro %}

layout.html格式:

<!doctype html>
<html>
<head>
  <title>Support Team Site</title>
</head>
<body>
<div class="page">
  <h1>Support Team Site</h1>
  {% for message in get_flashed_messages() %}
    <div class=flash>{{ message }}</div>
  {% endfor %}
  {% block body %}{% endblock %}
</div>
</body>
</html>

组员.html:

{% from "_formhelpers.html" import render_input %}

{% extends "layout.html" %}
{% block body %}

  <form action="/team-members" method="post">
    <table>
      <tr>
        <th>Name</th>
        <th>ID</th>
        <th>Inbox Share</th>
      </tr>
      {% for member in teammembers %}
      <tr>
        <td>{{member['name']}}</td>
        <td>{{member['id']}}</td>
        <td>{{ render_input(member['id'], 'share', member['share']) }}</td>
      </tr>
      {% endfor %}
    </table>
    <button type="submit">Send</button>
  </form>

{% endblock %}

这将呈现以下HTML:

<!doctype html>
<html>
<head>
  <title>Support Team Site</title>
</head>
<body>
<div class="page">
  <h1>Support Team Site</h1>



  <form action="/team-members" method="post">
    <table>
      <tr>
        <th>Name</th>
        <th>ID</th>
        <th>Inbox Share</th>
      </tr>

      <tr>
        <td>Ben</td>
        <td>55555</td>
        <td><input type="text" name="55555_share" value="0" /></td>
      </tr>

      <tr>
        <td>Amy</td>
        <td>66666</td>
        <td><input type="text" name="66666_share" value="1" /></td>
      </tr>

      <tr>
        <td>Ted</td>
        <td>77777</td>
        <td><input type="text" name="77777_share" value="1" /></td>
      </tr>

    </table>
    <button type="submit">Send</button>
  </form>


</div>
</body>
</html>

值得一提的是app.py的if request.method == 'POST':部分发生了什么。request.form变量将是ImmutableMultiDict类型,打印出来时类似于这样:

ImmutableMultiDict([('55555_share', u'0'), ('66666_share', u'1'), ('77777_share', u'1')])

这有点有用,但是我们仍然需要手工解析它来处理它。注意键的格式,格式为id_fieldname(例如55555_share)。这要感谢我们在formhelpers.html模板文件中放入的render_input宏。当我们处理post表单输入时,我们使用variabledecode.variable_decode(request.form, dict_char='_'),它解析表单数据并根据我们用于表单输入的name值的命名约定将其转换为字典。下面是它的样子:

{
    "55555": {
        "share": "0"
    },
    "66666": {
        "share": "1"
    },
    "77777": {
        "share": "1"
    }
}

这使得我们很容易映射回原始数据并对其进行更新。

相关问题 更多 >

    热门问题