======================================================== 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. .. sectnum:: :depth: 4 .. contents:: :depth: 4 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`_. .. _`A Quixote Application Skeleton`: http://www.rexx.com/~dkuhlman/quixote_skeleton.html 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. 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. Install the Skeleton Files ========================== Follow these steps: - Unroll the skeleton files. Something like the following should do it:: unzip quixote_iptables_firewall_config.zip - Rename the directory containing the sample files. In what follows, I'm going to explain things as if you had renamed it to "myapp", which, on a UNIX/Linux system, you would do as follows:: mv skeleton myapp - Add the directory in which you unrolled the sample files to your PYTHONPATH. For example, if, when you unrolled the sample files, you created /aaaa/bbbb/skeleton, then you will need something like the following (or the equivalent):: export PYTHONPATH=/aaaa/bbbb:$PYTHONPATH - Modify the start-up scripts in the ``scripts`` sub-directory -- You will need to change the lines containing "iptables_firewall_config" to reflect your renaming of the directory containing the application. - Test your application -- You can run either of the following in the ``scripts`` sub-directory:: python server-scgi.py -p 3001 where: - 3001 is the port that Apache is forwarding request to for the SCGI server for Quixote. Or:: python server-medusa.py -p 8081 Adapt the Application ===================== Here is a sequence of steps that you might follow. 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 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. 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') '
\n'
                content
                '
\n' '
\n' '
\n'
                configuration
                '
\n' footer() else: 'Configuration file
'
                configuration                                   # [4]
                '
' 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. .. _`A Quixote Application Skeleton`: http://www.rexx.com/~dkuhlman/quixote_skeleton.html Extensions and Enhancements =========================== 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: - `Using the Medusa Web Server`_ -- See, in particular, the section on `Creating and Reusing Connections, Proxies, Etc`_. .. _`Using the Medusa Web Server`: http://www.rexx.com/~dkuhlman/quixote_usingmedusa.html .. _`Creating and Reusing Connections, Proxies, Etc`: http://www.rexx.com/~dkuhlman/quixote_usingmedusa.html#creating-and-reusing-connections-proxies-etc - `A Quixote Application: Getting Started`_ -- See, in particular, the section on `Database connections`_. .. _`A Quixote Application: Getting Started`: http://www.rexx.com/~dkuhlman/quixote_appgetgo.html .. _`Database connections`: http://www.rexx.com/~dkuhlman/quixote_appgetgo.html#database-connections 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. 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. .. _ConfigParser: http://www.python.org/doc/current/lib/module-ConfigParser.html 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() 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. .. _`generateDS.py`: http://www.rexx.com/~dkuhlman/generateDS.html See Also ======== `http://www.mems-exchange.org/software/quixote/`_: The Quixote support Web site. .. _`http://www.mems-exchange.org/software/quixote/`: http://www.mems-exchange.org/software/quixote/ `Support for Quixote Etc`_: A variety of helpful documents on Quixote. .. _`Support for Quixote Etc`: http://www.rexx.com/~dkuhlman/quixote_index.html `A Quixote Application Skeleton`_: Example and skeleton files for building a Quixote application. .. _`A Quixote Application Skeleton`: http://www.rexx.com/~dkuhlman/quixote_skeleton.html `Arno's IPTables-firewall`_: Arno's IPTABLES firewall script .. _`Arno's IPTables-firewall`: http://rocky.molphys.leidenuniv.nl/.