A Quixote Application for Generating Configuration Files

Author: Dave Kuhlman
Address:
dkuhlman@rexx.com
http://www.rexx.com/~dkuhlman
Revision: 1.0a
Date: Feb. 6, 2004
Copyright: Copyright (c) 2004 Dave Kuhlman. This documentation is covered by The MIT License: http://www.opensource.org/licenses/mit-license.

Abstract

This document provides and explains a skeleton for Quixote applications that generate configuration/text files from user parameters and a template file.

Contents

1���Introduction

This small Quixote application is intended as an example or skeleton for applications that (1) request a few parameters from a user and then (2) generate a configuration (or other text) file from a template file.

The application source files, including start-up scripts for both Apache/SCGI and Medusa servers is here: http://www.rexx.com/~dkuhlman/quixote_iptables_firewall_config.zip.

The application actually generates a configuration file for Arno's IPTables-firewall. You can learn more about that at: http://rocky.molphys.leidenuniv.nl/.

You will find additional help for installing and running a Quixote application from a skeleton in the following document: A Quixote Application Skeleton.

1.1���Motivation and rational

Look at this example as a special case of more general class of applications, which is any application that needs to get some information from the client/user, then generate content (from a template) to be returned to the user.

2���Explanation

The organization of the files in the skeleton is as follows:

myapp/scripts/
Scripts for starting, restarting, and stopping the application.
myapp/ui/
The user interface.
myapp/ui/__init__.py
Parses and handles the URL. Maps requests to methods in the user interface handler.
myapp/ui/configui.ptl
The user interface handler. Implements the user interface.
myapp/scripts/iptables-firewall.conf.template
A sample template file.

3���Install the Skeleton Files

Follow these steps:

4���Adapt the Application

Here is a sequence of steps that you might follow.

4.1���Create a template file

You will need to replace the template file scripts/iptables-firewall.conf.template. Also, modify the line in ui/configui/ptl that refers to it.

If you look in scripts/iptables-firewall.conf.template, you will see expressions of the form "%(xxx)s" and %(yyy)d, where "xxx" and "yyy" are keys in a dictionary that you provide. So, in general, you provide a dictionary containing the keys in your template file, then do something like the following:

templateFile = file(str('mytemplatefile'), str('r'))
template = templateFile.read()
templateFile.close()
configuration = template % configDict

4.2���Modify the form

