Dynamic Web Pages

Instead of placing the form in an HTML file and the script in a .py file, we can have the Python scripts printing the forms, recursively as needed.

Forms in Python Scripts

To run a simple CGI server, a simple Python script suffices. The documentation string of myserver.py is

"""
Launch this code in the directory where
the CGI script is that you want to run.
Point the browser to localhost:8000
(or whatever the value of PORTNUMBER is),
followed by / and the name of the script.
"""

To run the CGI scripts in this lecture, one does not really need Apache. The portnumber defined in the script must be open.

Tthe script myserver.py is below.

PORTNUMBER = 8000
import http.server
import cgitb; cgitb.enable() # CGI error report

server = http.server.HTTPServer
handler = http.server.CGIHTTPRequestHandler
server_address = ("", PORTNUMBER)
handler.cgi_directories = ["/"]

httpd = server(server_address, handler)
try:
    print('welcome to our cgi server')
    print('press ctrl c to shut down the server')
    httpd.serve_forever()
except:
    print('ctrl c pressed, shutting down server')
    httpd.socket.close()

Serving Python scripts suffices, as we can have scripts print HTML code. What we did before:

  1. The form element in an HTML page triggers an action, for example: submit user name and password.
  2. The action triggered by the submit button is defined by a Python script.

The advantage is that the separate HTML separates the layout from the actions. The main disadvantage is the possibility of error by a mismatch between the names of the values in the form and the names used the retrieve the values in the script.

Consider for example a form which prompts the user for a word, illustrated in Fig. 59.

_images/figuseforms1.png

Fig. 59 Asking for a word.

The confirmation of the submitted word is shown in Fig. 60.

_images/figuseforms2.png

Fig. 60 Confirming the word.

The script use_forms.py starts with the printing of the header for the HTML page.

#!/usr/bin/python
import cgi

def print_header(title):
    """
    writes title and header of page
    """
    print("""Content-type: text/html

<html>
<head>
<title>%s</title>
</head>
<body>""" % title)

print_header("using forms")

Then the script use_forms.py continues with the printing of the form to prompt the user for a word:

print("""<b>Enter a word</b>
    <form method = "post" method = "use_forms.py">
    <p>
      <input type = "text" name = "word">
      <input type = "submit">
    </p>
    </form>""")

FORM = cgi.FieldStorage()
if "word" in FORM:
    print("""<p>Your word is <em>%s</em></p>
    """ % FORM["word"].value)

print("</body></html>\n")

For our next example, we introduce default values for input elements of type text. When the form is then printed, a default value appears in the text element, as shown in Fig. 61. The default value in this example is 2.

_images/figdefaults.png

Fig. 61 A form with a default value in a text element.

The code snippet which prints the form with the default value follows below.

print """<form method = "post"
               action = "web_det.py">
<p>
  Give dimension:
  <input type = "text" name = "dim"
         size = 4 value = 2>
  <input type = "submit">
</p>
</form>"""

A Web Interface for a Determinant

In our next example we make our interactive forms dynamic, in the sense that the content of the form is determined when the application is running.

In Fig. 62 we see the form to ask for a matrix.

_images/figwebdet.png

Fig. 62 Asking for a matrix.

The computation of the determinant of the matrix entered in the form is illustrated in Fig. 63.

_images/figcomputedet.png

Fig. 63 Computing the determinant.

The script is a sequence of two forms:

  1. We ask for the dimension of the matrix. The dimension is then used to generate a table of input elements to give the entries of the matrix.
  2. We ask for the entries of the matrix. The second form is processed by the same script. The action of this form brings up a new page with the matrix and the determinant.

The dimension is passed from one form to the other through an input element. The main function of web_det.py is below.

def main():
    """
    web interface to compute a determinant
    """
    print_header("compute determinant")
    ask_dimension()
    form = cgi.FieldStorage()
    if "dim" in form:
        dim = int(form["dim"].value)
        ask_matrix(dim)
    print("</body></html>\n")

The main() calls the functions print_header(), ask_dimension(), and ask_mastrix().

The function ask_matrix() is defined below.

def ask_matrix(dim):
    """
    form to enter a matrix of dimension dim
    """
    print("""<form method = "post"
                   action = "compute_det.py">""")
    print("""The dimension:
       <input type = "text" name = "dim"
              size = 4 value = %d>""" % dim)

Notice how the value of the dimension is set in the input element dim of the form. Then the ask_matrix() function continues with the printing of the text elements for all the entries in the matrix.

