This short tutorial shows how to create FieldList in Flask and add new lines dynamically.
In the first step w will crate a list inside form, and allow user to enter and save the first row.
In the forms.py
we should define a nested form, and link it to the main form as FieldList
.
class ResTagForm(FlaskForm):
def __init__(self, *args, **kwargs):
super().__init__(meta={'csrf': False}, *args, **kwargs)
ocr = StringField('OCR')
class ResEdForm(FlaskForm):
# ...
tags = FieldList(FormField(ResTagForm))
Furhter we extend routing in route.py
to add the first entry for the user to enter
@bp.route('/<int:id_pub>/res_new', methods=['POST', 'GET'])
@login_required
def res_new():
form = ResEdForm()
form.tags.append_entry() # add first row
return render_template('res_ed.html', form=form, pub=pub)
Then we embade the table for lines in the html template:
<form action="" method="post">
{# ... #}
<div class="mt-5">
<table id="tags" class="table table-striped tabele-responsive table-inverse table-sm">
<thead>
<tr>
<td>OCR</td>
</tr>
</thead>
<tbody>
{% for tag in form.tags %}
<tr data-toggle="fieldset-entry">
<td>{{ tag.ocr(class_='form-control form-control-sm') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
After this step we should have a form for a document wiht ability to enter the first row.
To save the data we need to handle it in routes.py
eg.:
route.py
def res_new():
form = ResEdForm()
if form.validate_on_submit():
res = Resource(name=form.name.data) # Creating the document
for tagf in form.tags: # Saving the lines
if not tagf.ocr.data.rstrip(): # ommit empty row
continue
tag = Tag(ocr=tagf.ocr.data)
res.tags.append(tag)
db.session.add(res)
db.session.commit()
return redirect(url_for('pub.res_list', id_pub=pub.id))
# ...
Add button for adding, and mark, the file list with data-toggle atribute
<div class="mt-5">
<div class="text-end">
<a id="tag-add" href="#"> <span data-feather="plus-circle"></span> Add </a>
</div>
<table id="tags" class="table table-striped tabele-responsive table-inverse table-sm">
...
{% for tag in form.tags %}
<tr data-toggle="fieldset-entry">
implement the add function in the <script>
section
$('#tag-add').click(function () {
let list = $('#tags');
let lastrow = list.find("[data-toggle=fieldset-entry]:last");
let newrow = lastrow.clone(true, true);
let elem_id = newrow.find(":input")[0].id;
let elem_num = parseInt(elem_id.replace(/.*-(\d{1,4})-.*/m, '$1')) + 1;
newrow.attr('data-id', elem_num);
newrow.find(":input").each(function () {
console.log(this);
var id = $(this).attr('id').replace('-' + (elem_num - 1) + '-', '-' + (elem_num) + '-');
$(this).attr('name', id).attr('id', id).val('').removeAttr("checked");
});
lastrow.after(newrow);
});
To edit the data we need to load sub forms to the Field list, and for submit update/delete/insert rows. We assume that the order of the item on the list is the same as the one stored in database.
def res_ed(id_res):
res = Resource.query.get_or_404(id_res)
form = ResEdForm()
if form.validate_on_submit():
# ... Saving the data
for i, tagf in enumerate(form.tags):
try:
res.tags[i].ocr = tagf.ocr.data # update rows
except IndexError: # add new rows
if not tagf.ocr.data.rstrip(): # ommit empty row
continue
tag = Tag(ocr=tagf.ocr.data)
tag.publication = res.publication
res.tags.append(tag)
for t in res.tags: # Delete empty rows (ocr is empty)
if not t.ocr.rstrip():
db.session.delete(t)
db.session.add(res)
db.session.commit()
return redirect(url_for('pub.res_list', id_pub=res.publication.id))
# .... Loading the data
if not len(res.tags):
form.tags.append_entry() # add first empty row if not provided when adding entry
for t in res.tags:
tf = ResTagForm()
tf.ocr = t.ocr
form.tags.append_entry(tf)
return render_template('res_ed.html', form=form, pub=res.publication, res=res)