Skip to content Skip to sidebar Skip to footer

Render An Editable Table Using Flask, Jinja2 Templates, Then Process The Form Data Returned

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: And here's HTML for that:
) 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'])defupdate_team_members():
    teamform = TeamForm()
    teamform.title.data = "My Team"# change the field's datafor 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><formaction=""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><inputtype="submit"name="edit"value="Send"></p></form></div></body></html>

Solution 2:

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) %}<inputtype="text" name="{{ id  }}_{{ fieldname }}" value="{{ value }}" />{% endmacro %}

layout.html:

<!doctype html><html><head><title>Support Team Site</title></head><body><divclass="page"><h1>Support Team Site</h1>
  {% for message in get_flashed_messages() %}
    <divclass=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><buttontype="submit">Send</button>
  </form>

{% endblock %}

This will render the following HTML:

<!doctype html><html><head><title>Support Team Site</title></head><body><divclass="page"><h1>Support Team Site</h1><formaction="/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><inputtype="text"name="55555_share"value="0" /></td></tr><tr><td>Amy</td><td>66666</td><td><inputtype="text"name="66666_share"value="1" /></td></tr><tr><td>Ted</td><td>77777</td><td><inputtype="text"name="77777_share"value="1" /></td></tr></table><buttontype="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.

Post a Comment for "Render An Editable Table Using Flask, Jinja2 Templates, Then Process The Form Data Returned"