Skip to main content

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 a String. The label of this input field is 'Math Expression'. We can specify validators for Python to check the validity of the input data.
  • The field assign_to is a SelectMultipleField 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 as CreateQuestionForm object instance which is imported from app/forms.py.
  • In the last line and last argument of render_template(), we have form=form where we pass on the CreateQuestionForm object instance that we create previously into the a keyword argument called form. This name form (the left hand side of the equal sign) is the one that is accessible inside questions.html in wtf.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 a StringField and submit which is SubmitField button.
  • Both challenge_id and elapsed_time are HiddenField 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().

References