Render an editable table using Flask, Jinja2 templates, then process the form data returned

前端 未结 2 2069
孤独总比滥情好
孤独总比滥情好 2020-12-25 09:17

I\'m using Flask and Jinja2 and I need to make an editable table with multiple rows.

This is what the table will look like:

相关标签:
2条回答
  • 2020-12-25 09:44

    I never was able to get WTForms to work quite how I wanted. I think it was a bit too heavy for my needs, so I ended up just using my own Jinja2 template to build the form and then used the formencode library to parse the post variables into a dict. This works well enough for me. (Thanks to this question for pointing me to the formencode library).

    I'll give you a rough look at the various files I'm using and then explain the important parts at the bottom:

    app.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)
    

    I have three template files, but you of course don't need this many. team-members.html has the code that's relevant to this problem.

    _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>
    

    team-members.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 %}
    

    This will render the following 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>
    

    It's worth mentioning what's going on in the if request.method == 'POST': part of app.py. The request.form variable will be of type ImmutableMultiDict, which would look kind of like this when printed out:

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

    This is somewhat useful, but we'd still have to parse this by hand to do anything with it. Note the format of the keys there, in the id_fieldname format (e.g. 55555_share). This was thanks to the render_input macro we put in our _formhelpers.html template file. When we process the post form input, we use variabledecode.variable_decode(request.form, dict_char='_'), which parses the form data and turns it into a dictionary based on the naming convention we used for the name values of the form inputs. Here's what it looks like:

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

    This makes it easy to map back to our original data and update it.

    0 讨论(0)
  • 2020-12-25 09:47

    FieldList will work, you need to make a list of a FormField. Specify your FormField like so:

    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))
    

    Then you can create the forms from your database in a view function like so:

    @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)
    

    And then in the template, you can iterate over each item in teammembers as you create your table rows:

    <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>
    
    0 讨论(0)
提交回复
热议问题