# printing a table of text elements
   print("<p>Enter matrix :</p>")
   print("<table>")
   for row in range(0, dim):
       print("<tr>")
       for col in range(0, dim):
           print("""<td><input type = "text"
           name = %d,%d size = 4></td>""" % (row, col))
   print("</table>")
   print("""<input type = "submit">""")
   print("</form>")

The names of the entries are strings row, col. To compute the determinant, we import the function det from the numpy.linalg package. The script compute_det.py begins with

#!/usr/bin/python
import cgi

from numpy import zeros
from numpy.linalg import det

The main function in compute_det.py is

def main():
    """
    web interface to compute a determinant
    """
    print("Content-type: text/plain\n")
    form = cgi.FieldStorage()
    if "dim" in form:
        dim = int(form["dim"].value)
        detmat = determinant(form, dim)
        print("The determinant :", detmat)

The determinant() function is defined below.

def determinant(form, dim):
    """
    returns the determinant of the matrix
    available in the form
    """
    mat = zeros((dim, dim), float)
    for row in range(dim):
        for col in range(dim):
            info = "%d,%d" % (row, col)
            if info in form:
                mat[row, col] = float(form[info].value)
    print("The matrix is")
    print(mat)
    return det(mat)

An Application

As an application of a web interface, consider the computation of stable pairs.

Suppose we have six programmers – Alice, Brian, Cindy, David, Eve, and Fred – and we want to form pairs of programmers.

Every programmer provides list of preferences, for example: the preference list for Alice is Eve, David, Cindy, Fred, Brian, listed in increasing order

Pairs (Brian, Eve) and (Cindy, David) are not stable if the pairing (Brian, Cindy) and (Eve, David) is preferable to all four: if Brian likes Cindy better than Eve, Cindy prefers Brian over David, David likes Eve better than Cindy, and Eve prefers David over Brian.

Likewise, if the pairing (Brian, David) and (Cindy, Eve) would be preferable to all four, then the pairs (Brian, Eve) and (Cindy, David) should then also not be generated.

Running web_pairs1.py in a browser shows the picture in Fig. 64.

_images/figwebpairs1.png

Fig. 64 The start of the web interface for the the stable pairs problem.

The submit launches the second script web_pairs2.py.

Each time the user submits, a new page comes up, asking for the preferences of the next person. Notice that the same script collects all input.

The preferences input for 1 are shown in Fig. 65.

_images/figwebpairs2a.png

Fig. 65 Reading preferences for 1 out of 4.

The preferences input for 2 are shown in Fig. 66.

_images/figwebpairs2b.png

Fig. 66 Reading preferences for 2 out of 4.

The preferences input for 3 are shown in Fig. 67.

_images/figwebpairs2c.png

Fig. 67 Reading preferences for 3 out of 4.

The preferences input for 4 are shown in Fig. 68.

_images/figwebpairs2d.png

Fig. 68 Reading preferences for 4 out of 4.

The result is displayed in Fig. 69.

_images/figwebpairs2end.png

Fig. 69 The computation of the stable pairs.

The recursive use of forms in illustrated in the script below.

def ask_preferences(n, k):
    """
    Form to enter the preferences for person k.
    """
    print("<p> reading preferences for %d out of %d </p>" \
        % (k, 2*n))
    print("""<form method = "post"
                   action = "web_pairs2.py">
    <p>
      enter your name:
      <input type = "text" name = "name" size = 20>
    </p>
    <p> enter your preferences: </p>""")
    print("<OL>")
    for i in range(0, 2*n-1):
        print(""" <LI> <input type = "text" name = %d
                        size = 20> </LI> """ % i)
    print("""</OL><input type = "submit"> </form>""")

The skeleton of the main program is below:

def main():

    if "dim" in form:
        ask_preferences(dim, 1)
    else:
        file = open('/tmp/preferences', 'r')
        if person < 2*dim:
            get_preferences(form, dim, person)
            ask_preferences(dim, person+1)
        else:
            get_preferences(form, dim, person)
            file = open('/tmp/preferences', 'r')

Exercises

  1. Modify the use_forms.py so that it places by default one word in the input element, with all vowels removed. Ask the user to supply the missing vowels. In reaction to the submit, compare the word of the user with the original word and give feedback.
  2. Write a Python script web_sum.py to sum n numbers. Like in web_det.py, first the user is asked to supply n and then n input elements have to be filled out before another script compute_sum.py can compute the sum.
  3. The current version of web_det.py takes undefined entries to equal zero. Change it so the user is asked to return and provide the missing entries.
  4. Make a recursive form. The input element asks the user for a number. With each submit, the number is doubled.