Forms
Flask has an extension that makes it easy to create web forms. WTForms is “a flexible forms validation and rendering library for Python Web development.” With Flask-WTF, we get WTForms in Flask. WTForms includes security features for submitting form data and submission validation techniques.
Pre-Requisite
You need to go through the following tutorial before reading the notes:
This notes will only provide brief description specific to the mini project 2.
Imports
To use webforms, we need to import FlaskForm
as well as the different input fields. This is what is done at the top few lines of app/forms.py
.
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectMultipleField, IntegerField, HiddenField
from wtforms.validators import DataRequired, ValidationError, EqualTo
Creating FlaskForm
To create a webform, we need to define a class of FlaskForm
instance.
class CreateQuestionForm(FlaskForm):
expression = StringField('Math Expression',
validators=[DataRequired()])
assign_to = SelectMultipleField('Send To',
validators=[DataRequired()])
submit = SubmitField('Submit')
- In this form to create questions, we have one input field
expression
which expects aString
. The label of this input field is 'Math Expression'. We can specifyvalidators
for Python to check the validity of the input data. - The field
assign_to
is aSelectMultipleField
which allows multiple select of users to assign the challenge to. - The last field is
submit
which is a Submit button with a label 'Submit'.
Rendering Web Form
When using Bootstrap's Forms, we can render the form as shown inside app/templates/questions.html
.
{% import "bootstrap/wtf.html" as wtf %} ...
<div class="row">
<div class="col-md-4">{{ wtf.quick_form(form)}}</div>
</div>
- First, we have to import
bootstrap/wtf.html
. - Next, we can use
{{ wtf.quick_form(form) }}
.
The argument inside wtf.quick_form()
is a variable that is passed on when rendering the template. This can be found inside app/routes.py
under the function definition for questions()
.
def questions():
...
form = CreateQuestionForm()
...
return render_template('questions.html',
title='Questions',
user=current_user,
questions=questions,
form=form)
- We must first create the
form
asCreateQuestionForm
object instance which is imported fromapp/forms.py
. - In the last line and last argument of
render_template()
, we haveform=form
where we pass on theCreateQuestionForm
object instance that we create previously into the a keyword argument calledform
. This nameform
(the left hand side of the equal sign) is the one that is accessible insidequestions.html
inwtf.quick_form(form)
.
Hidden Fields
Another example of form is used in ChallengeAnswerForm
where a user submit an answer from challenge.html
page. Inside app/forms.py
we see:
class ChallengeAnswerForm(FlaskForm):
challenge_id = HiddenField('Challenge ID')
answer = StringField('Answer', validators=[DataRequired()])
elapsed_time = HiddenField('Elapsed Time')
submit = SubmitField('Submit')
- The only input field here are
answer
which is aStringField
andsubmit
which isSubmitField
button. - Both
challenge_id
andelapsed_time
areHiddenField
because these two will be generated by the script rather than entered by the user.
When a user click the "Show/Hide" button to reveal the question, a callback is executed in clientlibrary.js
. Recall that we produced clientlibrary.js
using Transcrypt by compiling the Python's script clientlibrary.py
. Everytime the "Show/Hide" button is clicked, it calls start_time(question_id)
function.
class Records:
def __init__(self):
self.items = {}
def start_timer(self, question_id):
self.items[question_id] = AnswerTime(question_id)
When the user click the "Submit" button, it will call the stop_timer(form_id, question_id)
function.
def stop_timer(self, form_id, question_id):
self.items[question_id].stop()
curform = document.getElementById(f"form-{form_id:}")
answer = curform.elements["answer"].value
curform.elements["challenge_id"].value = str(question_id)
curform.elements["elapsed_time"].value = self.items[question_id].elapsedtime
curform.submit()
This function stop the timer and obtain data the answer
input text box. The function then fills in the value of the two HiddenField
: challenge_id
and elapsed_time
. Then the function submit the form to the server.
The way this form is implemented in templates/challenges.html
is shown in the code below.
<form id="form-{{ idx }}" action="" method="post" novalidate>
{{ form.hidden_tag() }}
<div>
{{ form.answer(size=32) }}
<button type="button" class="btn btn-primary"
onclick="library.records.stop_timer({{ idx }}, {{ challenges[idx].id }})">Submit</button>
</form>
Notice that we only implement the answer
field with {{ form.answer() }}
. Next, we create a button and bind the onclick
event to our stop_timer()
function inside our clientlibrary.js
script.
Notice that we have renamed our clientlibrary.js
as library
in our templates/base.html
.
<script type="module">
import * as library from "/static/__target__/clientlibrary.js";
window.library = library;
</script>
Moreover, inside our clientlibrary.py
we create an object called records
. You can find in the last line the following code:
records = Records()
With this, we can call the method inside the Records
class using library.records.stop_timer()
.