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 :numref:`figuseforms1`. .. _figuseforms1: .. figure:: ./figuseforms1.png :align: center Asking for a word. The confirmation of the submitted word is shown in :numref:`figuseforms2`. .. _figuseforms2: .. figure:: ./figuseforms2.png :align: center 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 %s """ % 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("""Enter a word

""") FORM = cgi.FieldStorage() if "word" in FORM: print("""

Your word is %s

""" % FORM["word"].value) print("\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 :numref:`figdefaults`. The default value in this example is ``2``. .. _figdefaults: .. figure:: ./figdefaults.png :align: center A form with a default value in a text element. The code snippet which prints the form with the default value follows below. :: print """

Give dimension:

""" 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 :numref:`figwebdet` we see the form to ask for a matrix. .. _figwebdet: .. figure:: ./figwebdet.png :align: center Asking for a matrix. The computation of the determinant of the matrix entered in the form is illustrated in :numref:`figcomputedet`. .. _figcomputedet: .. figure:: ./figcomputedet.png :align: center 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("\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("""
""") print("""The dimension: """ % 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("

Enter matrix :

") print("") for row in range(0, dim): print("") for col in range(0, dim): print("""""" % (row, col)) print("
") print("""""") print("
") 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 :numref:`figwebpairs1`. .. _figwebpairs1: .. figure:: ./figwebpairs1.png :align: center 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 :numref:`figwebpairs2a`. .. _figwebpairs2a: .. figure:: ./figwebpairs2a.png :align: center Reading preferences for 1 out of 4. The preferences input for 2 are shown in :numref:`figwebpairs2b`. .. _figwebpairs2b: .. figure:: ./figwebpairs2b.png :align: center Reading preferences for 2 out of 4. The preferences input for 3 are shown in :numref:`figwebpairs2c`. .. _figwebpairs2c: .. figure:: ./figwebpairs2c.png :align: center Reading preferences for 3 out of 4. The preferences input for 4 are shown in :numref:`figwebpairs2d`. .. _figwebpairs2d: .. figure:: ./figwebpairs2d.png :align: center Reading preferences for 4 out of 4. The result is displayed in :numref:`figwebpairs2end`. .. _figwebpairs2end: .. figure:: ./figwebpairs2end.png :align: center 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("

reading preferences for %d out of %d

" \ % (k, 2*n)) print("""

enter your name:

enter your preferences:

""") print("
    ") for i in range(0, 2*n-1): print("""
  1. """ % i) print("""
""") 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.