Simple dynamic forms with Flask/Flask-WTF - python

I need to build a simple form with a number of checkboxes. The problem is that I need the checkboxes to come from a csv file which would look like this:
data_so.csv
Name
A. Blabla
U. Blublu
I. Blibli
O. Bloblo
The form, right now, with some hard coded checkboxes, looks like this:
Instead of "Mr. 1", I'd need to have "A. Blabla", instead of "Ms. 2", I'd want "U. Blublu", etc. and instead of 3 checkboxes, I'd need 4, the number of entries in my csv file.
Here are my Flask files:
route_so.py
from flask import Flask, render_template, request, flash
from forms_so import ContactForm
import csv
app = Flask(__name__)
app.secret_key = 'development key'
#app.route('/', methods=['GET', 'POST'])
def home():
form = ContactForm()
if request.method == 'POST':
if form.validate() == False:
flash('All fields are required.')
return render_template('home_so.html', form=form)
else:
print(form.node_1.data,form.node_2.data,form.node_3.data)
return render_template('home_so.html', success=True)
elif request.method == 'GET':
return render_template('home_so.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
form_so.py
from flask.ext.wtf import Form
import csv
from wtforms import TextField, RadioField, TextAreaField, SubmitField, validators, BooleanField
class ContactForm(Form):
# my attempt to force the creation of dynamic global variables
with open('/data_so.csv', 'rb') as f:
reader = csv.reader(f)
r = list(reader)
nodes = {}
for i in range(1,len(r)):
globals()[''.join("node_"+str(i))] = BooleanField(r[i][0])
# end of my attempt to force the creation of dynamic global variables
node_1 = BooleanField("Mr. 1")
node_2 = BooleanField("Ms. 2")
node_3 = BooleanField("Dr. 3")
# this needs to be dynamically set instead
submit = SubmitField("Send")
So I tried and created dynamic variables (in a dirty, hacky way). The problem now is that I don't know how to make the home_so.html work with an undifined number of variables...
home_so.html
{% extends "layout_so.html" %}
{% block content %}
{% if success %}
<p>Thank you for filling up our survey. We'll get back to you shortly.</p>
{% else %}
<form action="{{ url_for('home') }}" method=post>
{{ form.hidden_tag() }}
<h2>List of check boxes dynamically built from local csv file</h2>
#this needs to be dynamically set
{{ form.node_1.label }}
{{ form.node_1 }}
{{ form.node_2.label }}
{{ form.node_2 }}
{{ form.node_3.label }}
{{ form.node_3 }}
{{ form.submit }}
</form>
{% endif %}
{% endblock %}
Is there a way to accomplish this sort of things with a simple csv file? If not, what's the usual way to go about dynamically producing a form as it loads client-side?

{% for node in node_list_from_app %}
<p class="field"><label><input type="checkbox" name="node" value="{{ node }}"> {{ node }}</label></p>
{% endfor %}

Related

flask wtforms dynamically show form fields based on select field

I have a form to kick off generation of a few different types of files, and each type has different input parameters. I'd like to use a generic form, and based on the type selected, show only the relevant parameters.
The python code is
from flask_wtf import FlaskForm
from wtforms import SelectField, DecimalField, IntegerField
from wtforms.validators import Optional, InputRequired
from flask import Blueprint, redirect, render_template, url_for
bp = Blueprint("routes", __name__)
class genericForm(FlaskForm):
# populate the file types, with default option empty
fileType = SelectField("File type",
choices=[(None, ""),
("file1", "file1"),
("file2", "file2"),
("file3", "file3")],
validators=[DataRequired()])
required1 = IntegerField("Required 1", validators=[InputRequired()])
required2 = IntegerField("Required 2", validators=[InputRequired()])
optional1 = DecimalField("Optional 1", validators=[Optional()])
optional2 = DecimalField("Optional 2", validators=[Optional()])
optional3 = DecimalField("Optional 3", validators=[Optional()])
optional4 = DecimalField("Optional 4", validators=[Optional()])
#bp.route("/index", methods=["GET", "POST"])
def index():
"""The index"""
form = genericForm()
if form.validate_on_submit():
identifier = createFile(form)
return redirect(url_for("routes.files", info=identifier))
# which file types require which form fields
fileFields = {"file1": "optional1", "optional3",
"file2": "optional2", "optional3",
"file4": "optional1", "optional4"}
return render_template("index.html", form=form, fileFields=fileFields)
and the jinja template I currently have is
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}File Creation{% endblock %}</h1>
{% endblock %}
{% block content %}
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{% for field in form %}
{% if field.flags.required %}
{{ field.label }} {{ field }}<br />
{% endif %}
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
But what I'm trying to do is determine whether to hide a field based on whether it's required or if it's in the list for the selected fileType, and update that when the selected fileType changes

Flask-WTF: multiple forms of same class all returning the same data on submission

I'm generating multiple forms, one per image, and packaging the image and corresponding form as a tuple in a list.
This list is then passed to Jinja where each tuple is unpacked and each image and form are inserted into a list for voting via the form.
My issue is that clicking on any one of the specific forms causes all form to return as if that button was clicked.
So, in effect, up or down voting one image acts as if that button was clicked for all other images.
I know I am creating legitimate forms as I have tried printing the form and it's return data to console. When I do this, each form does have a unique address, and all forms show the same data (True / False) in the form.field.data attribute.
Can somebody help me discover what's going on here?
Form:
class VoteForm(FlaskForm):
upvote = SubmitField('vote up')
downvote = SubmitField('vote down')
Route:
#index_mod.route('/', methods = ['GET', 'POST'])
def index():
pics = Pic.select().order_by(Pic.score.desc())
pics_and_forms = []
for pic in pics:
voteform = VoteForm()
#tuple of pic and corresponding form
pics_and_forms.append( (pic, voteform) )
for pic, form in pics_and_forms:
if form.validate_on_submit():
if form.upvote.data:
pic.score += 1
pic.save()
if form.downvote.data:
pic.score -= 1
pic.save()
return render_template('index.html', pics_and_forms = pics_and_forms)
Jinja:
<ul>
{% for pic, form in pics_and_forms %}
<li>
<b>{{ pic.name }} </b>
<i>Submitted by {{ pic.user.username }}</i>
Score: {{ pic.score }}
<img src="/pic/get/{{ pic.uuid }}" style="width:128px;" >
<form method="post" action=" {{ url_for('index_mod.index') }}">
{{ form.csrf_token }}
{{ form.upvote }}
{{ form.downvote }}
</form>
</li>
{% endfor %}
</ul>
EDIT
So I'm figuring out that while I can embed as many forms onto the page as I want the returning post request doesn't specify which specific form was clicked.
Instead I'm planning to embed the details into a hidden field and then use the flask request object to retrieve that field from the hidden form.
I would rather use Flask-WTF fully for this but it seems like there's no elegant way to dynamically add multiple forms to a page and retrieve which form was actually clicked.
You're only ever submitting one form at a time, so you really only need to be dealing with one Form object. I think a better approach would be to POST to a URL which contains the ID of the Pic you're voting on, and the up/down vote is captured from the submit button clicked.
I've refactored your code to illustrate this:
app.py
from flask import Flask, render_template, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import SubmitField
app = Flask(__name__)
app.secret_key = 'secret'
class VoteForm(FlaskForm):
upvote = SubmitField('vote up')
downvote = SubmitField('vote down')
#app.route("/", methods=['GET'])
def index():
form = VoteForm()
pics = [
{
"name": "test",
"user": {"username": "test"},
"score": 1,
"uuid": 'test'
},
{
"name": "test2",
"user": {"username": "test"},
"score": 2,
"uuid": 'test2'
}
]
return render_template("index.html", form=form, pics=pics)
#app.route("/pic/<id>/vote", methods=['POST'])
def vote(id):
form = VoteForm()
if form.validate_on_submit():
if form.upvote.data:
print("Upvote for pic {}".format(id))
if form.downvote.data:
print("Downvote for pic {}".format(id))
return redirect(url_for('index'))
if __name__ == "__main__":
app.run(debug=True)
index.html
<ul>
{% for pic in pics %}
<li>
<b>{{ pic.name }} </b>
<i>Submitted by {{ pic.user.username }}</i>
Score: {{ pic.score }}
<img src="/pic/get/{{ pic.uuid }}" style="width:128px;" >
<form method="post" action="{{ url_for('vote', id=pic['uuid']) }}">
{{ form.csrf_token }}
{{ form.upvote }}
{{ form.downvote }}
</form>
</li>
{% endfor %}
</ul>

Why validation not working on form request?

