The FloatField
objects can check that the input is compatible with
a number, but what if we want to control that \( A>0 \), \( b>0 \), and
\( T \) is not greater than 30 periods (otherwise the plot gets cluttered)?
We can write functions for checking appropriate conditions and
supply the function to the list of validator functions in the call to
the FloatField
constructor or other field constructors. The extra
code is a part of the model.py
and the presented extensions appear
in the directory vib2.
The simplest approach to validation is to use existing functionality
in the web framework. Checking that \( A>0 \) can be done by
the NumberRange
validator which checks that the value is inside
a prescribed interval:
from wtforms import Form, FloatField, validators
class InputForm(Form):
A = FloatField(
label='amplitude (m)', default=1.0,
validators=[validators.NumberRange(0, 1E+20)])
We can also easily provide our own more tailored validators.
As an example, let us explain how we can check that \( T \) is less than 30 periods.
One period is \( 2\pi /w \) so we need to check if \( T> 30\cdot 2\pi/w \)
and raise an exception in that case.
A validation function takes two arguments: the whole form
and the
specific field
to test:
def check_T(form, field):
"""Form validation: failure if T > 30 periods."""
w = form.w.data
T = field.data
period = 2*pi/w
if T > 30*period:
num_periods = int(round(T/period))
raise validators.ValidationError(
'Cannot plot as much as %d periods! T<%.2f' %
(num_periods, 30*period))
The appropriate exception is of type validators.ValidationError
.
Observe that through form
we have in fact access to all the input
data so we can easily use the value of \( w \) when checking the validity
of the value of \( T \). The check_T
function is easy to
add to the list of validator functions in the call to the FloatField
constructor for T
:
class InputForm(Form):
...
T = FloatField(
label='time interval', default=6*pi,
validators=[validators.InputRequired(), check_T])
The validator
objects are tested one by one as they appear in the list, and if
one fails, the others are not invoked.
We therefore add check_T
after the check of input such that we know we
have a value for all data when we run the computations and test
in check_T
.
Although there is already a NumberRange
validator for checking
whether a value is inside an interval, we can write our own
version with some improved functionality for open intervals where
the maximum or minimum value can be infinite.
The infinite value can on input be represented by None
.
A general such function may take the form
def check_interval(form, field, min_value=None, max_value=None):
"""For validation: failure if value is outside an interval."""
failure = False
if min_value is not None:
if field.data < min_value:
failure = True
if max_value is not None:
if field.data > max_value:
failure = True
if failure:
raise validators.ValidationError(
'%s=%s not in [%s, %s]' %
(field.name, field.data,
'-infty' if min_value is None else str(min_value),
'infty' if max_value is None else str(max_value)))
The problem is that check_interval
takes four arguments, not only
the form
and field
arguments that a validator function in the
Flask framework can accept.
The way out of this difficulty is to use a Python tool functools.partial
which allows us to call a function with some of the arguments set beforehand.
Here, we want to create a new function that calls check_interval
with some prescribed values of min_value
and max_value
.
This function looks like it does not have these arguments, only
form
and field
. The following function produces this function, which we
can use as a valid Flask validator function:
import functools
def interval(min_value=None, max_value=None):
return functools.partial(
check_interval, min_value=min_value, max_value=max_value)
We can now in any field constructor just add
interval(a, b)
as a validator function, here checking that \( b\in [0,\infty) \):
class InputForm(Form):
...
b = FloatField(
label='damping factor (kg/s)', default=0,
validators=[validators.InputRequired(), interval(0,None)])
Let us test our tailored error checking. Run python controller.py
in the vib2
directory and fill in \( -1.0 \) in the \( b \) field.
Pressing Compute invokes our interval(0,None)
function, which
is nothing but a call to check_interval
with the
arguments field
, form
, 0
, and None
.
Inside this function,
the test if field.data < min_value
becomes true, failure
is set, and the exception is raised. The message in the exception
is available in the field.errors
attribute so our template
will write it out in red, see Figure 9.
The template used in vib2
is basically the same as view_tex.html
in vib1
, i.e., it feaures LaTeX mathematics and checking of
field.errors
.
Finally, we mention a detail in the controller.py
file in the vib2
app: instead of sending form.var.data
to the compute
function we
may automatically generate a set of local variables such that the
application of data from the web page, here in the compute
call, looks nicer:
def index():
form = InputForm(request.form)
if request.method == 'POST' and form.validate():
for field in form:
# Make local variable (name field.name)
exec('%s = %s' % (field.name, field.data))
result = compute(A, b, w, T)
else:
result = None
return render_template(template, form=form, result=result)
if __name__ == '__main__':
app.run(debug=True)
The idea is just to run exec
on a declaration of a local variable
with name field.name
for each field in the form. This trick is often
neat if web variables are buried in objects (form.T.data
) and you want these
variables in your
code to look like they do in mathematical writing (T
for \( T \)).