-
-
Save kageurufu/6813878 to your computer and use it in GitHub Desktop.
from flask import Flask | |
from flask.ext.sqlalchemy import SQLAlchemy | |
from flask.ext.wtf import Form | |
from flask.ext.babel import gettext | |
from wtforms import SelectField, TelField, TextField, FormField, Fieldlist, SubmitField | |
from wtforms.validators import Optional, Required | |
app = Flask(__name__) | |
db = SQLAlchemy(app) | |
class User(db.Model): | |
id = db.Column(db.Integer(), primary_key=True) | |
username = db.Column(db.String(50)) | |
phone = db.relationship(lambda: PhoneNumber) | |
class PhoneNumber(db.Model): | |
id = db.Column(db.Integer(), primary_key=True) | |
user_id = db.Column(db.Integer(), db.ForeignKey(User.id)) | |
phonetype = db.Column(db.String(10)) | |
number = db.Column(db.String(20)) | |
ext = db.Column(db.String(10)) | |
class PhoneNumberForm(Form): | |
phonetype = SelectField(gettext("Type"), choices=[(c, c) for c in ['Mobile', 'Home', 'Work', 'Fax', 'Other']]) | |
number = TelField(gettext("Number"), validators=[Required()]) | |
ext = TextField(gettext("Notes"), validators=[Optional()]) | |
def __init__(self, csrf_enabled=False, *args, **kwargs): | |
super(PhoneNumberForm, self).__init__(csrf_enabled=False, *args, **kwargs) | |
class ModelFieldList(FieldList): | |
def __init__(self, *args, **kwargs): | |
self.model = kwargs.pop("model", None) | |
super(ModelFieldList, self).__init__(*args, **kwargs) | |
if not self.model: | |
raise ValueError("ModelFieldList requires model to be set") | |
def populate_obj(self, obj, name): | |
while len(getattr(obj, name)) < len(self.entries): | |
newModel = self.model() | |
db.session.add(newModel) | |
getattr(obj, name).append(newModel) | |
while len(getattr(obj, name)) > len(self.entries): | |
db.session.delete(getattr(obj, name).pop()) | |
super(ModelFieldList, self).populate_obj(obj, name) | |
class UserForm(Form): | |
username = TextField(gettext("Username"), validators=[Required()]) | |
phone = ModelFieldList(FormField(PhoneNumberForm), model=PhoneNumber) | |
submit = SubmitField(gettext("Submit")) | |
@app.route("/") | |
def index() | |
user = User.query.first() | |
form = UserForm(obj = user) | |
form.phone.min_entries=3 | |
if form.validate_on_submit(): | |
form.populate_obj(user) | |
db.session.commit() | |
return render_template("page.html", form = form) | |
if __name__ == '__main__': | |
db.drop_all() | |
db.create_all() | |
db.session.add(User(username="Frank")) | |
db.session.commit() | |
app.run(debug=True) |
<!doctype html> | |
<html> | |
<head> | |
<title>User Form Demo</title> | |
</head> | |
<body> | |
{{ form.username.label }}: {{ form.username }} | |
<div data-toggle="fieldset" id="phone-fieldset"> | |
{{ form.phone.label }} <button type="button" data-toggle="fieldset-add-row" data-target="#phone-fieldset">+</button> | |
<table> | |
<tr> | |
<th>Type</th> | |
<th>Number</th> | |
<th>Extension</th> | |
<th></th> | |
</tr> | |
{% for phone in form.phone %} | |
<tr data-toggle="fieldset-entry"> | |
<td>{{ phone.phonetype }}</td> | |
<td>{{ phone.number }}</td> | |
<td>{{ phone.ext }}</td> | |
<td><button type="button" data-toggle="fieldset-remove-row" id="phone-{{loop.index0}}-remove">-</button></td> | |
</tr> | |
{% endfor %} | |
</table> | |
</div> | |
{{ form.submit }} | |
<script src="{{ url_for("static", filename="js/jquery-1.10.2.min.js") }}"></script> | |
<script src="{{ url_for("static", filename="js/page.js") }}"></script> | |
</body> | |
</html> |
$(function() { | |
$("div[data-toggle=fieldset]").each(function() { | |
var $this = $(this); | |
//Add new entry | |
$this.find("button[data-toggle=fieldset-add-row]").click(function() { | |
var target = $($(this).data("target")) | |
console.log(target); | |
var oldrow = target.find("div[data-toggle=fieldset-entry]:last"); | |
var row = oldrow.clone(true, true); | |
console.log(row.find(":input")[0]); | |
var elem_id = row.find(":input")[0].id; | |
var elem_num = parseInt(elem_id.replace(/.*-(\d{1,4})-.*/m, '$1')) + 1; | |
row.attr('data-id', elem_num); | |
row.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"); | |
}); | |
oldrow.after(row); | |
}); //End add new entry | |
//Remove row | |
$this.find("button[data-toggle=fieldset-remove-row]").click(function() { | |
if($this.find("div[data-toggle=fieldset-entry]").length > 1) { | |
var thisRow = $(this).closest("div[data-toggle=fieldset-entry]"); | |
thisRow.remove(); | |
} | |
}); //End remove row | |
}); | |
}); |
Thanks for providing this code. It was a good starting point for me.
I've created a version of this that works out of the box: https://github.com/sebkouba/dynamic-flask-form
I've tried to expand this a bit
https://github.com/rienafairefr/dynamic-flask-form
In this case, there is a hidden first entry, that way we can show a list of field that is empty, without losing the ability to add a new row (this code adds a copy of the last row, in JS)
Has anyone tried getting a Dynamic output as such:
1.Question
RadioField1 RadioField1
2.Question
RadioField2 RadioField2
I tried FieldList but couldn't render a unique id for each pair.
I've been looking everywhere for an answer to this question -- seems like you need to programatically add the new row in the backend and then redirect the same/refresh the page with the new updated form each time you click the button :/
I suggest the following processes before the code sample above can work:
where app.cfg contains: