Skip to content

Instantly share code, notes, and snippets.

@giacomomarchioro
Forked from rmed/app.py
Last active November 13, 2022 21:10
Show Gist options
  • Save giacomomarchioro/f6f3d80d7d48bf8bc8e92486966b7ef3 to your computer and use it in GitHub Desktop.
Save giacomomarchioro/f6f3d80d7d48bf8bc8e92486966b7ef3 to your computer and use it in GitHub Desktop.
Dynamic Flask-WTF fields more than one dynamic form JQuery function parametrized with arguments (problem for removing items!)
# -*- coding: utf-8 -*-
# app.py
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import Form, FieldList, FormField, IntegerField, SelectField, \
StringField, TextAreaField, SubmitField
from wtforms import validators
globalvar = []
class LapForm(Form):
"""Subform.
CSRF is disabled for this subform (using `Form` as parent class) because
it is never used by itself.
"""
runner_name = StringField(
'Runner name',
validators=[validators.InputRequired(), validators.Length(max=100)]
)
lap_time = IntegerField(
'Lap time',
validators=[validators.InputRequired(), validators.NumberRange(min=1)]
)
category = SelectField(
'Category',
choices=[('cat1', 'Category 1'), ('cat2', 'Category 2')]
)
notes = TextAreaField(
'Notes',
validators=[validators.Length(max=255)]
)
class MainForm(FlaskForm):
"""Parent form."""
Segnatura = StringField("Segnatura",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
datazione = StringField("datazione",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
tipo_di_supporto_e_qualita = StringField("tipo di supporto e qualita",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
consistenza = StringField("consistenza",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
numerazione_carte = StringField("numerazione carte",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
carte_di_guardia = StringField("carte di guardia",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
prospetto_fascicolazione = StringField("prospetto fascicolazione",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
arrangiamento_fogli_gregory = StringField("arrangiamento fogli gregory",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
dimensioni = StringField("dimensioni",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
rigatura = StringField("rigatura",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
foratura = StringField("foratura",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
legatura = StringField("legatura",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
utenti_email = StringField("utenti email",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
Descrizione_Esterna_Segnatura = StringField("Descrizione Esterna Segnatura",
validators=[validators.InputRequired(), validators.Length(max=500)]
)
laps = FieldList(
FormField(LapForm),
min_entries=1,
max_entries=20
)
laps2 = FieldList(
FormField(LapForm),
min_entries=1,
max_entries=20
)
# Create models
db = SQLAlchemy()
class Race(db.Model):
"""Stores races."""
__tablename__ = 'races'
id = db.Column(db.Integer, primary_key=True)
class Lap(db.Model):
"""Stores laps of a race."""
__tablename__ = 'laps'
id = db.Column(db.Integer, primary_key=True)
race_id = db.Column(db.Integer, db.ForeignKey('races.id'))
runner_name = db.Column(db.String(100))
lap_time = db.Column(db.Integer)
category = db.Column(db.String(4))
notes = db.Column(db.String(255))
# Relationship
race = db.relationship(
'Race',
backref=db.backref('laps', lazy='dynamic', collection_class=list)
)
# Initialize app
app = Flask(__name__)
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
app.config['SECRET_KEY'] = 'sosecret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db.init_app(app)
db.create_all(app=app)
@app.route('/', methods=['GET', 'POST'])
def index():
form = MainForm()
template_form = LapForm(prefix='laps-_-')
template_form2 = LapForm(prefix='laps2-_-')
if form.validate_on_submit():
# Create race
new_race = Race()
globalvar.append(form)
db.session.add(new_race)
for lap in form.laps.data:
new_lap = Lap(**lap)
# Add to race
new_race.laps.append(new_lap)
db.session.commit()
print("Valido")
races = Race.query
return render_template(
'index.html',
form=form,
races=races,
_template=template_form,
_template2 = template_form2
)
@app.route('/<race_id>', methods=['GET'])
def show_race(race_id):
"""Show the details of a race."""
race = Race.query.filter_by(id=race_id).first()
return render_template(
'show.html',
race=race
)
if __name__ == '__main__':
app.run()
{# templates/index.html #}
{% import "macros.html" as macros %}
<html>
<head>
<title>Lap logging</title>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
{# Import JQuery #}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
const ID_RE = /(-)_(-)/;
/**
* Replace the template index of an element (-_-) with the
* given index.
*/
function replaceTemplateIndex(value, index) {
return value.replace(ID_RE, '$1'+index+'$2');
}
/**
* Adjust the indices of form fields when removing items.
*/
function adjustIndices(removedIndex,tgsubf) {
var $forms = $(tgsubf);
$forms.each(function(i) {
var $form = $(this);
var index = parseInt($form.data('index'));
var newIndex = index - 1;
if (index < removedIndex) {
// Skip
return true;
}
// This will replace the original index with the new one
// only if it is found in the format -num-, preventing
// accidental replacing of fields that may have numbers
// intheir names.
var regex = new RegExp('(-)'+index+'(-)');
var repVal = '$1'+newIndex+'$2';
// Change ID in form itself
$form.attr('id', $form.attr('id').replace(index, newIndex));
$form.data('index', newIndex);
// Change IDs in form fields
$form.find('label, input, select, textarea, h5').each(function(j) {
var $item = $(this);
if ($item.is('label')) {
// Update labels
$item.attr('for', $item.attr('for').replace(regex, repVal));
return;
}
if ($item.is('h5')) {
// Update labels
$item.attr('id', $item.attr('id').replace(regex, repVal));
$item.text(newIndex);
console.log(newIndex);
return;
}
// Update other fields
$item.attr('id', $item.attr('id').replace(regex, repVal));
$item.attr('name', $item.attr('name').replace(regex, repVal));
});
});
}
/**
* Remove a form.
*/
function removeForm(anchor,tgsubf) {
var $removedForm = $(anchor).closest(tgsubf);
var removedIndex = parseInt($removedForm.data('index'));
$removedForm.remove();
// Update indices
adjustIndices(removedIndex,tgsubf);
}
/**
* Add a new form.
*/
function addForm(idt,clst,tgsubf,rmvf) {
var $templateForm = $(idt);
if ($templateForm.length === 0) {
console.log('[ERROR] Cannot find template');
return;
}
// Get Last index
var $lastForm = $('.'+tgsubf).last();
var newIndex = 1;
if ($lastForm.length > 0) {
newIndex = parseInt($lastForm.data('index'))
if (newIndex == 0) {
newIndex = newIndex + 2;
}else{
newIndex = newIndex +1
}
}
// Maximum of 20 subforms
if (newIndex >= 20) {
console.log('[WARNING] Reached maximum number of elements');
return;
}
// Add elements
var $newForm = $templateForm.clone();
$newForm.attr('id', replaceTemplateIndex($newForm.attr('id'), newIndex));
$newForm.data('index', newIndex);
$newForm.find('label, input, select, textarea,h5').each(function(idx) {
var $item = $(this);
if ($item.is('label')) {
// Update labels
$item.attr('for', replaceTemplateIndex($item.attr('for'), newIndex));
return;
}
if ($item.is('h5')) {
// Update labels
$item.attr('id', replaceTemplateIndex($item.attr('id'), newIndex));
$item.text(newIndex)
return;
}
// Update other fields
$item.attr('id', replaceTemplateIndex($item.attr('id'), newIndex));
$item.attr('name', replaceTemplateIndex($item.attr('name'), newIndex));
});
// Append
$(clst).append($newForm);
$newForm.addClass(tgsubf);
$newForm.removeClass('is-hidden');
$newForm.find(rmvf).click(function(e) {e.preventDefault();removeForm(this,'.'+tgsubf)});
}
$(document).ready(function() {
$('#add').click(function(e) {
e.preventDefault();addForm('#lap-_-form','#subforms-container','subform','.remove_1')});
$('#add_cp').click(function(e) {
e.preventDefault();addForm('#lap2-_-form','#subforms-container_cp','subform2','.remove_2')});
$('.remove_1').click(function(e) {e.preventDefault();removeForm(this,'subform')});
$('.remove_2').click(function(e) {e.preventDefault();removeForm(this,'subform2')});
});
</script>
<style>
.is-hidden {
display: none;
}
</style>
</head>
<body style="margin:10;padding:10;margin-top:20px;background-color:papayawhip;">
<h3>Laps 1</h3>
<form id="lap-form" action="" method="POST" role="form">
{{ form.hidden_tag() }}
<div>
{{ form.Segnatura.label }}
{{ form.Segnatura}}
</div>
<div> <h3>Descrizioni interne </h3>
</div>
<hr/>
{# Show all subforms #}
<div id="subforms-container">
{% for subform in form.laps %}
{{ macros.render_lap_form(subform, loop.index0) }}
{% endfor %}
</div>
<a id="add" href="#subforms-container" class="btn btn-primary btn-sm" tabindex="-1" role="button" aria-disabled="true">Aggiungi descrizione interna</a>
<div> <h3>Laps 2 </h3>
</div>
<hr/>
{# Show all subforms #}
<div id="subforms-container_cp">
{% for subform in form.laps2 %}
{{ macros.render_lap_form2(subform, loop.index0) }}
{% endfor %}
</div>
<a id="add_cp" href="#subforms-container_cp" class="btn btn-primary btn-sm" tabindex="-1" role="button" aria-disabled="true">Aggiungi copista</a>
<button type="submit">Send</button>
</form>
{% if form.errors %}
{{ form.errors }}
{% endif %}
{# Form template #}
{{ macros.render_lap_form(_template, '_') }}
{{ macros.render_lap_form2(_template2, '_') }}
{# Show races #}
{% for race in races %}
<p><a href="{{ url_for('show_race', race_id=race.id) }}">Race {{ race.id }}</a></p>
{% endfor %}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
</body>
</html>
{# templates/macros.html #}
{# Render lap form.
This macro is intended to render both regular lap subforms (received from the
server) and the template form used to dynamically add more forms.
Arguments:
- subform: Form object to render
- index: Index of the form. For proper subforms rendered in the form loop,
this should match `loop.index0`, and for the template it should be
'_'
#}
{%- macro render_lap_form(subform, index) %}
<div id="lap-{{ index }}-form" class="{% if index != '_' %}subform{% else %}is-hidden{% endif %}" data-index="{{ index }}">
<div>
<h5 id="inc-0-">1</h5>
{{ subform.runner_name.label }}
{{ subform.runner_name }}
</div>
<div>
{{ subform.lap_time.label }}
{{ subform.lap_time}}
</div>
<div>
{{ subform.category.label }}
{{ subform.category }}
</div>
<div>
{{ subform.notes.label }}
{{ subform.notes }}
</div>
<a class="remove">Remove</a>
<hr/>
</div>
{%- endmacro %}
{%- macro render_lap_form2(subform, index) %}
<div id="lap-{{ index }}-form" class="{% if index != '_' %}subform{% else %}is-hidden{% endif %}" data-index="{{ index }}">
<div>{{ index }} </div>
<div>
{{ subform.runner_name.label }}
{{ subform.runner_name }}
</div>
<div>
{{ subform.lap_time.label }}
{{ subform.lap_time}}
</div>
<div>
{{ subform.category.label }}
{{ subform.category }}
</div>
<div>
{{ subform.notes.label }}
{{ subform.notes }}
</div>
<a class="remove">Remove</a>
<hr/>
</div>
{%- endmacro %}
{# templates/show.html #}
<html>
<head>
<title>Race details</title>
</head>
<body>
<a href="{{ url_for('index') }}">Back to index</a>
{% if not race %}
<p>Could not find race details</p>
{% else %}
<table>
<thead>
<tr>
<th>Runner name</th>
<th>Lap time</th>
<th>Category</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{% for lap in race.laps %}
<tr>
<td>{{ lap.runner_name }}</td>
<td>{{ lap.lap_time }}</td>
<td>
{%- if lap.category == 'cat1' %}
Category 1
{%- elif lap.category == 'cat2' %}
Category 2
{%- else %}
Unknown
{%- endif %}
</td>
<td>{{ lap.notes }}</td>
</tr>
{% endfor%}
</tbody>
</table>
{% endif %}
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment