Handling multiple input variables in Django

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.

Programming the Django application

Adding the app to a project

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
  1. add relative2absolute_path('../../apps/django_apps/vib1/templates'), to the TEMPLATE_DIRS tuple in settings.py,
  2. add vib1 to the INSTALLED_APPS tuple, and
  3. add url(r'^vib1/', 'django_apps.vib1.views.index') to the patterns call in urls.py.
These steps ensure that Django can find our application as a module/package, that Django can find our templates associated with this application, and that the URL address applies the name vib1 to reach the application.

The compute part

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.

The model

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 view

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:
  1. Doing an 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.
  2. The 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.
  3. Images, media files, style sheets, javascript files, etc. must reside in a subdirectory 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.
The template for rendering the page is listed next.

<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.

Custom validation

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.

Customizing widgets

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.