In ui/configui.ptl, you will want to modify the function that creates the form used to collect values from the end user. Note the following:

  • This form is created using the form2 package from the Quixote distribution. (I'm using version Quixote-0.7a3.)
  • The form (or the method that creates it) is used in two steps in the process. (1) It is used to render the form so that it can be displayed to the user. (2) It is used to create a form object that can be used to extract values from the form in the request object.

You will need to modify method create_config_form so that it creates the form you need. You should add and subtract widgets, rename them, etc.

4.3���Modify the use of the values

In this step we assume that the form (or request, whatever) holds the values from the user. This is currently done in method do_finish, which does the following:

  • Extract the values from the form/request and put them in a dictionary.

  • Clean-up or regularize the values. For example, where None is returned because the value was not entered, we might want to replace that with an empty string.

  • Read in the template file.

  • Fill the values from the dictionary into the template. For example:

    content = template % configDict
    
  • Return the content to the user.

So, the most significant changes that you will need to make is to change the variables that are extracted from the form. Here is the example from the distribution:

class ConfigUI:
    o
    o
    o
    def do_finish [html] (self, request):
        self.request = request
        self.configForm = self.create_config_form()
        # Create the dictionary of configuration values.
        self.configDict = {}
        self.update('ext_if_dhcp_ip')                       # [1]
        self.update('nat')
        self.update('ext_if')
        self.update('modem_if')
        self.update('modem_if_ip')
        self.update('modem_ip')
        self.update('int_if')
        self.update('internal_net')
        # Clean up the configuration dictionary.  For example, some missing
        #   values should be blank strings instead of None.
        if not self.configDict['modem_if_ip']:              # [2]
            self.configDict['modem_if_ip'] = ''
        if not self.configDict['modem_ip']:
            self.configDict['modem_ip'] = ''
        contentList = ['[config]']
        self.add_content(self.configDict, contentList)
        content = '\n'.join(contentList)
        # Read the config template file.                    # [3]
        templateFile = file(str('iptables-firewall.conf.template'), str('r'))
        template = templateFile.read()
        templateFile.close()
        # Plug the configuration values into the template.
        configuration = template % self.configDict
        # Did the user ask for debug/configuration information.
        if self.configForm['debug']:
            header('Finished')
            '<pre>\n'
            content
            '</pre>\n'
            '<hr/>\n'
            '<pre>\n'
            configuration
            '</pre>\n'
            footer()
        else:
            '<html><head><title>Configuration file</title></head><body><pre>'
            configuration                                   # [4]
            '</pre></body></html>'

Explanation:

  1. Replace these with lines that capture your own variables. Note that these variable names are also the ones referenced in your template file.
  2. Clean-up values and if needed, create new keys in your dictionary. You may also need to compute or look-up some values based on the entries made by the user. If you need to access a relational database, you may want to look at A Quixote Application Skeleton to learn how to set that up.
  3. Read in your own template file. Then fill the values from your dictionary into it.
  4. Return the content as your HTTP response.

5���Extensions and Enhancements

5.1���Database access

Python makes access to a relational database easy to do. And, with Quixote, it is easy to reuse database connections for satisfying more than one request. You can see the following document for suggestions on how to access a relational database from a Quixote request handler:

5.2���State and sessions

I'd like to avoid the use of sessions.

The question here is: How can we make a Web application that continues over several requests and responses (i.e. over several Web pages) more REST-full than it would be with the use of state and sessions on the server?

There are a variety of answers to this question. I'll give several of them.

5.2.1���For a Web browser client

The technique we'll use here is to pass state and session information in a hidden field in the form. One way to do this is to write it in ".ini" format. You can use the ConfigParser module in the Python standard library to parse this content.

And, something like the following could (1) pass the state information in a response and (2) retrieve the state information from the next request:

def step_2 [html] (self, request):
    myform = form2.Form()
    myhidden = myform.add(form2.HiddenWidget, 'hidden_state')   # [1]
    o
    o
    o
    state_info = '[general]\n'                                  # [2]
        'option1=value1\n'
        'option2=value2\n'
        'option3=value3\n'
    myform['hidden_state'] = state_info                         # [3]
    o
    o
    o
    header()
    myform.render()                                             # [4]
    footer()

def step_3 [html] (self, request):
    myform = form2.Form()
    myhidden = myform.add(form2.HiddenWidget, 'hidden_state')
    o
    o
    o
    state_info = myform['hidden_state']                         # [5]
    state_file = StringIO.StringIO()
    state_file.write(state_info)                                # [6]
    parser = configparser.ConfigParser()
    parser.readfp(state_file)                                   # [7]
    option2 = parser.get('general', 'option2')                  # [8]

Explanation:

  1. Create a hidden field.
  2. Format the state information in the .ini style.
  3. Store the formatted state information in the hidden field.
  4. Render the form, along with the hidden field.
  5. Retrieve the formatted state information from the hidden field.
  6. Write the state information to a hidden file. We do this because the ConfigParser module only reads from files and file objects. See, however, the subclass of ConfigParser, below.
  7. Parse the formatted state information.
  8. Obtain the value of a property in the state information.

It is easy to implement a subclass of ConfigParser that gets its input from a string. Here is one:

class StringConfigParser(ConfigParser.ConfigParser):
    def read_string(self, instring):
        configFile = StringIO.StringIO()
        configFile.write(instring)
        configFile.seek(0)
        self.readfp(configFile)
        configFile.close()

5.2.2���For a programmed client

One solution is to format the state information as XML, pack it into the body of the response, and to require that the client return that XML content in the body of the next request.

There are several additional things that you will want to know in doing this:

  1. When generating the response:

    • Render the XML content as you would HTML content. A PTL function or method will return this content automatically.

    • Set the Content-type header as follows:

      response = request.response
      response.set_content_type('text/xml')
      
  2. When processing the (next) request):

    • Read the XML content from the body of the request as follows:

      from xml.dom import minidom
      o
      o
      o
      def sample [html] (self, request):
          content = request.stdin.read()
          doc = minidom.parseString(content)
          o
          o
          o
      
    • Also, you may want to consider using generateDS.py to build parsers for your XML content.

6���See Also

http://www.mems-exchange.org/software/quixote/: The Quixote support Web site.

Support for Quixote Etc: A variety of helpful documents on Quixote.

A Quixote Application Skeleton: Example and skeleton files for building a Quixote application.

Arno's IPTables-firewall: Arno's IPTABLES firewall script