I am learning flask and made a small application. Now I am trying to learn form. I used a simple code to validate a name request and it should give error when the field remains empty. But it isn't giving one.
Main file :
from flask import Flask, render_template
from flask.ext.moment import Moment
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField, validators
import requests
import json
from datetime import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'abcd'
moment = Moment(app)
class Nameform(Form):
name = StringField("whats your name?", [validators.Required()])
submit = SubmitField('submit')
#app.route('/')
#app.route('/index')
def index():
api_call = requests.get('https://api.stackexchange.com/2.2/users/moderators?order=desc&sort=reputation&site=stackoverflow') # api call to stack for user with highest scores in des order
var_1 = json.loads(api_call.text)
var_2 = [{'link': value['link'], 'name': value['display_name'], 'user_id': value['user_id']} for value in var_1['items']]
return render_template('index.html', posts=var_2, current_time=datetime.utcnow())
#app.route('/user/<id>/<user_name>')
def user(id, user_name):
print id
api_call = requests.get('https://api.stackexchange.com//2.2/users/'+id+'/reputation?site=stackoverflow') # api call for reputation of click user
var_1 = json.loads(api_call.text)
return render_template('reputation.html', result=var_1, user_name=user_name)
#app.route('/test', methods=['GET', 'POST'])
def user_form():
name = None
form = Nameform()
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template('test_form.html', form=form, name=name)
if __name__ == '__main__':
app.run(debug=True)
Template for rendering:
<div class="page-header">
<h1>Hello, {% if name!= None %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
<form method=post>
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
Why it is not throwing any error? when the field remains empty
You can render the error messages using form.errors. Note that you're also missing your CSRF token, which is required for validation since you didn't disable WTF_CSRF_ENABLED, so I've added {{ form.csrf_token }}. See CSRF Protection.
<div class="page-header">
<h1>Hello, {% if name!= None %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{% for field in form.errors %}
{% for error in form.errors[field] %}
<div class="error">{{ error }}</div>
{% endfor %}
{% endfor %}
<form method=post>
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
I think you have not included the novalidate attribute with form.
The novalidate attribute is used to tell the web browser to not apply validation to the fields in this form, which effectively leaves this task to the Flask application running in the server.
For Sample code

How to use a WTForms FieldList of FormFields?

I'm building a website using Flask in which I use WTForms. In a Form I now want to use a FieldList of FormFields as follows:
class LocationForm(Form):
location_id = StringField('location_id')
city = StringField('city')
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm))
so to give people the ability to enter a company with two locations (dynamic adding of locations comes later) I do this on the front side:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
<input type="submit" value="Submit!" />
</form>
So on submit I print the locations:
print companyForm.locations.data
but I get
[{'location_id': u'', 'city': u''}]
I can print the values of the first location using the locationForm (see below), but I still don't know how to get the data of the second location.
print locationForm.location_id.data
print locationForm.city.data
So the list of locations does have one dict with empty values, but:
Why does the list of locations have only one, and not two dicts?
And why are the values in the location dict empty?
Does anybody know what I'm doing wrong here? All tips are welcome!
For starters, there's an argument for the FieldList called min_entries, that will make space for your data:
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm), min_entries=2)
This will setup the list the way you need. Next you should render the fields directly from the locations property, so names are generated correctly:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ companyForm.locations() }}
<input type="submit" value="Submit!" />
</form>
Look at the rendered html, the inputs should have names like locations-0-city, this way WTForms will know which is which.
Alternatively, for custom rendering of elements do
{% for l in companyForms.locations %}
{{ l.form.city }}
{% endfor %}
(in wtforms alone l.city is shorthand for l.form.city. However, that syntax seems to clash with Jinja, and there it is necessary to use the explicit l.form.city in the template.)
Now to ready the submitted data, just create the CompanyForm and iterate over the locations:
for entry in form.locations.entries:
print entry.data['location_id']
print entry.data['city']
This is an old question, but still a good one.
I'd like to add a working Flask based example of a toy database (just a list of strings) with focus on the Python part - how to initialize the form with variable number of subforms and how to process the posted data.
This is the example.py file:
import flask
import wtforms
import flask_wtf
app = flask.Flask(__name__)
app.secret_key = 'fixme!'
# not subclassing from flask_wtf.FlaskForm
# in order to avoid CSRF on subforms
class EntryForm(wtforms.Form):
city = wtforms.fields.StringField('city name:')
delete = wtforms.fields.BooleanField('delete?')
class MainForm(flask_wtf.FlaskForm):
entries = wtforms.fields.FieldList(wtforms.fields.FormField(EntryForm))
submit = wtforms.fields.SubmitField('SUBMIT')
city_db = "Graz Poprad Brno Basel Rosenheim Torino".split() # initial value
#app.route("/", methods=['POST'])
def demo_view_function_post():
global city_db
form = MainForm()
if form.validate_on_submit():
city_db = [
entry['city'] for entry in form.entries.data
if entry['city'] and not entry['delete']]
return flask.redirect(flask.url_for('demo_view_function_get'))
# handle the validation error, i.e. flash a warning
return flask.render_template('demo.html', form=form)
#app.route("/")
def demo_view_function_get():
form = MainForm()
entries_data = [{'city': city, 'delete': False} for city in city_db]
entries_data.append({'city': '', 'delete': False}) # "add new" placeholder
form.process(data={'entries': entries_data})
return flask.render_template('demo.html', form=form)
This is the demo.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo</title>
</head>
<body>
<h1>Subform demo</h1>
<p>Edit names / mark for deletion / add new</p>
<form method="post">
{{ form.csrf_token() }}
{% for entry in form.entries %}
{% if loop.last %}
<div>Add new:</div>
{% endif %}
<div>
{{ entry.city.label }} {{ entry.city() }}
{{ entry.delete() }} {{ entry.delete.label }}
</div>
{% endfor %}
{{ form.submit() }}
</form>
</body>
Run with: FLASK_APP=example flask run

