We shall briefly how to work with multi-variable input in Django.
The text is the Django counterpart to the section Handling multiple input variables in Flask.
There are four float input variables: \( A \), \( b \), \( w \), and \( T \).
A function compute
in the file compute.py
makes
a plot of the function \( u(t)=Ae^{-bt}\sin (wt) \)
depending on these four parameters and returns
the name of the plot file. Our task is to define four input fields,
execute the compute
function and show the input fields together with
the resulting plot,
cf. Figures 7
and 8.
Any Django app needs a project, but here we reuse the project
we set up for the scientific hello world examples. We go to
the directory apps/django_apps
and create the Django app vib1
:
Terminal> python ../../django_project/manage.py startapp vib1
Then we
relative2absolute_path('../../apps/django_apps/vib1/templates'),
to the TEMPLATE_DIRS
tuple in settings.py
,vib1
to the INSTALLED_APPS
tuple, andurl(r'^vib1/', 'django_apps.vib1.views.index')
to the patterns
call in urls.py
.vib1
to reach the application.
The computations in our application are put in a file compute.py
and explained in detail in the section Programming the Flask application.
We can equally well utilize the Bokeh library for plotting using
the code shown in the section Plotting with the Bokeh library.
We can now write models.py
and the Input
class that defines
the form fields for the four input variables:
from django.db import models
from django.forms import ModelForm
from math import pi
class Input(models.Model):
A = models.FloatField(
verbose_name=' amplitude (m)', default=1.0)
b = models.FloatField(
verbose_name=' damping coefficient (kg/s)', default=0.0)
w = models.FloatField(
verbose_name=' frequency (1/s)', default=2*pi)
T = models.FloatField(
verbose_name=' time interval (s)', default=18)
class InputForm(ModelForm):
class Meta:
model = Input
Note here that we can provide a more explanatory name than just
the variable name, e.g., ' amplitude (m)'
for A
. However,
Django will always capitalize these descriptions, so if one really
needs lower case names (e.g., to be compatible with a mathematical notation
or when just listing the unit),
one must start the text with a space, as we have demonstrated above.
We also provide a default
value such that all fields have a value when the user sees
the page.
The views.py
file looks as follows:
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponse
from models import InputForm
from compute import compute
import os
def index(request):
os.chdir(os.path.dirname(__file__))
result = None
if request.method == 'POST':
form = InputForm(request.POST)
if form.is_valid():
form2 = form.save(commit=False)
result = compute(form2.A, form2.b, form2.w, form2.T)
result = result.replace('static/', '')
else:
form = InputForm()
return render_to_response('vib1.html',
{'form': form,
'result': result,
}, context_instance=RequestContext(request))
Some remarks are necessary:
os.chdir
to the current working directory is necessary
as Django may be left back in another working directory if you have
tested other apps.form2
object from form.save
is the object we extract
data from and send to compute
, but the original form
object is needed when making the HTML page through the template.static
. The specifications of the
URL applies tools to find this static
directory and then
the static
prefix in the result
filename must be removed.
<form method=post action="">{% csrf_token %}
<table>
{% for field in form %}
<tr>
<td>{{ field.name }}</td>
<td>{{ field }}</td>
<td>{{ field.label }}</td>
<td>{{ field.errors }}</td>
<td></td>
</tr>
{% endfor %}
</table>
<p><input type=submit value=Compute></form></p>
<p>
{% if result != None %}
{% load static %}
<img src="{% get_static_prefix %}{{ result }}" width=500>
{% endif %}
</p>
The tricky part is the syntax for displaying static content, such as
the plot file made in the compute
function.
Django has a series of methods available for user-provided validation
of form data. These are exemplified in the app vib2
, which
is an extension of the vib1
app with additional code. (This other
app needs of course registrations in settings.py
and urls.py
, similar
to what we did for the vib1
app.)
Making sure that \( A>0 \) is easiest done with a built-in Django validator for minimum value checking:
class Input(models.Model):
A = models.FloatField(
verbose_name=' amplitude (m)', default=1.0,
validators=[MinValueValidator(0)])
We can write our own validators, which are functions taking the value
is the only argument and raising a ValidationError
exception if the
value is wrong. Checking that a value is inside an interval can first
be implemented by
def check_interval(value, min_value=None, max_value=None):
"""Validate that a value is inside an interval."""
failure = False
if min_value is not None:
if value < min_value:
failure = True
if max_value is not None:
if value > max_value:
failure = True
if failure:
raise ValidationError(
'value=%s not in [%s, %s]' %
(value,
'-infty' if min_value is None else str(min_value),
'infty' if max_value is None else str(max_value)))
However, this function takes more than the value as argument. We therefore
need to wrap it by a function with value
as the only argument. The following
utility returns such a
function (see the section Custom validation for more
explanation):
import functools
def interval(min_value=None, max_value=None):
"""Django-compatible interface to check_interval."""
return functools.partial(
check_interval, min_value=min_value, max_value=max_value)
Now, interval(0, 1)
returns a function that takes value
as its
only argument and checks if it is inside \( [0,1] \).
Such a function can be inserted in the validators
list in
the field constructor, here to tell that \( b \) must be in \( [0,\infty) \):
class Input(models.Model):
...
b = models.FloatField(
verbose_name=' damping coefficient (kg/s)', default=0.0,
validators=[interval(0,None)])
A final example on custom validation is to avoid plotting more
than 30 periods of the oscillating function \( u \). This translates
to checking that \( T \) is geater
than 30 periods, i.e., \( T>30\cdot 2\pi/w \). The task is done in
the InputForm
class, where any method clean_name
can do
validation and adjustment of the field name name
. The code for
a clean_T
method goes as follows:
class InputForm(ModelForm):
class Meta:
model = Input
def clean_T(self):
T = self.cleaned_data['T']
w = self.cleaned_data['w']
period = 2*pi/w
if T > 30*period:
num_periods = int(round(T/period))
raise ValidationError(
'Cannot plot as much as %d periods! T < %.2f' %
(num_periods, 30*period))
return T
We refer to the vast Django documentation for many other ways of
validating forms. The reader is encouraged to run the vib2
application and test out the validations we have implemented.
One will occasionally have full control of the layout of the individual
elements in a web form. These are typeset inside input
tags in
HTML. Django associates the term widget with an HTML form field. To set
the size (width) of the field and other properties, one must in
Django specify a widgets
dictionary in the form class. The key
is the name of the parameter in the model class, while the value
is a widget class name. Standard input fields for numbers and
text apply the TextInput
widget. An example
on setting the size of the T
field to a width of 10 characters goes
like
from django.forms import TextInput
class InputForm(ModelForm):
class Meta:
model = Input
widgets = {
'T': TextInput(attrs={'size': 10}),
}
At the time of this writing, Django does not yet support the many
additional HTML5 input fields. Nevertheless, the parampool
package
gives access to HTML5 widgets in a Django context.
We recommend to use parampool
to automatically generate the necessary
Django files, and
then one can view the form class in the models.py
file for how
HTML5 widgets can be used in the definition of the widgets
dictionary.