Flask-WTF form posts successfully but validation never occurs

I have been struggling around the WTF forms for quite a while now. But this error, never seems to go away. I When ever I try to run this code the form never validates
Views :
#bundle.route('/content/add/', methods=['GET', 'POST'])
#bundle.route('/content/add', methods=['GET', 'POST'])
#bundle.route('/content/edit/<posturl>/', methods=['GET', 'POST'])
#bundle.route('/content/edit/<posturl>', methods=['GET', 'POST'])
#fas_login_required
def addcontent(posturl=None):
form = CreateContent()
form_action = url_for('content.addcontent')
if posturl is not None:
content = Content.query.filter_by(slug=posturl).first_or_404()
form = CreateContent(obj=content)
if form.slug.data == posturl and request.method == 'POST' and form.validate():
form.populate_obj(content)
db.session.commit()
return redirect(url_for('content.addcontent',
posturl=posturl, updated="True"))
else:
if request.method == 'POST' and form.validate():
query = Content(form.title.data,
form.slug.data,
form.description.data,
form.media_added_ids.data,
form.active.data,
form.tags.data,
g.fas_user['username'],
form.type_content.data
)
try:
db.session.add(query)
db.session.commit()
# Duplicate entry
except Exception as e:
return str(e)
return redirect(url_for('content.addcontent',
posturl=form.slug.data, updated="True"))
else:
print "Please validate form"
return render_template('content/edit_content.html', form=form,
form_action=form_action, title="Create Content")
Form Class :
# -*- coding: utf-8 -*-
from flask.ext.wtf import Form
from wtforms import TextField, TextAreaField
from wtforms import BooleanField, SelectField, validators
from wtforms.validators import Required
__all__ = ['CreateContent']
class CreateContent(Form):
title = TextField(
'Title', [validators.Length(min=4, max=255)])
slug = TextField(
'Url-Slug', [validators.Length(min=4, max=255)])
description = TextAreaField('Content', [validators.Length(min=4)])
media_added_ids = TextField('media')
type_content = SelectField(u'Content Type',
[Required()],
choices=[('blog', 'Blog Post'),
('media', 'Lecture'),
('doc', 'Documentation')]
)
# Comma seprated media id's
active = BooleanField('Published')
tags = TextField('Tags', [Required()])
# Comma seprated tag id's
And my Template :
{% extends "base.html" %}
{% block title %}
{{ title }}
{% endblock %}
{% block content %}
{% from "_formhelpers.html" import render_field %}
<div id="Create Content">
<center><h3> {{ updated }} </h3></center>
<h2>{{ title }}</h2>
<form method="post" action="">
<fieldset>
<legend></legend>
{{ render_field(form.title) }}
{{ render_field(form.slug ) }}
{{ render_field(form.description ) }}
{{ render_field(form.media_added_ids)}}
{{ render_field(form.type_content) }}
{{ render_field(form.active) }}
{{ render_field(form.tags )}}
</fieldset>
<input type="submit" class="button" value="Save"/>
</form>
</div>
{% endblock %}
Any help will be highly apprieciated
If the CSFR tokens are activated in a flask application setting, a CSFR token is included in each form. If developer has activated the setting and not included it in the form template the flask WTF would automatically reject the request.
The solution to this problem was to the following tag :
{{form.hidden_tag()}}
Once added, a CSFR id is included in the request and sent to the views for validation by the WTForms.
If you haven't included this token no errors will appear in the form.errors dictionary. If you iterate over this dictonary no errors will be show, but the form.validate method will return false.
Flask-WTF adds the CSRF token automatically if it is activated in your Flask settings. If this setting is active and it isn't included in the form submission the submission will be rejected. The solution in this case is to add the hidden_tag field in the template so that it is included in the form submission.
{{form.hidden_tag()}}

Categories

Resources