[Python-modules-commits] r532 - in /packages/quixote1: ./ branches/
branches/upstream/
branches/upstream/current/ branches/upstream/current/demo/
branches/upstream/current/doc/ branches/upstream/current/form/
branches/upstream/current/form2/ branches/upstream/current/server/
branches/upstream/current/src/ branches/upstream/current/test/ tags/
santiago at users.alioth.debian.org
santiago at users.alioth.debian.org
Mon May 8 19:18:13 UTC 2006
Author: santiago
Date: Mon May 8 19:16:16 2006
New Revision: 532
URL: http://svn.debian.org/wsvn/python-modules/?sc=1&rev=532
Log:
[svn-inject] Installing original source of quixote1
Added:
packages/quixote1/
packages/quixote1/branches/
packages/quixote1/branches/upstream/
packages/quixote1/branches/upstream/current/
packages/quixote1/branches/upstream/current/ACKS
packages/quixote1/branches/upstream/current/CHANGES
packages/quixote1/branches/upstream/current/LICENSE
packages/quixote1/branches/upstream/current/MANIFEST
packages/quixote1/branches/upstream/current/MANIFEST.in
packages/quixote1/branches/upstream/current/PKG-INFO
packages/quixote1/branches/upstream/current/README
packages/quixote1/branches/upstream/current/TODO
packages/quixote1/branches/upstream/current/__init__.py
packages/quixote1/branches/upstream/current/_py_htmltext.py
packages/quixote1/branches/upstream/current/config.py
packages/quixote1/branches/upstream/current/demo/
packages/quixote1/branches/upstream/current/demo/__init__.py
packages/quixote1/branches/upstream/current/demo/demo.cgi (with props)
packages/quixote1/branches/upstream/current/demo/demo.conf
packages/quixote1/branches/upstream/current/demo/demo_scgi.py (with props)
packages/quixote1/branches/upstream/current/demo/demo_scgi.sh (with props)
packages/quixote1/branches/upstream/current/demo/forms.ptl
packages/quixote1/branches/upstream/current/demo/integer_ui.py
packages/quixote1/branches/upstream/current/demo/pages.ptl
packages/quixote1/branches/upstream/current/demo/run_cgi.py
packages/quixote1/branches/upstream/current/demo/session.ptl
packages/quixote1/branches/upstream/current/demo/session_demo.cgi (with props)
packages/quixote1/branches/upstream/current/demo/upload.cgi (with props)
packages/quixote1/branches/upstream/current/demo/widgets.ptl
packages/quixote1/branches/upstream/current/doc/
packages/quixote1/branches/upstream/current/doc/INSTALL.html
packages/quixote1/branches/upstream/current/doc/INSTALL.txt
packages/quixote1/branches/upstream/current/doc/Makefile
packages/quixote1/branches/upstream/current/doc/PTL.html
packages/quixote1/branches/upstream/current/doc/PTL.txt
packages/quixote1/branches/upstream/current/doc/ZPL.txt
packages/quixote1/branches/upstream/current/doc/default.css
packages/quixote1/branches/upstream/current/doc/demo.html
packages/quixote1/branches/upstream/current/doc/demo.txt
packages/quixote1/branches/upstream/current/doc/form2conversion.html
packages/quixote1/branches/upstream/current/doc/form2conversion.txt
packages/quixote1/branches/upstream/current/doc/multi-threaded.html
packages/quixote1/branches/upstream/current/doc/multi-threaded.txt
packages/quixote1/branches/upstream/current/doc/programming.html
packages/quixote1/branches/upstream/current/doc/programming.txt
packages/quixote1/branches/upstream/current/doc/session-mgmt.html
packages/quixote1/branches/upstream/current/doc/session-mgmt.txt
packages/quixote1/branches/upstream/current/doc/static-files.html
packages/quixote1/branches/upstream/current/doc/static-files.txt
packages/quixote1/branches/upstream/current/doc/upgrading.html
packages/quixote1/branches/upstream/current/doc/upgrading.txt
packages/quixote1/branches/upstream/current/doc/upload.html
packages/quixote1/branches/upstream/current/doc/upload.txt
packages/quixote1/branches/upstream/current/doc/web-server.html
packages/quixote1/branches/upstream/current/doc/web-server.txt
packages/quixote1/branches/upstream/current/doc/web-services.html
packages/quixote1/branches/upstream/current/doc/web-services.txt
packages/quixote1/branches/upstream/current/doc/widgets.html
packages/quixote1/branches/upstream/current/doc/widgets.txt
packages/quixote1/branches/upstream/current/errors.py
packages/quixote1/branches/upstream/current/fcgi.py
packages/quixote1/branches/upstream/current/form/
packages/quixote1/branches/upstream/current/form/__init__.py
packages/quixote1/branches/upstream/current/form/form.py
packages/quixote1/branches/upstream/current/form/widget.py
packages/quixote1/branches/upstream/current/form2/
packages/quixote1/branches/upstream/current/form2/__init__.py
packages/quixote1/branches/upstream/current/form2/compatibility.py
packages/quixote1/branches/upstream/current/form2/css.py
packages/quixote1/branches/upstream/current/form2/form.py
packages/quixote1/branches/upstream/current/form2/widget.py
packages/quixote1/branches/upstream/current/html.py
packages/quixote1/branches/upstream/current/http_request.py
packages/quixote1/branches/upstream/current/http_response.py
packages/quixote1/branches/upstream/current/mod_python_handler.py
packages/quixote1/branches/upstream/current/ptl_compile.py
packages/quixote1/branches/upstream/current/ptl_import.py
packages/quixote1/branches/upstream/current/ptlc_dump.py
packages/quixote1/branches/upstream/current/publish.py
packages/quixote1/branches/upstream/current/qx_distutils.py
packages/quixote1/branches/upstream/current/sendmail.py
packages/quixote1/branches/upstream/current/server/
packages/quixote1/branches/upstream/current/server/__init__.py
packages/quixote1/branches/upstream/current/server/medusa_http.py
packages/quixote1/branches/upstream/current/server/twisted_http.py
packages/quixote1/branches/upstream/current/session.py
packages/quixote1/branches/upstream/current/setup.py
packages/quixote1/branches/upstream/current/src/
packages/quixote1/branches/upstream/current/src/Makefile
packages/quixote1/branches/upstream/current/src/_c_htmltext.c
packages/quixote1/branches/upstream/current/src/cimport.c
packages/quixote1/branches/upstream/current/src/setup.py
packages/quixote1/branches/upstream/current/test/
packages/quixote1/branches/upstream/current/test/__init__.py
packages/quixote1/branches/upstream/current/test/ua_test.py
packages/quixote1/branches/upstream/current/test/utest_html.py (with props)
packages/quixote1/branches/upstream/current/upload.py
packages/quixote1/branches/upstream/current/util.py
packages/quixote1/tags/
Added: packages/quixote1/branches/upstream/current/ACKS
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/ACKS?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/ACKS (added)
+++ packages/quixote1/branches/upstream/current/ACKS Mon May 8 19:16:16 2006
@@ -1,0 +1,44 @@
+Acknowledgements
+================
+
+The Quixote developer team would like to thank everybody who
+contributed in any way, with code, hints, bug reports, ideas, moral
+support, endorsement, or even complaints. Listed in alphabetical
+order:
+
+David Ascher
+Anton Benard
+Titus Brown
+Oleg Broytmann
+David M. Cooke
+Jonathan Corbet
+Herman Cuppens
+Toby Dickenson
+Ray Drew
+Jim Dukarm
+Quinn Dunkan
+Robin Dunn (original author of fcgi.py)
+Jon Dyte
+David Edwards
+Graham Fawcett
+Jim Fulton (original author of the *Request and *Response classes)
+David Goodger
+Neal M. Holtz
+Kaweh Kazemi
+Shahms E. King
+A.M. Kuchling <amk at amk.ca>
+Erno Kuusela
+Nicola Larosa
+Hamish Lawson
+Patrick K. O'Brien
+Brendan T O'Connor
+Ed Overly
+Paul Richardson
+Jeff Rush
+Neil Schemenauer <nascheme at mems-exchange.org>
+Jason Sibre
+Gregory P. Smith
+Mikhail Sobolev
+Johann Visagie
+Greg Ward <gward at mems-exchange.org>
+The whole gang at the Zope Corporation
Added: packages/quixote1/branches/upstream/current/CHANGES
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/CHANGES?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/CHANGES (added)
+++ packages/quixote1/branches/upstream/current/CHANGES Mon May 8 19:16:16 2006
@@ -1,0 +1,902 @@
+1.2 (2004-10-06) r25277:
+
+ * Fix Medusa server bug introduced in 1.1. HTTP headers must have
+ "HTTP_" prepended to them.
+
+
+1.1 (2004-10-05) r25259:
+
+ * Fix example Apache directive for SCGI demo.
+
+ * On Windows, put both stdin and stdout in binary mode.
+
+ * Add 'buffered' flag to HTTPResponse (defaulting to True).
+
+ * Support for the Python 2.4 'compiler' package.
+
+ * Add HTTPRequest.get_scheme() method.
+
+ * Use os.urandom() if it is available. Move randbytes() function
+ to quixote.util module.
+
+ * Convert tests to Sancho's utest framework.
+
+ * Add 'index_filenames' keyword option to StaticDirectory.
+
+ Medusa server:
+
+ * Unquote the PATH_INFO environment variable.
+
+ * Simplify propagation of HTTP headers.
+
+ Form2 library:
+
+ * Don't parse empty POSTs.
+
+ * Fix a subtle bug in the multiple select widget. If nothing
+ is selected then the value should be None, not [None].
+
+ * Add Widget.set_title().
+
+
+1.0 (2004-07-01) r24581:
+
+ * No changes from 1.0c1.
+
+
+1.0c1 (2004-06-15) r24468:
+
+ * Fix some bugs in form2 library and improve the default look.
+
+ * Add a 'content_type' attribute to Upload instances.
+
+
+1.0b2 (2004-05-20) r24301:
+
+ * Pass along more request headers when using twisted_http.py.
+
+ * Add filter_output() hook (as suggested by Thomas Guettler).
+
+ * Use plain text for the content of redirections. It's extremely
+ unlikely that they will be seen anyhow. Use a modern DOCTYPE
+ declaration for other pages that Quixote itself generates.
+
+ * Fix code in upload.py that handles filename collisions.
+
+ * Use a twisted producer for twisted_server.py (allows Stream
+ responses to be efficient). Use twisted reactor instead of
+ Application. Thanks to Jason Sibre.
+
+ Form2 library changes:
+
+ * Change how form2 widgets render themselves. The new approach
+ makes it easier to build composite widgets and also provides
+ more freedom to control the layout using CSS. Unfortunately,
+ the forms will not look nice unless some CSS rules are provided.
+
+ * Add recursive .has_error() and .clear_error() to widgets.
+
+ * Ensure that has_errors() completely parse all the widgets.
+
+ * Fix .clear_errors() for form2 Form. Errors can only be reliably
+ cleared after the widget has parsed
+
+ * Fix OptionSelect to always return a value from the options list.
+
+ * Fix some bugs in WidgetList.
+
+ * Check form token validity if tokens are enabled.
+
+
+1.0b1 (2004-04-12) r23961:
+
+ * Remove an underscore from the Session attributes __remote_address,
+ __creation_time, and __access_time. Note that if you have pickled
+ Session objects you will need to upgrade them somehow.
+
+ * Use a simpler version of ptl_compile.parse() if using Python >= 2.3.
+ Unfortunately, the hacks are still needed for older versions.
+
+ * Change the behavior of session cookies if SESSION_COOKIE_PATH is
+ unset. The old behavior was to not set the 'path' attribute of the
+ session cookie. The new behavior is to use SCRIPT_NAME. Also, set
+ the 'path' attribute when revoking the session cookie. Some
+ browsers treat it as a separate cookie if it has a different path.
+
+ * Don't try to compress stream responses.
+
+ * Refactor PublishError exception handling. Requests will now be
+ logged even if they raise an exception. This also allows some
+ simplification to the Medusa and Twisted server code.
+
+ * Make HTTPRequest.get_server() return HTTP_HOST if it is available
+ rather than using SERVER_NAME and SERVER_PORT.
+
+ * If "Host" header is present, use it to set SERVER_NAME and
+ SERVER_PORT.
+
+ * Make HTTPRequest.get_url() escape unsafe characters in the path part
+ of the URL.
+
+ * Use a simple counter instead of using the 'random' module when
+ trying to generate unique names for uploaded files.
+
+ * Allow arbitrary mapping objects to be used as the right hand operand
+ when formating htmltext objects (i.e. as an argument to the
+ htmltext.__mod__ method). The _c_htmltext implemention used to only
+ allow 'dict' instances. Change _c_htmltext to allow any mapping.
+ Also, instead of accessing every item in the mapping, use
+ PyObject_GetItem() (aka __getitem__) to retrieve the values as they
+ are required. That matches the behavior of PyString_Format().
+
+ * Don't set DEBUG_LOG in demo.conf. Extra logs files are confusing
+ when people are starting out.
+
+ * Significant form2 package changes. Remove FormComponent and related
+ machinery. Allow widgets to control how they are rendered within
+ forms (by overriding the form_render() method. Reorganize rendering
+ code in the form module so that it is easier to subclass form and
+ provide a different look. Change the parsing behavior of widgets.
+ They no longer parse themselves when __init__ is called. Instead,
+ each widget has a parse() method that calls _parse(), if it hasn't
+ been called already, and returns the widgets value. Allow
+ Widget._parse() to raise WidgetValueError in order to signal a
+ parsing error. Add a CompositeWidget class. Remove
+ Widget.get_value() since parse() seems to be sufficient.
+
+ * Add HTTPS header when running under twisted_http.py.
+
+ * Add cimport as a standard Quixote extension.
+
+ * Disable FIX_TRAILING_SLASH in demo.conf. It can be confusing when
+ trying to get the demo running.
+
+ * Allow _q_exports to contain mappings of external names to internal
+ names.
+
+ * Twisted does not unquote the path so make twisted_http.py do it.
+
+ * Implement error logging for mod_python handler.
+
+
+0.7a3 (2003-12-03) r23260:
+
+ * Improve init for ListWidget. The value of added_elements_widget
+ needs to be set in the request if the list widget was added to the
+ form after the first form submission.
+
+ * Rename Form.WIDGET_ROW_CLASS to COMPONENT_CLASS, since that's
+ what it really is. Make it an instance attribute as well as a class
+ attribute, so it can be overridden at form construction time.
+
+ * Remove 'allowed_values' and 'descriptions' from
+ RadiobuttonsWidget.__init__.
+
+ * Make htmltag() more efficient by avoiding repeated string
+ concatenation.
+
+ * Establish a pattern for way to specify html attributes for form2
+ widget constructors. The attributes may be provided in a dictionary
+ using the 'attr' keyword. An html attribute 'foo' may also be
+ specified in the widget constructor using using keyword 'foo' or
+ 'foo_'.
+
+
+0.7a2 (2003-11-11) r23130:
+
+ * Implement publish_fcgi(). Don't use fcgi module for publish_cgi()
+ since it's not completely portable. If you want to use FastCGI, you
+ need to call publish_fcgi() instead of publish_cgi(). Don't import
+ fcgi unless it's needed (since it does stuff at import time).
+
+ * Allow StaticFile class to be overridden. Also, make it easier to
+ subclass StaticDirectory.
+
+ * When loading a PTL module, check if it exists in sys.modules. If it
+ does, exec the code in that namespace rather than creating a new
+ module. This is necessary to make reload() work.
+
+ * Move HTTPUploadRequest import out of .create_request().
+
+ * Make HTTPRequest.guess_browser_version() more robust.
+
+ * Remove 'allowed_values' and 'description' parameters from form2
+ SelectWidget.
+
+ * Remove unused 'request' parameter from render() method of
+ form2 RadiobuttonsWidget and OptionSelectWidget.
+
+ * Improve form2 WidgetRow docstring. Don't use apply().
+
+ * Fix 'required' check in form2 WidgetRow.
+
+ * Set the nb_inplace_add slot of TemplateIO types instead of
+ sq_inplace_concat. nb_inplace_add takes precedence over __radd__
+ on instance types while sq_inplace_concat does not.
+
+ * Fix typo in repr of _c_htmltext TemplateIO.
+
+ * Use the quixote.sendmail module to send traceback email rather than
+ calling /usr/sbin/sendmail.
+
+ * Allow negative values to be passed to HTTPRequest.get_path().
+
+ * Add WidgetRow to form2.__init__.
+
+ * Implement add_button() and add_reset() methods for form2.Form and
+ rename Form._render_submit_buttons() to _render_button_widgets().
+
+
+0.7a1 (2003-10-09) r22720:
+
+ * By default, don't build _c_htmltext on Win32.
+
+ * In medusa_http.py, propagate 'GATEWAY_INTERFACE', 'SCRIPT_FILENAME',
+ and HTTP headers.
+
+ * Add HTTPRequest.get_method().
+
+ * Add support for keyword attributes on string widget.
+
+ * Add a CollapsibleListWidget.
+
+ * Allow HTTPResponse.body to be a Stream object as well as a string.
+ Stream objects are useful if the body of the response is very large.
+
+ * Change StaticFile to return Stream objects. Also, fix a minor bug in
+ StaticDirectory (_q_lookup should return a namespace).
+
+ * Improve installation instructions.
+
+ * Add 'Redirector' helper class.
+
+ * In publish.py, call _q_lookup(request, "") if _q_index does not exist
+ and _q_lookup does exist.
+
+ * In medusa_http.py, strip fragments from URLs (some versions of MSIE
+ incorrectly send them at certain times; for example, a redirect
+ includes them). Also, make REQUEST_URI contain the URI, not just the
+ path.
+
+ * In ptl_import, explicitly close the .ptlc file. This fixes a bug
+ where a subsequent import may go for this .ptlc file and find it
+ incomplete.
+
+ * Add css_class keyword to htmltag so that class attributes can be
+ specified easily.
+
+ * Implement next generation form framework (currently called 'form2').
+
+
+0.6.1 (2003-07-14) r22004:
+
+ * Make Form.add_widget() return the widget.
+
+ * Allow the "Expires" header to be suppressed by setting the 'cache'
+ attribute of HTTPResponse to None.
+
+ * Use title case for header names added by HTTPResponse.set_headers().
+ Clients are not supposed to care about case but it's better to be
+ conservative.
+
+ * Catch IOError exceptions raised when writing the response and log a
+ message (rather than exiting with a traceback).
+
+ * Fix bug regarding _q_exception_handler. namespace_stack needs to be
+ updated while the traversal is occuring. Thanks to Jason Sibre for
+ spotting it and for the fix.
+
+ * Add If-Modified-Since support for StaticFile objects. Also, don't
+ set the Expires header by default. Instead, set the Last-Modified
+ header based on the modification time of the file. The Expires
+ header can be enabled by providing a value for the 'cache_time'
+ argument.
+
+
+0.6 final (2003-04-30) r21480:
+
+ * Add a 'pass' to setup.py to make it easier to comment out the C
+ extension.
+
+ * Simplify 'From:' header in traceback e-mails.
+
+
+0.6b6 (2003-04-15):
+
+ * Rename _q_getname() to _q_lookup(). The name '_q_getname' is still
+ supported, but will log a warning whenever it's encountered.
+ This change will require users to modify their applications.
+
+ * quixote.form.form has been translated from PTL to Python, meaning
+ that you can now use the form framework without enabling PTL.
+ (Original suggestion by Jim Dukarm, who also provided a patch
+ that underwent considerable tweaking.)
+
+ * Fix generation of temporary filenames in upload.py: filename
+ collisions should be impossible now.
+
+ * In medusa_http.py, convert HTTP headers into title case before
+ passing them to Medusa, fixing duplicate Content-length headers.
+ (Fix by Graham Fawcett.)
+
+ * Added quixote.server.twisted_http, which serves a Quixote application
+ using the Twisted event-driven framework (www.twistedmatrix.com).
+ Contributed by Graham Fawcett. We don't use this code ourselves,
+ but patches and bug fixes from Twisted users will be gratefully
+ accepted.
+
+
+0.6b5 (2003-03-13):
+
+ * Fix incorrect parameter name for _traverse_url()
+
+
+0.6b4 (2003-03-13):
+
+ * The C version of the htmltext type is now compiled by default;
+ you can edit setup.py manually to disable it.
+
+ * StaticDirectory's list_folder argument renamed to list_directory.
+
+ * StaticFile now supports the HTTP encoding for files.
+ (Absence of this feature noted by Jim Dukarm.)
+
+ * If Quixote looks for _q_index() in a namespace and doesn't find
+ it, it raises AccessError (resulting in an HTTP 403 Forbidden error)
+ rather than failing with an ImportError. A minor side effect of
+ this change: Quixote will never attempt to import a module named
+ '_q_index', nor will it pass '_q_index' to any _q_resolve() function.
+ We don't expect this to be a backward compatibility problem .
+
+ * Factored out the traverse_url() and get_component() method
+ from the Publisher class.
+
+ * Documented _q_exception_handler().
+
+
+0.6b3 (2003-03-07):
+
+ * Fixed errors in demo_scgi.py. (David M. Cooke)
+
+ * Avoid using True/False and enable nested scopes in _py_htmltext.py
+ for Python 2.1 compatibility. (Noted by Jeff Bauer)
+
+ Note that this means HTML templates will not work with Python 2.0
+ unless you compile the C extension.
+
+ * Added StaticFile and StaticDirectory classes to quixote.util.
+ Consult doc/static-files.txt for examples. (Contributed and
+ documented by Hamish Lawson.)
+
+
+0.6b2 (2003-01-27):
+
+ * Added a new hook, _q_resolve(), that can be used to delay
+ importing modules until they're actually accessed. Consult
+ doc/programming.txt for an explanation. (Original
+ suggestion and patch by Jon Corbet. In the process of adding it,
+ Publisher.get_component() was rearranged to clarify the logic.)
+
+ * Fixed the Medusa HTTP server to work with HTML templates (David M. Cooke)
+ and to call finish_failed_request (pointed out by Graham Fawcett).
+
+ * Added HTTP_USER_AGENT and ACCEPT_ENCODING to Medusa HTTP server.
+ (Graham Fawcett)
+
+ * Fixed _c_htmltext.c to compile on Windows. (Graham Fawcett)
+
+ * Fixed two bugs in _c_htmltext.c found by code review,
+ and one bug in _py_htmltext.py found by Nicola Larosa.
+ (Neil Schemenauer)
+
+ * Added a page to the demo that dumps the contents of the HTTPRequest.
+
+ * Made upload.py write out HTTP upload data in binary mode, so
+ binary content-types work correctly on Windows. (Graham Fawcett)
+
+ * Added classifiers for use with Python 2.3's "register" command.
+
+
+0.6b1 (2003-01-09):
+
+ * Merged form/form.py and form/form_templates.ptl into
+ form/form.ptl. (This means that you should completely remove (or
+ rename) your old Quixote installation directory *before* installing
+ 0.6, or the old form/form.py will shadow the new form.ptl.)
+
+ * A new and preferred syntax for declaring PTL templates has been added.
+ Instead of 'template func(): ...', the new form is 'def func [plain] ()'.
+
+ This uses a notation that's been suggested for adding type
+ information to Python functions. The Emacs Python mode already handles
+ this properly, and it may be more compatible with future versions of
+ Python.
+
+ The 'template' keyword is still supported, but we encourage you
+ to switch to the new syntax when you get a chance.
+
+ * Quixote now supports a new kind of template that automatically performs
+ HTML escaping. Here's an example. (Notice that the '[plain]'
+ annotation is changed to '[html]' to enable this feature.)
+
+ def header [html] (title):
+ "<title>%s</title>" % title
+
+ If the 'title' argument is something like "R&D", it will
+ automatically be converted to "R&D" following the rules
+ for escaping HTML special characters. The aim is to avoid cross-site
+ scripting attacks by removing the need for the programmer to remember
+ to HTML-escape unsafe text, instead relying on Quixote to
+ escape text where necessary.
+
+ See doc/PTL.txt for more information about how this works.
+
+ This escaping is implemented using a 'htmltext' class implemented in
+ Python, and is currently in production use on our web site.
+
+ * An experimental C implementation of the 'htmltext' type is also
+ included; it hasn't been put into production use yet. Edit
+ setup.py and uncomment the appropriate line if you want to try the
+ C implementation.
+
+ * The form framework now uses automatic HTML escaping. This
+ means that applications using the form framework will have
+ to either be changed to use automatic HTML escaping themselves,
+ or to use str() to convert 'htmltext' instances back to Python
+ strings.
+
+ See doc/upgrading.txt for more information.
+
+ * Make Quixote a bit more friendly to multi-threaded applications
+ by allowing multiple simultaneous requests (patch by Titus Brown).
+
+ * Make util.xmlrpc() return an HTTP 405 Method Not Allowed error
+ if the method isn't a POST.
+
+ * Added demo/run_cgi.py, a script that makes it easy to write one
+ file CGI applications that use Quixote. See the comments at the
+ top of the demo/run_cgi.py file for instructions.
+
+
+0.5.1 (2002-10-08):
+
+ * (incompatible change for anyone doing HTTP upload with Quixote)
+ Improved support for HTTP upload requests: any HTTP request with
+ a Content-Type of "multipart/form-data" -- which is generally only
+ used for uploads -- is now represented by HTTPUploadRequest, a
+ subclass of HTTPRequest, and the uploaded files themselves are
+ represented by Upload objects. See doc/upload.txt for details.
+
+ * (possible incompatible changes for anyone subclassing Publisher,
+ or using it for custom purposes)
+ Various rearrangements and refactoring in the Publisher class.
+ Added create_request() method, which takes responsibility for
+ creating the HTTPRequest (or HTTPUploadRequest) object away
+ from parse_request(). As a consequence, the signature of
+ parse_request() has changed. Added process_request() method.
+ Changed publish() so it catches exceptions coming from either
+ parse_request() or process_request(). Consult the source code
+ (publish.py) for details.
+
+ * A new subpackage, quixote.server, is intended for code that
+ publishes a Quixote application through HTTP, making it possible
+ to run Quixote applications without having to configure Apache or
+ some other full-blown Web server. Right now there's only an
+ implementation on top of Medusa; contributions of support for
+ Python's BaseHTTPServer, Twisted, or other frameworks would be
+ welcome.
+
+ * Modified SessionManager.maintain_session() so it explicitly removes
+ a session if that session used to have useful info (ie. exists in
+ the session manager), but no longer does (patch by Jon Corbet).
+
+ * Make the PTL compiler a bit smarter about recognizing "template"
+ lines; PTL code should now be able to use 'template' as an
+ identifier, which is handy when converting existing Python code
+ to PTL.
+
+ * Replaced HTTPRequest.redirect() with a cleaner, more general
+ version supplied by Andreas Kostyrka <andreas at kostyrka.priv.at>.
+ Redirects to fully relative URLs (no leading slash) now work.
+
+ * Added support for putting bits of JavaScript into HTML form
+ documents: added HTTPResponse.add_javascript() to collect snippts
+ of JavaScript code, and Form._render_javascript() to emit them
+ as part of the HTML document.
+
+ * Added global convenience functions get_path() and redirect().
+
+ * Change Publisher so it never modifies SCRIPT_NAME or PATH_INFO.
+
+ * Fixed bug in quixote.sendmail._add_recip_headers(): it crashed
+ if passed an empty list.
+
+ * Factor out get_action_url() method in Form class.
+
+ * Add the HTML version of the documentation to the source release.
+
+
+0.5 (2002-06-10):
+
+ * To fix installation problems on Win98 and Mac OS (pre OS X),
+ setup.py now uses os.curdir instead of ''.
+
+ * Overhauled handling of PublishError exceptions: Quixote now
+ looks for the nearest _q_exception_handler() function in your
+ application's namespace; the format_*error() methods of Publisher
+ are gone.
+
+ * Documented and overhauled the session management API. If you
+ were previously using session management, you will almost certainly
+ need to change your code; see doc/session-mgmt.txt and
+ doc/session-upgrade.txt. If you've been wanting to use session
+ management in your application but were put off by the lack of
+ documentation, see doc/session-mgmt.txt.
+
+ Specific changes:
+ * removed the global singleton SessionManager object in session.py
+ and several related functions
+ * removed everything having to do with "application state", an
+ unnecessary abstraction caused by premature over-generalization
+ * removed the 'actual_user' attribute from Session -- it is
+ specific to the MEMS Exchange and just confuses matters
+ in Quixote
+ * made most instance attributes of Session private
+ * defined a sensible persistence API that should work with
+ a wide variety of session persistence schemes
+ * COOKIE_* config variables renamed to SESSION_COOKIE_*
+
+ * Fix HTTPResponse so that the cookie domain and path can be None,
+ and they will simply not be set in the cookie sent to the client --
+ that way the browser will simply do the right thing. Set
+ COOKIE_DOMAIN and COOKIE_PATH in config.py to None, since that's
+ usually fine. (You may need to set COOKIE_PATH to "/".)
+
+ * Subtle but far-reaching change to the publishing algorithm: objects
+ found by the publisher to handle the terminal component of a URL can
+ now be strings as well as callables; a string simply substitutes for
+ a callable's return value. The immediate reason for this was to
+ allow _q_lookup() functions to return a string, but a consequence
+ is that you can now put static text in global variables and simply
+ publish them.
+
+ * Add CHECK_SESSION_ADDR config variable to control whether we
+ check that requests in a session all come from the same IP address,
+ as a defence against playback attacks. (Thanks to Jonathan Corbet.)
+
+ * In error reports, print the traceback first, ahead of form variables,
+ cookies, and the environment.
+
+ * Include the HTTP_USER_AGENT variable in access log lines.
+
+ * Add 'sort' option to SelectWidget class, to force the list of
+ allowed values to be sorted in case-insensitive lexicographic order,
+ with None first.
+
+
+0.4.7 (2002-04-18):
+
+ * Move ACCESS_TIME_RESOLUTION to SessionManager class. This was another
+ embarrassing bug introduced in 0.4.5.
+
+ * In http_request.py, make the test that prevents stdin from being consumed
+ less restrictive (e.g. for PUT methods).
+
+ * Add some simple test code.
+
+
+0.4.6 (2002-04-12):
+
+ * a last-minute patch to http_request.py just before release 0.4.5 broke
+ extracting form data from GET requests -- fixed that
+
+
+0.4.5 (2002-04-11):
+
+ * The meaning of the DISPLAY_EXCEPTIONS configuration variable has
+ changed. It's no longer a Boolean, and instead can take three
+ different values:
+ None (or any false value) [default]
+ an "Internal Server Error" page that exposes no information
+ about the traceback
+ 'plain'
+ a plain text page showing the traceback and the request variables
+ 'html'
+ a more elaborate HTML display showing the local variables and a
+ few lines of context for each level of the traceback. (This
+ setting requires the cgitb module that comes with Python 2.2.)
+
+ (Idea and first version of the patch by David Ascher)
+
+ * Fixed SessionManager.expire_session() method so it actually works
+ (spotted by Robin Wohler).
+
+ * Fixed docs so they don't refer to the obsolete URL_PREFIX
+ configuration variable (spotted by Robin Wohler).
+
+ * Various other documentation tweaks and improvements.
+
+ * Fixed sample Apache rewrite rules in demo.txt and web-server.txt
+ (spotted by Joel Shprentz).
+
+ * Generate new form tokens when rendering a form rather then when
+ intializing it. This prevents an extra token from being created when
+ processing a valid form (suggested by Robin Wohler).
+
+ * Ensure filenames are included in SyntaxError tracebacks from PTL modules.
+
+ * Changed format of session cookies: they're now just random 64-bit
+ numbers in hex.
+
+ * Use HTTP 1.1 cache control headers ("Date" and "Expires") instead
+ of the older "Pragma: no-cache".
+
+ * In the form/widget library: make some effort to generate HTML that
+ is XHTML-compliant.
+
+ * New method: HTTPRequest.get_accepted_types() returns the
+ MIME content types a client will accept as a dictionary mapping
+ MIME type to the quality factor. (Example: {'text/html':1.0,
+ 'text/plain':0.5, ...})
+
+ * Changed escape hatch for XML-RPC handlers; standard input will
+ only be consumed when the HTTP method is POST and the Content-Type
+ is either application/x-www-form-urlencoded or multipart/form-data.
+
+ * Added quixote.util module to contain various miscellaneous utility
+ functions. Right now, it contains a single function for
+ processing requests as XML-RPC invocations.
+
+
+0.4.4 (2002-01-29):
+
+ * Simplify munging of SCRIPT_NAME variable, fixing a bug.
+ Depending on how Quixote was called, the path could have been
+ appended to SCRIPT_NAME without a separating slash. (Found by
+ Quinn Dunkan.)
+
+ * On Windows, set mode of sys.stdout to binary. This is important
+ because responses may contain binary data. Also, EOL translation
+ can throw off content length calculations. (Found by David Ascher)
+
+ * Added a demonstration of the form framework. (Neil)
+
+ * Added an escape hatch for XML-RPC handlers;
+ http_request.process_inputs() will no longer consume all of standard
+ input when the Content-Type is text/xml.
+
+ * Removed a debug print from form.widget.
+
+
+0.4.3 (2001-12-17):
+
+ * Removed the URL_PREFIX configuration variable; it's not actually
+ needed anywhere, and caused some user confusion.
+
+ * Added FORM_TOKENS configuration variable to enable/disable
+ unique form identifiers. (These are useful as a measure against
+ cross-site request forgery [CSRF] attacks, but disabled by default
+ because some form of persistent session management is required,
+ which is not currently included with Quixote.)
+
+ * Added demonstration and documentation for the widget classes
+ (the first part of the Quixote Form Library).
+
+ * Added HTTPResponse.set_content_type() method.
+
+ * Fixed some minor bugs in the widget library.
+
+ * Fixed to work with Python 2.2.
+
+ * Greatly reduced the set of symbols imported by
+ "from quixote import *" -- it's useful for interactive sessions.
+
+
+0.4.2 (2001-11-14):
+
+ * Made the quixote.sendmail module a bit more flexible and robust.
+
+ * Fix so it doesn't blow up under Windows if debug logging is disabled
+ (ie. write to NUL, not /dev/null).
+
+ * Clarified some documenation inconsistencies, and added description
+ of logging to doc/programming.txt.
+
+ * Fixed some places that we forgot to update when the PTL-related
+ modules were renamed.
+
+ * Fixed ptl_compile.py so PTL tracebacks include the full path of
+ source file(s).
+
+ * Fixed bug where a missing _q_index() triggered a confusing
+ ImportError; now it triggers a TraversalError, as expected.
+
+ * Various fixes and improvements to the Config class.
+
+ * Miscellaneous fixes to session.py.
+
+ * Miscellaneous fixes to widget classes.
+
+ * Reorganized internal PTL methods of the Form class.
+
+ * Removed the "test" directory from the distribution, since it's not
+ used for anything -- ie., there's no formal test suite yet ;-(
+
+
+0.4.1 (2001-10-10):
+
+ * Made access logging a little more portable (don't depend on Apache's
+ REQUEST_URI environment variable).
+
+ * Work around the broken value of PATH_INFO returned by IIS.
+
+ * Work around IIS weird handling of SERVER_SECURE_PORT (for non-SSL
+ requests, it is set to "0").
+
+ * Reassign sys.stderr so all application output to stderr goes to the
+ Quixote error log.
+
+
+0.4 (2001-10-04):
+
+ * TraversalError now takes a public and a private message, instead of
+ just a single message string. The private message is shown if
+ SECURE_ERRORS is false; otherwise, the public message is shown. See
+ the class docstring for TraversalError for more details.
+
+ * Add the Quixote Form Library, a basic form and widget framework
+ for HTML.
+
+ * Allow classes and functions inside PTL modules.
+
+ * Return a string object from templates rather than a TemplateIO
+ instance.
+
+ * Improve the security of session cookies.
+
+ * Don't save empty sessions.
+
+ * Detect expired sessions.
+
+ * Add the quixote.sendmail module, useful for applications that need
+ to send outgoing mail (as many web apps do).
+
+ * Code reorganization -- various modules moved or renamed:
+ quixote.util.fcgi -> quixote.fcgi
+ quixote.compile_template -> quixote.ptl_compile
+ quixote.imphooks -> quixote.ptl_import
+ quixote.dumpptlc -> quixote.ptcl_dump
+
+ * More code reorganization: the quixote.zope package is gone, as are
+ the BaseRequest and BaseResponse modules. Only HTTPRequest and
+ HTTPResponse survive, in the quixote.http_request and
+ quixote.http_response modules. All remaining Zope-isms have been
+ removed, so the code now looks much like the rest of Quixote. Many
+ internal interfaces changed.
+
+ * Added the quixote.mod_python module, contributed by Erno Kuusela
+ <erno at iki.fi>. Allows Quixote applications to be driven by the
+ Apache module mod_python, so no CGI or or FastCGI driver script is
+ required.
+
+
+0.3 (2001-06-11):
+
+ * Now supports Python 2.1.
+
+ * Names of the form __*__ are reserved for Python, and 2.1 is
+ beginning to enforce this rule. Accordingly the Quixote special
+ methods have been renamed:
+ __access__ -> _q_access
+ __exports__ -> _q_exports
+ __getname__ -> _q_getname
+ index -> _q_index
+
+ * Massive changes to quixote.publisher and quixote.config, to make the
+ publishing loop more flexible and more easily changed by
+ applications. For example, it's now possible to catch the ZODB's
+ ConflictErrors and retry an operation.
+
+ * Added an ACCESS_LOG configuration setting, which allows setting up a
+ file logging every call made to Quixote.
+
+ * The error log now contains the time of each error, and a dump of the
+ user's session object.
+
+ * Added handy functions for getting request, session, user, etc.:
+ quixote.get_publisher(), quixote.get_request(),
+ quixote.get_session(), quixote.get_user().
+
+ * quixote.publish can now gzip-compress its output if the browser
+ claims to support it. Only the 'gzip' and 'x-gzip' content
+ encodings are supported; 'deflate' isn't because we couldn't get it
+ to work reliably. Compression can be enabled by setting the
+ 'compress_pages' config option to true.
+
+ * Some fixes and minor optimizations to the FCGI code.
+
+ * Added HTTPRequest.get_encoding() method to find the encodings a
+ client accepts.
+
+
+0.2 (2001-01-16):
+
+ * Only pass HTTPRequest object to published functions. The
+ HTTPResponse object is available as an attribute of the request.
+
+ * Removed more unused Zope code from HTTPRequest. Add redirect()
+ method to HTTPRequest.
+
+ * Simplify HTTPResponse. __init__() no longer requires the server
+ name. redirect() requires a full URL.
+
+ * Fix a bug in the PTL compiler. PTL modules can now have doc
+ strings.
+
+ * Added a config parser. Individual Quixote applications can now have
+ their own configuration settings (overriding the Quixote defaults).
+ See the config.py module for details.
+
+ * Re-wrote the exception handling code for exceptions raised inside of
+ published functions.
+
+ * Non-empty PATH_INFO is no longer supported. __getname__ or query
+ strings are a cleaner solution.
+
+ * Add FIX_TRAILING_SLASH option and make code changes to carefully
+ preserve trailing slashes (ie. an empty component on the end of
+ paths).
+
+ * Session management has been over-hauled. DummySessionManager can be
+ used for applications that don't require sessions.
+
+ * Set Content-length header correctly in HTTPResponse object
+
+ * Added a demo application.
+
+0.1:
+
+ * Officially given a license (the Python 1.6 license, so it's free
+ software).
+
+ * Added SECURE_ERRORS variable to prevent exception tracebacks from
+ being returned to the Web browser
+
+ * Added a __getname__() function to traversal, which is called if the
+ current name isn't in the current namespace's export list. This
+ allows interpolation of arbitrary user object IDs into the URL,
+ which is why it has to circumvent the __exports__ check: the object
+ IDs won't be known until runtime, so it would be silly to add them
+ to __exports__. Very useful and powerful feature, but it has
+ security implications, so be careful!
+
+ * compile_template.py should now work for both Python 1.5.2 or 2.0.
+
+ * Better reporting of syntax errors in PTL
+
+ * Always assume regular CGI on Windows
+
+0.02 (2000-08-12):
+
+ * Neil Schemenauer has completely rewritten the PTL compiler and
+ changed the syntax to match Python's. The compiler now relies on
+ Jeremy Hylton's compiler code from the Python 2.0 CVS tree.
+
+ * Added functions to quixote.sessions: get_session(), has_session(),
+ get_app_state()
+
+ * Simplified reload-checking logic
+
+ * Added .browser_version() method to HTTPRequest
+
+ * Various bugfixes
+
+ * session classes slightly tweaked, so you can subclass them
+
+ * Added .session attribute to request object
+
+ * Added quixote.errors module to hold exceptions
+
+0.01:
+
+ * Initial release.
Added: packages/quixote1/branches/upstream/current/LICENSE
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/LICENSE?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/LICENSE (added)
+++ packages/quixote1/branches/upstream/current/LICENSE Mon May 8 19:16:16 2006
@@ -1,0 +1,60 @@
+CNRI OPEN SOURCE LICENSE AGREEMENT
+
+IMPORTANT: PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY. BY
+COPYING, INSTALLING OR OTHERWISE USING QUIXOTE-1.2 SOFTWARE, YOU
+ARE DEEMED TO HAVE AGREED TO THE TERMS AND CONDITIONS OF THIS
+LICENSE AGREEMENT.
+
+1. This LICENSE AGREEMENT is between Corporation for National
+ Research Initiatives, having an office at 1895 Preston White
+ Drive, Reston, VA 20191 ("CNRI"), and the Individual or
+ Organization ("Licensee") copying, installing or otherwise using
+ Quixote-1.2 software in source or binary form and its associated
+ documentation ("Quixote-1.2").
+
+2. Subject to the terms and conditions of this License Agreement,
+ CNRI hereby grants Licensee a nonexclusive, royalty-free, world-
+ wide license to reproduce, analyze, test, perform and/or display
+ publicly, prepare derivative works, distribute, and otherwise use
+ Quixote-1.2 alone or in any derivative version, provided,
+ however, that CNRI's License Agreement and CNRI's notice of
+ copyright, i.e., "Copyright (c) 2004 Corporation for National
+ Research Initiatives; All Rights Reserved" are retained in
+ Quixote-1.2 alone or in any derivative version prepared by
+ Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+ or incorporates Quixote-1.2 or any part thereof, and wants to
+ make the derivative work available to others as provided herein,
+ then Licensee hereby agrees to include in any such work a brief
+ summary of the changes made to Quixote-1.2.
+
+4. CNRI is making Quixote-1.2 available to Licensee on an "AS IS"
+ basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+ IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO
+ AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY
+ OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF QUIXOTE-
+ 1.2 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF
+ QUIXOTE-1.2 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES
+ OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE
+ USING QUIXOTE-1.2, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF
+ THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a
+ material breach of its terms and conditions.
+
+7. This License Agreement shall be governed by and interpreted in
+ all respects by the law of the State of Virginia, excluding
+ Virginia's conflict of law provisions. Nothing in this License
+ Agreement shall be deemed to create any relationship of agency,
+ partnership, or joint venture between CNRI and Licensee. This
+ License Agreement does not grant permission to use CNRI
+ trademarks or trade name in a trademark sense to endorse or
+ promote products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Quixote-1.2, Licensee
+ agrees to be bound by the terms and conditions of this License
+ Agreement.
+
Added: packages/quixote1/branches/upstream/current/MANIFEST
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/MANIFEST?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/MANIFEST (added)
+++ packages/quixote1/branches/upstream/current/MANIFEST Mon May 8 19:16:16 2006
@@ -1,0 +1,123 @@
+ACKS
+CHANGES
+LICENSE
+MANIFEST
+MANIFEST.in
+README
+TODO
+__init__.py
+_py_htmltext.py
+config.py
+errors.py
+fcgi.py
+html.py
+http_request.py
+http_response.py
+mod_python_handler.py
+ptl_compile.py
+ptl_import.py
+ptlc_dump.py
+publish.py
+qx_distutils.py
+sendmail.py
+session.py
+setup.py
+upload.py
+util.py
+./__init__.py
+./_py_htmltext.py
+./config.py
+./errors.py
+./fcgi.py
+./html.py
+./http_request.py
+./http_response.py
+./mod_python_handler.py
+./ptl_compile.py
+./ptl_import.py
+./ptlc_dump.py
+./publish.py
+./qx_distutils.py
+./sendmail.py
+./session.py
+./upload.py
+./util.py
+./demo/__init__.py
+./demo/demo_scgi.py
+./demo/forms.ptl
+./demo/integer_ui.py
+./demo/pages.ptl
+./demo/run_cgi.py
+./demo/session.ptl
+./demo/widgets.ptl
+./form/__init__.py
+./form/form.py
+./form/widget.py
+./form2/__init__.py
+./form2/compatibility.py
+./form2/css.py
+./form2/form.py
+./form2/widget.py
+./server/__init__.py
+./server/medusa_http.py
+./server/twisted_http.py
+demo/__init__.py
+demo/demo.cgi
+demo/demo.conf
+demo/demo_scgi.py
+demo/demo_scgi.sh
+demo/forms.ptl
+demo/integer_ui.py
+demo/pages.ptl
+demo/run_cgi.py
+demo/session.ptl
+demo/session_demo.cgi
+demo/upload.cgi
+demo/widgets.ptl
+doc/INSTALL.html
+doc/INSTALL.txt
+doc/Makefile
+doc/PTL.html
+doc/PTL.txt
+doc/ZPL.txt
+doc/default.css
+doc/demo.html
+doc/demo.txt
+doc/form2conversion.html
+doc/form2conversion.txt
+doc/multi-threaded.html
+doc/multi-threaded.txt
+doc/programming.html
+doc/programming.txt
+doc/session-mgmt.html
+doc/session-mgmt.txt
+doc/static-files.html
+doc/static-files.txt
+doc/upgrading.html
+doc/upgrading.txt
+doc/upload.html
+doc/upload.txt
+doc/web-server.html
+doc/web-server.txt
+doc/web-services.html
+doc/web-services.txt
+doc/widgets.html
+doc/widgets.txt
+form/__init__.py
+form/form.py
+form/widget.py
+form2/__init__.py
+form2/compatibility.py
+form2/css.py
+form2/form.py
+form2/widget.py
+server/__init__.py
+server/medusa_http.py
+server/twisted_http.py
+src/Makefile
+src/_c_htmltext.c
+src/cimport.c
+src/setup.py
+test/__init__.py
+test/ua_test.py
+test/utest_html.py
Added: packages/quixote1/branches/upstream/current/MANIFEST.in
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/MANIFEST.in?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/MANIFEST.in (added)
+++ packages/quixote1/branches/upstream/current/MANIFEST.in Mon May 8 19:16:16 2006
@@ -1,0 +1,6 @@
+global-include *.py *.ptl
+include README LICENSE MANIFEST.in MANIFEST CHANGES TODO ACKS
+include doc/*.txt doc/*.css doc/Makefile
+recursive-include doc *.html
+include demo/*.cgi demo/*.conf demo/*.sh
+include src/*.c src/Makefile
Added: packages/quixote1/branches/upstream/current/PKG-INFO
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/PKG-INFO?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/PKG-INFO (added)
+++ packages/quixote1/branches/upstream/current/PKG-INFO Mon May 8 19:16:16 2006
@@ -1,0 +1,24 @@
+Metadata-Version: 1.0
+Name: Quixote
+Version: 1.2
+Summary: A highly Pythonic Web application framework
+Home-page: http://www.mems-exchange.org/software/quixote/
+Author: MEMS Exchange
+Author-email: quixote at mems-exchange.org
+License: CNRI Open Source License (see LICENSE.txt)
+Provides: quixote-1.2
+Provides: quixote.demo-1.2
+Provides: quixote.form-1.2
+Provides: quixote.form2-1.2
+Provides: quixote.server-1.2
+Download-URL: http://www.mems-exchange.org/software/files/quixote/Quixote-1.2.tar.gz
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: License :: OSI Approved :: Python License (CNRI Python License)
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: Unix
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Added: packages/quixote1/branches/upstream/current/README
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/README?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/README (added)
+++ packages/quixote1/branches/upstream/current/README Mon May 8 19:16:16 2006
@@ -1,0 +1,166 @@
+Quixote
+=======
+
+Quixote is yet another framework for developing Web applications in
+Python. The design goals were:
+
+ 1) To allow easy development of Web applications where the
+ emphasis is more on complicated programming logic than
+ complicated templating.
+
+ 2) To make the templating language as similar to Python as possible,
+ in both syntax and semantics. The aim is to make as many of the
+ skills and structural techniques used in writing regular Python
+ code applicable to Web applications built using Quixote.
+
+ 3) No magic. When it's not obvious what to do in
+ a certain case, Quixote refuses to guess.
+
+If you view a web site as a program, and web pages as subroutines,
+Quixote just might be the tool for you. If you view a web site as a
+graphic design showcase, and each web page as an individual work of art,
+Quixote is probably not what you're looking for.
+
+An additional requirement was that the entire system had to be
+implementable in a week or two. The initial version of Quixote was
+indeed cranked out in about that time -- thank you, Python!
+
+We've tried to reuse as much existing code as possible:
+
+ * The HTTPRequest and HTTPResponse classes are distantly
+ derived from their namesakes in Zope, but we've removed
+ huge amounts of Zope-specific code.
+
+ * The quixote.fcgi module is derived from Robin Dunn's FastCGI module,
+ available at
+ http://alldunn.com/python/#fcgi
+
+Quixote requires Python 2.1 or greater to run. We only test Quixote
+with Python 2.3, but it should still work with 2.1 and 2.2.
+
+For installation instructions, see the doc/INSTALL.txt file (or
+http://www.mems-exchange.org/software/quixote/doc/INSTALL.html).
+
+If you're switching to a newer version of Quixote from an older
+version, please refer to doc/upgrading.txt for explanations of any
+backward-incompatible changes.
+
+
+Overview
+========
+
+Quixote works by using a Python package to store all the code and HTML
+for a Web-based application. There's a simple framework for
+publishing code and objects on the Web, and the publishing loop can be
+customized by subclassing the Publisher class. You can think of it as
+a toolkit to build your own smaller, simpler version of Zope,
+specialized for your application.
+
+An application using Quixote is a Python package containing .py and
+.ptl files.
+
+webapp/ # Root of package
+ __init__.py
+ module1.py
+ module2.py
+ pages1.ptl
+ pages2.ptl
+
+PTL, the Python Template Language, is used to mix HTML with Python code.
+More importantly, Python can be used to drive the generation of HTML.
+An import hook is defined so that PTL files can be imported just like
+Python modules. The basic syntax of PTL is Python's, with a few small
+changes:
+
+def plain [text] barebones_header(title=None,
+ description=None):
+ """
+ <html><head>
+ <title>%s</title>
+ """ % html_quote(str(title))
+ if description:
+ '<meta name="description" content="%s">' % html_quote(description)
+
+ '</head><body bgcolor="#ffffff">'
+
+See doc/PTL.txt for a detailed explanation of PTL.
+
+
+Quick start
+===========
+
+For instant gratification, see doc/demo.txt. This explains how to get
+the Quixote demo up and running, so you can play with Quixote without
+actually having to write any code.
+
+
+Documentation
+=============
+
+All the documentation is in the doc/ subdirectory, in both text and
+HTML. Or you can browse it online from
+ http://www.mems-exchange.org/software/quixote/doc/
+
+Recommended reading:
+
+ demo.txt getting the Quixote demo up and running, and
+ how the demo works
+ programming.txt the components of a Quixote application: how
+ to write your own Quixote apps
+ PTL.txt the Python Template Language, used by Quixote
+ apps to generate web pages
+ web-server.txt how to configure your web server for Quixote
+
+Optional reading (more advanced or arcane stuff):
+
+ session-mgmt.txt session management: how to track information
+ across requests
+ static-files.txt making static files and CGI scripts available
+ upload.txt how to handle HTTP uploads with Quixote
+ upgrading.txt info on backward-incompatible changes that may
+ affect applications written with earlier versions
+ widgets.txt reference documentation for the Quixote Widget
+ classes (which underly the form library)
+ web-services.txt how to write web services using Quixote and
+ XML-RPC
+
+
+Authors, copyright, and license
+===============================
+
+Copyright (c) 2000-2003 CNRI.
+
+Quixote was primarily written by Andrew Kuchling, Neil Schemenauer, and
+Greg Ward.
+
+Overall, Quixote is covered by the CNRI Open Source License Agreement;
+see LICENSE for details.
+
+Portions of Quixote are derived from Zope, and are also covered by the
+ZPL (Zope Public License); see ZPL.txt.
+
+Full acknowledgments are in the ACKS file.
+
+
+Availability, home page, and mailing lists
+==========================================
+
+The Quixote home page is:
+ http://www.mems-exchange.org/software/quixote/
+
+You'll find the latest stable release there. The current development
+code is also available via CVS; for instructions, see
+ http://www.mems-exchange.org/software/quixote/cvs.html
+
+Discussion of Quixote occurs on the quixote-users mailing list:
+ http://mail.mems-exchange.org/mailman/listinfo/quixote-users/
+
+To follow development at the most detailed level by seeing every CVS
+checkin, join the quixote-checkins mailing list:
+ http://mail.mems-exchange.org/mailman/listinfo/quixote-checkins/
+
+
+--
+A.M. Kuchling <akuchlin at mems-exchange.org>
+Neil Schemenauer <nascheme at mems-exchange.org>
+Greg Ward <gward at mems-exchange.org>
Added: packages/quixote1/branches/upstream/current/TODO
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/TODO?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/TODO (added)
+++ packages/quixote1/branches/upstream/current/TODO Mon May 8 19:16:16 2006
@@ -1,0 +1,27 @@
+
+* Extend HTTPRequest to support single/multiple-valued fields.
+
+* Add way to strip redundant whitespace from output HTML (or pipe it
+ through Tidy, or other transformations).
+
+* Logging doesn't work with CGI scripts (something about our
+ log-opening code depends on how fastcgi.py fiddles stdout).
+
+* Make bare return statements inside of PTL templates work as expected.
+
+* Allow __init__.ptl files to be used as package markers. It looks like
+ something is wrong with the way ihooks handles __init__ modules.
+
+* Figure out how to unify the various deployment methods (CGI, FastCGI,
+ SCGI, mod_python, ...). Ideally, a one-line change to the config file
+ and/or driver script (along with corresponding web server config
+ changes) would be enough to change how a Quixote application is
+ deployed.
+
+* For OpenBSD: fcgi.py should catch SIGTERM and, umm, do something.
+ (Terminate the process?) Otherwise, the FastCGI process can no longer
+ accept() on its socket. (Reported by Robin Wöhler
+ <rw at robinwoehler.de>, 2002/08/02.)
+
+* For Mac OS X: _startup() in fcgi.py doesn't work for some reason on
+ OS X. Figure out why and fix it (or kludge around it).
Added: packages/quixote1/branches/upstream/current/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/__init__.py Mon May 8 19:16:16 2006
@@ -1,0 +1,35 @@
+"""quixote
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/__init__.py $
+$Id: __init__.py 25278 2004-10-06 15:49:42Z nascheme $
+
+A highly Pythonic web application framework.
+"""
+
+__revision__ = "$Id: __init__.py 25278 2004-10-06 15:49:42Z nascheme $"
+
+__version__ = "1.2"
+
+__all__ = ['Publisher',
+ 'get_publisher', 'get_request', 'get_session', 'get_user',
+ 'get_path', 'enable_ptl', 'redirect']
+
+
+# These are frequently needed by Quixote applications, so make them easy
+# to get at.
+from quixote.publish import Publisher, \
+ get_publisher, get_request, get_path, redirect, \
+ get_session, get_session_manager, get_user
+
+# Can't think of anywhere better to put this, so here it is.
+def enable_ptl():
+ """
+ Installs the import hooks needed to import PTL modules. This must
+ be done explicitly because not all Quixote applications need to use
+ PTL, and import hooks are deep magic that can cause all sorts of
+ mischief and deeply confuse innocent bystanders. Thus, we avoid
+ invoking them behind the programmer's back. One known problem is
+ that, if you use ZODB, you must import ZODB before calling this
+ function.
+ """
+ from quixote import ptl_import
+ ptl_import.install()
Added: packages/quixote1/branches/upstream/current/_py_htmltext.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/_py_htmltext.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/_py_htmltext.py (added)
+++ packages/quixote1/branches/upstream/current/_py_htmltext.py Mon May 8 19:16:16 2006
@@ -1,0 +1,231 @@
+"""Python implementation of the htmltext type, the htmlescape function and
+TemplateIO.
+"""
+
+#$HeadURL: svn+ssh://svn/repos/trunk/quixote/_py_htmltext.py $
+#$Id: _py_htmltext.py 25234 2004-09-30 17:36:19Z nascheme $
+
+import sys
+from types import UnicodeType, TupleType, StringType, IntType, FloatType, \
+ LongType
+import re
+
+if sys.hexversion < 0x20200b1:
+ # 2.2 compatibility hacks
+ class object:
+ pass
+
+ def classof(o):
+ if hasattr(o, "__class__"):
+ return o.__class__
+ else:
+ return type(o)
+
+else:
+ classof = type
+
+_format_codes = 'diouxXeEfFgGcrs%'
+_format_re = re.compile(r'%%[^%s]*[%s]' % (_format_codes, _format_codes))
+
+def _escape_string(s):
+ if not isinstance(s, StringType):
+ raise TypeError, 'string required'
+ s = s.replace("&", "&")
+ s = s.replace("<", "<")
+ s = s.replace(">", ">")
+ s = s.replace('"', """)
+ return s
+
+class htmltext(object):
+ """The htmltext string-like type. This type serves as a tag
+ signifying that HTML special characters do not need to be escaped
+ using entities.
+ """
+
+ __slots__ = ['s']
+
+ def __init__(self, s):
+ self.s = str(s)
+
+ # XXX make read-only
+ #def __setattr__(self, name, value):
+ # raise AttributeError, 'immutable object'
+
+ def __getstate__(self):
+ raise ValueError, 'htmltext objects should not be pickled'
+
+ def __repr__(self):
+ return '<htmltext %r>' % self.s
+
+ def __str__(self):
+ return self.s
+
+ def __len__(self):
+ return len(self.s)
+
+ def __cmp__(self, other):
+ return cmp(self.s, other)
+
+ def __hash__(self):
+ return hash(self.s)
+
+ def __mod__(self, args):
+ codes = []
+ usedict = 0
+ for format in _format_re.findall(self.s):
+ if format[-1] != '%':
+ if format[1] == '(':
+ usedict = 1
+ codes.append(format[-1])
+ if usedict:
+ args = _DictWrapper(args)
+ else:
+ if len(codes) == 1 and not isinstance(args, TupleType):
+ args = (args,)
+ args = tuple([_wraparg(arg) for arg in args])
+ return self.__class__(self.s % args)
+
+ def __add__(self, other):
+ if isinstance(other, StringType):
+ return self.__class__(self.s + _escape_string(other))
+ elif classof(other) is self.__class__:
+ return self.__class__(self.s + other.s)
+ else:
+ return NotImplemented
+
+ def __radd__(self, other):
+ if isinstance(other, StringType):
+ return self.__class__(_escape_string(other) + self.s)
+ else:
+ return NotImplemented
+
+ def __mul__(self, n):
+ return self.__class__(self.s * n)
+
+ def join(self, items):
+ quoted_items = []
+ for item in items:
+ if classof(item) is self.__class__:
+ quoted_items.append(str(item))
+ elif isinstance(item, StringType):
+ quoted_items.append(_escape_string(item))
+ else:
+ raise TypeError(
+ 'join() requires string arguments (got %r)' % item)
+ return self.__class__(self.s.join(quoted_items))
+
+ def startswith(self, s):
+ if isinstance(s, htmltext):
+ s = s.s
+ else:
+ s = _escape_string(s)
+ return self.s.startswith(s)
+
+ def endswith(self, s):
+ if isinstance(s, htmltext):
+ s = s.s
+ else:
+ s = _escape_string(s)
+ return self.s.endswith(s)
+
+ def replace(self, old, new, maxsplit=-1):
+ if isinstance(old, htmltext):
+ old = old.s
+ else:
+ old = _escape_string(old)
+ if isinstance(new, htmltext):
+ new = new.s
+ else:
+ new = _escape_string(new)
+ return self.__class__(self.s.replace(old, new))
+
+ def lower(self):
+ return self.__class__(self.s.lower())
+
+ def upper(self):
+ return self.__class__(self.s.upper())
+
+ def capitalize(self):
+ return self.__class__(self.s.capitalize())
+
+class _QuoteWrapper(object):
+ # helper for htmltext class __mod__
+
+ __slots__ = ['value', 'escape']
+
+ def __init__(self, value, escape):
+ self.value = value
+ self.escape = escape
+
+ def __str__(self):
+ return self.escape(str(self.value))
+
+ def __repr__(self):
+ return self.escape(`self.value`)
+
+class _DictWrapper(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __getitem__(self, key):
+ return _wraparg(self.value[key])
+
+def _wraparg(arg):
+ if (classof(arg) is htmltext or
+ isinstance(arg, IntType) or
+ isinstance(arg, LongType) or
+ isinstance(arg, FloatType)):
+ # ints, longs, floats, and htmltext are okay
+ return arg
+ else:
+ # everything is gets wrapped
+ return _QuoteWrapper(arg, _escape_string)
+
+def htmlescape(s):
+ """htmlescape(s) -> htmltext
+
+ Return an 'htmltext' object using the argument. If the argument is not
+ already a 'htmltext' object then the HTML markup characters \", <, >,
+ and & are first escaped.
+ """
+ if classof(s) is htmltext:
+ return s
+ elif isinstance(s, UnicodeType):
+ s = s.encode('iso-8859-1')
+ else:
+ s = str(s)
+ # inline _escape_string for speed
+ s = s.replace("&", "&") # must be done first
+ s = s.replace("<", "<")
+ s = s.replace(">", ">")
+ s = s.replace('"', """)
+ return htmltext(s)
+
+
+class TemplateIO(object):
+ """Collect output for PTL scripts.
+ """
+
+ __slots__ = ['html', 'data']
+
+ def __init__(self, html=0):
+ self.html = html
+ self.data = []
+
+ def __iadd__(self, other):
+ if other is not None:
+ self.data.append(other)
+ return self
+
+ def __repr__(self):
+ return ("<%s at %x: %d chunks>" %
+ (self.__class__.__name__, id(self), len(self.data)))
+
+ def __str__(self):
+ return str(self.getvalue())
+
+ def getvalue(self):
+ if self.html:
+ return htmltext('').join(map(htmlescape, self.data))
+ else:
+ return ''.join(map(str, self.data))
Added: packages/quixote1/branches/upstream/current/config.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/config.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/config.py (added)
+++ packages/quixote1/branches/upstream/current/config.py Mon May 8 19:16:16 2006
@@ -1,0 +1,300 @@
+"""quixote.config
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/config.py $
+$Id: config.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Quixote configuration information. This module provides both the
+default configuration values, and some code that Quixote uses for
+dealing with configuration info. You should not edit the configuration
+values in this file, since your edits will be lost if you upgrade to a
+newer Quixote version in the future. However, this is the canonical
+source of information about Quixote configuration variables, and editing
+the defaults here is harmless if you're just playing around and don't
+care what happens in the future.
+"""
+
+__revision__ = "$Id: config.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+# Note that the default values here are geared towards a production
+# environment, preferring security and performance over verbosity and
+# debug-ability. If you just want to get a Quixote application
+# up-and-running in a production environment, these settings are mostly
+# right; all you really need to customize are ERROR_EMAIL, and ERROR_LOG.
+# If you need to test/debug/develop a Quixote application, though, you'll
+# probably want to also change DISPLAY_EXCEPTIONS, SECURE_ERRORS, and
+# maybe RUN_ONCE. Again, you shouldn't edit this file unless you don't
+# care what happens in the future (in particular, an upgrade to Quixote
+# would clobber your edits).
+
+
+# E-mail address to send application errors to; None to send no mail at
+# all. This should probably be the email address of your web
+# administrator.
+ERROR_EMAIL = None
+#ERROR_EMAIL = 'webmaster at example.com'
+
+# Filename for writing the Quixote access log; None for no access log.
+ACCESS_LOG = None
+#ACCESS_LOG = "/www/log/quixote-access.log"
+
+# Filename for logging error messages; if None, everything will be sent
+# to standard error, so it should wind up in the Web server's error log
+# file.
+ERROR_LOG = None
+
+# Filename for logging debugging output; if None then debugging output
+# goes to the error log. (Anything that application code prints to
+# stdout is debug output.)
+DEBUG_LOG = None
+
+# Controls what's done when uncaught exceptions occur. If set to
+# 'plain', the traceback will be returned to the browser in addition
+# to being logged, If set to 'html' and the cgitb module is installed,
+# a more elaborate display will be returned to the browser, showing
+# the local variables and a few lines of context for each level of the
+# traceback. If set to None, a generic error display, containing no
+# information about the traceback, will be used.
+#
+# It is convenient to enable this on development servers. On publicly
+# accessible servers it should be disabled for security reasons.
+#
+# (For backward compatibility reasons, 0 and 1 are also legal values
+# for this setting.)
+DISPLAY_EXCEPTIONS = None
+
+# If true, then any "resource not found" errors will result in a
+# consistent, terse, mostly-useless message. If false, then the
+# exact cause of failure will be returned.
+SECURE_ERRORS = 1
+
+# If true, Quixote will service exactly one request at a time, and
+# then exit. This makes no difference when you're running as a
+# straight CGI script, but it makes it easier to debug while running
+# as a FastCGI script.
+RUN_ONCE = 0
+
+# Automatically redirect paths referencing non-callable objects to a path
+# with a trailing slash. This is convienent for external users of the
+# site but should be disabled for development. Internal links on the
+# site should not require redirects. They are costly, especially on high
+# latency links like dialup lines.
+FIX_TRAILING_SLASH = 1
+
+# Compress large pages using gzip if the client accepts that encoding.
+COMPRESS_PAGES = 0
+
+# If true, then a cryptographically secure token will be inserted into forms
+# as a hidden field. The token will be checked when the form is submitted.
+# This prevents cross-site request forgeries (CSRF). It is off by default
+# since it doesn't work if sessions are not persistent across requests.
+FORM_TOKENS = 0
+
+# If true, the remote IP address of requests will be checked against the
+# IP address that created the session; this is a defense against playback
+# attacks. It will frustrate mobile laptop users, though.
+CHECK_SESSION_ADDR = 0
+
+# Session-related variables
+# =========================
+
+# Name of the cookie that will hold the session ID string.
+SESSION_COOKIE_NAME = "QX_session"
+
+# Domain and path to which the session cookie is restricted. Leaving
+# these undefined is fine. Quixote does not have a default "domain"
+# option, meaning the session cookie will only be sent to the
+# originating server. If you don't set the cookie path, Quixote will
+# use your application's root URL (ie. SCRIPT_NAME in a CGI-like
+# environment), meaning the session cookie will be sent to all URLs
+# controlled by your application, but no other.
+SESSION_COOKIE_DOMAIN = None # eg. ".example.com"
+SESSION_COOKIE_PATH = None # eg. "/"
+
+
+# Mail-related variables
+# ======================
+# These are only used by the quixote.sendmail module, which is
+# provided for use by Quixote applications that need to send
+# e-mail. This is a common task for web apps, but by no means
+# universal.
+#
+# E-mail addresses can be specified either as a lone string
+# containing a bare e-mail address ("addr-spec" in the RFC 822
+# grammar), or as an (address, real_name) tuple.
+
+# MAIL_FROM is used as the default for the "From" header and the SMTP
+# sender for all outgoing e-mail. If you don't set it, your application
+# will crash the first time it tries to send e-mail without an explicit
+# "From" address.
+MAIL_FROM = None # eg. "webmaster at example.com"
+ # or ("webmaster at example.com", "Example Webmaster")
+
+# E-mail is sent by connecting to an SMTP server on MAIL_SERVER. This
+# server must be configured to relay outgoing e-mail from the current
+# host (ie., the host where your Quixote application runs, most likely
+# your web server) to anywhere on the Internet. If you don't know what
+# this means, talk to your system administrator.
+MAIL_SERVER = "localhost"
+
+# If MAIL_DEBUG_ADDR is set, then all e-mail will actually be sent to
+# this address rather than the intended recipients. This should be a
+# single, bare e-mail address.
+MAIL_DEBUG_ADDR = None # eg. "developers at example.com"
+
+
+# HTTP file upload variables
+# ==========================
+
+# Any files upload via HTTP will be written to temporary files
+# in UPLOAD_DIR. If UPLOAD_DIR is not defined, any attempts to
+# upload via HTTP will crash (ie. uncaught exception).
+UPLOAD_DIR = None
+
+# If UPLOAD_DIR does not exist, Quixote will create it with
+# mode UPLOAD_DIR_MODE. No idea what this should be on Windows.
+UPLOAD_DIR_MODE = 0755
+
+
+# -- End config variables ----------------------------------------------
+# (no user serviceable parts after this point)
+
+# Note that this module is designed to not export any names apart from
+# the above config variables and the following Config class -- hence,
+# all imports are done in local scopes. This allows application config
+# modules to safely say "from quixote.config import *".
+
+class ConfigError(Exception):
+
+ def __init__(self, msg, source=None, var=None):
+ self.msg = msg
+ self.source = source
+ self.var = var
+
+ def __str__(self):
+ chunks = []
+ if self.source:
+ chunks.append(self.source)
+ if self.var:
+ chunks.append(self.var)
+ chunks.append(self.msg)
+ return ": ".join(chunks)
+
+
+class Config:
+ """Holds all Quixote configuration variables -- see above for
+ documentation of them. The naming convention is simple:
+ downcase the above variables to get the names of instance
+ attributes of this class.
+ """
+
+ config_vars = [
+ 'error_email',
+ 'access_log',
+ 'debug_log',
+ 'display_exceptions',
+ 'secure_errors',
+ 'error_log',
+ 'run_once',
+ 'fix_trailing_slash',
+ 'compress_pages',
+ 'form_tokens',
+ 'session_cookie_domain',
+ 'session_cookie_name',
+ 'session_cookie_path',
+ 'check_session_addr',
+ 'mail_from',
+ 'mail_server',
+ 'mail_debug_addr',
+ 'upload_dir',
+ 'upload_dir_mode',
+ ]
+
+
+ def __init__(self, read_defaults=1):
+ for var in self.config_vars:
+ setattr(self, var, None)
+ if read_defaults:
+ self.read_defaults()
+
+ def __setattr__(self, attr, val):
+ if not attr in self.config_vars:
+ raise AttributeError, "no such configuration variable: %s" % `attr`
+ self.__dict__[attr] = val
+
+
+ def dump(self, file=None):
+ import sys
+ if file is None:
+ file = sys.stdout
+ file.write("<%s.%s instance at %x>:\n" %
+ (self.__class__.__module__,
+ self.__class__.__name__,
+ id(self)))
+ for var in self.config_vars:
+ file.write(" %s = %s\n" % (var, `getattr(self, var)`))
+
+
+ def set_from_dict(self, dict, source=None):
+ import string, re
+ ucstring_re = re.compile(r'^[A-Z_]+$')
+
+ for (var, val) in dict.items():
+ if ucstring_re.match(var):
+ setattr(self, string.lower(var), val)
+
+ self.check_values(source)
+
+ def check_values(self, source):
+ """
+ check_values(source : string)
+
+ Check the configuration variables to ensure that they
+ are all valid. Raise ConfigError with 'source' as the
+ second argument if any problems are found.
+ """
+ # Check value of DISPLAY_EXCEPTIONS. Values that are
+ # equivalent to 'false' are set to None; a value of 1
+ # is changed to 'plain'.
+ if not self.display_exceptions:
+ self.display_exceptions = None
+ elif self.display_exceptions == 1:
+ self.display_exceptions = 'plain'
+ if self.display_exceptions not in (None, 'plain', 'html'):
+ raise ConfigError("Must be None,"
+ " 'plain', or 'html'",
+ source,
+ "DISPLAY_EXCEPTIONS")
+
+
+ def read_file(self, filename):
+ """Read configuration from a file. Any variables already
+ defined in this Config instance, but not in the file, are
+ unchanged, so you can use this to build up a configuration
+ by accumulating data from several config files.
+ """
+ # The config file is Python code -- makes life easy.
+ config_vars = {}
+ try:
+ execfile(filename, config_vars)
+ except IOError, exc:
+ if exc.filename is None: # arg! execfile() loses filename
+ exc.filename = filename
+ raise exc
+
+ self.set_from_dict(config_vars, source=filename)
+
+ def read_from_module(self, modname):
+ """Read configuration info from a Python module (default
+ is the module where the Config class is defined, ie.
+ quixote.config). Also accumulates config data, just like
+ 'read_file()'.
+ """
+ import sys
+ __import__(modname)
+ module = sys.modules[modname]
+ self.set_from_dict(vars(module), source=module.__file__)
+
+ def read_defaults(self):
+ self.read_from_module("quixote.config")
+
+# class Config
Added: packages/quixote1/branches/upstream/current/demo/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/demo/__init__.py Mon May 8 19:16:16 2006
@@ -1,0 +1,41 @@
+
+_q_exports = ["simple", "error", "publish_error", "widgets",
+ "form_demo", "dumpreq", "srcdir",
+ ("favicon.ico", "q_ico")]
+
+import sys
+from quixote.demo.pages import _q_index, _q_exception_handler, dumpreq
+from quixote.demo.widgets import widgets
+from quixote.demo.integer_ui import IntegerUI
+from quixote.errors import PublishError
+from quixote.util import StaticDirectory, StaticFile
+
+def simple(request):
+ # This function returns a plain text document, not HTML.
+ request.response.set_content_type("text/plain")
+ return "This is the Python function 'quixote.demo.simple'.\n"
+
+def error(request):
+ raise ValueError, "this is a Python exception"
+
+def publish_error(request):
+ raise PublishError(public_msg="Publishing error raised by publish_error")
+
+def _q_lookup(request, component):
+ return IntegerUI(request, component)
+
+def _q_resolve(component):
+ # _q_resolve() is a hook that can be used to import only
+ # when it's actually accessed. This can be used to make
+ # start-up of your application faster, because it doesn't have
+ # to import every single module when it starts running.
+ if component == 'form_demo':
+ from quixote.demo.forms import form_demo
+ return form_demo
+
+# Get current directory
+import os
+from quixote.demo import forms
+curdir = os.path.dirname(forms.__file__)
+srcdir = StaticDirectory(curdir, list_directory=1)
+q_ico = StaticFile(os.path.join(curdir, 'q.ico'))
Added: packages/quixote1/branches/upstream/current/demo/demo.cgi
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/demo.cgi?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/demo.cgi (added)
+++ packages/quixote1/branches/upstream/current/demo/demo.cgi Mon May 8 19:16:16 2006
@@ -1,0 +1,21 @@
+#!/www/python/bin/python
+
+# Example driver script for the Quixote demo: publishes the contents of
+# the quixote.demo package.
+
+from quixote import enable_ptl, Publisher
+
+# Install the import hook that enables PTL modules.
+enable_ptl()
+
+# Create a Publisher instance
+app = Publisher('quixote.demo')
+
+# (Optional step) Read a configuration file
+app.read_config("demo.conf")
+
+# Open the configured log files
+app.setup_logs()
+
+# Enter the publishing main loop
+app.publish_cgi()
Propchange: packages/quixote1/branches/upstream/current/demo/demo.cgi
------------------------------------------------------------------------------
svn:executable =
Added: packages/quixote1/branches/upstream/current/demo/demo.conf
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/demo.conf?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/demo.conf (added)
+++ packages/quixote1/branches/upstream/current/demo/demo.conf Mon May 8 19:16:16 2006
@@ -1,0 +1,9 @@
+# Config file for the Quixote demo. This ensures that debug and error
+# messages will be logged, and that you will see full error information
+# in your browser. (The default settings shipped in Quixote's config.py
+# module are for security rather than ease of testing/development.)
+
+ERROR_LOG = "/tmp/quixote-demo-error.log"
+DISPLAY_EXCEPTIONS = "plain"
+SECURE_ERRORS = 0
+FIX_TRAILING_SLASH = 0
Added: packages/quixote1/branches/upstream/current/demo/demo_scgi.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/demo_scgi.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/demo_scgi.py (added)
+++ packages/quixote1/branches/upstream/current/demo/demo_scgi.py Mon May 8 19:16:16 2006
@@ -1,0 +1,35 @@
+#!/www/python/bin/python
+
+# Example SCGI driver script for the Quixote demo: publishes the contents of
+# the quixote.demo package. To use this script with mod_scgi and Apache
+# add the following section of your Apache config file:
+#
+# <Location "^/qdemo/">
+# SCGIServer 127.0.0.1 4000
+# SCGIHandler On
+# </Location>
+
+
+from scgi.quixote_handler import QuixoteHandler, main
+from quixote import enable_ptl, Publisher
+
+class DemoPublisher(Publisher):
+ def __init__(self, *args, **kwargs):
+ Publisher.__init__(self, *args, **kwargs)
+
+ # (Optional step) Read a configuration file
+ self.read_config("demo.conf")
+
+ # Open the configured log files
+ self.setup_logs()
+
+
+class DemoHandler(QuixoteHandler):
+ publisher_class = DemoPublisher
+ root_namespace = "quixote.demo"
+ prefix = "/qdemo"
+
+
+# Install the import hook that enables PTL modules.
+enable_ptl()
+main(DemoHandler)
Propchange: packages/quixote1/branches/upstream/current/demo/demo_scgi.py
------------------------------------------------------------------------------
svn:executable =
Added: packages/quixote1/branches/upstream/current/demo/demo_scgi.sh
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/demo_scgi.sh?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/demo_scgi.sh (added)
+++ packages/quixote1/branches/upstream/current/demo/demo_scgi.sh Mon May 8 19:16:16 2006
@@ -1,0 +1,52 @@
+#!/bin/sh
+#
+# Example init.d script for demo_scgi.py server
+
+PATH=/bin:/usr/bin:/usr/local/bin
+DAEMON=./demo_scgi.py
+PIDFILE=/var/tmp/demo_scgi.pid
+
+NAME=`basename $DAEMON`
+case "$1" in
+ start)
+ if [ -f $PIDFILE ]; then
+ if ps -p `cat $PIDFILE` > /dev/null 2>&1 ; then
+ echo "$NAME appears to be already running ($PIDFILE exists)."
+ exit 1
+ else
+ echo "$PIDFILE exists, but appears to be obsolete; removing it"
+ rm $PIDFILE
+ fi
+ fi
+
+ echo -n "Starting $NAME: "
+ env -i PATH=$PATH \
+ $DAEMON -P $PIDFILE -l /var/tmp/quixote-error.log
+ echo "done"
+ ;;
+
+ stop)
+ if [ -f $PIDFILE ]; then
+ echo -n "Stopping $NAME: "
+ kill `cat $PIDFILE`
+ echo "done"
+ if ps -p `cat $PIDFILE` > /dev/null 2>&1 ; then
+ echo "$NAME is still running, not removing $PIDFILE"
+ else
+ rm -f $PIDFILE
+ fi
+ else
+ echo "$NAME does not appear to be running ($PIDFILE doesn't exist)."
+ fi
+ ;;
+
+ restart)
+ $0 stop
+ $0 start
+ ;;
+
+ *)
+ echo "Usage: $0 {start|stop|restart}"
+ exit 1
+ ;;
+esac
Propchange: packages/quixote1/branches/upstream/current/demo/demo_scgi.sh
------------------------------------------------------------------------------
svn:executable =
Added: packages/quixote1/branches/upstream/current/demo/forms.ptl
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/forms.ptl?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/forms.ptl (added)
+++ packages/quixote1/branches/upstream/current/demo/forms.ptl Mon May 8 19:16:16 2006
@@ -1,0 +1,114 @@
+# quixote.demo.forms
+#
+# Demonstrate the Quixote form class.
+
+__revision__ = "$Id: forms.ptl 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+import time
+from quixote.form import Form
+
+class Topping:
+ def __init__(self, name, cost):
+ self.name = name
+ self.cost = cost # in cents
+
+ def __str__(self):
+ return "%s: $%.2f" % (self.name, self.cost/100.)
+
+ def __repr__(self):
+ return "<%s at %08x: %s>" % (self.__class__.__name__,
+ id(self), self)
+
+
+TOPPINGS = [Topping('cheese', 50),
+ Topping('pepperoni', 110),
+ Topping('green peppers', 75),
+ Topping('mushrooms', 90),
+ Topping('sausage', 100),
+ Topping('anchovies', 30),
+ Topping('onions', 25)]
+
+class FormDemo(Form):
+ def __init__(self):
+ # build form
+ Form.__init__(self)
+ self.add_widget("string", "name", title="Your Name",
+ size=20, required=1)
+ self.add_widget("password", "password", title="Password",
+ size=20, maxlength=20, required=1)
+ self.add_widget("checkbox", "confirm",
+ title="Are you sure?")
+ self.add_widget("radiobuttons", "color", title="Eye color",
+ allowed_values=['green', 'blue', 'brown', 'other'])
+ self.add_widget("single_select", "size", title="Size of pizza",
+ value='medium',
+ allowed_values=['tiny', 'small', 'medium', 'large',
+ 'enormous'],
+ descriptions=['Tiny (4")', 'Small (6")', 'Medium (10")',
+ 'Large (14")', 'Enormous (18")'],
+ size=1)
+ # select widgets can use any type of object, no just strings
+ self.add_widget("multiple_select", "toppings", title="Pizza Toppings",
+ value=TOPPINGS[0],
+ allowed_values=TOPPINGS,
+ size=5)
+ self.add_widget('hidden', 'time', value=time.time())
+ self.add_submit_button("go", "Go!")
+
+
+ def render [html] (self, request, action_url):
+ """
+ <html>
+ <head><title>Quixote Form Demo</title></head>
+ <body>
+ <h1>Quixote Form Demo</h1>
+ """
+ Form.render(self, request, action_url)
+ """
+ </body>
+ </html>
+ """
+
+
+ def process(self, request):
+ # check data
+ form_data = Form.process(self, request)
+ if not form_data["name"]:
+ self.error["name"] = "You must provide your name."
+ if not form_data["password"]:
+ self.error["password"] = "You must provide a password."
+ return form_data
+
+
+ def action [html] (self, request, submit_button, form_data):
+ # The data has been submitted and verified. Do something interesting
+ # with it (save it in DB, send email, etc.). We'll just display it.
+ """
+ <html>
+ <head><title>Quixote Form Demo</title></head>
+ <body>
+ <h2>Form data:</h2>
+ <table>
+ <tr>
+ <th align=left>Name</th>
+ <th align=left>Type</th>
+ <th align=left>Value</th>
+ </tr>
+ """
+ for name, value in form_data.items():
+ '<tr>'
+ ' <td>%s</td>' % name
+ ' <td>%s</td>' % type(value).__name__
+ if value is None:
+ value = "<i>no value</i>"
+ ' <td>%s</td>' % value
+ '</tr>'
+ """
+ </table>
+ </body>
+ </html>
+ """
+
+def form_demo(request):
+ return FormDemo().handle(request)
Added: packages/quixote1/branches/upstream/current/demo/integer_ui.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/integer_ui.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/integer_ui.py (added)
+++ packages/quixote1/branches/upstream/current/demo/integer_ui.py Mon May 8 19:16:16 2006
@@ -1,0 +1,59 @@
+import sys
+from quixote.errors import TraversalError
+
+def fact(n):
+ f = 1L
+ while n > 1:
+ f *= n
+ n -= 1
+ return f
+
+class IntegerUI:
+
+ _q_exports = ["factorial", "prev", "next"]
+
+ def __init__(self, request, component):
+ try:
+ self.n = int(component)
+ except ValueError, exc:
+ raise TraversalError(str(exc))
+
+ def factorial(self, request):
+ if self.n > 10000:
+ sys.stderr.write("warning: possible denial-of-service attack "
+ "(request for factorial(%d))\n" % self.n)
+ request.response.set_header("content-type", "text/plain")
+ return "%d! = %d\n" % (self.n, fact(self.n))
+
+ def _q_index(self, request):
+ return """\
+<html>
+<head><title>The Number %d</title></head>
+<body>
+You have selected the integer %d.<p>
+
+You can compute its <a href="factorial">factorial</a> (%d!)<p>
+
+Or, you can visit the web page for the
+<a href="../%d/">previous</a> or
+<a href="../%d/">next</a> integer.<p>
+
+Or, you can use redirects to visit the
+<a href="prev">previous</a> or
+<a href="next">next</a> integer. This makes
+it a bit easier to generate this HTML code, but
+it's less efficient -- your browser has to go through
+two request/response cycles. And someone still
+has to generate the URLs for the previous/next
+pages -- only now it's done in the <code>prev()</code>
+and <code>next()</code> methods for this integer.<p>
+
+</body>
+</html>
+""" % (self.n, self.n, self.n, self.n-1, self.n+1)
+
+ def prev(self, request):
+ return request.redirect("../%d/" % (self.n-1))
+
+ def next(self, request):
+ return request.redirect("../%d/" % (self.n+1))
Added: packages/quixote1/branches/upstream/current/demo/pages.ptl
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/pages.ptl?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/pages.ptl (added)
+++ packages/quixote1/branches/upstream/current/demo/pages.ptl Mon May 8 19:16:16 2006
@@ -1,0 +1,87 @@
+# quixote.demo.pages
+#
+# Provides miscellaneous pages for the Quixote demo (currently
+# just the index page).
+
+__revision__ = "$Id: pages.ptl 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+def _q_index [html] (request):
+ print "debug message from the index page"
+ package_name = str('.').join(__name__.split(str('.'))[:-1])
+ module_name = __name__
+ module_file = __file__
+ """
+ <html>
+ <head><title>Quixote Demo</title></head>
+ <body>
+ <h1>Hello, world!</h1>
+
+ <p>(This page is generated by the index function for the
+ <code>%(package_name)s</code> package. This index function is
+ actually a PTL template, <code>_q_index()</code>, in the
+ <code>%(module_name)s</code> PTL module. Look in
+ <a href="srcdir/pages.ptl">%(module_file)s</a> to
+ see the source code for this PTL template.)
+ </p>
+
+ <p>To understand what's going on here, be sure to read the
+ <code>doc/demo.txt</code> file included with Quixote.</p>
+
+ <p>
+ Here are some other features of this demo:
+ <ul>
+ <li><a href="simple">simple</a>:
+ A Python function that generates a very simple document.
+ <li><a href="error">error</a>:
+ A Python function that raises an exception.
+ <li><a href="publish_error">publish_error</a>:
+ A Python function that raises
+ a <code>PublishError</code> exception. This exception
+ will be caught by a <code>_q_exception_handler</code> method.
+ <li><a href="12/">12/</a>:
+ A Python object published through <code>_q_lookup()</code>.
+ <li><a href="12/factorial">12/factorial</a>:
+ A method on a published Python object.
+ <li><a href="dumpreq">dumpreq</a>:
+ Print out the contents of the HTTPRequest object.
+ <li><a href="widgets">widgets</a>:
+ Try out the Quixote widget classes.
+ <li><a href="form_demo">form demo</a>:
+ A Quixote form in action.
+ <li><a href="srcdir/">srcdir</a>:
+ A static directory published through Quixote.
+ </ul>
+ </p>
+ </body>
+ </html>
+ """ % vars()
+
+def _q_exception_handler [html] (request, exc):
+ """
+ <html>
+ <head><title>Quixote Demo</title></head>
+ <body>
+ <h1>Exception Handler</h1>
+ <p>A <code>_q_exception_handler</code> method, if present, is
+ called when a <code>PublishError</code> exception is raised. It
+ can do whatever it likes to provide a friendly page.
+ </p>
+ <p>Here's the exception that was raised:<br />
+ <code>%s (%s)</code>.</p>
+ </body>
+ </html>
+ """ % (repr(exc), str(exc))
+
+def dumpreq [html] (request):
+ """
+ <html>
+ <head><title>HTTPRequest Object</title></head>
+ <body>
+ <h1>HTTPRequest Object</h1>
+ """
+ htmltext(request.dump_html())
+ """
+ </body>
+ </html>
+ """
Added: packages/quixote1/branches/upstream/current/demo/run_cgi.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/run_cgi.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/run_cgi.py (added)
+++ packages/quixote1/branches/upstream/current/demo/run_cgi.py Mon May 8 19:16:16 2006
@@ -1,0 +1,29 @@
+# This is a simple script that makes it easy to write one file CGI
+# applications that use Quixote. To use, add the following line to the top
+# of your CGI script:
+#
+# #!/usr/local/bin/python <some_path>/run_cgi.py
+#
+# Your CGI script becomes the root namespace and you may use PTL syntax
+# inside the script. Errors will go to stderr and should end up in the server
+# error log.
+#
+# Note that this will be quite slow since the script will be recompiled on
+# every hit. If you are using Apache with mod_fastcgi installed you should be
+# able to use .fcgi as an extension instead of .cgi and get much better
+# performance. Maybe someday I will write code that caches the compiled
+# script on the filesystem. :-)
+
+import sys
+import new
+from quixote import enable_ptl, ptl_compile, Publisher
+
+enable_ptl()
+filename = sys.argv[1]
+root_code = ptl_compile.compile_template(open(filename), filename)
+root = new.module("root")
+root.__file__ = filename
+root.__name__ = "root"
+exec root_code in root.__dict__
+p = Publisher(root)
+p.publish_cgi()
Added: packages/quixote1/branches/upstream/current/demo/session.ptl
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/session.ptl?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/session.ptl (added)
+++ packages/quixote1/branches/upstream/current/demo/session.ptl Mon May 8 19:16:16 2006
@@ -1,0 +1,151 @@
+# quixote.demo.session
+#
+# Application code for the Quixote session management demo.
+# Driver script is session_demo.cgi.
+
+__revision__ = "$Id: session.ptl 23532 2004-02-20 22:38:57Z dbinger $"
+
+from quixote import get_session_manager
+from quixote.errors import QueryError
+
+_q_exports = ['login', 'logout']
+
+
+# Typical stuff for any Quixote app.
+
+def page_header [html] (title):
+ '''\
+<html>
+<head><title>%s</title></head>
+<body>
+<h1>%s</h1>
+''' % (title, title)
+
+def page_footer [html] ():
+ '''\
+</body>
+</html>
+'''
+
+
+# We include the login form on two separate pages, so it's been factored
+# out to a separate template.
+
+def login_form [html] ():
+ '''
+<form method="POST" action="login">
+ <input name="name" width=30>
+ <input type="submit">
+</form>
+'''
+
+
+def _q_index [html] (request):
+ page_header("Quixote Session Management Demo")
+
+ session = request.session
+
+ # All Quixote sessions have the ability to track the user's identity
+ # in session.user. In this simple application, session.user is just
+ # a string which the user enters directly into this form. In the
+ # real world, you would of course use a more sophisticated form of
+ # authentication (eg. enter a password over an SSL connection), and
+ # session.user might be an object with information about the user
+ # (their email address, password hash, preferences, etc.).
+
+ if session.user is None:
+ '''
+<p>You haven\'t introduced yourself yet.<br>
+Please tell me your name:
+'''
+ login_form()
+ else:
+ '<p>Hello, %s. Good to see you again.</p>\n' % session.user
+
+ '''
+You can now:
+<ul>
+'''
+ if session.user:
+ ' <li><a href="login">become someone else</a> (login again)\n'
+ ' <li><a href="logout">leave this session</a> (logout)\n'
+ '</ul>\n'
+
+ # The other piece of information we track here is the number of
+ # requests made in each session; report that information for the
+ # current session here.
+ """\
+<p>Your session is <code>%s</code><br>
+You have made %d request(s) (including this one) in this session.</p>
+""" % (repr(session), session.num_requests)
+
+ # The session manager is the collection of all sessions managed by
+ # the current publisher, ie. in this process. Poking around in the
+ # session manager is not something you do often, but it's really
+ # handy for debugging/site administration.
+ mgr = get_session_manager()
+ session_ids = mgr.keys()
+ '''
+<p>The current session manager is <code>%s</code><br>
+It has %d session(s) in it right now:</p>
+<table border=1>
+ <tr><th>session id</th><th>user</th><th>num requests</th></tr>
+''' % (repr(mgr), len(session_ids))
+ for sess_id in session_ids:
+ sess = mgr[sess_id]
+ (' <tr><td>%s</td><td>%s</td><td>%d</td>\n'
+ % (sess.id,
+ sess.user and sess.user or "<i>none</i>",
+ sess.num_requests))
+ '<table>\n'
+
+ page_footer()
+
+
+# The login() template has two purposes: to display a page with just a
+# login form, and to process the login form submitted either from the
+# index page or from login() itself. This is a fairly common idiom in
+# Quixote (just as it's a fairly common idiom with CGI scripts -- it's
+# just cleaner with Quixote).
+
+def login [html] (request):
+ page_header("Quixote Session Demo: Login")
+ session = request.session
+
+ # We seem to be processing the login form.
+ if request.form:
+ user = request.form.get("name")
+ if not user:
+ raise QueryError("no user name supplied")
+
+ session.user = user
+
+ '<p>Welcome, %s! Thank you for logging in.</p>\n' % user
+ '<a href="./">back to start</a>\n'
+
+ # No form data to process, so generate the login form instead. When
+ # the user submits it, we'll return to this template and take the
+ # above branch.
+ else:
+ '<p>Please enter your name here:</p>\n'
+ login_form()
+
+ page_footer()
+
+
+# logout() just expires the current session, ie. removes it from the
+# session manager and instructs the client to forget about the session
+# cookie. The only code necessary is the call to
+# SessionManager.expire_session() -- the rest is just user interface.
+
+def logout [html] (request):
+ page_header("Quixote Session Demo: Logout")
+ session = request.session
+ if session.user:
+ '<p>Goodbye, %s. See you around.</p>\n' % session.user
+
+ get_session_manager().expire_session(request)
+
+ '<p>Your session has been expired.</p>\n'
+ '<p><a href="./">start over</a></p>\n'
+ page_footer()
Added: packages/quixote1/branches/upstream/current/demo/session_demo.cgi
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/session_demo.cgi?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/session_demo.cgi (added)
+++ packages/quixote1/branches/upstream/current/demo/session_demo.cgi Mon May 8 19:16:16 2006
@@ -1,0 +1,173 @@
+#!/www/python/bin/python
+
+# Demonstrate Quixote session management, along with the application
+# code in session.ptl (aka quixote.demo.session).
+
+__revision__ = "$Id: session_demo.cgi 21182 2003-03-17 21:46:52Z gward $"
+
+import os
+from stat import ST_MTIME
+from time import time
+from cPickle import load, dump
+from quixote import enable_ptl
+from quixote.session import Session, SessionManager
+from quixote.publish import SessionPublisher
+
+class DemoSession (Session):
+ """
+ Session class that tracks the number of requests made within a
+ session.
+ """
+
+ def __init__ (self, request, id):
+ Session.__init__(self, request, id)
+ self.num_requests = 0
+
+ def start_request (self, request):
+
+ # This is called from the main object publishing loop whenever
+ # we start processing a new request. Obviously, this is a good
+ # place to track the number of requests made. (If we were
+ # interested in the number of *successful* requests made, then
+ # we could override finish_request(), which is called by
+ # the publisher at the end of each successful request.)
+
+ Session.start_request(self, request)
+ self.num_requests += 1
+
+ def has_info (self):
+
+ # Overriding has_info() is essential but non-obvious. The
+ # session manager uses has_info() to know if it should hang on
+ # to a session object or not: if a session is "dirty", then it
+ # must be saved. This prevents saving sessions that don't need
+ # to be saved, which is especially important as a defensive
+ # measure against clients that don't handle cookies: without it,
+ # we might create and store a new session object for every
+ # request made by such clients. With has_info(), we create the
+ # new session object every time, but throw it away unsaved as
+ # soon as the request is complete.
+ #
+ # (Of course, if you write your session class such that
+ # has_info() always returns true after a request has been
+ # processed, you're back to the original problem -- and in fact,
+ # this class *has* been written that way, because num_requests
+ # is incremented on every request, which makes has_info() return
+ # true, which makes SessionManager always store the session
+ # object. In a real application, think carefully before putting
+ # data in a session object that causes has_info() to return
+ # true.)
+
+ return (self.num_requests > 0) or Session.has_info(self)
+
+ is_dirty = has_info
+
+
+class DirMapping:
+ """A mapping object that stores values as individual pickle
+ files all in one directory. You wouldn't want to use this in
+ production unless you're using a filesystem optimized for
+ handling large numbers of small files, like ReiserFS. However,
+ it's pretty easy to implement and understand, it doesn't require
+ any external libraries, and it's really easy to browse the
+ "database".
+ """
+
+ def __init__ (self, save_dir=None):
+ self.set_save_dir(save_dir)
+ self.cache = {}
+ self.cache_time = {}
+
+ def set_save_dir (self, save_dir):
+ self.save_dir = save_dir
+ if save_dir and not os.path.isdir(save_dir):
+ os.mkdir(save_dir, 0700)
+
+ def keys (self):
+ return os.listdir(self.save_dir)
+
+ def values (self):
+ # This is pretty expensive!
+ return [self[id] for id in self.keys()]
+
+ def items (self):
+ return [(id, self[id]) for id in self.keys()]
+
+ def _gen_filename (self, session_id):
+ return os.path.join(self.save_dir, session_id)
+
+ def __getitem__ (self, session_id):
+
+ filename = self._gen_filename(session_id)
+ if (self.cache.has_key(session_id) and
+ os.stat(filename)[ST_MTIME] <= self.cache_time[session_id]):
+ return self.cache[session_id]
+
+ if os.path.exists(filename):
+ try:
+ file = open(filename, "rb")
+ try:
+ print "loading session from %r" % file
+ session = load(file)
+ self.cache[session_id] = session
+ self.cache_time[session_id] = time()
+ return session
+ finally:
+ file.close()
+ except IOError, err:
+ raise KeyError(session_id,
+ "error reading session from %s: %s"
+ % (filename, err))
+ else:
+ raise KeyError(session_id,
+ "no such file %s" % filename)
+
+ def get (self, session_id, default=None):
+ try:
+ return self[session_id]
+ except KeyError:
+ return default
+
+ def has_key (self, session_id):
+ return os.path.exists(self._gen_filename(session_id))
+
+ def __setitem__ (self, session_id, session):
+ filename = self._gen_filename(session.id)
+ file = open(filename, "wb")
+ print "saving session to %s" % file
+ dump(session, file, 1)
+ file.close()
+
+ self.cache[session_id] = session
+ self.cache_time[session_id] = time()
+
+ def __delitem__ (self, session_id):
+ filename = self._gen_filename(session_id)
+ if os.path.exists(filename):
+ os.remove(filename)
+ if self.cache.has_key(session_id):
+ del self.cache[session_id]
+ del self.cache_time[session_id]
+ else:
+ raise KeyError(session_id, "no such file: %s" % filename)
+
+
+# This is mostly the same as the standard boilerplate for any Quixote
+# driver script. The main difference is that we have to instantiate a
+# session manager, and use SessionPublisher instead of the normal
+# Publisher class. Just like demo.cgi, we use demo.conf to setup log
+# files and ensure that error messages are more informative than secure.
+
+# You can use the 'shelve' module to create an alternative persistent
+# mapping to the DirMapping class above.
+#import shelve
+#sessions = shelve.open("/tmp/quixote-sessions")
+
+enable_ptl()
+sessions = DirMapping(save_dir="/tmp/quixote-session-demo")
+session_mgr = SessionManager(session_class=DemoSession,
+ session_mapping=sessions)
+app = SessionPublisher('quixote.demo.session', session_mgr=session_mgr)
+app.read_config("demo.conf")
+app.setup_logs()
+app.publish_cgi()
Propchange: packages/quixote1/branches/upstream/current/demo/session_demo.cgi
------------------------------------------------------------------------------
svn:executable =
Added: packages/quixote1/branches/upstream/current/demo/upload.cgi
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/upload.cgi?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/upload.cgi (added)
+++ packages/quixote1/branches/upstream/current/demo/upload.cgi Mon May 8 19:16:16 2006
@@ -1,0 +1,64 @@
+#!/www/python/bin/python
+
+# Simple demo of HTTP upload with Quixote. Also serves as an example
+# of how to put a (simple) Quixote application into a single file.
+
+__revision__ = "$Id: upload.cgi 21182 2003-03-17 21:46:52Z gward $"
+
+import os
+import stat
+from quixote import Publisher
+from quixote.html import html_quote
+
+_q_exports = ['receive']
+
+def header (title):
+ return '''\
+ <html><head><title>%s</title></head>
+ <body>
+ ''' % title
+
+def footer ():
+ return '</body></html>\n'
+
+def _q_index (request):
+ return header("Quixote Upload Demo") + '''\
+ <form enctype="multipart/form-data"
+ method="POST"
+ action="receive">
+ Your name:<br>
+ <input type="text" name="name"><br>
+ File to upload:<br>
+ <input type="file" name="upload"><br>
+ <input type="submit" value="Upload">
+ </form>
+ ''' + footer()
+
+def receive (request):
+ result = []
+ name = request.form.get("name")
+ if name:
+ result.append("<p>Thanks, %s!</p>" % html_quote(name))
+
+ upload = request.form.get("upload")
+ size = os.stat(upload.tmp_filename)[stat.ST_SIZE]
+ if not upload.base_filename or size == 0:
+ title = "Empty Upload"
+ result.append("<p>You appear not to have uploaded anything.</p>")
+ else:
+ title = "Upload Received"
+ result.append("<p>You just uploaded <code>%s</code> (%d bytes)<br>"
+ % (html_quote(upload.base_filename), size))
+ result.append("which is temporarily stored in <code>%s</code>.</p>"
+ % html_quote(upload.tmp_filename))
+
+ return header(title) + "\n".join(result) + footer()
+
+def main ():
+ pub = Publisher('__main__')
+ pub.read_config("demo.conf")
+ pub.configure(UPLOAD_DIR="/tmp/quixote-upload-demo")
+ pub.setup_logs()
+ pub.publish_cgi()
+
+main()
Propchange: packages/quixote1/branches/upstream/current/demo/upload.cgi
------------------------------------------------------------------------------
svn:executable =
Added: packages/quixote1/branches/upstream/current/demo/widgets.ptl
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/demo/widgets.ptl?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/demo/widgets.ptl (added)
+++ packages/quixote1/branches/upstream/current/demo/widgets.ptl Mon May 8 19:16:16 2006
@@ -1,0 +1,129 @@
+# quixote.demo.widgets
+#
+# Demonstrate the Quixote widget classes.
+
+__revision__ = "$Id: widgets.ptl 23532 2004-02-20 22:38:57Z dbinger $"
+
+
+import time
+from quixote.form import widget
+
+
+def widgets(request):
+
+ # Whether we are generating or processing the form with these
+ # widgets, we need all the widget objects -- so create them now.
+ widgets = {}
+ widgets['name'] = widget.StringWidget('name', size=20)
+ widgets['password'] = widget.PasswordWidget(
+ 'password', size=20, maxlength=20)
+ widgets['confirm'] = widget.CheckboxWidget('confirm')
+ widgets['colour'] = widget.RadiobuttonsWidget(
+ 'colour', allowed_values=['green', 'blue', 'brown', 'other'])
+ widgets['size'] = widget.SingleSelectWidget(
+ 'size', value='medium',
+ allowed_values=['tiny', 'small', 'medium', 'large', 'enormous'],
+ descriptions=['Tiny (4")', 'Small (6")', 'Medium (10")',
+ 'Large (14")', 'Enormous (18")'])
+ widgets['toppings'] = widget.MultipleSelectWidget(
+ 'toppings', value=['cheese'],
+ allowed_values=['cheese', 'pepperoni', 'green peppers', 'mushrooms',
+ 'sausage', 'anchovies', 'onions'],
+ size=5)
+ widgets['time'] = widget.HiddenWidget('time', value=time.time())
+
+ if request.form:
+ # If we have some form data, then we're being invoked to process
+ # the form; call process_widgets() to do the real work. We only
+ # handle it in this page to conserve urls: the "widget" url both
+ # generates the form and processes it, and behaves very
+ # differently depending on whether there are form variables
+ # present when it is invoked.
+ return process_widgets(request, widgets)
+ else:
+ # No form data, so generate the form from scratch. When the
+ # user submits it, we'll come back to this page, but
+ # request.form won't be empty that time -- so we'll call
+ # process_widgets() instead.
+ return render_widgets(request, widgets)
+
+
+def render_widgets [html] (request, widgets):
+ """\
+<html>
+<head><title>Quixote Widget Demo</title></head>
+<body>
+<h1>Quixote Widget Demo</h1>
+"""
+
+ """\
+<form method="POST" action="widgets">
+<table>
+"""
+ row_fmt = '''\
+ <tr>
+ <th align="left">%s</th>
+ <td colspan=2>%s</td>
+ </tr>
+'''
+ row_fmt % ("Your name", widgets['name'].render(request))
+ row_fmt % ("Password", widgets['password'].render(request))
+ row_fmt % ("Are you sure?", widgets['confirm'].render(request))
+ row_fmt % ("Eye colour", widgets['colour'].render(request))
+
+ '''\
+ <tr>
+ <th align="left" valign="top">Select a<br>size of pizza</th>
+ <td valign="top">%s</td>
+ <th align="left" valign="top">And some<br>pizza toppings</th>
+ <td valign="top">%s</td>
+ </tr>
+''' % (widgets['size'].render(request),
+ widgets['toppings'].render(request))
+
+ widgets['time'].render(request)
+
+ '</table>\n'
+ widget.SubmitButtonWidget(value="Submit").render(request)
+ '''\
+</form>
+</body>
+</html>
+'''
+
+def process_widgets [html] (request, widgets):
+ """\
+<html>
+<head><title>Quixote Widget Demo</title></head>
+<body>
+<h2>You entered the following values:</h2>
+<table>
+"""
+
+ row_fmt = ' <tr><th align="left">%s</th><td>%s</td></tr>\n'
+ fallback = '<i>nothing</i>'
+ row_fmt % ("name",
+ widgets['name'].parse(request) or fallback)
+ row_fmt % ("password",
+ widgets['password'].parse(request) or fallback)
+ row_fmt % ("confirmation",
+ widgets['confirm'].parse(request))
+ row_fmt % ("eye colour",
+ widgets['colour'].parse(request) or fallback)
+ row_fmt % ("pizza size",
+ widgets['size'].parse(request) or fallback)
+ toppings = widgets['toppings'].parse(request)
+ row_fmt % ("pizza toppings",
+ toppings and (", ".join(toppings)) or fallback)
+
+ '</table>\n'
+
+ form_time = float(widgets['time'].parse(request))
+ now = time.time()
+ ("<p>It took you %.1f sec to fill out and submit the form</p>\n"
+ % (now - form_time))
+
+ """\
+</body>
+</html>
+"""
Added: packages/quixote1/branches/upstream/current/doc/INSTALL.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/INSTALL.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/INSTALL.html (added)
+++ packages/quixote1/branches/upstream/current/doc/INSTALL.html Mon May 8 19:16:16 2006
@@ -1,0 +1,55 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Installing Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="installing-quixote">
+<h1 class="title">Installing Quixote</h1>
+<div class="section" id="best-case-scenario">
+<h1><a name="best-case-scenario">Best-case scenario</a></h1>
+<p>If you are using Python 2.2 or later, and have never installed Quixote
+with your current version of Python, you're in luck. Just run</p>
+<blockquote>
+python setup.py install</blockquote>
+<p>and you're done. Proceed to the demo documentation to learn how to get
+Quixote working.</p>
+<p>If you're using an older Python, or if you're upgrading from an older
+Quixote version, read on.</p>
+</div>
+<div class="section" id="upgrading-from-an-older-quixote-version">
+<h1><a name="upgrading-from-an-older-quixote-version">Upgrading from an older Quixote version</a></h1>
+<p>We strongly recommend that you remove any old Quixote version before
+installing a new one. First, find out where your old Quixote
+installation is:</p>
+<blockquote>
+python -c "import os, quixote; print os.path.dirname(quixote.__file__)"</blockquote>
+<p>and then remove away the reported directory. (If the import fails, then
+you don't have an existing Quixote installation.)</p>
+<p>Then proceed as above:</p>
+<blockquote>
+python setup.py install</blockquote>
+</div>
+<div class="section" id="using-quixote-with-python-2-0-or-2-1">
+<h1><a name="using-quixote-with-python-2-0-or-2-1">Using Quixote with Python 2.0 or 2.1</a></h1>
+<p>If you are using Python 2.0 or 2.1 then you need to install the
+<tt class="literal"><span class="pre">compiler</span></tt> package from the Python source distribution. The
+<tt class="literal"><span class="pre">compiler</span></tt> package is for parsing Python source code and generating
+Python bytecode, and the PTL compiler is built on top of it. With
+Python 2.0 and 2.1, this package was included in Python's source
+distribution, but not installed as part of the standard library.</p>
+<p>Assuming your Python source distribution is in <tt class="literal"><span class="pre">/tmp/Python-2.1.2</span></tt>:</p>
+<pre class="literal-block">
+cd /tmp/Python-2.1.2/Tools/compiler
+python setup.py install
+</pre>
+<p>(Obviously, you'll have to adjust this to reflect your Python version
+and where you kept the source distribution after installing Python.)</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/INSTALL.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/INSTALL.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/INSTALL.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/INSTALL.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,53 @@
+Installing Quixote
+==================
+
+Best-case scenario
+------------------
+
+If you are using Python 2.2 or later, and have never installed Quixote
+with your current version of Python, you're in luck. Just run
+
+ python setup.py install
+
+and you're done. Proceed to the demo documentation to learn how to get
+Quixote working.
+
+If you're using an older Python, or if you're upgrading from an older
+Quixote version, read on.
+
+
+Upgrading from an older Quixote version
+---------------------------------------
+
+We strongly recommend that you remove any old Quixote version before
+installing a new one. First, find out where your old Quixote
+installation is:
+
+ python -c "import os, quixote; print os.path.dirname(quixote.__file__)"
+
+and then remove away the reported directory. (If the import fails, then
+you don't have an existing Quixote installation.)
+
+Then proceed as above:
+
+ python setup.py install
+
+
+Using Quixote with Python 2.0 or 2.1
+------------------------------------
+
+If you are using Python 2.0 or 2.1 then you need to install the
+``compiler`` package from the Python source distribution. The
+``compiler`` package is for parsing Python source code and generating
+Python bytecode, and the PTL compiler is built on top of it. With
+Python 2.0 and 2.1, this package was included in Python's source
+distribution, but not installed as part of the standard library.
+
+Assuming your Python source distribution is in ``/tmp/Python-2.1.2``::
+
+ cd /tmp/Python-2.1.2/Tools/compiler
+ python setup.py install
+
+(Obviously, you'll have to adjust this to reflect your Python version
+and where you kept the source distribution after installing Python.)
+
Added: packages/quixote1/branches/upstream/current/doc/Makefile
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/Makefile?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/Makefile (added)
+++ packages/quixote1/branches/upstream/current/doc/Makefile Mon May 8 19:16:16 2006
@@ -1,0 +1,31 @@
+#
+# Makefile to convert Quixote docs to HTML
+#
+# $Id: Makefile 20217 2003-01-16 20:51:53Z akuchlin $
+#
+
+TXT_FILES = $(wildcard *.txt)
+HTML_FILES = $(filter-out ZPL%,$(TXT_FILES:%.txt=%.html))
+
+RST2HTML = /www/python/bin/rst2html
+RST2HTML_OPTS = -o us-ascii
+
+DEST_HOST = staging.mems-exchange.org
+DEST_DIR = /www/www-docroot/software/quixote/doc
+
+SS = default.css
+
+%.html: %.txt
+ $(RST2HTML) $(RST2HTML_OPTS) $< $@
+
+all: $(HTML_FILES)
+
+clean:
+ rm -f $(HTML_FILES)
+
+install:
+ rsync -vptgo *.html $(SS) $(DEST_HOST):$(DEST_DIR)
+
+local-install:
+ dir=`pwd` ; \
+ cd $(DEST_DIR) && ln -sf $$dir/*.html $$dir/$(SS) .
Added: packages/quixote1/branches/upstream/current/doc/PTL.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/PTL.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/PTL.html (added)
+++ packages/quixote1/branches/upstream/current/doc/PTL.html Mon May 8 19:16:16 2006
@@ -1,0 +1,257 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>PTL: Python Template Language</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="ptl-python-template-language">
+<h1 class="title">PTL: Python Template Language</h1>
+<div class="section" id="introduction">
+<h1><a name="introduction">Introduction</a></h1>
+<p>PTL is the templating language used by Quixote. Most web templating
+languages embed a real programming language in HTML, but PTL inverts
+this model by merely tweaking Python to make it easier to generate
+HTML pages (or other forms of text). In other words, PTL is basically
+Python with a novel way to specify function return values.</p>
+<p>Specifically, a PTL template is designated by inserting a <tt class="literal"><span class="pre">[plain]</span></tt>
+or <tt class="literal"><span class="pre">[html]</span></tt> modifier after the function name. The value of
+expressions inside templates are kept, not discarded. If the type is
+<tt class="literal"><span class="pre">[html]</span></tt> then non-literal strings are passed through a function that
+escapes HTML special characters.</p>
+</div>
+<div class="section" id="plain-text-templates">
+<h1><a name="plain-text-templates">Plain text templates</a></h1>
+<p>Here's a sample plain text template:</p>
+<pre class="literal-block">
+def foo [plain] (x, y = 5):
+ "This is a chunk of static text."
+ greeting = "hello world" # statement, no PTL output
+ print 'Input values:', x, y
+ z = x + y
+ """You can plug in variables like x (%s)
+in a variety of ways.""" % x
+
+ "\n\n"
+ "Whitespace is important in generated text.\n"
+ "z = "; z
+ ", but y is "
+ y
+ "."
+</pre>
+<p>Obviously, templates can't have docstrings, but otherwise they follow
+Python's syntactic rules: indentation indicates scoping, single-quoted
+and triple-quoted strings can be used, the same rules for continuing
+lines apply, and so forth. PTL also follows all the expected semantics
+of normal Python code: so templates can have parameters, and the
+parameters can have default values, be treated as keyword arguments,
+etc.</p>
+<p>The difference between a template and a regular Python function is that
+inside a template the result of expressions are saved as the return
+value of that template. Look at the first part of the example again:</p>
+<pre class="literal-block">
+def foo [plain] (x, y = 5):
+ "This is a chunk of static text."
+ greeting = "hello world" # statement, no PTL output
+ print 'Input values:', x, y
+ z = x + y
+ """You can plug in variables like x (%s)
+in a variety of ways.""" % x
+</pre>
+<p>Calling this template with <tt class="literal"><span class="pre">foo(1,</span> <span class="pre">2)</span></tt> results in the following
+string:</p>
+<pre class="literal-block">
+This is a chunk of static text.You can plug in variables like x (1)
+in a variety of ways.
+</pre>
+<p>Normally when Python evaluates expressions inside functions, it just
+discards their values, but in a <tt class="literal"><span class="pre">[plain]</span></tt> PTL template the value is
+converted to a string using <tt class="literal"><span class="pre">str()</span></tt> and appended to the template's
+return value. There's a single exception to this rule: <tt class="literal"><span class="pre">None</span></tt> is the
+only value that's ever ignored, adding nothing to the output. (If this
+weren't the case, calling methods or functions that return <tt class="literal"><span class="pre">None</span></tt>
+would require assigning their value to a variable. You'd have to write
+<tt class="literal"><span class="pre">dummy</span> <span class="pre">=</span> <span class="pre">list.sort()</span></tt> in PTL code, which would be strange and
+confusing.)</p>
+<p>The initial string in a template isn't treated as a docstring, but is
+just incorporated in the generated output; therefore, templates can't
+have docstrings. No whitespace is ever automatically added to the
+output, resulting in <tt class="literal"><span class="pre">...text.You</span> <span class="pre">can</span> <span class="pre">...</span></tt> from the example. You'd
+have to add an extra space to one of the string literals to correct
+this.</p>
+<p>The assignment to the <tt class="literal"><span class="pre">greeting</span></tt> local variable is a statement, not an
+expression, so it doesn't return a value and produces no output. The
+output from the <tt class="literal"><span class="pre">print</span></tt> statement will be printed as usual, but won't
+go into the string generated by the template. Quixote directs standard
+output into Quixote's debugging log; if you're using PTL on its own, you
+should consider doing something similar. <tt class="literal"><span class="pre">print</span></tt> should never be used
+to generate output returned to the browser, only for adding debugging
+traces to a template.</p>
+<p>Inside templates, you can use all of Python's control-flow statements:</p>
+<pre class="literal-block">
+def numbers [plain] (n):
+ for i in range(n):
+ i
+ " " # PTL does not add any whitespace
+</pre>
+<p>Calling <tt class="literal"><span class="pre">numbers(5)</span></tt> will return the string <tt class="literal"><span class="pre">"1</span> <span class="pre">2</span> <span class="pre">3</span> <span class="pre">4</span> <span class="pre">5</span> <span class="pre">"</span></tt>. You can
+also have conditional logic or exception blocks:</p>
+<pre class="literal-block">
+def international_hello [plain] (language):
+ if language == "english":
+ "hello"
+ elif language == "french":
+ "bonjour"
+ else:
+ raise ValueError, "I don't speak %s" % language
+</pre>
+</div>
+<div class="section" id="html-templates">
+<h1><a name="html-templates">HTML templates</a></h1>
+<p>Since PTL is usually used to generate HTML documents, an <tt class="literal"><span class="pre">[html]</span></tt>
+template type has been provided to make generating HTML easier.</p>
+<p>A common error when generating HTML is to grab data from the browser
+or from a database and incorporate the contents without escaping
+special characters such as '<' and '&'. This leads to a class of
+security bugs called "cross-site scripting" bugs, where a hostile user
+can insert arbitrary HTML in your site's output that can link to other
+sites or contain JavaScript code that does something nasty (say,
+popping up 10,000 browser windows).</p>
+<p>Such bugs occur because it's easy to forget to HTML-escape a string,
+and forgetting it in just one location is enough to open a hole. PTL
+offers a solution to this problem by being able to escape strings
+automatically when generating HTML output, at the cost of slightly
+diminished performance (a few percent).</p>
+<p>Here's how this feature works. PTL defines a class called
+<tt class="literal"><span class="pre">htmltext</span></tt> that represents a string that's already been HTML-escaped
+and can be safely sent to the client. The function <tt class="literal"><span class="pre">htmlescape(string)</span></tt>
+is used to escape data, and it always returns an <tt class="literal"><span class="pre">htmltext</span></tt>
+instance. It does nothing if the argument is already <tt class="literal"><span class="pre">htmltext</span></tt>.
+Both <tt class="literal"><span class="pre">htmltext</span></tt> and <tt class="literal"><span class="pre">htmlescape</span></tt> are available from the global
+namespace in PTL modules.</p>
+<p>If a template function is declared <tt class="literal"><span class="pre">[html]</span></tt> instead of <tt class="literal"><span class="pre">[text]</span></tt>
+then two things happen. First, all literal strings in the function
+become instances of <tt class="literal"><span class="pre">htmltext</span></tt> instead of Python's <tt class="literal"><span class="pre">str</span></tt>. Second,
+the values of expressions are passed through <tt class="literal"><span class="pre">htmlescape()</span></tt> instead
+of <tt class="literal"><span class="pre">str()</span></tt>.</p>
+<p><tt class="literal"><span class="pre">htmltext</span></tt> type is like the <tt class="literal"><span class="pre">str</span></tt> type except that operations
+combining strings and <tt class="literal"><span class="pre">htmltext</span></tt> instances will result in the string
+being passed through <tt class="literal"><span class="pre">htmlescape()</span></tt>. For example:</p>
+<pre class="literal-block">
+>>> from quixote.html import htmltext
+>>> htmltext('a') + 'b'
+<htmltext 'ab'>
+>>> 'a' + htmltext('b')
+<htmltext 'ab'>
+>>> htmltext('a%s') % 'b'
+<htmltext 'ab'>
+>>> response = 'green eggs & ham'
+>>> htmltext('The response was: %s') % response
+<htmltext 'The response was: green eggs &amp; ham'>
+</pre>
+<p>Note that calling <tt class="literal"><span class="pre">str()</span></tt> strips the <tt class="literal"><span class="pre">htmltext</span></tt> type and should be
+avoided since it usually results in characters being escaped more than
+once. While <tt class="literal"><span class="pre">htmltext</span></tt> behaves much like a regular string, it is
+sometimes necessary to insert a <tt class="literal"><span class="pre">str()</span></tt> inside a template in order
+to obtain a genuine string. For example, the <tt class="literal"><span class="pre">re</span></tt> module requires
+genuine strings. We have found that explicit calls to <tt class="literal"><span class="pre">str()</span></tt> can
+often be avoided by splitting some code out of the template into a
+helper function written in regular Python.</p>
+<p>It is also recommended that the <tt class="literal"><span class="pre">htmltext</span></tt> constructor be used as
+sparingly as possible. The reason is that when using the htmltext
+feature of PTL, explicit calls to <tt class="literal"><span class="pre">htmltext</span></tt> become the most likely
+source of cross-site scripting holes. Calling <tt class="literal"><span class="pre">htmltext</span></tt> is like
+saying "I am absolutely sure this piece of data cannot contain malicious
+HTML code injected by a user. Don't escape HTML special characters
+because I want them."</p>
+<p>Note that literal strings in template functions declared with
+<tt class="literal"><span class="pre">[html]</span></tt> are htmltext instances, and therefore won't be escaped.
+You'll only need to use <tt class="literal"><span class="pre">htmltext</span></tt> when HTML markup comes from
+outside the template. For example, if you want to include a file
+containing HTML:</p>
+<pre class="literal-block">
+def output_file [html] ():
+ '<html><body>' # does not get escaped
+ htmltext(open("myfile.html").read())
+ '</body></html>'
+</pre>
+<p>In the common case, templates won't be dealing with HTML markup from
+external sources, so you can write straightforward code. Consider
+this function to generate the contents of the <tt class="literal"><span class="pre">HEAD</span></tt> element:</p>
+<pre class="literal-block">
+def meta_tags [html] (title, description):
+ '<title>%s</title>' % title
+ '<meta name="description" content="%s">\n' % description
+</pre>
+<p>There are no calls to <tt class="literal"><span class="pre">htmlescape()</span></tt> at all, but string literals
+such as <tt class="literal"><span class="pre"><title>%s</title></span></tt> have all be turned into <tt class="literal"><span class="pre">htmltext</span></tt>
+instances, so the string variables will be automatically escaped:</p>
+<pre class="literal-block">
+>>> t.meta_tags('Catalog', 'A catalog of our cool products')
+<htmltext '<title>Catalog</title>
+ <meta name="description" content="A catalog of our cool products">\n'>
+>>> t.meta_tags('Dissertation on <HEAD>',
+... 'Discusses the "LINK" and "META" tags')
+<htmltext '<title>Dissertation on &lt;HEAD&gt;</title>
+ <meta name="description"
+ content="Discusses the &quot;LINK&quot; and &quot;META&quot; tags">\n'>
+>>>
+</pre>
+<p>Note how the title and description have had HTML-escaping applied to them.
+(The output has been manually pretty-printed to be more readable.)</p>
+<p>Once you start using <tt class="literal"><span class="pre">htmltext</span></tt> in one of your templates, mixing
+plain and HTML templates is tricky because of <tt class="literal"><span class="pre">htmltext</span></tt>'s automatic
+escaping; plain templates that generate HTML tags will be
+double-escaped. One approach is to just use HTML templates throughout
+your application. Alternatively you can use <tt class="literal"><span class="pre">str()</span></tt> to convert
+<tt class="literal"><span class="pre">htmltext</span></tt> instances to regular Python strings; just be sure the
+resulting string isn't HTML-escaped again.</p>
+<p>Two implementations of <tt class="literal"><span class="pre">htmltext</span></tt> are provided, one written in pure
+Python and a second one implemented as a C extension. Both versions
+have seen production use.</p>
+</div>
+<div class="section" id="ptl-modules">
+<h1><a name="ptl-modules">PTL modules</a></h1>
+<p>PTL templates are kept in files with the extension .ptl. Like Python
+files, they are byte-compiled on import, and the byte-code is written to
+a compiled file with the extension <tt class="literal"><span class="pre">.ptlc</span></tt>. Since vanilla Python
+doesn't know anything about PTL, Quixote provides an import hook to let
+you import PTL files just like regular Python modules. The standard way
+to install this import hook is by calling the <tt class="literal"><span class="pre">enable_ptl()</span></tt> function:</p>
+<pre class="literal-block">
+from quixote import enable_ptl
+enable_ptl()
+</pre>
+<p>(Note: if you're using ZODB, always import ZODB <em>before</em> installing the
+PTL import hook. There's some interaction which causes importing the
+TimeStamp module to fail when the PTL import hook is installed; we
+haven't debugged the problem.)</p>
+<p>Once the import hook is installed, PTL files can be imported as if they
+were Python modules. If all the example templates shown here were put
+into a file named <tt class="literal"><span class="pre">foo.ptl</span></tt>, you could then write Python code that did
+this:</p>
+<pre class="literal-block">
+from foo import numbers
+def f():
+ return numbers(10)
+</pre>
+<p>You may want to keep this little function in your <tt class="literal"><span class="pre">PYTHONSTARTUP</span></tt>
+file:</p>
+<pre class="literal-block">
+def ptl():
+ try:
+ import ZODB
+ except ImportError:
+ pass
+ from quixote import enable_ptl
+ enable_ptl()
+</pre>
+<p>This is useful if you want to interactively play with a PTL module.</p>
+<p>$Id: PTL.txt 25237 2004-09-30 18:28:31Z nascheme $</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/PTL.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/PTL.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/PTL.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/PTL.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,267 @@
+PTL: Python Template Language
+=============================
+
+Introduction
+------------
+
+PTL is the templating language used by Quixote. Most web templating
+languages embed a real programming language in HTML, but PTL inverts
+this model by merely tweaking Python to make it easier to generate
+HTML pages (or other forms of text). In other words, PTL is basically
+Python with a novel way to specify function return values.
+
+Specifically, a PTL template is designated by inserting a ``[plain]``
+or ``[html]`` modifier after the function name. The value of
+expressions inside templates are kept, not discarded. If the type is
+``[html]`` then non-literal strings are passed through a function that
+escapes HTML special characters.
+
+
+Plain text templates
+--------------------
+
+Here's a sample plain text template::
+
+ def foo [plain] (x, y = 5):
+ "This is a chunk of static text."
+ greeting = "hello world" # statement, no PTL output
+ print 'Input values:', x, y
+ z = x + y
+ """You can plug in variables like x (%s)
+ in a variety of ways.""" % x
+
+ "\n\n"
+ "Whitespace is important in generated text.\n"
+ "z = "; z
+ ", but y is "
+ y
+ "."
+
+Obviously, templates can't have docstrings, but otherwise they follow
+Python's syntactic rules: indentation indicates scoping, single-quoted
+and triple-quoted strings can be used, the same rules for continuing
+lines apply, and so forth. PTL also follows all the expected semantics
+of normal Python code: so templates can have parameters, and the
+parameters can have default values, be treated as keyword arguments,
+etc.
+
+The difference between a template and a regular Python function is that
+inside a template the result of expressions are saved as the return
+value of that template. Look at the first part of the example again::
+
+ def foo [plain] (x, y = 5):
+ "This is a chunk of static text."
+ greeting = "hello world" # statement, no PTL output
+ print 'Input values:', x, y
+ z = x + y
+ """You can plug in variables like x (%s)
+ in a variety of ways.""" % x
+
+Calling this template with ``foo(1, 2)`` results in the following
+string::
+
+ This is a chunk of static text.You can plug in variables like x (1)
+ in a variety of ways.
+
+Normally when Python evaluates expressions inside functions, it just
+discards their values, but in a ``[plain]`` PTL template the value is
+converted to a string using ``str()`` and appended to the template's
+return value. There's a single exception to this rule: ``None`` is the
+only value that's ever ignored, adding nothing to the output. (If this
+weren't the case, calling methods or functions that return ``None``
+would require assigning their value to a variable. You'd have to write
+``dummy = list.sort()`` in PTL code, which would be strange and
+confusing.)
+
+The initial string in a template isn't treated as a docstring, but is
+just incorporated in the generated output; therefore, templates can't
+have docstrings. No whitespace is ever automatically added to the
+output, resulting in ``...text.You can ...`` from the example. You'd
+have to add an extra space to one of the string literals to correct
+this.
+
+The assignment to the ``greeting`` local variable is a statement, not an
+expression, so it doesn't return a value and produces no output. The
+output from the ``print`` statement will be printed as usual, but won't
+go into the string generated by the template. Quixote directs standard
+output into Quixote's debugging log; if you're using PTL on its own, you
+should consider doing something similar. ``print`` should never be used
+to generate output returned to the browser, only for adding debugging
+traces to a template.
+
+Inside templates, you can use all of Python's control-flow statements::
+
+ def numbers [plain] (n):
+ for i in range(n):
+ i
+ " " # PTL does not add any whitespace
+
+Calling ``numbers(5)`` will return the string ``"1 2 3 4 5 "``. You can
+also have conditional logic or exception blocks::
+
+ def international_hello [plain] (language):
+ if language == "english":
+ "hello"
+ elif language == "french":
+ "bonjour"
+ else:
+ raise ValueError, "I don't speak %s" % language
+
+
+HTML templates
+--------------
+
+Since PTL is usually used to generate HTML documents, an ``[html]``
+template type has been provided to make generating HTML easier.
+
+A common error when generating HTML is to grab data from the browser
+or from a database and incorporate the contents without escaping
+special characters such as '<' and '&'. This leads to a class of
+security bugs called "cross-site scripting" bugs, where a hostile user
+can insert arbitrary HTML in your site's output that can link to other
+sites or contain JavaScript code that does something nasty (say,
+popping up 10,000 browser windows).
+
+Such bugs occur because it's easy to forget to HTML-escape a string,
+and forgetting it in just one location is enough to open a hole. PTL
+offers a solution to this problem by being able to escape strings
+automatically when generating HTML output, at the cost of slightly
+diminished performance (a few percent).
+
+Here's how this feature works. PTL defines a class called
+``htmltext`` that represents a string that's already been HTML-escaped
+and can be safely sent to the client. The function ``htmlescape(string)``
+is used to escape data, and it always returns an ``htmltext``
+instance. It does nothing if the argument is already ``htmltext``.
+Both ``htmltext`` and ``htmlescape`` are available from the global
+namespace in PTL modules.
+
+If a template function is declared ``[html]`` instead of ``[text]``
+then two things happen. First, all literal strings in the function
+become instances of ``htmltext`` instead of Python's ``str``. Second,
+the values of expressions are passed through ``htmlescape()`` instead
+of ``str()``.
+
+``htmltext`` type is like the ``str`` type except that operations
+combining strings and ``htmltext`` instances will result in the string
+being passed through ``htmlescape()``. For example::
+
+ >>> from quixote.html import htmltext
+ >>> htmltext('a') + 'b'
+ <htmltext 'ab'>
+ >>> 'a' + htmltext('b')
+ <htmltext 'ab'>
+ >>> htmltext('a%s') % 'b'
+ <htmltext 'ab'>
+ >>> response = 'green eggs & ham'
+ >>> htmltext('The response was: %s') % response
+ <htmltext 'The response was: green eggs & ham'>
+
+Note that calling ``str()`` strips the ``htmltext`` type and should be
+avoided since it usually results in characters being escaped more than
+once. While ``htmltext`` behaves much like a regular string, it is
+sometimes necessary to insert a ``str()`` inside a template in order
+to obtain a genuine string. For example, the ``re`` module requires
+genuine strings. We have found that explicit calls to ``str()`` can
+often be avoided by splitting some code out of the template into a
+helper function written in regular Python.
+
+It is also recommended that the ``htmltext`` constructor be used as
+sparingly as possible. The reason is that when using the htmltext
+feature of PTL, explicit calls to ``htmltext`` become the most likely
+source of cross-site scripting holes. Calling ``htmltext`` is like
+saying "I am absolutely sure this piece of data cannot contain malicious
+HTML code injected by a user. Don't escape HTML special characters
+because I want them."
+
+Note that literal strings in template functions declared with
+``[html]`` are htmltext instances, and therefore won't be escaped.
+You'll only need to use ``htmltext`` when HTML markup comes from
+outside the template. For example, if you want to include a file
+containing HTML::
+
+ def output_file [html] ():
+ '<html><body>' # does not get escaped
+ htmltext(open("myfile.html").read())
+ '</body></html>'
+
+In the common case, templates won't be dealing with HTML markup from
+external sources, so you can write straightforward code. Consider
+this function to generate the contents of the ``HEAD`` element::
+
+ def meta_tags [html] (title, description):
+ '<title>%s</title>' % title
+ '<meta name="description" content="%s">\n' % description
+
+There are no calls to ``htmlescape()`` at all, but string literals
+such as ``<title>%s</title>`` have all be turned into ``htmltext``
+instances, so the string variables will be automatically escaped::
+
+ >>> t.meta_tags('Catalog', 'A catalog of our cool products')
+ <htmltext '<title>Catalog</title>
+ <meta name="description" content="A catalog of our cool products">\n'>
+ >>> t.meta_tags('Dissertation on <HEAD>',
+ ... 'Discusses the "LINK" and "META" tags')
+ <htmltext '<title>Dissertation on <HEAD></title>
+ <meta name="description"
+ content="Discusses the "LINK" and "META" tags">\n'>
+ >>>
+
+Note how the title and description have had HTML-escaping applied to them.
+(The output has been manually pretty-printed to be more readable.)
+
+Once you start using ``htmltext`` in one of your templates, mixing
+plain and HTML templates is tricky because of ``htmltext``'s automatic
+escaping; plain templates that generate HTML tags will be
+double-escaped. One approach is to just use HTML templates throughout
+your application. Alternatively you can use ``str()`` to convert
+``htmltext`` instances to regular Python strings; just be sure the
+resulting string isn't HTML-escaped again.
+
+Two implementations of ``htmltext`` are provided, one written in pure
+Python and a second one implemented as a C extension. Both versions
+have seen production use.
+
+
+PTL modules
+-----------
+
+PTL templates are kept in files with the extension .ptl. Like Python
+files, they are byte-compiled on import, and the byte-code is written to
+a compiled file with the extension ``.ptlc``. Since vanilla Python
+doesn't know anything about PTL, Quixote provides an import hook to let
+you import PTL files just like regular Python modules. The standard way
+to install this import hook is by calling the ``enable_ptl()`` function::
+
+ from quixote import enable_ptl
+ enable_ptl()
+
+(Note: if you're using ZODB, always import ZODB *before* installing the
+PTL import hook. There's some interaction which causes importing the
+TimeStamp module to fail when the PTL import hook is installed; we
+haven't debugged the problem.)
+
+Once the import hook is installed, PTL files can be imported as if they
+were Python modules. If all the example templates shown here were put
+into a file named ``foo.ptl``, you could then write Python code that did
+this::
+
+ from foo import numbers
+ def f():
+ return numbers(10)
+
+You may want to keep this little function in your ``PYTHONSTARTUP``
+file::
+
+ def ptl():
+ try:
+ import ZODB
+ except ImportError:
+ pass
+ from quixote import enable_ptl
+ enable_ptl()
+
+This is useful if you want to interactively play with a PTL module.
+
+
+$Id: PTL.txt 25237 2004-09-30 18:28:31Z nascheme $
Added: packages/quixote1/branches/upstream/current/doc/ZPL.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/ZPL.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/ZPL.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/ZPL.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,59 @@
+Zope Public License (ZPL) Version 2.0
+-----------------------------------------------
+
+This software is Copyright (c) Zope Corporation (tm) and
+Contributors. All rights reserved.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the above
+ copyright notice, this list of conditions, and the following
+ disclaimer.
+
+2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions, and the following
+ disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+3. The name Zope Corporation (tm) must not be used to
+ endorse or promote products derived from this software
+ without prior written permission from Zope Corporation.
+
+4. The right to distribute this software or to use it for
+ any purpose does not give you the right to use Servicemarks
+ (sm) or Trademarks (tm) of Zope Corporation. Use of them is
+ covered in a separate agreement (see
+ http://www.zope.com/Marks).
+
+5. If any files are modified, you must cause the modified
+ files to carry prominent notices stating that you changed
+ the files and the date of any change.
+
+Disclaimer
+
+ THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS''
+ AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ DAMAGE.
+
+
+This software consists of contributions made by Zope
+Corporation and many individuals on behalf of Zope
+Corporation. Specific attributions are listed in the
+accompanying credits file.
Added: packages/quixote1/branches/upstream/current/doc/default.css
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/default.css?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/default.css (added)
+++ packages/quixote1/branches/upstream/current/doc/default.css Mon May 8 19:16:16 2006
@@ -1,0 +1,16 @@
+/*
+Cascading style sheet for the Quixote documentation.
+Just overrides what I don't like about the standard docutils
+stylesheet.
+
+$Id: default.css 20217 2003-01-16 20:51:53Z akuchlin $
+*/
+
+ at import url(/misc/docutils.css);
+
+pre.literal-block, pre.doctest-block {
+ margin-left: 1em ;
+ margin-right: 1em ;
+ background-color: #f4f4f4 }
+
+tt { background-color: transparent }
Added: packages/quixote1/branches/upstream/current/doc/demo.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/demo.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/demo.html (added)
+++ packages/quixote1/branches/upstream/current/doc/demo.html Mon May 8 19:16:16 2006
@@ -1,0 +1,587 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Running the Quixote Demo</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="running-the-quixote-demo">
+<h1 class="title">Running the Quixote Demo</h1>
+<p>Quixote comes with a tiny demonstration application that you can install
+and run on your web server. In a few dozen lines of Python and PTL
+code, it demonstrates most of Quixote's basic capabilities. It's also
+an easy way to make sure that your Python installation and web server
+configuration are cooperating so that Quixote applications can work.</p>
+<div class="section" id="installation">
+<h1><a name="installation">Installation</a></h1>
+<p>The demo is included in the quixote.demo package, which is installed
+along with the rest of Quixote when you run <tt class="literal"><span class="pre">python</span> <span class="pre">setup.py</span> <span class="pre">install</span></tt>.
+The driver script (demo.cgi) and associated configuration file
+(demo.conf) are <em>not</em> installed automatically -- you'll have to copy
+them from the demo/ subdirectory to your web server's CGI directory.
+Eg., if you happen to use the same web server tree as we do:</p>
+<pre class="literal-block">
+cp -p demo/demo.cgi demo/demo.conf /www/cgi-bin
+</pre>
+<p>You'll almost certainly need to edit the <tt class="literal"><span class="pre">#!</span></tt> line of demo.cgi to ensure
+that it points to the correct Python interpreter -- it should be the
+same interpreter that you used to run <tt class="literal"><span class="pre">setup.py</span> <span class="pre">install</span></tt>.</p>
+</div>
+<div class="section" id="verifying-the-installation">
+<h1><a name="verifying-the-installation">Verifying the installation</a></h1>
+<p>Before we try to access the demo via your web server, let's make sure
+that the quixote and quixote.demo packages are installed on your system:</p>
+<pre class="literal-block">
+$ python
+Python 2.1.1 (#2, Jul 30 2001, 12:04:51)
+[GCC 2.95.2 20000220 (Debian GNU/Linux)] on linux2
+Type "copyright", "credits" or "license" for more information.
+>>> import quixote
+>>> quixote.enable_ptl()
+>>> import quixote.demo
+</pre>
+<p>(Quixote requires Python 2.0 or greater; you might have to name an
+explicit Python interpreter, eg. <tt class="literal"><span class="pre">/usr/local/bin/python2.1</span></tt>. Make
+sure that the Python interpreter you use here is the same as you put in
+the <tt class="literal"><span class="pre">#!</span></tt> line of demo.cgi, and the same that you used to install
+Quixote.)</p>
+<p>If this runs without errors, then Quixote (and its demo) are installed
+such that you can import them. It remains to be seen if the user that
+will run the driver script -- usually <tt class="literal"><span class="pre">nobody</span></tt> -- can import them.</p>
+</div>
+<div class="section" id="running-the-demo-directly">
+<h1><a name="running-the-demo-directly">Running the demo directly</a></h1>
+<p>Assuming that</p>
+<ul class="simple">
+<li>your web server is running on the current host</li>
+<li>your web server is configured to handle requests to
+/cgi-bin/demo.cgi by running the demo.cgi script that you just
+installed (eg. to /www/cgi-bin/demo.cgi)</li>
+</ul>
+<p>then you should now be able to run the Quixote demo by directly
+referring to the demo.cgi script.</p>
+<p>Another option, if you have the Medusa package installed, is to run:</p>
+<pre class="literal-block">
+python server/medusa_http.py
+</pre>
+<p>This will start a small pure-Python web server running on port 8080.
+(Medusa is available from <a class="reference" href="http://www.amk.ca/python/code/medusa.html">http://www.amk.ca/python/code/medusa.html</a> .)</p>
+<p>Start a web browser and load</p>
+<pre class="literal-block">
+http://localhost/cgi-bin/demo.cgi/
+</pre>
+<p>or</p>
+<pre class="literal-block">
+http://localhost:8080/
+</pre>
+<p>if you're using Medusa.</p>
+<p>You should see a page titled "Quixote Demo" with the headline "Hello,
+world!". Feel free to poke around; you can't break anything through the
+demo. (That's not to say you can't break things with Quixote in
+general; since Quixote gives you the full power of Python for your web
+applications, you have the power to create stupid security holes.)</p>
+<p>If you don't get the "Quixote Demo" page, go look in your web server's
+error log. Some things that might go wrong:</p>
+<ul>
+<li><p class="first">your web server is not configured to run CGI scripts, or it
+might use a different base URL for them. If you're running
+Apache, look for something like</p>
+<pre class="literal-block">
+ScriptAlias /cgi-bin/ /www/cgi-bin/
+</pre>
+<p>in your httpd.conf (for some value of "/www/cgi-bin").</p>
+<p>(This is not a problem with Quixote or the Quixote demo; this is a
+problem with your web server's configuration.)</p>
+</li>
+<li><p class="first">your web server was unable to execute the script. Make sure
+its permissions are correct:</p>
+<pre class="literal-block">
+chmod 755 /www/cgi-bin/demo.cgi
+</pre>
+<p>(This shouldn't happen if you installed demo.cgi with <tt class="literal"><span class="pre">cp</span> <span class="pre">-p</span></tt> as
+illustrated above.)</p>
+</li>
+<li><p class="first">demo.cgi started, but was unable to import the Quixote modules.
+In this case, there should be a short Python traceback in your web
+server's error log ending with a message like</p>
+<pre class="literal-block">
+ImportError: No module named quixote
+</pre>
+<p>Remember, just because you can "import quixote" in a Python
+interpreter doesn't mean the user that runs CGI scripts (usually
+"nobody") can. You might have installed Quixote in a non-standard
+location, in which case you should either install it in the standard
+location (your Python interpreter's "site-packages" directory) or
+instruct your web server to set the PYTHONPATH environment variable.
+Or you might be using the wrong Python interpreter -- check the <tt class="literal"><span class="pre">#!</span></tt>
+line of demo.cgi.</p>
+</li>
+<li><p class="first">demo.cgi started and imported Quixote, but was unable to read its
+config file. There should be a short Python traceback in your web
+server's error log ending with a message like</p>
+<pre class="literal-block">
+IOError: [Errno 2] No such file or directory: 'demo.conf'
+</pre>
+<p>in this case.</p>
+<p>Make sure you copied demo.conf to the same directory as demo.cgi,
+and make sure it is readable:</p>
+<pre class="literal-block">
+chmod 644 /www/cgi-bin/demo.conf
+</pre>
+<p>(This shouldn't happen if you install demo.conf with <tt class="literal"><span class="pre">cp</span> <span class="pre">-p</span></tt> as
+illustrated above.)</p>
+</li>
+</ul>
+</div>
+<div class="section" id="running-the-demo-indirectly">
+<h1><a name="running-the-demo-indirectly">Running the demo indirectly</a></h1>
+<p>One of the main tenets of Quixote's design is that, in a web
+application, the URL is part of the user interface. We consider it
+undesirable to expose implementation details -- such as
+"/cgi-bin/demo.cgi" -- to users. That sort of thing should be tucked
+away out of sight. Depending on your web server, this should be easy to
+do with a simple tweak to its configuration.</p>
+<p>For example, say you want the "/qdemo" URL to be the location of the
+Quixote demo. If you're using Apache with the rewrite engine loaded and
+enabled, all you need to do is add this to your httpd.conf:</p>
+<pre class="literal-block">
+RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+</pre>
+<p>With this rule in effect (don't forget to restart your server!),
+accesses to "/qdemo/" are the same as accesses to "/cgi-bin/demo.cgi/" --
+except they're a lot easier for the user to understand and don't expose
+implementation details of your application.</p>
+<p>Try it out. In your web browser, visit <tt class="literal"><span class="pre">http://localhost/qdemo/</span></tt>.</p>
+<p>You should get exactly the same page as you got visiting
+"/cgi-bin/demo.cgi/" earlier, and all the links should work exactly the
+same.</p>
+<p>You can use any URL prefix you like -- there's nothing special about
+"/qdemo".</p>
+<p>One small but important detail here is "/qdemo" versus "/qdemo/". In
+the above configuration, requests for "/qdemo" will fail, and requests
+for "/qdemo/" will succeed. See the "URL rewriting" section of
+web-server.txt for details and how to fix this.</p>
+</div>
+<div class="section" id="understanding-the-demo">
+<h1><a name="understanding-the-demo">Understanding the demo</a></h1>
+<p>Now that you've gotten the demo to run successfully, let's look under
+the hood and see how it works. Before we start following links in the
+demo (don't worry if you already have, you can't hurt anything), make
+sure you're watching all the relevant log files. As with any web
+application, log files are essential for debugging Quixote applications.</p>
+<p>Assuming that your web server's error log is in /www/log/error_log, and
+that you haven't changed the DEBUG_LOG and ERROR_LOG settings in
+demo.conf:</p>
+<pre class="literal-block">
+$ tail -f /www/log/error_log & \
+ tail -f /tmp/quixote-demo-debug.log & \
+ tail -f /tmp/quixote-demo-error.log
+</pre>
+<p>(Note that recent versions of GNU tail let you tail multiple files with
+the same command. Cool!)</p>
+</div>
+<div class="section" id="lesson-1-the-top-page">
+<h1><a name="lesson-1-the-top-page">Lesson 1: the top page</a></h1>
+<p>Reload the top of the demo, presumably <tt class="literal"><span class="pre">http://localhost/qdemo/</span></tt>. You
+should see "debug message from the index page" in the debug log file.</p>
+<p>Where is this message coming from? To find out, we need to delve into
+the source code for the demo. Load up demo/__init__.py and let's take a
+look. In the process, we'll learn how to explore a Quixote application
+and find the source code that corresponds to a given URL.</p>
+<p>First, why are we loading demo/__init__.py? Because that's where some
+of the names in the "quixote.demo" namespace are defined, and it's where
+the list of names that may be "exported" by Quixote from this namespace
+to the web is given. Recall that under Quixote, every URL boils down to
+a callable Python object -- usually a function or method. The root of
+this application is a Python package ("quixote.demo"), which is just a
+special kind of module. But modules aren't callable -- so what does the
+"/qdemo/" URL boil down to? That's what <tt class="literal"><span class="pre">_q_index()</span></tt> is for -- you
+can define a special function that is called by default when Quixote
+resolves a URL to a namespace rather than a callable. That is,
+"/qdemo/" resolves to the "quixote.demo" package; a package is a
+namespace, so it can't be called; therefore Quixote looks for a function
+called <tt class="literal"><span class="pre">_q_index()</span></tt> in that namespace and calls it.</p>
+<p>In this case, <tt class="literal"><span class="pre">_q_index()</span></tt> is not defined in demo/__init__.py -- but
+it is imported there from the quixote.demo.pages module. This is
+actually a PTL module -- demo/pages.py does not exist, but
+demo/pages.ptl does. So load it up and take a look:</p>
+<pre class="literal-block">
+def _q_index [plain] (request):
+ print "debug message from the index page"
+ """
+ <html>
+ <head><title>Quixote Demo</title></head>
+ <body>
+ <h1>Hello, world!</h1>
+ [...]
+ </body>
+ </html>
+ """
+</pre>
+<p>A-ha! There's the PTL code that generates the "Quixote Demo" page.
+This <tt class="literal"><span class="pre">_q_index()</span></tt> template is quite simple PTL -- it's mostly an HTML
+document with a single debug print thrown in to demonstrate Quixote's
+debug logging facility.</p>
+<p>Outcome of lesson 1:</p>
+<ul class="simple">
+<li>a URL maps to either a namespace (package, module, class instance)
+or a callable (function, method, PTL template)</li>
+<li>if a URL maps to a namespace, Quixote looks for a callable
+<tt class="literal"><span class="pre">_q_index()</span></tt> in that namespace and calls it</li>
+<li><tt class="literal"><span class="pre">_q_index()</span></tt> doesn't have to be explicitly exported by your
+namespace; if it exists, it will be used</li>
+<li>anything your application prints to standard output goes to
+Quixote's debug log. (If you didn't specify a debug log in
+your config file, debug messages are discarded.)</li>
+</ul>
+</div>
+<div class="section" id="lesson-2-a-link-to-a-simple-document">
+<h1><a name="lesson-2-a-link-to-a-simple-document">Lesson 2: a link to a simple document</a></h1>
+<p>The first two links in the "Quixote Demo" page are quite simple. Each
+one is handled by a Python function defined in the "quixote.demo"
+namespace, i.e. in demo/__init__.py. For example, following the
+"simple" link is equivalent to calling the <tt class="literal"><span class="pre">simple()</span></tt> function in
+"quixote.demo". Let's take a look at that function:</p>
+<pre class="literal-block">
+def simple (request):
+ request.response.set_content_type("text/plain")
+ return "This is the Python function 'quixote.demo.simple'.\n"
+</pre>
+<p>Note that this could equivalently be coded in PTL:</p>
+<pre class="literal-block">
+def simple [plain] (request):
+ request.response.set_content_type("text/plain")
+ "This is the Python function 'quixote.demo.simple'.\n"
+</pre>
+<p>...but for such a simple document, why bother?</p>
+<p>Since this function doesn't generate an HTML document, it would be
+misleading for the HTTP response that Quixote generates to claim a
+"Content-type" of "text/html". That is the default for Quixote's HTTP
+responses, however, since most HTTP responses are indeed HTML documents.
+Therefore, if the content you're returning is anything other than an
+HTML document, you should set the "Content-type" header on the HTTP
+response.</p>
+<p>This brings up a larger issue: request and response objects. Quixote
+includes two classes, HTTPRequest and HTTPResponse, to encapsulate every
+HTTP request and its accompanying response. Whenever Quixote resolves a
+URL to a callable and calls it, it passes precisely one argument: an
+HTTPRequest object.</p>
+<p>The HTTPRequest object includes (almost) everything you might want to
+know about the HTTP request that caused Quixote to be invoked and to
+call a particular function, method, or PTL template. You have access to
+CGI environment variables, HTML form variables (parsed and
+ready-to-use), and HTTP cookies. Finally, the HTTPRequest object also
+includes an HTTPResponse object -- after all, every request implies a
+response. You can set the response status, set response headers, set
+cookies, or force a redirect using the HTTPResponse object.</p>
+<p>Note that it's not enough that the <tt class="literal"><span class="pre">simple()</span></tt> function merely exists.
+If that were the case, then overly-curious users or attackers could
+craft URLs that point to any Python function in any module under your
+application's root namespace, potentially causing all sorts of havoc.
+You need to explicitly declare which names are exported from your
+application to the web, using the <tt class="literal"><span class="pre">_q_exports</span></tt> variable. For example,
+demo/__init__.py has this export list:</p>
+<pre class="literal-block">
+_q_exports = ["simple", "error"]
+</pre>
+<p>This means that only these two names are explicitly exported by the
+Quixote demo. (The empty string is implicitly exported from a namespace
+if a <tt class="literal"><span class="pre">_q_index()</span></tt> callable exists there -- thus "/qdemo/" is handled
+by <tt class="literal"><span class="pre">_q_index()</span></tt> in the "quixote.demo" namespace. Arbitrary names may
+be implicitly exported using a <tt class="literal"><span class="pre">_q_lookup()</span></tt> function; see Lesson 4
+below.)</p>
+</div>
+<div class="section" id="lesson-3-error-handling">
+<h1><a name="lesson-3-error-handling">Lesson 3: error-handling</a></h1>
+<p>The next link in the "Quixote Demo" page is to the "error" document,
+which is handled by the <tt class="literal"><span class="pre">error()</span></tt> function in demo/__init__.py. All
+this function does is raise an exception:</p>
+<pre class="literal-block">
+def error (request):
+ raise ValueError, "this is a Python exception"
+</pre>
+<p>Follow the link, and you should see a Python traceback followed by a
+dump of the CGI environment for this request (along with other request
+data, such as a list of cookies).</p>
+<p>This is extremely useful when developing, testing, and debugging. In a
+production environment, though, it reveals way too much about your
+implementation to hapless users who should happen to hit an error, and
+it also reveals internal details to attackers who might use it to crack
+your site. (It's just as easy to write an insecure web application with
+Quixote as with any other tool.)</p>
+<p>Thus, Quixote offers the <tt class="literal"><span class="pre">DISPLAY_EXCEPTIONS</span></tt> config variable. This
+is false by default, but the demo.conf file enables it. To see what
+happens with <tt class="literal"><span class="pre">DISPLAY_EXCEPTIONS</span></tt> off, edit demo.conf and reload the
+"error" page. You should see a bland, generic error message that
+reveals very little about your implementation. (This error page is
+deliberately very similar, but not identical, to Apache's "Internal
+Server Error" page.)</p>
+<p>Unhandled exceptions raised by application code (aka "application bugs")
+are only one kind of error you're likely to encounter when developing a
+Quixote application. The other ones are:</p>
+<ul class="simple">
+<li>driver script crashes or doesn't run (eg. can't import quixote
+modules, can't load config file). This is covered under
+"Running the demo directly" above</li>
+<li>publishing errors, such as a request for "/simpel" that should
+have been "/simple", or a request for a resource that exists but is
+denied to the current user. Quixote has a family of exception
+classes for dealing with these; such exceptions may be raised by
+Quixote itself or by your application. They are usually handled by
+Quixote and turned into HTTP error responses (4xx status code),
+but it's possible for your application to define a special handler
+for such exceptions.</li>
+<li>bugs in Quixote itself; hopefully this won't happen, but you
+never know. These usually look a lot like problems in the driver
+script: the script crashes and prints a traceback to stderr, which
+most likely winds up in your web server's error log. The length of
+the traceback is generally a clue as to whether there's a problem
+with your driver script or a bug in Quixote.</li>
+</ul>
+<p>Publishing errors result in a 4xx HTTP response code, and can be
+entirely handled by Quixote -- that is, your web server just returns
+the HTTP response that Quixote prepares. It's also possible to write
+a <tt class="literal"><span class="pre">_q_exception_handler()</span></tt> method that will be called on triggering
+a publishing error. This method can then provide a friendlier
+response; for example, the page might provide a link to the site map,
+or the method might look at the problematic path and try to correct
+misspellings. The demo defines a <tt class="literal"><span class="pre">_q_exception_handler()</span></tt> in
+demo/pages.ptl.</p>
+<p>Application bugs result in a 5xx HTTP response code, and are
+entirely handled by Quixote. Don't get confused by the fact that
+Quixote's and Apache's "Internal Server Error" pages are quite similar!</p>
+<p>Driver script crashes and Quixote bugs (which are essentially the same
+thing; the main difference is who to blame) are handled by your web
+server. (In the first case, Quixote doesn't even enter into it; in the
+second case, Quixote dies horribly and is no longer in control.) Under
+Apache, the Python traceback resulting from the crash is written to
+Apache's error log, and a 5xx response is returned to the client with
+Apache's "Internal Server Error" error page.</p>
+</div>
+<div class="section" id="lesson-4-object-publishing">
+<h1><a name="lesson-4-object-publishing">Lesson 4: object publishing</a></h1>
+<p>Publishing Python callables on the web -- i.e., translating URLs to
+Python functions/methods/PTL templates and calling them to determine the
+HTTP response -- is a very powerful way of writing web applications.
+However, Quixote has one more trick up its sleeve: object publishing.
+You can translate arbitrary names to arbitrary objects which are then
+published on the web, and you can create URLs that call methods on those
+objects.</p>
+<p>This is all accomplished with the <tt class="literal"><span class="pre">_q_lookup()</span></tt> function. Every
+namespace that Quixote encounters may have a <tt class="literal"><span class="pre">_q_lookup()</span></tt>, just like
+it may have a <tt class="literal"><span class="pre">_q_index()</span></tt>. <tt class="literal"><span class="pre">_q_index()</span></tt> is used to handle requests for
+the empty name -- as we saw in Lesson 1, a request for "/qdemo/", maps to
+the "quixote.demo" namespace; the empty string after the last slash
+means that Quixote will call <tt class="literal"><span class="pre">_q_index()</span></tt> in this namespace to handle
+the request.</p>
+<p><tt class="literal"><span class="pre">_q_lookup()</span></tt> is for requests that aren't handled by a Python callable
+in the namespace. As seen in Lessons 2 and 3, requests for
+"/qdemo/simple" and "/qdemo/error" are handled by the <tt class="literal"><span class="pre">simple()</span></tt> and
+<tt class="literal"><span class="pre">error()</span></tt> functions in the "quixote.demo" namespace. What if someone
+requests "/qdemo/foo"? There's no function <tt class="literal"><span class="pre">foo()</span></tt> in the
+"quixote.demo" namespace, so normally this would be an error.
+(Specifically, it would be a publishing error: Quixote would raise
+TraversalError, which is the error used for non-existent or non-exported
+names. Another part of Quixote then turns this into an HTTP 404
+response.)</p>
+<p>However, this particular namespace also defines a <tt class="literal"><span class="pre">_q_lookup()</span></tt>
+function. That means that the application wants a chance to handle
+unknown names before Quixote gives up entirely. Let's take a look at
+the implementation of <tt class="literal"><span class="pre">_q_lookup()</span></tt>:</p>
+<pre class="literal-block">
+from quixote.demo.integer_ui import IntegerUI
+[...]
+def _q_lookup(request, component):
+ return IntegerUI(request, component)
+</pre>
+<p>Pretty simple: we just construct an IntegerUI object and return it. So
+what is IntegerUI? Take a look in the demo/integer_ui.py file to see;
+it's just a web interface to integers. (Normally, you would write a
+wrapper class that provides a web interface to something more
+interesting than integers. This just demonstrates how simple an object
+published by Quixote can be.)</p>
+<p>So, what is an IntegerUI object? From Quixote's point of view, it's
+just another namespace to publish: like modules and packages, class
+instances have attributes, some of which (methods) are callable. In the
+case of IntegerUI, two of those attributes are <tt class="literal"><span class="pre">_q_exports</span></tt> and
+<tt class="literal"><span class="pre">_q_index</span></tt> -- every namespace published by Quixote must have an export
+list, and an index function is almost always advisable.</p>
+<p>What this means is that any name that the IntegerUI constructor accepts
+is a valid name to tack onto the "/qdemo/" URL. Take a look at the
+IntegerUI constructor; you'll see that it works fine when passed
+something that can be converted to an integer (eg. "12" or 1.0), and
+raises Quixote's TraversalError if not. As it happens, Quixote always
+passes in a string -- URLs are just strings, after all -- so we only
+have to worry about things like "12" or "foo".</p>
+<p>The error case is actually easier to understand, so try to access
+<tt class="literal"><span class="pre">http://localhost/qdemo/foo/</span></tt>. You should get an error page that
+complains about an "invalid literal for int()".</p>
+<p>Now let's build a real IntegerUI object and see the results. Follow the
+third link in the "Quixote Demo" page, or just go to
+<tt class="literal"><span class="pre">http://localhost/qdemo/12/</span></tt>. You should see a web page titled "The
+Number 12".</p>
+<p>This web page is generated by the <tt class="literal"><span class="pre">_q_index()</span></tt> method of IntegerUI:
+after all, you've selected a namespace (the IntegerUI object
+corresponding to the number 12) with no explicit callable, so Quixote
+falls back on the <tt class="literal"><span class="pre">_q_index()</span></tt> attribute of that namespace.</p>
+<p>IntegerUI only exports one interesting method, <tt class="literal"><span class="pre">factorial()</span></tt>. You can
+call this method by following the "factorial" link, or just by accessing
+<tt class="literal"><span class="pre">http://localhost/qdemo/12/factorial</span></tt>.</p>
+<p>Remember how I said the URL is part of the user interface? Here's a
+great example: edit the current URL to point to a different integer. A
+fun one to try is 2147483646. If you follow the "next" link, you'll get
+an OverflowError traceback (unless you're using a 64-bit Python!),
+because the web page for 2147483647 attempts to generate its own "next"
+link to the web page for 2147483648 -- but that fails because current
+versions of Python on 32-bit platforms can't handle regular integers
+larger than 2147483647.</p>
+<p>Now go back to the page for 2147483646 and hit the "factorial" link.
+Run "top" on the web server. Get yourself a coffee. Await the heat
+death of the universe. (Actually, your browser will probably timeout
+first.) This doesn't overflow, because the factorial() function uses
+Python long integers, which can handle any integer -- they just take a
+while to get there. However, it illustrates another interesting
+vulnerability: an attacker could use this to launch a denial-of-service
+attack on the server running the Quixote demo. (Hey, it's just a demo!)</p>
+<p>Rather than fix the DoS vulnerability, I decided to use it to illustrate
+another Quixote feature: if you write to stderr, the message winds up in
+the Quixote error log for this application (/tmp/quixote-demo-error.log
+by default). The IntegerUI.factorial() method uses this to log a
+warning of an apparent denial-of-service attack:</p>
+<pre class="literal-block">
+def factorial (self, request):
+ if self.n > 10000:
+ sys.stderr.write("warning: possible denial-of-service attack "
+ "(request for factorial(%d))\n" % self.n)
+ return "%d! = %d" % (self.n, fact(self.n))
+</pre>
+<p>Since the Quixote error log is where application tracebacks are
+recorded, you should be watching this log file regularly, so you would
+presumably notice these messages.</p>
+<p>In real life, you'd probably just deny such a ludicrous request. You
+could do this by raising a Quixote publishing error. For example:</p>
+<pre class="literal-block">
+def factorial (self, request):
+ from quixote.errors import AccessError
+ if self.n > 10000:
+ raise AccessError("ridiculous request denied")
+ return "%d! = %d" % (self.n, fact(self.n))
+</pre>
+</div>
+<div class="section" id="lesson-5-widgets">
+<h1><a name="lesson-5-widgets">Lesson 5: widgets</a></h1>
+<p>You can't get very far writing web applications without writing forms,
+and the building blocks of web forms are generally called "form
+elements": string input, checkboxes, radiobuttons, select lists, and so
+forth. Quixote provides an abstraction for all of these form elements:
+the Widget class hierarchy. The widget classes are explained in detail
+in widget.txt; I'm going to give a brief description of the "Quixote
+Widget Demo" page and the code behind it here.</p>
+<p>If you follow the "widgets" link from the main page, you'll see a fairly
+ordinary-looking web form -- the sort of thing you might have to fill
+out to order a pizza on-line, with the oddity that this pizza shop is
+asking for your eye colour. (Hey, I had to demonstrate radiobuttons
+somehow!) This form demonstrates most of HTML's basic form
+capabilities: a simple string, a password, a checkbox, a set of
+radiobuttons, a single-select list, and a multiple-select list.</p>
+<p>Whenever you implement a web form, there are two things you have to
+worry about: generating the form elements and processing the
+client-submitted form data. There are as many ways of dividing up this
+work as there are web programmers. (Possibly more: every time I tackle
+this problem, I seem to come up with a different way of solving it.)
+The form in the Quixote widget demo is implemented in three parts, all
+of them in demo/widgets.ptl:</p>
+<ul class="simple">
+<li><tt class="literal"><span class="pre">widgets()</span></tt> is the callable that handles the "/qdemo/widgets" URL.
+This template creates all the widget objects needed for the form and
+then calls either <tt class="literal"><span class="pre">render_widgets()</span></tt> or <tt class="literal"><span class="pre">process_widgets()</span></tt> as
+appropriate.</li>
+<li><tt class="literal"><span class="pre">render_widgets()</span></tt> is called by <tt class="literal"><span class="pre">widgets()</span></tt> when there is no form
+data to process, eg. on the first access to the "/qdemo/widgets" URL.
+It generates the form elements and returns an HTML document consisting
+of a table that lays them out in an attractive form.</li>
+<li><tt class="literal"><span class="pre">process_widgets()</span></tt> is called by <tt class="literal"><span class="pre">widgets()</span></tt> when there is form data
+to process, ie. when the form generated by <tt class="literal"><span class="pre">render_widgets()</span></tt> is
+submitted by the user. It processes the form data, ie. it looks up
+the user-submitted form values and returns an HTML document listing
+those values.</li>
+</ul>
+<p>This division of labour works well with Quixote's widget classes, since
+you need a collection of Widget objects whether you are generating the
+form elements or processing form data. For generating the form, we (1)
+create all the widget objects, and (2) generate an HTML document that
+includes the output of each widget object's <tt class="literal"><span class="pre">render()</span></tt> method. (Laying
+out the form is the responsibility of render_widgets(), which is why
+it's littered with table tags.) For processing the form, we (1) create
+all the widget objects, and (2) generate an HTML document incorporating
+the user-submitted form values. In both cases, step (1) is handled by
+the widgets() template, which then calls either render_widgets() or
+process_widgets() for step (2).</p>
+<p>Thus, there are three things you have to understand about widget
+objects: how to create them, how to render them, and how to use them to
+parse form values. Widget creation is the only step that's very
+interesting, since each widget class has different constructor
+arguments. For example, here's how we create the "name" widget in the
+pizza shop form:</p>
+<pre class="literal-block">
+widgets['name'] = widget.StringWidget('name', size=20)
+</pre>
+<p>When rendered, this widget will produce the following HTML:</p>
+<pre class="literal-block">
+<input size="20" name="name" type="text">
+</pre>
+<p>A more complex example is the "pizza size" widget:</p>
+<pre class="literal-block">
+widgets['size'] = widget.SingleSelectWidget(
+ 'size', value='medium',
+ allowed_values=['tiny', 'small', 'medium', 'large', 'enormous'],
+ descriptions=['Tiny (4")', 'Small (6")', 'Medium (10")',
+ 'Large (14")', 'Enormous (18")'],
+ size=5)
+</pre>
+<p>which will generate the following HTML when rendered:</p>
+<pre class="literal-block">
+<select size="5" name="size">
+ <option value="0">Tiny (4")
+ <option value="1">Small (6")
+ <option selected value="2">Medium (10")
+ <option value="3">Large (14")
+ <option value="4">Enormous (18")
+</select>
+</pre>
+<p>Some things you might need to know about widget creation:</p>
+<ul class="simple">
+<li>the standard widget classes are in the quixote.form.widget module;
+see widget.txt and/or the source code for the complete list</li>
+<li>every widget class constructor has exactly one required argument:
+the widget name. This is used as the form element name
+in the generated HTML. (Things are a bit different for compound
+widgets, but I'm not covering them in this document.)</li>
+<li>every widget class supports a number of keyword arguments that
+generally correspond to attributes of some HTML tag. The one
+argument common to all widget classes is <tt class="literal"><span class="pre">value</span></tt>, the current
+value for this widget.</li>
+</ul>
+<p>Rendering widgets is easy: just call the render() method, passing in the
+current HTTPRequest object. (It's currently not used by the standard
+widget classes, but could be used by derived or compound widget classes
+for context-sensitive widget rendering.)</p>
+<p>Parsing form values is just as easy: call the parse() method, again
+passing in the current HTTPRequest object. The return value depends on
+the nature of the widget, eg.:</p>
+<ul class="simple">
+<li>StringWidget and PasswordWidget return a string</li>
+<li>CheckboxWidget returns a boolean</li>
+<li>RadiobuttonsWidget, SingleSelectWidget, and MultipleSelectWidget
+return one of the values supplied in <tt class="literal"><span class="pre">allowed_values</span></tt> -- in the
+demo these are all strings, but they can be any Python value.
+(If the client submits bogus data, the widget will return None.)</li>
+</ul>
+<p>$Id: demo.txt 21292 2003-04-08 16:48:47Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/demo.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/demo.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/demo.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/demo.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,643 @@
+Running the Quixote Demo
+========================
+
+Quixote comes with a tiny demonstration application that you can install
+and run on your web server. In a few dozen lines of Python and PTL
+code, it demonstrates most of Quixote's basic capabilities. It's also
+an easy way to make sure that your Python installation and web server
+configuration are cooperating so that Quixote applications can work.
+
+
+Installation
+------------
+
+The demo is included in the quixote.demo package, which is installed
+along with the rest of Quixote when you run ``python setup.py install``.
+The driver script (demo.cgi) and associated configuration file
+(demo.conf) are *not* installed automatically -- you'll have to copy
+them from the demo/ subdirectory to your web server's CGI directory.
+Eg., if you happen to use the same web server tree as we do::
+
+ cp -p demo/demo.cgi demo/demo.conf /www/cgi-bin
+
+You'll almost certainly need to edit the ``#!`` line of demo.cgi to ensure
+that it points to the correct Python interpreter -- it should be the
+same interpreter that you used to run ``setup.py install``.
+
+
+Verifying the installation
+--------------------------
+
+Before we try to access the demo via your web server, let's make sure
+that the quixote and quixote.demo packages are installed on your system::
+
+ $ python
+ Python 2.1.1 (#2, Jul 30 2001, 12:04:51)
+ [GCC 2.95.2 20000220 (Debian GNU/Linux)] on linux2
+ Type "copyright", "credits" or "license" for more information.
+ >>> import quixote
+ >>> quixote.enable_ptl()
+ >>> import quixote.demo
+
+(Quixote requires Python 2.0 or greater; you might have to name an
+explicit Python interpreter, eg. ``/usr/local/bin/python2.1``. Make
+sure that the Python interpreter you use here is the same as you put in
+the ``#!`` line of demo.cgi, and the same that you used to install
+Quixote.)
+
+If this runs without errors, then Quixote (and its demo) are installed
+such that you can import them. It remains to be seen if the user that
+will run the driver script -- usually ``nobody`` -- can import them.
+
+
+Running the demo directly
+-------------------------
+
+Assuming that
+
+* your web server is running on the current host
+* your web server is configured to handle requests to
+ /cgi-bin/demo.cgi by running the demo.cgi script that you just
+ installed (eg. to /www/cgi-bin/demo.cgi)
+
+then you should now be able to run the Quixote demo by directly
+referring to the demo.cgi script.
+
+Another option, if you have the Medusa package installed, is to run::
+
+ python server/medusa_http.py
+
+This will start a small pure-Python web server running on port 8080.
+(Medusa is available from http://www.amk.ca/python/code/medusa.html .)
+
+Start a web browser and load ::
+
+ http://localhost/cgi-bin/demo.cgi/
+
+or ::
+
+ http://localhost:8080/
+
+if you're using Medusa.
+
+You should see a page titled "Quixote Demo" with the headline "Hello,
+world!". Feel free to poke around; you can't break anything through the
+demo. (That's not to say you can't break things with Quixote in
+general; since Quixote gives you the full power of Python for your web
+applications, you have the power to create stupid security holes.)
+
+If you don't get the "Quixote Demo" page, go look in your web server's
+error log. Some things that might go wrong:
+
+* your web server is not configured to run CGI scripts, or it
+ might use a different base URL for them. If you're running
+ Apache, look for something like ::
+
+ ScriptAlias /cgi-bin/ /www/cgi-bin/
+
+ in your httpd.conf (for some value of "/www/cgi-bin").
+
+ (This is not a problem with Quixote or the Quixote demo; this is a
+ problem with your web server's configuration.)
+
+* your web server was unable to execute the script. Make sure
+ its permissions are correct::
+
+ chmod 755 /www/cgi-bin/demo.cgi
+
+ (This shouldn't happen if you installed demo.cgi with ``cp -p`` as
+ illustrated above.)
+
+* demo.cgi started, but was unable to import the Quixote modules.
+ In this case, there should be a short Python traceback in your web
+ server's error log ending with a message like ::
+
+ ImportError: No module named quixote
+
+ Remember, just because you can "import quixote" in a Python
+ interpreter doesn't mean the user that runs CGI scripts (usually
+ "nobody") can. You might have installed Quixote in a non-standard
+ location, in which case you should either install it in the standard
+ location (your Python interpreter's "site-packages" directory) or
+ instruct your web server to set the PYTHONPATH environment variable.
+ Or you might be using the wrong Python interpreter -- check the ``#!``
+ line of demo.cgi.
+
+* demo.cgi started and imported Quixote, but was unable to read its
+ config file. There should be a short Python traceback in your web
+ server's error log ending with a message like ::
+
+ IOError: [Errno 2] No such file or directory: 'demo.conf'
+
+ in this case.
+
+ Make sure you copied demo.conf to the same directory as demo.cgi,
+ and make sure it is readable::
+
+ chmod 644 /www/cgi-bin/demo.conf
+
+ (This shouldn't happen if you install demo.conf with ``cp -p`` as
+ illustrated above.)
+
+
+Running the demo indirectly
+---------------------------
+
+One of the main tenets of Quixote's design is that, in a web
+application, the URL is part of the user interface. We consider it
+undesirable to expose implementation details -- such as
+"/cgi-bin/demo.cgi" -- to users. That sort of thing should be tucked
+away out of sight. Depending on your web server, this should be easy to
+do with a simple tweak to its configuration.
+
+For example, say you want the "/qdemo" URL to be the location of the
+Quixote demo. If you're using Apache with the rewrite engine loaded and
+enabled, all you need to do is add this to your httpd.conf::
+
+ RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+
+With this rule in effect (don't forget to restart your server!),
+accesses to "/qdemo/" are the same as accesses to "/cgi-bin/demo.cgi/" --
+except they're a lot easier for the user to understand and don't expose
+implementation details of your application.
+
+Try it out. In your web browser, visit ``http://localhost/qdemo/``.
+
+You should get exactly the same page as you got visiting
+"/cgi-bin/demo.cgi/" earlier, and all the links should work exactly the
+same.
+
+You can use any URL prefix you like -- there's nothing special about
+"/qdemo".
+
+One small but important detail here is "/qdemo" versus "/qdemo/". In
+the above configuration, requests for "/qdemo" will fail, and requests
+for "/qdemo/" will succeed. See the "URL rewriting" section of
+web-server.txt for details and how to fix this.
+
+
+Understanding the demo
+----------------------
+
+Now that you've gotten the demo to run successfully, let's look under
+the hood and see how it works. Before we start following links in the
+demo (don't worry if you already have, you can't hurt anything), make
+sure you're watching all the relevant log files. As with any web
+application, log files are essential for debugging Quixote applications.
+
+Assuming that your web server's error log is in /www/log/error_log, and
+that you haven't changed the DEBUG_LOG and ERROR_LOG settings in
+demo.conf::
+
+ $ tail -f /www/log/error_log & \
+ tail -f /tmp/quixote-demo-debug.log & \
+ tail -f /tmp/quixote-demo-error.log
+
+(Note that recent versions of GNU tail let you tail multiple files with
+the same command. Cool!)
+
+Lesson 1: the top page
+----------------------
+
+Reload the top of the demo, presumably ``http://localhost/qdemo/``. You
+should see "debug message from the index page" in the debug log file.
+
+Where is this message coming from? To find out, we need to delve into
+the source code for the demo. Load up demo/__init__.py and let's take a
+look. In the process, we'll learn how to explore a Quixote application
+and find the source code that corresponds to a given URL.
+
+First, why are we loading demo/__init__.py? Because that's where some
+of the names in the "quixote.demo" namespace are defined, and it's where
+the list of names that may be "exported" by Quixote from this namespace
+to the web is given. Recall that under Quixote, every URL boils down to
+a callable Python object -- usually a function or method. The root of
+this application is a Python package ("quixote.demo"), which is just a
+special kind of module. But modules aren't callable -- so what does the
+"/qdemo/" URL boil down to? That's what ``_q_index()`` is for -- you
+can define a special function that is called by default when Quixote
+resolves a URL to a namespace rather than a callable. That is,
+"/qdemo/" resolves to the "quixote.demo" package; a package is a
+namespace, so it can't be called; therefore Quixote looks for a function
+called ``_q_index()`` in that namespace and calls it.
+
+In this case, ``_q_index()`` is not defined in demo/__init__.py -- but
+it is imported there from the quixote.demo.pages module. This is
+actually a PTL module -- demo/pages.py does not exist, but
+demo/pages.ptl does. So load it up and take a look::
+
+ def _q_index [plain] (request):
+ print "debug message from the index page"
+ """
+ <html>
+ <head><title>Quixote Demo</title></head>
+ <body>
+ <h1>Hello, world!</h1>
+ [...]
+ </body>
+ </html>
+ """
+
+A-ha! There's the PTL code that generates the "Quixote Demo" page.
+This ``_q_index()`` template is quite simple PTL -- it's mostly an HTML
+document with a single debug print thrown in to demonstrate Quixote's
+debug logging facility.
+
+Outcome of lesson 1:
+
+* a URL maps to either a namespace (package, module, class instance)
+ or a callable (function, method, PTL template)
+
+* if a URL maps to a namespace, Quixote looks for a callable
+ ``_q_index()`` in that namespace and calls it
+
+* ``_q_index()`` doesn't have to be explicitly exported by your
+ namespace; if it exists, it will be used
+
+* anything your application prints to standard output goes to
+ Quixote's debug log. (If you didn't specify a debug log in
+ your config file, debug messages are discarded.)
+
+
+Lesson 2: a link to a simple document
+-------------------------------------
+
+The first two links in the "Quixote Demo" page are quite simple. Each
+one is handled by a Python function defined in the "quixote.demo"
+namespace, i.e. in demo/__init__.py. For example, following the
+"simple" link is equivalent to calling the ``simple()`` function in
+"quixote.demo". Let's take a look at that function::
+
+ def simple (request):
+ request.response.set_content_type("text/plain")
+ return "This is the Python function 'quixote.demo.simple'.\n"
+
+Note that this could equivalently be coded in PTL::
+
+ def simple [plain] (request):
+ request.response.set_content_type("text/plain")
+ "This is the Python function 'quixote.demo.simple'.\n"
+
+...but for such a simple document, why bother?
+
+Since this function doesn't generate an HTML document, it would be
+misleading for the HTTP response that Quixote generates to claim a
+"Content-type" of "text/html". That is the default for Quixote's HTTP
+responses, however, since most HTTP responses are indeed HTML documents.
+Therefore, if the content you're returning is anything other than an
+HTML document, you should set the "Content-type" header on the HTTP
+response.
+
+This brings up a larger issue: request and response objects. Quixote
+includes two classes, HTTPRequest and HTTPResponse, to encapsulate every
+HTTP request and its accompanying response. Whenever Quixote resolves a
+URL to a callable and calls it, it passes precisely one argument: an
+HTTPRequest object.
+
+The HTTPRequest object includes (almost) everything you might want to
+know about the HTTP request that caused Quixote to be invoked and to
+call a particular function, method, or PTL template. You have access to
+CGI environment variables, HTML form variables (parsed and
+ready-to-use), and HTTP cookies. Finally, the HTTPRequest object also
+includes an HTTPResponse object -- after all, every request implies a
+response. You can set the response status, set response headers, set
+cookies, or force a redirect using the HTTPResponse object.
+
+Note that it's not enough that the ``simple()`` function merely exists.
+If that were the case, then overly-curious users or attackers could
+craft URLs that point to any Python function in any module under your
+application's root namespace, potentially causing all sorts of havoc.
+You need to explicitly declare which names are exported from your
+application to the web, using the ``_q_exports`` variable. For example,
+demo/__init__.py has this export list::
+
+ _q_exports = ["simple", "error"]
+
+This means that only these two names are explicitly exported by the
+Quixote demo. (The empty string is implicitly exported from a namespace
+if a ``_q_index()`` callable exists there -- thus "/qdemo/" is handled
+by ``_q_index()`` in the "quixote.demo" namespace. Arbitrary names may
+be implicitly exported using a ``_q_lookup()`` function; see Lesson 4
+below.)
+
+
+Lesson 3: error-handling
+------------------------
+
+The next link in the "Quixote Demo" page is to the "error" document,
+which is handled by the ``error()`` function in demo/__init__.py. All
+this function does is raise an exception::
+
+ def error (request):
+ raise ValueError, "this is a Python exception"
+
+Follow the link, and you should see a Python traceback followed by a
+dump of the CGI environment for this request (along with other request
+data, such as a list of cookies).
+
+This is extremely useful when developing, testing, and debugging. In a
+production environment, though, it reveals way too much about your
+implementation to hapless users who should happen to hit an error, and
+it also reveals internal details to attackers who might use it to crack
+your site. (It's just as easy to write an insecure web application with
+Quixote as with any other tool.)
+
+Thus, Quixote offers the ``DISPLAY_EXCEPTIONS`` config variable. This
+is false by default, but the demo.conf file enables it. To see what
+happens with ``DISPLAY_EXCEPTIONS`` off, edit demo.conf and reload the
+"error" page. You should see a bland, generic error message that
+reveals very little about your implementation. (This error page is
+deliberately very similar, but not identical, to Apache's "Internal
+Server Error" page.)
+
+Unhandled exceptions raised by application code (aka "application bugs")
+are only one kind of error you're likely to encounter when developing a
+Quixote application. The other ones are:
+
+* driver script crashes or doesn't run (eg. can't import quixote
+ modules, can't load config file). This is covered under
+ "Running the demo directly" above
+
+* publishing errors, such as a request for "/simpel" that should
+ have been "/simple", or a request for a resource that exists but is
+ denied to the current user. Quixote has a family of exception
+ classes for dealing with these; such exceptions may be raised by
+ Quixote itself or by your application. They are usually handled by
+ Quixote and turned into HTTP error responses (4xx status code),
+ but it's possible for your application to define a special handler
+ for such exceptions.
+
+* bugs in Quixote itself; hopefully this won't happen, but you
+ never know. These usually look a lot like problems in the driver
+ script: the script crashes and prints a traceback to stderr, which
+ most likely winds up in your web server's error log. The length of
+ the traceback is generally a clue as to whether there's a problem
+ with your driver script or a bug in Quixote.
+
+Publishing errors result in a 4xx HTTP response code, and can be
+entirely handled by Quixote -- that is, your web server just returns
+the HTTP response that Quixote prepares. It's also possible to write
+a ``_q_exception_handler()`` method that will be called on triggering
+a publishing error. This method can then provide a friendlier
+response; for example, the page might provide a link to the site map,
+or the method might look at the problematic path and try to correct
+misspellings. The demo defines a ``_q_exception_handler()`` in
+demo/pages.ptl.
+
+Application bugs result in a 5xx HTTP response code, and are
+entirely handled by Quixote. Don't get confused by the fact that
+Quixote's and Apache's "Internal Server Error" pages are quite similar!
+
+Driver script crashes and Quixote bugs (which are essentially the same
+thing; the main difference is who to blame) are handled by your web
+server. (In the first case, Quixote doesn't even enter into it; in the
+second case, Quixote dies horribly and is no longer in control.) Under
+Apache, the Python traceback resulting from the crash is written to
+Apache's error log, and a 5xx response is returned to the client with
+Apache's "Internal Server Error" error page.
+
+
+Lesson 4: object publishing
+---------------------------
+
+Publishing Python callables on the web -- i.e., translating URLs to
+Python functions/methods/PTL templates and calling them to determine the
+HTTP response -- is a very powerful way of writing web applications.
+However, Quixote has one more trick up its sleeve: object publishing.
+You can translate arbitrary names to arbitrary objects which are then
+published on the web, and you can create URLs that call methods on those
+objects.
+
+This is all accomplished with the ``_q_lookup()`` function. Every
+namespace that Quixote encounters may have a ``_q_lookup()``, just like
+it may have a ``_q_index()``. ``_q_index()`` is used to handle requests for
+the empty name -- as we saw in Lesson 1, a request for "/qdemo/", maps to
+the "quixote.demo" namespace; the empty string after the last slash
+means that Quixote will call ``_q_index()`` in this namespace to handle
+the request.
+
+``_q_lookup()`` is for requests that aren't handled by a Python callable
+in the namespace. As seen in Lessons 2 and 3, requests for
+"/qdemo/simple" and "/qdemo/error" are handled by the ``simple()`` and
+``error()`` functions in the "quixote.demo" namespace. What if someone
+requests "/qdemo/foo"? There's no function ``foo()`` in the
+"quixote.demo" namespace, so normally this would be an error.
+(Specifically, it would be a publishing error: Quixote would raise
+TraversalError, which is the error used for non-existent or non-exported
+names. Another part of Quixote then turns this into an HTTP 404
+response.)
+
+However, this particular namespace also defines a ``_q_lookup()``
+function. That means that the application wants a chance to handle
+unknown names before Quixote gives up entirely. Let's take a look at
+the implementation of ``_q_lookup()``::
+
+ from quixote.demo.integer_ui import IntegerUI
+ [...]
+ def _q_lookup(request, component):
+ return IntegerUI(request, component)
+
+Pretty simple: we just construct an IntegerUI object and return it. So
+what is IntegerUI? Take a look in the demo/integer_ui.py file to see;
+it's just a web interface to integers. (Normally, you would write a
+wrapper class that provides a web interface to something more
+interesting than integers. This just demonstrates how simple an object
+published by Quixote can be.)
+
+So, what is an IntegerUI object? From Quixote's point of view, it's
+just another namespace to publish: like modules and packages, class
+instances have attributes, some of which (methods) are callable. In the
+case of IntegerUI, two of those attributes are ``_q_exports`` and
+``_q_index`` -- every namespace published by Quixote must have an export
+list, and an index function is almost always advisable.
+
+What this means is that any name that the IntegerUI constructor accepts
+is a valid name to tack onto the "/qdemo/" URL. Take a look at the
+IntegerUI constructor; you'll see that it works fine when passed
+something that can be converted to an integer (eg. "12" or 1.0), and
+raises Quixote's TraversalError if not. As it happens, Quixote always
+passes in a string -- URLs are just strings, after all -- so we only
+have to worry about things like "12" or "foo".
+
+The error case is actually easier to understand, so try to access
+``http://localhost/qdemo/foo/``. You should get an error page that
+complains about an "invalid literal for int()".
+
+Now let's build a real IntegerUI object and see the results. Follow the
+third link in the "Quixote Demo" page, or just go to
+``http://localhost/qdemo/12/``. You should see a web page titled "The
+Number 12".
+
+This web page is generated by the ``_q_index()`` method of IntegerUI:
+after all, you've selected a namespace (the IntegerUI object
+corresponding to the number 12) with no explicit callable, so Quixote
+falls back on the ``_q_index()`` attribute of that namespace.
+
+IntegerUI only exports one interesting method, ``factorial()``. You can
+call this method by following the "factorial" link, or just by accessing
+``http://localhost/qdemo/12/factorial``.
+
+Remember how I said the URL is part of the user interface? Here's a
+great example: edit the current URL to point to a different integer. A
+fun one to try is 2147483646. If you follow the "next" link, you'll get
+an OverflowError traceback (unless you're using a 64-bit Python!),
+because the web page for 2147483647 attempts to generate its own "next"
+link to the web page for 2147483648 -- but that fails because current
+versions of Python on 32-bit platforms can't handle regular integers
+larger than 2147483647.
+
+Now go back to the page for 2147483646 and hit the "factorial" link.
+Run "top" on the web server. Get yourself a coffee. Await the heat
+death of the universe. (Actually, your browser will probably timeout
+first.) This doesn't overflow, because the factorial() function uses
+Python long integers, which can handle any integer -- they just take a
+while to get there. However, it illustrates another interesting
+vulnerability: an attacker could use this to launch a denial-of-service
+attack on the server running the Quixote demo. (Hey, it's just a demo!)
+
+Rather than fix the DoS vulnerability, I decided to use it to illustrate
+another Quixote feature: if you write to stderr, the message winds up in
+the Quixote error log for this application (/tmp/quixote-demo-error.log
+by default). The IntegerUI.factorial() method uses this to log a
+warning of an apparent denial-of-service attack::
+
+ def factorial (self, request):
+ if self.n > 10000:
+ sys.stderr.write("warning: possible denial-of-service attack "
+ "(request for factorial(%d))\n" % self.n)
+ return "%d! = %d" % (self.n, fact(self.n))
+
+Since the Quixote error log is where application tracebacks are
+recorded, you should be watching this log file regularly, so you would
+presumably notice these messages.
+
+In real life, you'd probably just deny such a ludicrous request. You
+could do this by raising a Quixote publishing error. For example::
+
+ def factorial (self, request):
+ from quixote.errors import AccessError
+ if self.n > 10000:
+ raise AccessError("ridiculous request denied")
+ return "%d! = %d" % (self.n, fact(self.n))
+
+
+Lesson 5: widgets
+-----------------
+
+You can't get very far writing web applications without writing forms,
+and the building blocks of web forms are generally called "form
+elements": string input, checkboxes, radiobuttons, select lists, and so
+forth. Quixote provides an abstraction for all of these form elements:
+the Widget class hierarchy. The widget classes are explained in detail
+in widget.txt; I'm going to give a brief description of the "Quixote
+Widget Demo" page and the code behind it here.
+
+If you follow the "widgets" link from the main page, you'll see a fairly
+ordinary-looking web form -- the sort of thing you might have to fill
+out to order a pizza on-line, with the oddity that this pizza shop is
+asking for your eye colour. (Hey, I had to demonstrate radiobuttons
+somehow!) This form demonstrates most of HTML's basic form
+capabilities: a simple string, a password, a checkbox, a set of
+radiobuttons, a single-select list, and a multiple-select list.
+
+Whenever you implement a web form, there are two things you have to
+worry about: generating the form elements and processing the
+client-submitted form data. There are as many ways of dividing up this
+work as there are web programmers. (Possibly more: every time I tackle
+this problem, I seem to come up with a different way of solving it.)
+The form in the Quixote widget demo is implemented in three parts, all
+of them in demo/widgets.ptl:
+
+* ``widgets()`` is the callable that handles the "/qdemo/widgets" URL.
+ This template creates all the widget objects needed for the form and
+ then calls either ``render_widgets()`` or ``process_widgets()`` as
+ appropriate.
+
+* ``render_widgets()`` is called by ``widgets()`` when there is no form
+ data to process, eg. on the first access to the "/qdemo/widgets" URL.
+ It generates the form elements and returns an HTML document consisting
+ of a table that lays them out in an attractive form.
+
+* ``process_widgets()`` is called by ``widgets()`` when there is form data
+ to process, ie. when the form generated by ``render_widgets()`` is
+ submitted by the user. It processes the form data, ie. it looks up
+ the user-submitted form values and returns an HTML document listing
+ those values.
+
+This division of labour works well with Quixote's widget classes, since
+you need a collection of Widget objects whether you are generating the
+form elements or processing form data. For generating the form, we (1)
+create all the widget objects, and (2) generate an HTML document that
+includes the output of each widget object's ``render()`` method. (Laying
+out the form is the responsibility of render_widgets(), which is why
+it's littered with table tags.) For processing the form, we (1) create
+all the widget objects, and (2) generate an HTML document incorporating
+the user-submitted form values. In both cases, step (1) is handled by
+the widgets() template, which then calls either render_widgets() or
+process_widgets() for step (2).
+
+Thus, there are three things you have to understand about widget
+objects: how to create them, how to render them, and how to use them to
+parse form values. Widget creation is the only step that's very
+interesting, since each widget class has different constructor
+arguments. For example, here's how we create the "name" widget in the
+pizza shop form::
+
+ widgets['name'] = widget.StringWidget('name', size=20)
+
+When rendered, this widget will produce the following HTML::
+
+ <input size="20" name="name" type="text">
+
+A more complex example is the "pizza size" widget::
+
+ widgets['size'] = widget.SingleSelectWidget(
+ 'size', value='medium',
+ allowed_values=['tiny', 'small', 'medium', 'large', 'enormous'],
+ descriptions=['Tiny (4")', 'Small (6")', 'Medium (10")',
+ 'Large (14")', 'Enormous (18")'],
+ size=5)
+
+which will generate the following HTML when rendered::
+
+ <select size="5" name="size">
+ <option value="0">Tiny (4")
+ <option value="1">Small (6")
+ <option selected value="2">Medium (10")
+ <option value="3">Large (14")
+ <option value="4">Enormous (18")
+ </select>
+
+Some things you might need to know about widget creation:
+
+* the standard widget classes are in the quixote.form.widget module;
+ see widget.txt and/or the source code for the complete list
+
+* every widget class constructor has exactly one required argument:
+ the widget name. This is used as the form element name
+ in the generated HTML. (Things are a bit different for compound
+ widgets, but I'm not covering them in this document.)
+
+* every widget class supports a number of keyword arguments that
+ generally correspond to attributes of some HTML tag. The one
+ argument common to all widget classes is ``value``, the current
+ value for this widget.
+
+Rendering widgets is easy: just call the render() method, passing in the
+current HTTPRequest object. (It's currently not used by the standard
+widget classes, but could be used by derived or compound widget classes
+for context-sensitive widget rendering.)
+
+Parsing form values is just as easy: call the parse() method, again
+passing in the current HTTPRequest object. The return value depends on
+the nature of the widget, eg.:
+
+* StringWidget and PasswordWidget return a string
+* CheckboxWidget returns a boolean
+* RadiobuttonsWidget, SingleSelectWidget, and MultipleSelectWidget
+ return one of the values supplied in ``allowed_values`` -- in the
+ demo these are all strings, but they can be any Python value.
+ (If the client submits bogus data, the widget will return None.)
+
+
+$Id: demo.txt 21292 2003-04-08 16:48:47Z akuchlin $
Added: packages/quixote1/branches/upstream/current/doc/form2conversion.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/form2conversion.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/form2conversion.html (added)
+++ packages/quixote1/branches/upstream/current/doc/form2conversion.html Mon May 8 19:16:16 2006
@@ -1,0 +1,369 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Converting form1 forms to use the form2 library</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="converting-form1-forms-to-use-the-form2-library">
+<h1 class="title">Converting form1 forms to use the form2 library</h1>
+<div class="section" id="introduction">
+<h1><a name="introduction">Introduction</a></h1>
+<p>These are some notes and examples for converting Quixote form1 forms,
+that is forms derived from <tt class="literal"><span class="pre">quixote.form.Form</span></tt>, to the newer form2
+forms.</p>
+<p>Form2 forms are more flexible than their form1 counterparts in that they
+do not require you to use the <tt class="literal"><span class="pre">Form</span></tt> class as a base to get form
+functionality as form1 forms did. Form2 forms can be instantiated
+directly and then manipulated as instances. You may also continue to
+use inheritance for your form2 classes to get form functionality,
+particularly if the structured separation of <tt class="literal"><span class="pre">process</span></tt>, <tt class="literal"><span class="pre">render</span></tt>,
+and <tt class="literal"><span class="pre">action</span></tt> is desirable.</p>
+<p>There are many ways to get from form1 code ported to form2. At one
+end of the spectrum is to rewrite the form class using a functional
+programing style. This method is arguably best since the functional
+style makes the flow of control clearer.</p>
+<p>The other end of the spectrum and normally the easiest way to port
+form1 forms to form2 is to use the <tt class="literal"><span class="pre">compatibility</span></tt> module provided
+in the form2 package. The compatibility module's Form class provides
+much of the same highly structured machinery (via a <tt class="literal"><span class="pre">handle</span></tt> master
+method) that the form1 framework uses.</p>
+</div>
+<div class="section" id="converting-form1-forms-using-using-the-compatibility-module">
+<h1><a name="converting-form1-forms-using-using-the-compatibility-module">Converting form1 forms using using the compatibility module</a></h1>
+<p>Here's the short list of things to do to convert form1 forms to
+form2 using compatibility.</p>
+<blockquote>
+<ol class="arabic">
+<li><p class="first">Import the Form base class from <tt class="literal"><span class="pre">quixote.form2.compatibility</span></tt>
+rather than from quixote.form.</p>
+</li>
+<li><p class="first">Getting and setting errors is slightly different. In your form's
+process method, where errors are typically set, form2
+has a new interface for marking a widget as having an error.</p>
+<blockquote>
+<p>Form1 API:</p>
+<pre class="literal-block">
+self.error['widget_name'] = 'the error message'
+</pre>
+<p>Form2 API:</p>
+<pre class="literal-block">
+self.set_error('widget_name', 'the error message')
+</pre>
+</blockquote>
+<p>If you want to find out if the form already has errors, change
+the form1 style of direct references to the <tt class="literal"><span class="pre">self.errors</span></tt>
+dictionary to a call to the <tt class="literal"><span class="pre">has_errors</span></tt> method.</p>
+<blockquote>
+<p>Form1 API:</p>
+<pre class="literal-block">
+if not self.error:
+ do some more error checking...
+</pre>
+<p>Form2 API:</p>
+<pre class="literal-block">
+if not self.has_errors():
+ do some more error checking...
+</pre>
+</blockquote>
+</li>
+<li><p class="first">Form2 select widgets no longer take <tt class="literal"><span class="pre">allowed_values</span></tt> or
+<tt class="literal"><span class="pre">descriptions</span></tt> arguments. If you are adding type of form2 select
+widget, you must provide the <tt class="literal"><span class="pre">options</span></tt> argument instead. Options
+are the way you define the list of things that are selectable and
+what is returned when they are selected. the options list can be
+specified in in one of three ways:</p>
+<pre class="literal-block">
+ options: [objects:any]
+or
+ options: [(object:any, description:any)]
+or
+ options: [(object:any, description:any, key:any)]
+</pre>
+<p>An easy way to construct options if you already have
+allowed_values and descriptions is to use the built-in function
+<tt class="literal"><span class="pre">zip</span></tt> to define options:</p>
+<pre class="literal-block">
+options=zip(allowed_values, descriptions)
+</pre>
+</li>
+</ol>
+<blockquote>
+Note, however, that often it is simpler to to construct the
+<tt class="literal"><span class="pre">options</span></tt> list directly.</blockquote>
+<ol class="arabic simple" start="4">
+<li>You almost certainly want to include some kind of cascading style
+sheet (since form2 forms render with minimal markup). There is a
+basic set of CSS rules in <tt class="literal"><span class="pre">quixote.form2.css</span></tt>.</li>
+</ol>
+</blockquote>
+<p>Here's the longer list of things you may need to tweak in order for
+form2 compatibility forms to work with your form1 code.</p>
+<blockquote>
+<ul>
+<li><p class="first"><tt class="literal"><span class="pre">widget_type</span></tt> widget class attribute is gone. This means when
+adding widgets other than widgets defined in <tt class="literal"><span class="pre">quixote.form2.widget</span></tt>,
+you must import the widget class into your module and pass the
+widget class as the first argument to the <tt class="literal"><span class="pre">add_widget</span></tt> method
+rather than using the <tt class="literal"><span class="pre">widget_type</span></tt> string.</p>
+</li>
+<li><p class="first">The <tt class="literal"><span class="pre">action_url</span></tt> argument to the form's render method is now
+a keyword argument.</p>
+</li>
+<li><p class="first">If you use <tt class="literal"><span class="pre">OptionSelectWidget</span></tt>, there is no longer a
+<tt class="literal"><span class="pre">get_current_option</span></tt> method. You can get the current value
+in the normal way.</p>
+</li>
+<li><p class="first"><tt class="literal"><span class="pre">ListWidget</span></tt> has been renamed to <tt class="literal"><span class="pre">WidgetList</span></tt>.</p>
+</li>
+<li><p class="first">There is no longer a <tt class="literal"><span class="pre">CollapsibleListWidget</span></tt> class. If you need
+this functionality, consider writing a 'deletable composite widget'
+to wrap your <tt class="literal"><span class="pre">WidgetList</span></tt> widgets in it:</p>
+<pre class="literal-block">
+class DeletableWidget(CompositeWidget):
+
+ def __init__(self, name, value=None,
+ element_type=StringWidget,
+ element_kwargs={}, **kwargs):
+ CompositeWidget.__init__(self, name, value=value, **kwargs)
+ self.add(HiddenWidget, 'deleted', value='0')
+ if self.get('deleted') != '1':
+ self.add(element_type, 'element', value=value,
+ **element_kwargs)
+ self.add(SubmitWidget, 'delete', value='Delete')
+ if self.get('delete'):
+ self.get_widget('deleted').set_value('1')
+
+ def _parse(self, request):
+ if self.get('deleted') == '1':
+ self.value = None
+ else:
+ self.value = self.get('element')
+
+ def render(self):
+ if self.get('deleted') == '1':
+ return self.get_widget('deleted').render()
+ else:
+ return CompositeWidget.render(self)
+</pre>
+</li>
+</ul>
+</blockquote>
+<p>Congratulations, now that you've gotten your form1 forms working in form2,
+you may wish to simplify this code using some of the new features available
+in form2 forms. Here's a list of things you may wish to consider:</p>
+<blockquote>
+<ul>
+<li><p class="first">In your process method, you don't really need to get a <tt class="literal"><span class="pre">form_data</span></tt>
+dictionary by calling <tt class="literal"><span class="pre">Form.process</span></tt> to ensure your widgets are
+parsed. Instead, the parsed value of any widget is easy to obtain
+using the widget's <tt class="literal"><span class="pre">get_value</span></tt> method or the form's
+<tt class="literal"><span class="pre">__getitem__</span></tt> method. So, instead of:</p>
+<pre class="literal-block">
+form_data = Form.process(self, request)
+val = form_data['my_widget']
+</pre>
+<p>You can use:</p>
+<pre class="literal-block">
+val = self['my_widget']
+</pre>
+<p>If the widget may or may not be in the form, you can use <tt class="literal"><span class="pre">get</span></tt>:</p>
+<pre class="literal-block">
+val = self.get('my_widget')
+</pre>
+</li>
+<li><p class="first">It's normally not necessary to provide the <tt class="literal"><span class="pre">action_url</span></tt> argument
+to the form's <tt class="literal"><span class="pre">render</span></tt> method.</p>
+</li>
+<li><p class="first">You don't need to save references to your widgets in your form
+class. You may have a particular reason for wanting to do that,
+but any widget added to the form using <tt class="literal"><span class="pre">add</span></tt> (or <tt class="literal"><span class="pre">add_widget</span></tt> in
+the compatibility module) can be retrieved using the form's
+<tt class="literal"><span class="pre">get_widget</span></tt> method.</p>
+</li>
+</ul>
+</blockquote>
+</div>
+<div class="section" id="converting-form1-forms-to-form2-by-functional-rewrite">
+<h1><a name="converting-form1-forms-to-form2-by-functional-rewrite">Converting form1 forms to form2 by functional rewrite</a></h1>
+<p>The best way to get started on a functional version of a form2 rewrite
+is to look at a trivial example form first written using the form1
+inheritance model followed by it's form2 functional equivalent.</p>
+<p>First the form1 form:</p>
+<pre class="literal-block">
+class MyForm1Form(Form):
+ def __init__(self, request, obj):
+ Form.__init__(self)
+
+ if obj is None:
+ self.obj = Obj()
+ self.add_submit_button('add', 'Add')
+ else:
+ self.obj = obj
+ self.add_submit_button('update', 'Update')
+
+ self.add_cancel_button('Cancel', request.get_path(1) + '/')
+
+ self.add_widget('single_select', 'obj_type',
+ title='Object Type',
+ value=self.obj.get_type(),
+ allowed_values=list(obj.VALID_TYPES),
+ descriptions=['type1', 'type2', 'type3'])
+ self.add_widget('float', 'cost',
+ title='Cost',
+ value=obj.get_cost())
+
+ def render [html] (self, request, action_url):
+ title = 'Obj %s: Edit Object' % self.obj
+ header(title)
+ Form.render(self, request, action_url)
+ footer(title)
+
+ def process(self, request):
+ form_data = Form.process(self, request)
+
+ if not self.error:
+ if form_data['cost'] is None:
+ self.error['cost'] = 'A cost is required.'
+ elif form_data['cost'] < 0:
+ self.error['cost'] = 'The amount must be positive'
+ return form_data
+
+ def action(self, request, submit, form_data):
+ self.obj.set_type(form_data['obj_type'])
+ self.obj.set_cost(form_data['cost'])
+ if submit == 'add':
+ db = get_database()
+ db.add(self.obj)
+ else:
+ assert submit == 'update'
+ return request.redirect(request.get_path(1) + '/')
+</pre>
+<p>Here's the same form using form2 where the function operates on a Form
+instance it keeps a reference to it as a local variable:</p>
+<pre class="literal-block">
+def obj_form(request, obj):
+ form = Form() # quixote.form2.Form
+ if obj is None:
+ obj = Obj()
+ form.add_submit('add', 'Add')
+ else:
+ form.add_submit('update', 'Update')
+ form.add_submit('cancel', 'Cancel')
+
+ form.add_single_select('obj_type',
+ title='Object Type',
+ value=obj.get_type(),
+ options=zip(obj.VALID_TYPES,
+ ['type1', 'type2', 'type3']))
+ form.add_float('cost',
+ title='Cost',
+ value=obj.get_cost(),
+ required=1)
+
+ def render [html] ():
+ title = 'Obj %s: Edit Object' % obj
+ header(title)
+ form.render()
+ footer(title)
+
+ def process():
+ if form['cost'] < 0:
+ self.set_error('cost', 'The amount must be positive')
+
+ def action(submit):
+ obj.set_type(form['obj_type'])
+ obj.set_cost(form['cost'])
+ if submit == 'add':
+ db = get_database()
+ db.add(self.obj)
+ else:
+ assert submit == 'update'
+
+ exit_path = request.get_path(1) + '/'
+ submit = form.get_submit()
+ if submit == 'cancel':
+ return request.redirect(exit_path)
+
+ if not form.is_submitted() or form.has_errors():
+ return render()
+ process()
+ if form.has_errors():
+ return render()
+
+ action(submit)
+ return request.redirect(exit_path)
+</pre>
+<p>As you can see in the example, the function still has all of the same
+parts of it's form1 equivalent.</p>
+<blockquote>
+<ol class="arabic simple">
+<li>It determines if it's to create a new object or edit an existing one</li>
+<li>It adds submit buttons and widgets</li>
+<li>It has a function that knows how to render the form</li>
+<li>It has a function that knows how to do error processing on the form</li>
+<li>It has a function that knows how to register permanent changes to
+objects when the form is submitted successfully.</li>
+</ol>
+</blockquote>
+<p>In the form2 example, we have used inner functions to separate out these
+parts. This, of course, is optional, but it does help readability once
+the form gets more complicated and has the additional advantage of
+mapping directly with it's form1 counterparts.</p>
+<p>Form2 functional forms do not have the <tt class="literal"><span class="pre">handle</span></tt> master-method that
+is called after the form is initialized. Instead, we deal with this
+functionality manually. Here are some things that the <tt class="literal"><span class="pre">handle</span></tt>
+portion of your form might need to implement illustrated in the
+order that often makes sense.</p>
+<blockquote>
+<ol class="arabic simple">
+<li>Get the value of any submit buttons using <tt class="literal"><span class="pre">form.get_submit</span></tt></li>
+<li>If the form has not been submitted yet, return <tt class="literal"><span class="pre">render()</span></tt>.</li>
+<li>See if the cancel button was pressed, if so return a redirect.</li>
+<li>Call your <tt class="literal"><span class="pre">process</span></tt> inner function to do any widget-level error
+checks. The form may already have set some errors, so you
+may wish to check for that before trying additional error checks.</li>
+<li>See if the form was submitted by an unknown submit button.
+This will be the case if the form was submitted via a JavaScript
+action, which is the case when an option select widget is selected.
+The value of <tt class="literal"><span class="pre">get_submit</span></tt> is <tt class="literal"><span class="pre">True</span></tt> in this case and if it is,
+you want to clear any errors and re-render the form.</li>
+<li>If the form has not been submitted or if the form has errors,
+you simply want to render the form.</li>
+<li>Check for your named submit buttons which you expect for
+successful form posting e.g. <tt class="literal"><span class="pre">add</span></tt> or <tt class="literal"><span class="pre">update</span></tt>. If one of
+these is pressed, call you action inner function.</li>
+<li>Finally, return a redirect to the expected page following a
+form submission.</li>
+</ol>
+</blockquote>
+<p>These steps are illustrated by the following snippet of code and to a
+large degree in the above functional form2 code example. Often this
+<tt class="literal"><span class="pre">handle</span></tt> block of code can be simplified. For example, if you do not
+expect form submissions from unregistered submit buttons, you can
+eliminate the test for that. Similarly, if your form does not do any
+widget-specific error checking, there's no reason to have an error
+checking <tt class="literal"><span class="pre">process</span></tt> function or the call to it:</p>
+<pre class="literal-block">
+exit_path = request.get_path(1) + '/'
+submit = form.get_submit()
+if not submit:
+ return render()
+if submit == 'cancel':
+ return request.redirect(exit_path)
+if submit == True:
+ form.clear_errors()
+ return render()
+process()
+if form.has_errors():
+ return render()
+action(submit)
+return request.redirect(exit_path)
+</pre>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/form2conversion.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/form2conversion.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/form2conversion.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/form2conversion.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,347 @@
+Converting form1 forms to use the form2 library
+===============================================
+
+Introduction
+------------
+
+These are some notes and examples for converting Quixote form1 forms,
+that is forms derived from ``quixote.form.Form``, to the newer form2
+forms.
+
+Form2 forms are more flexible than their form1 counterparts in that they
+do not require you to use the ``Form`` class as a base to get form
+functionality as form1 forms did. Form2 forms can be instantiated
+directly and then manipulated as instances. You may also continue to
+use inheritance for your form2 classes to get form functionality,
+particularly if the structured separation of ``process``, ``render``,
+and ``action`` is desirable.
+
+There are many ways to get from form1 code ported to form2. At one
+end of the spectrum is to rewrite the form class using a functional
+programing style. This method is arguably best since the functional
+style makes the flow of control clearer.
+
+The other end of the spectrum and normally the easiest way to port
+form1 forms to form2 is to use the ``compatibility`` module provided
+in the form2 package. The compatibility module's Form class provides
+much of the same highly structured machinery (via a ``handle`` master
+method) that the form1 framework uses.
+
+Converting form1 forms using using the compatibility module
+-----------------------------------------------------------
+
+Here's the short list of things to do to convert form1 forms to
+form2 using compatibility.
+
+ 1. Import the Form base class from ``quixote.form2.compatibility``
+ rather than from quixote.form.
+
+ 2. Getting and setting errors is slightly different. In your form's
+ process method, where errors are typically set, form2
+ has a new interface for marking a widget as having an error.
+
+ Form1 API::
+
+ self.error['widget_name'] = 'the error message'
+
+ Form2 API::
+
+ self.set_error('widget_name', 'the error message')
+
+ If you want to find out if the form already has errors, change
+ the form1 style of direct references to the ``self.errors``
+ dictionary to a call to the ``has_errors`` method.
+
+ Form1 API::
+
+ if not self.error:
+ do some more error checking...
+
+ Form2 API::
+
+ if not self.has_errors():
+ do some more error checking...
+
+ 3. Form2 select widgets no longer take ``allowed_values`` or
+ ``descriptions`` arguments. If you are adding type of form2 select
+ widget, you must provide the ``options`` argument instead. Options
+ are the way you define the list of things that are selectable and
+ what is returned when they are selected. the options list can be
+ specified in in one of three ways::
+
+ options: [objects:any]
+ or
+ options: [(object:any, description:any)]
+ or
+ options: [(object:any, description:any, key:any)]
+
+ An easy way to construct options if you already have
+ allowed_values and descriptions is to use the built-in function
+ ``zip`` to define options::
+
+ options=zip(allowed_values, descriptions)
+
+ Note, however, that often it is simpler to to construct the
+ ``options`` list directly.
+
+ 4. You almost certainly want to include some kind of cascading style
+ sheet (since form2 forms render with minimal markup). There is a
+ basic set of CSS rules in ``quixote.form2.css``.
+
+
+Here's the longer list of things you may need to tweak in order for
+form2 compatibility forms to work with your form1 code.
+
+ * ``widget_type`` widget class attribute is gone. This means when
+ adding widgets other than widgets defined in ``quixote.form2.widget``,
+ you must import the widget class into your module and pass the
+ widget class as the first argument to the ``add_widget`` method
+ rather than using the ``widget_type`` string.
+
+ * The ``action_url`` argument to the form's render method is now
+ a keyword argument.
+
+ * If you use ``OptionSelectWidget``, there is no longer a
+ ``get_current_option`` method. You can get the current value
+ in the normal way.
+
+ * ``ListWidget`` has been renamed to ``WidgetList``.
+
+ * There is no longer a ``CollapsibleListWidget`` class. If you need
+ this functionality, consider writing a 'deletable composite widget'
+ to wrap your ``WidgetList`` widgets in it::
+
+ class DeletableWidget(CompositeWidget):
+
+ def __init__(self, name, value=None,
+ element_type=StringWidget,
+ element_kwargs={}, **kwargs):
+ CompositeWidget.__init__(self, name, value=value, **kwargs)
+ self.add(HiddenWidget, 'deleted', value='0')
+ if self.get('deleted') != '1':
+ self.add(element_type, 'element', value=value,
+ **element_kwargs)
+ self.add(SubmitWidget, 'delete', value='Delete')
+ if self.get('delete'):
+ self.get_widget('deleted').set_value('1')
+
+ def _parse(self, request):
+ if self.get('deleted') == '1':
+ self.value = None
+ else:
+ self.value = self.get('element')
+
+ def render(self):
+ if self.get('deleted') == '1':
+ return self.get_widget('deleted').render()
+ else:
+ return CompositeWidget.render(self)
+
+
+Congratulations, now that you've gotten your form1 forms working in form2,
+you may wish to simplify this code using some of the new features available
+in form2 forms. Here's a list of things you may wish to consider:
+
+ * In your process method, you don't really need to get a ``form_data``
+ dictionary by calling ``Form.process`` to ensure your widgets are
+ parsed. Instead, the parsed value of any widget is easy to obtain
+ using the widget's ``get_value`` method or the form's
+ ``__getitem__`` method. So, instead of::
+
+ form_data = Form.process(self, request)
+ val = form_data['my_widget']
+
+ You can use::
+
+ val = self['my_widget']
+
+ If the widget may or may not be in the form, you can use ``get``::
+
+ val = self.get('my_widget')
+
+
+ * It's normally not necessary to provide the ``action_url`` argument
+ to the form's ``render`` method.
+
+ * You don't need to save references to your widgets in your form
+ class. You may have a particular reason for wanting to do that,
+ but any widget added to the form using ``add`` (or ``add_widget`` in
+ the compatibility module) can be retrieved using the form's
+ ``get_widget`` method.
+
+
+Converting form1 forms to form2 by functional rewrite
+-----------------------------------------------------
+
+The best way to get started on a functional version of a form2 rewrite
+is to look at a trivial example form first written using the form1
+inheritance model followed by it's form2 functional equivalent.
+
+First the form1 form::
+
+ class MyForm1Form(Form):
+ def __init__(self, request, obj):
+ Form.__init__(self)
+
+ if obj is None:
+ self.obj = Obj()
+ self.add_submit_button('add', 'Add')
+ else:
+ self.obj = obj
+ self.add_submit_button('update', 'Update')
+
+ self.add_cancel_button('Cancel', request.get_path(1) + '/')
+
+ self.add_widget('single_select', 'obj_type',
+ title='Object Type',
+ value=self.obj.get_type(),
+ allowed_values=list(obj.VALID_TYPES),
+ descriptions=['type1', 'type2', 'type3'])
+ self.add_widget('float', 'cost',
+ title='Cost',
+ value=obj.get_cost())
+
+ def render [html] (self, request, action_url):
+ title = 'Obj %s: Edit Object' % self.obj
+ header(title)
+ Form.render(self, request, action_url)
+ footer(title)
+
+ def process(self, request):
+ form_data = Form.process(self, request)
+
+ if not self.error:
+ if form_data['cost'] is None:
+ self.error['cost'] = 'A cost is required.'
+ elif form_data['cost'] < 0:
+ self.error['cost'] = 'The amount must be positive'
+ return form_data
+
+ def action(self, request, submit, form_data):
+ self.obj.set_type(form_data['obj_type'])
+ self.obj.set_cost(form_data['cost'])
+ if submit == 'add':
+ db = get_database()
+ db.add(self.obj)
+ else:
+ assert submit == 'update'
+ return request.redirect(request.get_path(1) + '/')
+
+Here's the same form using form2 where the function operates on a Form
+instance it keeps a reference to it as a local variable::
+
+ def obj_form(request, obj):
+ form = Form() # quixote.form2.Form
+ if obj is None:
+ obj = Obj()
+ form.add_submit('add', 'Add')
+ else:
+ form.add_submit('update', 'Update')
+ form.add_submit('cancel', 'Cancel')
+
+ form.add_single_select('obj_type',
+ title='Object Type',
+ value=obj.get_type(),
+ options=zip(obj.VALID_TYPES,
+ ['type1', 'type2', 'type3']))
+ form.add_float('cost',
+ title='Cost',
+ value=obj.get_cost(),
+ required=1)
+
+ def render [html] ():
+ title = 'Obj %s: Edit Object' % obj
+ header(title)
+ form.render()
+ footer(title)
+
+ def process():
+ if form['cost'] < 0:
+ self.set_error('cost', 'The amount must be positive')
+
+ def action(submit):
+ obj.set_type(form['obj_type'])
+ obj.set_cost(form['cost'])
+ if submit == 'add':
+ db = get_database()
+ db.add(self.obj)
+ else:
+ assert submit == 'update'
+
+ exit_path = request.get_path(1) + '/'
+ submit = form.get_submit()
+ if submit == 'cancel':
+ return request.redirect(exit_path)
+
+ if not form.is_submitted() or form.has_errors():
+ return render()
+ process()
+ if form.has_errors():
+ return render()
+
+ action(submit)
+ return request.redirect(exit_path)
+
+
+As you can see in the example, the function still has all of the same
+parts of it's form1 equivalent.
+
+ 1. It determines if it's to create a new object or edit an existing one
+ 2. It adds submit buttons and widgets
+ 3. It has a function that knows how to render the form
+ 4. It has a function that knows how to do error processing on the form
+ 5. It has a function that knows how to register permanent changes to
+ objects when the form is submitted successfully.
+
+In the form2 example, we have used inner functions to separate out these
+parts. This, of course, is optional, but it does help readability once
+the form gets more complicated and has the additional advantage of
+mapping directly with it's form1 counterparts.
+
+Form2 functional forms do not have the ``handle`` master-method that
+is called after the form is initialized. Instead, we deal with this
+functionality manually. Here are some things that the ``handle``
+portion of your form might need to implement illustrated in the
+order that often makes sense.
+
+ 1. Get the value of any submit buttons using ``form.get_submit``
+ 2. If the form has not been submitted yet, return ``render()``.
+ 3. See if the cancel button was pressed, if so return a redirect.
+ 4. Call your ``process`` inner function to do any widget-level error
+ checks. The form may already have set some errors, so you
+ may wish to check for that before trying additional error checks.
+ 5. See if the form was submitted by an unknown submit button.
+ This will be the case if the form was submitted via a JavaScript
+ action, which is the case when an option select widget is selected.
+ The value of ``get_submit`` is ``True`` in this case and if it is,
+ you want to clear any errors and re-render the form.
+ 6. If the form has not been submitted or if the form has errors,
+ you simply want to render the form.
+ 7. Check for your named submit buttons which you expect for
+ successful form posting e.g. ``add`` or ``update``. If one of
+ these is pressed, call you action inner function.
+ 8. Finally, return a redirect to the expected page following a
+ form submission.
+
+These steps are illustrated by the following snippet of code and to a
+large degree in the above functional form2 code example. Often this
+``handle`` block of code can be simplified. For example, if you do not
+expect form submissions from unregistered submit buttons, you can
+eliminate the test for that. Similarly, if your form does not do any
+widget-specific error checking, there's no reason to have an error
+checking ``process`` function or the call to it::
+
+ exit_path = request.get_path(1) + '/'
+ submit = form.get_submit()
+ if not submit:
+ return render()
+ if submit == 'cancel':
+ return request.redirect(exit_path)
+ if submit == True:
+ form.clear_errors()
+ return render()
+ process()
+ if form.has_errors():
+ return render()
+ action(submit)
+ return request.redirect(exit_path)
Added: packages/quixote1/branches/upstream/current/doc/multi-threaded.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/multi-threaded.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/multi-threaded.html (added)
+++ packages/quixote1/branches/upstream/current/doc/multi-threaded.html Mon May 8 19:16:16 2006
@@ -1,0 +1,48 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Multi-Threaded Quixote Applications</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="multi-threaded-quixote-applications">
+<h1 class="title">Multi-Threaded Quixote Applications</h1>
+<p>Starting with Quixote 0.6, it's possible to write multi-threaded Quixote
+applications. In previous versions, Quixote stored the current
+HTTPRequest object in a global variable, meaning that processing
+multiple requests in the same process simultaneously was impossible.</p>
+<p>However, the Publisher class as shipped still can't handle multiple
+simultaneous requests; you'll need to subclass Publisher to make it
+re-entrant. Here's a starting point:</p>
+<pre class="literal-block">
+import thread
+from quixote.publish import Publisher
+
+[...]
+
+class ThreadedPublisher (Publisher):
+ def __init__ (self, root_namespace, config=None):
+ Publisher.__init__(self, root_namespace, config)
+ self._request_dict = {}
+
+ def _set_request(self, request):
+ self._request_dict[thread.get_ident()] = request
+
+ def _clear_request(self):
+ try:
+ del self._request_dict[thread.get_ident()]
+ except KeyError:
+ pass
+
+ def get_request(self):
+ return self._request_dict.get(thread.get_ident())
+</pre>
+<p>Using ThreadedPublisher, you now have one current request per thread,
+rather than one for the entire process.</p>
+<p>$Id: multi-threaded.txt 20217 2003-01-16 20:51:53Z akuchlin $</p>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/multi-threaded.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/multi-threaded.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/multi-threaded.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/multi-threaded.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,39 @@
+Multi-Threaded Quixote Applications
+===================================
+
+Starting with Quixote 0.6, it's possible to write multi-threaded Quixote
+applications. In previous versions, Quixote stored the current
+HTTPRequest object in a global variable, meaning that processing
+multiple requests in the same process simultaneously was impossible.
+
+However, the Publisher class as shipped still can't handle multiple
+simultaneous requests; you'll need to subclass Publisher to make it
+re-entrant. Here's a starting point::
+
+ import thread
+ from quixote.publish import Publisher
+
+ [...]
+
+ class ThreadedPublisher (Publisher):
+ def __init__ (self, root_namespace, config=None):
+ Publisher.__init__(self, root_namespace, config)
+ self._request_dict = {}
+
+ def _set_request(self, request):
+ self._request_dict[thread.get_ident()] = request
+
+ def _clear_request(self):
+ try:
+ del self._request_dict[thread.get_ident()]
+ except KeyError:
+ pass
+
+ def get_request(self):
+ return self._request_dict.get(thread.get_ident())
+
+Using ThreadedPublisher, you now have one current request per thread,
+rather than one for the entire process.
+
+
+$Id: multi-threaded.txt 20217 2003-01-16 20:51:53Z akuchlin $
Added: packages/quixote1/branches/upstream/current/doc/programming.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/programming.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/programming.html (added)
+++ packages/quixote1/branches/upstream/current/doc/programming.html Mon May 8 19:16:16 2006
@@ -1,0 +1,445 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Quixote Programming Overview</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="quixote-programming-overview">
+<h1 class="title">Quixote Programming Overview</h1>
+<p>This document explains how a Quixote application is structured. Be sure
+you have read the "Understanding the demo" section of demo.txt first --
+this explains a lot of Quixote fundamentals.</p>
+<p>There are three components to a Quixote application:</p>
+<ol class="arabic">
+<li><p class="first">A driver script, usually a CGI or FastCGI script. This is
+the interface between your web server (eg., Apache) and the bulk of
+your application code.</p>
+<p>The driver script is responsible for creating a Quixote publisher
+customized for your application and invoking its publishing loop.</p>
+</li>
+<li><p class="first">A configuration file. This file specifies various features of the
+Publisher class, such as how errors are handled, the paths of
+various log files, and various other things. Read through
+quixote/config.py for the full list of configuration settings.</p>
+<p>The most important configuration parameters are:</p>
+<blockquote>
+<dl>
+<dt><tt class="literal"><span class="pre">ERROR_EMAIL</span></tt></dt>
+<dd><p class="first last">e-mail address to which errors will be mailed</p>
+</dd>
+<dt><tt class="literal"><span class="pre">ERROR_LOG</span></tt></dt>
+<dd><p class="first last">file to which errors will be logged</p>
+</dd>
+</dl>
+</blockquote>
+<p>For development/debugging, you should also set <tt class="literal"><span class="pre">DISPLAY_EXCEPTIONS</span></tt>
+true and <tt class="literal"><span class="pre">SECURE_ERRORS</span></tt> false; the defaults are the reverse, to
+favour security over convenience.</p>
+</li>
+<li><p class="first">Finally, the bulk of the code will be in a Python package or
+module, called the root namespace. The Quixote publisher will be set
+up to start traversing at the root namespace.</p>
+</li>
+</ol>
+<div class="section" id="driver-script">
+<h1><a name="driver-script">Driver script</a></h1>
+<p>The driver script is the interface between your web server and Quixote's
+"publishing loop", which in turn is the gateway to your application
+code. Thus, there are two things that your Quixote driver script must
+do:</p>
+<ul class="simple">
+<li>create a Quixote publisher -- that is, an instance of the Publisher
+class provided by the quixote.publish module -- and customize it for
+your application</li>
+<li>invoke the Quixote publishing loop by calling the 'publish_cgi()'
+method of the publisher</li>
+</ul>
+<p>The publisher is responsible for translating URLs to Python objects and
+calling the appropriate function, method, or PTL template to retrieve
+the information and/or carry out the action requested by the URL.</p>
+<p>The most important application-specific customization done by the driver
+script is to set the root namespace of your application. Broadly
+speaking, a namespace is any Python object with attributes. The most
+common namespaces are modules, packages, and class instances. The root
+namespace of a Quixote application is usually a Python package, although
+for a small application it could be a regular module.</p>
+<p>The driver script can be very simple; for example, here is a
+trimmed-down version of demo.cgi, the driver script for the Quixote
+demo:</p>
+<pre class="literal-block">
+from quixote import enable_ptl, Publisher
+enable_ptl()
+app = Publisher("quixote.demo")
+app.setup_logs()
+app.publish_cgi()
+</pre>
+<p>(Whether you install this as <tt class="literal"><span class="pre">demo.cgi</span></tt>, <tt class="literal"><span class="pre">demo.fcgi</span></tt>, <tt class="literal"><span class="pre">demo.py</span></tt>,
+or whatever is up to you and your web server.)</p>
+<p>That's almost the simplest possible case -- there's no
+application-specific configuration info apart from the root namespace.
+(The only way to make this simpler would be to remove the
+<tt class="literal"><span class="pre">enable_ptl()</span></tt> and <tt class="literal"><span class="pre">setup_logs()</span></tt> calls. The former would remove
+the ability to import PTL modules, which is at least half the fun with
+Quixote; the latter would disable Quixote's debug and error logging,
+which is very useful.)</p>
+<p>Here's a slightly more elaborate example, for a hypothetical database of
+books:</p>
+<pre class="literal-block">
+from quixote import enable_ptl, Publisher
+from quixote.config import Config
+
+# Install the PTL import hook, so we can use PTL modules in this app
+enable_ptl()
+
+# Create a Publisher instance with the default configuration.
+pub = Publisher('books')
+
+# Read a config file to override some default values.
+pub.read_config('/www/conf/books.conf')
+
+# Setup error and debug logging (do this after read_config(), so
+# the settings in /www/conf/books.conf have an effect!).
+pub.setup_logs()
+
+# Enter the publishing main loop
+pub.publish_cgi()
+</pre>
+<p>The application code is kept in a package named simply 'books' in this
+example, so its name is provided as the root namespace when creating the
+Publisher instance.</p>
+<p>The SessionPublisher class in quixote.publish can also be used; it
+provides session tracking. The changes required to use
+SessionPublisher would be:</p>
+<pre class="literal-block">
+...
+from quixote.publish import SessionPublisher
+...
+pub = SessionPublisher(PACKAGE_NAME)
+...
+</pre>
+<p>For details on session management, see session-mgmt.txt.</p>
+<p>Getting the driver script to actually run is between you and your web
+server. See the web-server.txt document for help, especially with
+Apache (which is the only web server we currently know anything about).</p>
+</div>
+<div class="section" id="configuration-file">
+<h1><a name="configuration-file">Configuration file</a></h1>
+<p>In the <tt class="literal"><span class="pre">books.cgi</span></tt> driver script, configuration information is read
+from a file by this line:</p>
+<pre class="literal-block">
+pub.read_config('/www/conf/books.conf')
+</pre>
+<p>You should never edit the default values in quixote/config.py, because
+your edits will be lost if you upgrade to a newer Quixote version. You
+should certainly read it, though, to understand what all the
+configuration variables are.</p>
+<p>The configuration file contains Python code, which is then evaluated
+using Python's built-in function <tt class="literal"><span class="pre">execfile()</span></tt>. Since it's Python code,
+it's easy to set config variables:</p>
+<pre class="literal-block">
+ACCESS_LOG = "/www/log/access/books.log"
+DEBUG_LOG = "/www/log/books-debug.log"
+ERROR_LOG = "/www/log/books-error.log"
+</pre>
+<p>You can also execute arbitrary Python code to figure out what the
+variables should be. The following example changes some settings to
+be more convenient for a developer when the <tt class="literal"><span class="pre">WEB_MODE</span></tt> environment
+variable is the string <tt class="literal"><span class="pre">DEVEL</span></tt>:</p>
+<pre class="literal-block">
+web_mode = os.environ["WEB_MODE"]
+if web_mode == "DEVEL":
+ DISPLAY_EXCEPTIONS = 1
+ SECURE_ERRORS = 0
+ RUN_ONCE = 1
+elif web_mode in ("STAGING", "LIVE"):
+ DISPLAY_EXCEPTIONS = 0
+ SECURE_ERRORS = 1
+ RUN_ONCE = 0
+else:
+ raise RuntimeError, "unknown server mode: %s" % web_mode
+</pre>
+<p>At the MEMS Exchange, we use this flexibility to display tracebacks in
+<tt class="literal"><span class="pre">DEVEL</span></tt> mode, to redirect generated e-mails to a staging address in
+<tt class="literal"><span class="pre">STAGING</span></tt> mode, and to enable all features in <tt class="literal"><span class="pre">LIVE</span></tt> mode.</p>
+</div>
+<div class="section" id="logging">
+<h1><a name="logging">Logging</a></h1>
+<p>Every Quixote application can have up to three log files, each of
+which is selected by a different configuration variable:</p>
+<ul class="simple">
+<li>access log (<tt class="literal"><span class="pre">ACCESS_LOG</span></tt>)</li>
+<li>error log (<tt class="literal"><span class="pre">ERROR_LOG</span></tt>)</li>
+<li>debug log (<tt class="literal"><span class="pre">DEBUG_LOG</span></tt>)</li>
+</ul>
+<p>If you want logging to work, you must call <tt class="literal"><span class="pre">setup_logs()</span></tt> on your
+Publisher object after creating it and reading any application-specific
+config file. (This only applies for CGI/FastCGI driver scripts, where
+you are responsible for creating the Publisher object. With mod_python
+under Apache, it's taken care of for you.)</p>
+<p>Quixote writes one (rather long) line to the access log for each request
+it handles; we have split that line up here to make it easier to read:</p>
+<pre class="literal-block">
+127.0.0.1 - 2001-10-15 09:48:43
+ 2504 "GET /catalog/ HTTP/1.1"
+ 200 'Opera/6.0 (Linux; U)' 0.10sec
+</pre>
+<p>This line consists of:</p>
+<ul class="simple">
+<li>client IP address</li>
+<li>current user (according to Quixote session management mechanism,
+so this will be "-" unless you're using a session manager that
+does authentication)</li>
+<li>date and time of request in local timezone, as YYYY-MM-DD hh:mm:ss</li>
+<li>process ID of the process serving the request (eg. your CGI/FastCGI
+driver script)</li>
+<li>the HTTP request line (request method, URI, and protocol)</li>
+<li>response status code</li>
+<li>HTTP user agent string (specifically, this is
+<tt class="literal"><span class="pre">repr(os.environ.get('HTTP_USER_AGENT',</span> <span class="pre">''))</span></tt>)</li>
+<li>time to complete the request</li>
+</ul>
+<p>If no access log is configured (ie., <tt class="literal"><span class="pre">ACCESS_LOG</span></tt> is <tt class="literal"><span class="pre">None</span></tt>), then
+Quixote will not do any access logging.</p>
+<p>The error log is used for two purposes:</p>
+<ul class="simple">
+<li>all application output to standard error (<tt class="literal"><span class="pre">sys.stderr</span></tt>) goes to
+Quixote's error log</li>
+<li>all application tracebacks will be written to Quixote's error log</li>
+</ul>
+<p>If no error log is configured (with <tt class="literal"><span class="pre">ERROR_LOG</span></tt>), then both types of
+messages will be written to the stderr supplied to Quixote for this
+request by your web server. At least for CGI/FastCGI scripts under
+Apache, this winds up in Apache's error log.</p>
+<p>The debug log is where any application output to stdout goes. Thus, you
+can just sprinkle <tt class="literal"><span class="pre">print</span></tt> statements into your application for
+debugging; if you have configured a debug log, those print statements
+will wind up there. If you don't configure a debug log, they go to the
+bit bucket (<tt class="literal"><span class="pre">/dev/null</span></tt> on Unix, <tt class="literal"><span class="pre">NUL</span></tt> on Windows).</p>
+</div>
+<div class="section" id="application-code">
+<h1><a name="application-code">Application code</a></h1>
+<p>Finally, we reach the most complicated part of a Quixote application.
+However, thanks to Quixote's design, everything you've ever learned
+about designing and writing Python code is applicable, so there are no
+new hoops to jump through. The only new language to learn is PTL, which
+is simply Python with a novel way of generating function return values
+-- see PTL.txt for details.</p>
+<p>An application's code lives in a Python package that contains both .py
+and .ptl files. Complicated logic should be in .py files, while .ptl
+files, ideally, should contain only the logic needed to render your Web
+interface and basic objects as HTML. As long as your driver script
+calls <tt class="literal"><span class="pre">enable_ptl()</span></tt>, you can import PTL modules (.ptl files) just as
+if they were Python modules.</p>
+<p>Quixote's publisher will start at the root of this package, and will
+treat the rest of the URL as a path into the package's contents. Here
+are some examples, assuming that the <tt class="literal"><span class="pre">URL_PREFIX</span></tt> is <tt class="literal"><span class="pre">"/q"</span></tt>, your
+web server is setup to rewrite <tt class="literal"><span class="pre">/q</span></tt> requests as calls to (eg.)
+<tt class="literal"><span class="pre">/www/cgi-bin/books.cgi</span></tt>, and the root package for your application is
+'books':</p>
+<pre class="literal-block">
+http://.../q/ call books._q_index()
+http://.../q/other call books.other(), if books.other
+ is callable (eg. a function or
+ method)
+http://.../q/other redirect to /q/other/, if books.other is a
+ namespace (eg. a module or sub-package)
+http://.../q/other/ call books.other._q_index(), if books.other
+ is a namespace
+</pre>
+<p>One of Quixote's design principles is "Be explicit." Therefore there's
+no complicated rule for remembering which functions in a module are
+public; you just have to list them all in the _q_exports variable, which
+should be a list of strings naming the public functions. You don't need
+to list the <tt class="literal"><span class="pre">_q_index()</span></tt> function as being public; that's assumed.
+Eg. if <tt class="literal"><span class="pre">foo()</span></tt> is a function to be exported (via Quixote to the web)
+from your application's namespace, you should have this somewhere in
+that namespace (ie. at module level in a module or __init__.py file):</p>
+<pre class="literal-block">
+_q_exports = ['foo']
+</pre>
+<p>At times it is desirable for URLs to contain path components that are
+not valid Python identifiers. In these cases you can provide an
+explicit external to internal name mapping. For example:</p>
+<pre class="literal-block">
+_q_exports = ['foo', ('stylesheet.css', 'stylesheet_css')]
+</pre>
+<p>When a function is callable from the web, it must expect a single
+parameter, which will be an instance of the HTTPRequest class. This
+object contains everything Quixote could discover about the current HTTP
+request -- CGI environment variables, form data, cookies, etc. When
+using SessionPublisher, request.session is a Session object for the user
+agent making the request.</p>
+<p>The function should return a string; all PTL templates return a string
+automatically. <tt class="literal"><span class="pre">request.response</span></tt> is an HTTPResponse instance, which
+has methods for setting the content-type of the function's output,
+generating an HTTP redirect, specifying arbitrary HTTP response headers,
+and other common tasks. (Actually, the request object also has a method
+for generating a redirect. It's usually better to use this -- ie. code
+<tt class="literal"><span class="pre">request.redirect(...)</span></tt> because generating a redirect correctly
+requires knowledge of the request, and only the request object has that
+knowledge. <tt class="literal"><span class="pre">request.response.redirect(...)</span></tt> only works if you supply
+an absolute URL, eg. <tt class="literal"><span class="pre">"http://www.example.com/foo/bar"</span></tt>.)</p>
+<p>Use</p>
+<pre class="literal-block">
+pydoc quixote.http_request
+pydoc quixote.http_response
+</pre>
+<p>to view the documentation for the HTTPRequest and HTTPResponse classes,
+or consult the source code for all the gory details.</p>
+<p>There are a few special functions that affect Quixote's
+traversal of a URL to determine how to handle it: <tt class="literal"><span class="pre">_q_access()</span></tt>,
+<tt class="literal"><span class="pre">_q_lookup()</span></tt>, and <tt class="literal"><span class="pre">_q_resolve()</span></tt>.</p>
+</div>
+<div class="section" id="q-access-request">
+<h1><a name="q-access-request"><tt class="literal"><span class="pre">_q_access(request)</span></tt></a></h1>
+<p>If this function is present in a module, it will be called before
+attempting to traverse any further. It can look at the contents of
+request and decide if the traversal can continue; if not, it should
+raise quixote.errors.AccessError (or a subclass), and Quixote will
+return a 403 ("forbidden") HTTP status code. The return value is
+ignored if <tt class="literal"><span class="pre">_q_access()</span></tt> doesn't raise an exception.</p>
+<p>For example, in the MEMS Exchange code, we have some sets of pages that
+are only accessible to signed-in users of a certain type. The
+<tt class="literal"><span class="pre">_q_access()</span></tt> function looks like this:</p>
+<pre class="literal-block">
+def _q_access (request):
+ if request.session.user is None:
+ raise NotLoggedInError("You must be signed in.")
+ if not (request.session.user.is_admin() or
+ request.session.user.is_fab()):
+ raise AccessError("You don't have access to the reports page.")
+</pre>
+<p>This is less error-prone than having to remember to add checks to
+every single public function.</p>
+</div>
+<div class="section" id="q-lookup-request-name">
+<h1><a name="q-lookup-request-name"><tt class="literal"><span class="pre">_q_lookup(request,</span> <span class="pre">name)</span></tt></a></h1>
+<p>This function translates an arbitrary string into an object that we
+continue traversing. This is very handy; it lets you put user-space
+objects into your URL-space, eliminating the need for digging ID
+strings out of a query, or checking <tt class="literal"><span class="pre">PATH_INFO</span></tt> after Quixote's done
+with it. But it is a compromise with security: it opens up the
+traversal algorithm to arbitrary names not listed in <tt class="literal"><span class="pre">_q_exports</span></tt>.
+(<tt class="literal"><span class="pre">_q_lookup()</span></tt> is never called for names listed in <tt class="literal"><span class="pre">_q_exports</span></tt>.)
+You should therefore be extremely paranoid about checking the value of
+<tt class="literal"><span class="pre">name</span></tt>.</p>
+<p><tt class="literal"><span class="pre">request</span></tt> is the request object, as it is everywhere else; <tt class="literal"><span class="pre">name</span></tt> is
+a string containing the next component of the path. <tt class="literal"><span class="pre">_q_lookup()</span></tt> should
+return either a string (a complete document that will be returned to the
+client) or some object that can be traversed further. Returning a
+string is useful in simple cases, eg. if you want the <tt class="literal"><span class="pre">/user/joe</span></tt> URI
+to show everything about user "joe" in your database, you would define a
+<tt class="literal"><span class="pre">_q_lookup()</span></tt> in the namespace that handles <tt class="literal"><span class="pre">/user/</span></tt> requests:</p>
+<pre class="literal-block">
+def _q_lookup [plain] (request, name):
+ if not request.session.user.is_admin():
+ raise AccessError("permission denied")
+ user = get_database().get_user(name)
+ if user is None:
+ raise TraversalError("no such user: %r" % name)
+ else:
+ "<h1>User %s</h1>\n" % html_quote(name)
+ "<table>\n"
+ " <tr><th>real name</th><td>%s</td>\n" % user.real_name
+ # ...
+</pre>
+<p>(This assumes that the namespace in question is a PTL module, not a
+Python module.)</p>
+<p>To publish more complex objects, you'll want to use <tt class="literal"><span class="pre">_q_lookup()</span></tt>'s
+ability to return a new namespace that Quixote continues traversing.
+The usual way to do this is to return an instance of a class that
+implements the web front-end to your object. That class must have a
+<tt class="literal"><span class="pre">_q_exports</span></tt> attribute, and it will almost certainly have a
+<tt class="literal"><span class="pre">_q_index()</span></tt> method. It might also have <tt class="literal"><span class="pre">_q_access()</span></tt> and
+<tt class="literal"><span class="pre">_q_lookup()</span></tt> (yes, <tt class="literal"><span class="pre">_q_lookup()</span></tt> calls can nest arbitrarily
+deeply).</p>
+<p>For example, you might want <tt class="literal"><span class="pre">/user/joe/</span></tt> to show a summary,
+<tt class="literal"><span class="pre">/user/joe/history</span></tt> to show a login history, <tt class="literal"><span class="pre">/user/joe/prefs</span></tt> to be
+a page where joe can edit his personal preferences, etc. The
+<tt class="literal"><span class="pre">_q_lookup()</span></tt> function would then be</p>
+<pre class="literal-block">
+def _q_lookup (request, name):
+ return UserUI(request, name)
+</pre>
+<p>and the UserUI class, which implements the web interface to user
+objects, might look like</p>
+<pre class="literal-block">
+class UserUI:
+ _q_exports = ['history', 'prefs']
+
+ def __init__ (self, request, name):
+ if not request.session.user.is_admin():
+ raise AccessError("permission denied")
+ self.user = get_database().get_user(name)
+ if self.user is None:
+ raise TraversalError("no such user: %r" % name)
+
+ def _q_index (self, request):
+ # ... generate summary page ...
+
+ def history (self, request):
+ # ... generate history page ...
+
+ def prefs (self, request):
+ # ... generate prefs-editing page ...
+</pre>
+</div>
+<div class="section" id="q-resolve-name">
+<h1><a name="q-resolve-name"><tt class="literal"><span class="pre">_q_resolve(name)</span></tt></a></h1>
+<p><tt class="literal"><span class="pre">_q_resolve()</span></tt> looks a bit like <tt class="literal"><span class="pre">_q_lookup()</span></tt>, but is intended for
+a different purpose. Quixote applications can be slow to start up
+because they have to import a large number of Python and PTL modules.
+<tt class="literal"><span class="pre">_q_resolve()</span></tt> is a hook that lets time-consuming imports
+be postponed until the code is actually needed</p>
+<p><tt class="literal"><span class="pre">name</span></tt> is a string containing the next component of the path.
+<tt class="literal"><span class="pre">_q_resolve()</span></tt> should do whatever imports are necessary and return a
+module that will be traversed further. (Nothing enforces that this
+function return a module, so you could also return other types, such
+as a class instance, a callable object, or even a string) if the last
+component of the path is being resolved. Given <tt class="literal"><span class="pre">_q_resolve()</span></tt>'s
+memoization feature, though, returning a module is the most useful
+thing to do.)</p>
+<p><tt class="literal"><span class="pre">_q_resolve()</span></tt> is only ever called for names that are in
+<tt class="literal"><span class="pre">_q_exports</span></tt> and that don't already exist in the containing
+namespace. It is not passed the request object, so its return value
+can't depend on the client in any way. Calls are also memoized; after
+being called the object returned will be added to the containing
+namespace, so <tt class="literal"><span class="pre">_q_resolve()</span></tt> will be called at most once for a given
+name.</p>
+<p>Most commonly, <tt class="literal"><span class="pre">_q_resolve()</span></tt> will look something like this:</p>
+<pre class="literal-block">
+_q_exports = [..., 'expensive', ...]
+
+def _q_resolve(name):
+ if name == 'expensive':
+ from otherpackage import expensive
+ return expensive
+</pre>
+<p>Let's say this function is in <tt class="literal"><span class="pre">app.ui</span></tt>. The first time
+<tt class="literal"><span class="pre">/expensive</span></tt> is accessed, <tt class="literal"><span class="pre">_q_resolve('expensive')</span></tt> is called, the
+<tt class="literal"><span class="pre">otherpackage.expensive</span></tt> module is returned and traversal continues.
+The imported module is also saved as <tt class="literal"><span class="pre">app.ui.expensive</span></tt>, so future
+references to <tt class="literal"><span class="pre">/expensive</span></tt> won't need to invoke the <tt class="literal"><span class="pre">_q_resolve()</span></tt>
+hook.</p>
+</div>
+<div class="section" id="q-exception-handler-request-exception">
+<h1><a name="q-exception-handler-request-exception"><tt class="literal"><span class="pre">_q_exception_handler(request,</span> <span class="pre">exception)</span></tt></a></h1>
+<p>Quixote will display a default error page when a <tt class="literal"><span class="pre">PublishError</span></tt>
+exception is raised. Before displaying the default page, Quixote will
+search back through the list of namespaces traversed looking for an
+object with a <tt class="literal"><span class="pre">_q_exception_handler</span></tt> attribute. That attribute is
+expected to be a function and is called with the request and exception
+instance as arguments and should return the error page (e.g. a
+string). If the handler doesn't want to handle a particular error it
+can re-raise it and the next nearest handler will be found. If no
+<tt class="literal"><span class="pre">_q_exception_handler</span></tt> is found, the default Quixote handler is
+used.</p>
+<p>$Id: programming.txt 23893 2004-04-06 19:31:20Z nascheme $</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/programming.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/programming.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/programming.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/programming.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,468 @@
+Quixote Programming Overview
+============================
+
+This document explains how a Quixote application is structured. Be sure
+you have read the "Understanding the demo" section of demo.txt first --
+this explains a lot of Quixote fundamentals.
+
+There are three components to a Quixote application:
+
+1) A driver script, usually a CGI or FastCGI script. This is
+ the interface between your web server (eg., Apache) and the bulk of
+ your application code.
+
+ The driver script is responsible for creating a Quixote publisher
+ customized for your application and invoking its publishing loop.
+
+2) A configuration file. This file specifies various features of the
+ Publisher class, such as how errors are handled, the paths of
+ various log files, and various other things. Read through
+ quixote/config.py for the full list of configuration settings.
+
+ The most important configuration parameters are:
+
+ ``ERROR_EMAIL``
+ e-mail address to which errors will be mailed
+ ``ERROR_LOG``
+ file to which errors will be logged
+
+ For development/debugging, you should also set ``DISPLAY_EXCEPTIONS``
+ true and ``SECURE_ERRORS`` false; the defaults are the reverse, to
+ favour security over convenience.
+
+3) Finally, the bulk of the code will be in a Python package or
+ module, called the root namespace. The Quixote publisher will be set
+ up to start traversing at the root namespace.
+
+
+Driver script
+-------------
+
+The driver script is the interface between your web server and Quixote's
+"publishing loop", which in turn is the gateway to your application
+code. Thus, there are two things that your Quixote driver script must
+do:
+
+* create a Quixote publisher -- that is, an instance of the Publisher
+ class provided by the quixote.publish module -- and customize it for
+ your application
+
+* invoke the Quixote publishing loop by calling the 'publish_cgi()'
+ method of the publisher
+
+The publisher is responsible for translating URLs to Python objects and
+calling the appropriate function, method, or PTL template to retrieve
+the information and/or carry out the action requested by the URL.
+
+The most important application-specific customization done by the driver
+script is to set the root namespace of your application. Broadly
+speaking, a namespace is any Python object with attributes. The most
+common namespaces are modules, packages, and class instances. The root
+namespace of a Quixote application is usually a Python package, although
+for a small application it could be a regular module.
+
+The driver script can be very simple; for example, here is a
+trimmed-down version of demo.cgi, the driver script for the Quixote
+demo::
+
+ from quixote import enable_ptl, Publisher
+ enable_ptl()
+ app = Publisher("quixote.demo")
+ app.setup_logs()
+ app.publish_cgi()
+
+(Whether you install this as ``demo.cgi``, ``demo.fcgi``, ``demo.py``,
+or whatever is up to you and your web server.)
+
+That's almost the simplest possible case -- there's no
+application-specific configuration info apart from the root namespace.
+(The only way to make this simpler would be to remove the
+``enable_ptl()`` and ``setup_logs()`` calls. The former would remove
+the ability to import PTL modules, which is at least half the fun with
+Quixote; the latter would disable Quixote's debug and error logging,
+which is very useful.)
+
+Here's a slightly more elaborate example, for a hypothetical database of
+books::
+
+ from quixote import enable_ptl, Publisher
+ from quixote.config import Config
+
+ # Install the PTL import hook, so we can use PTL modules in this app
+ enable_ptl()
+
+ # Create a Publisher instance with the default configuration.
+ pub = Publisher('books')
+
+ # Read a config file to override some default values.
+ pub.read_config('/www/conf/books.conf')
+
+ # Setup error and debug logging (do this after read_config(), so
+ # the settings in /www/conf/books.conf have an effect!).
+ pub.setup_logs()
+
+ # Enter the publishing main loop
+ pub.publish_cgi()
+
+The application code is kept in a package named simply 'books' in this
+example, so its name is provided as the root namespace when creating the
+Publisher instance.
+
+The SessionPublisher class in quixote.publish can also be used; it
+provides session tracking. The changes required to use
+SessionPublisher would be::
+
+ ...
+ from quixote.publish import SessionPublisher
+ ...
+ pub = SessionPublisher(PACKAGE_NAME)
+ ...
+
+For details on session management, see session-mgmt.txt.
+
+Getting the driver script to actually run is between you and your web
+server. See the web-server.txt document for help, especially with
+Apache (which is the only web server we currently know anything about).
+
+
+Configuration file
+------------------
+
+In the ``books.cgi`` driver script, configuration information is read
+from a file by this line::
+
+ pub.read_config('/www/conf/books.conf')
+
+You should never edit the default values in quixote/config.py, because
+your edits will be lost if you upgrade to a newer Quixote version. You
+should certainly read it, though, to understand what all the
+configuration variables are.
+
+The configuration file contains Python code, which is then evaluated
+using Python's built-in function ``execfile()``. Since it's Python code,
+it's easy to set config variables::
+
+ ACCESS_LOG = "/www/log/access/books.log"
+ DEBUG_LOG = "/www/log/books-debug.log"
+ ERROR_LOG = "/www/log/books-error.log"
+
+You can also execute arbitrary Python code to figure out what the
+variables should be. The following example changes some settings to
+be more convenient for a developer when the ``WEB_MODE`` environment
+variable is the string ``DEVEL``::
+
+ web_mode = os.environ["WEB_MODE"]
+ if web_mode == "DEVEL":
+ DISPLAY_EXCEPTIONS = 1
+ SECURE_ERRORS = 0
+ RUN_ONCE = 1
+ elif web_mode in ("STAGING", "LIVE"):
+ DISPLAY_EXCEPTIONS = 0
+ SECURE_ERRORS = 1
+ RUN_ONCE = 0
+ else:
+ raise RuntimeError, "unknown server mode: %s" % web_mode
+
+At the MEMS Exchange, we use this flexibility to display tracebacks in
+``DEVEL`` mode, to redirect generated e-mails to a staging address in
+``STAGING`` mode, and to enable all features in ``LIVE`` mode.
+
+
+Logging
+-------
+
+Every Quixote application can have up to three log files, each of
+which is selected by a different configuration variable:
+
+* access log (``ACCESS_LOG``)
+* error log (``ERROR_LOG``)
+* debug log (``DEBUG_LOG``)
+
+If you want logging to work, you must call ``setup_logs()`` on your
+Publisher object after creating it and reading any application-specific
+config file. (This only applies for CGI/FastCGI driver scripts, where
+you are responsible for creating the Publisher object. With mod_python
+under Apache, it's taken care of for you.)
+
+Quixote writes one (rather long) line to the access log for each request
+it handles; we have split that line up here to make it easier to read::
+
+ 127.0.0.1 - 2001-10-15 09:48:43
+ 2504 "GET /catalog/ HTTP/1.1"
+ 200 'Opera/6.0 (Linux; U)' 0.10sec
+
+This line consists of:
+
+* client IP address
+* current user (according to Quixote session management mechanism,
+ so this will be "-" unless you're using a session manager that
+ does authentication)
+* date and time of request in local timezone, as YYYY-MM-DD hh:mm:ss
+* process ID of the process serving the request (eg. your CGI/FastCGI
+ driver script)
+* the HTTP request line (request method, URI, and protocol)
+* response status code
+* HTTP user agent string (specifically, this is
+ ``repr(os.environ.get('HTTP_USER_AGENT', ''))``)
+* time to complete the request
+
+If no access log is configured (ie., ``ACCESS_LOG`` is ``None``), then
+Quixote will not do any access logging.
+
+The error log is used for two purposes:
+
+* all application output to standard error (``sys.stderr``) goes to
+ Quixote's error log
+* all application tracebacks will be written to Quixote's error log
+
+If no error log is configured (with ``ERROR_LOG``), then both types of
+messages will be written to the stderr supplied to Quixote for this
+request by your web server. At least for CGI/FastCGI scripts under
+Apache, this winds up in Apache's error log.
+
+The debug log is where any application output to stdout goes. Thus, you
+can just sprinkle ``print`` statements into your application for
+debugging; if you have configured a debug log, those print statements
+will wind up there. If you don't configure a debug log, they go to the
+bit bucket (``/dev/null`` on Unix, ``NUL`` on Windows).
+
+
+Application code
+----------------
+
+Finally, we reach the most complicated part of a Quixote application.
+However, thanks to Quixote's design, everything you've ever learned
+about designing and writing Python code is applicable, so there are no
+new hoops to jump through. The only new language to learn is PTL, which
+is simply Python with a novel way of generating function return values
+-- see PTL.txt for details.
+
+An application's code lives in a Python package that contains both .py
+and .ptl files. Complicated logic should be in .py files, while .ptl
+files, ideally, should contain only the logic needed to render your Web
+interface and basic objects as HTML. As long as your driver script
+calls ``enable_ptl()``, you can import PTL modules (.ptl files) just as
+if they were Python modules.
+
+Quixote's publisher will start at the root of this package, and will
+treat the rest of the URL as a path into the package's contents. Here
+are some examples, assuming that the ``URL_PREFIX`` is ``"/q"``, your
+web server is setup to rewrite ``/q`` requests as calls to (eg.)
+``/www/cgi-bin/books.cgi``, and the root package for your application is
+'books'::
+
+ http://.../q/ call books._q_index()
+ http://.../q/other call books.other(), if books.other
+ is callable (eg. a function or
+ method)
+ http://.../q/other redirect to /q/other/, if books.other is a
+ namespace (eg. a module or sub-package)
+ http://.../q/other/ call books.other._q_index(), if books.other
+ is a namespace
+
+One of Quixote's design principles is "Be explicit." Therefore there's
+no complicated rule for remembering which functions in a module are
+public; you just have to list them all in the _q_exports variable, which
+should be a list of strings naming the public functions. You don't need
+to list the ``_q_index()`` function as being public; that's assumed.
+Eg. if ``foo()`` is a function to be exported (via Quixote to the web)
+from your application's namespace, you should have this somewhere in
+that namespace (ie. at module level in a module or __init__.py file)::
+
+ _q_exports = ['foo']
+
+At times it is desirable for URLs to contain path components that are
+not valid Python identifiers. In these cases you can provide an
+explicit external to internal name mapping. For example::
+
+ _q_exports = ['foo', ('stylesheet.css', 'stylesheet_css')]
+
+When a function is callable from the web, it must expect a single
+parameter, which will be an instance of the HTTPRequest class. This
+object contains everything Quixote could discover about the current HTTP
+request -- CGI environment variables, form data, cookies, etc. When
+using SessionPublisher, request.session is a Session object for the user
+agent making the request.
+
+The function should return a string; all PTL templates return a string
+automatically. ``request.response`` is an HTTPResponse instance, which
+has methods for setting the content-type of the function's output,
+generating an HTTP redirect, specifying arbitrary HTTP response headers,
+and other common tasks. (Actually, the request object also has a method
+for generating a redirect. It's usually better to use this -- ie. code
+``request.redirect(...)`` because generating a redirect correctly
+requires knowledge of the request, and only the request object has that
+knowledge. ``request.response.redirect(...)`` only works if you supply
+an absolute URL, eg. ``"http://www.example.com/foo/bar"``.)
+
+Use ::
+
+ pydoc quixote.http_request
+ pydoc quixote.http_response
+
+to view the documentation for the HTTPRequest and HTTPResponse classes,
+or consult the source code for all the gory details.
+
+There are a few special functions that affect Quixote's
+traversal of a URL to determine how to handle it: ``_q_access()``,
+``_q_lookup()``, and ``_q_resolve()``.
+
+
+``_q_access(request)``
+----------------------
+
+If this function is present in a module, it will be called before
+attempting to traverse any further. It can look at the contents of
+request and decide if the traversal can continue; if not, it should
+raise quixote.errors.AccessError (or a subclass), and Quixote will
+return a 403 ("forbidden") HTTP status code. The return value is
+ignored if ``_q_access()`` doesn't raise an exception.
+
+For example, in the MEMS Exchange code, we have some sets of pages that
+are only accessible to signed-in users of a certain type. The
+``_q_access()`` function looks like this::
+
+ def _q_access (request):
+ if request.session.user is None:
+ raise NotLoggedInError("You must be signed in.")
+ if not (request.session.user.is_admin() or
+ request.session.user.is_fab()):
+ raise AccessError("You don't have access to the reports page.")
+
+This is less error-prone than having to remember to add checks to
+every single public function.
+
+
+``_q_lookup(request, name)``
+-----------------------------
+
+This function translates an arbitrary string into an object that we
+continue traversing. This is very handy; it lets you put user-space
+objects into your URL-space, eliminating the need for digging ID
+strings out of a query, or checking ``PATH_INFO`` after Quixote's done
+with it. But it is a compromise with security: it opens up the
+traversal algorithm to arbitrary names not listed in ``_q_exports``.
+(``_q_lookup()`` is never called for names listed in ``_q_exports``.)
+You should therefore be extremely paranoid about checking the value of
+``name``.
+
+``request`` is the request object, as it is everywhere else; ``name`` is
+a string containing the next component of the path. ``_q_lookup()`` should
+return either a string (a complete document that will be returned to the
+client) or some object that can be traversed further. Returning a
+string is useful in simple cases, eg. if you want the ``/user/joe`` URI
+to show everything about user "joe" in your database, you would define a
+``_q_lookup()`` in the namespace that handles ``/user/`` requests::
+
+ def _q_lookup [plain] (request, name):
+ if not request.session.user.is_admin():
+ raise AccessError("permission denied")
+ user = get_database().get_user(name)
+ if user is None:
+ raise TraversalError("no such user: %r" % name)
+ else:
+ "<h1>User %s</h1>\n" % html_quote(name)
+ "<table>\n"
+ " <tr><th>real name</th><td>%s</td>\n" % user.real_name
+ # ...
+
+(This assumes that the namespace in question is a PTL module, not a
+Python module.)
+
+To publish more complex objects, you'll want to use ``_q_lookup()``'s
+ability to return a new namespace that Quixote continues traversing.
+The usual way to do this is to return an instance of a class that
+implements the web front-end to your object. That class must have a
+``_q_exports`` attribute, and it will almost certainly have a
+``_q_index()`` method. It might also have ``_q_access()`` and
+``_q_lookup()`` (yes, ``_q_lookup()`` calls can nest arbitrarily
+deeply).
+
+For example, you might want ``/user/joe/`` to show a summary,
+``/user/joe/history`` to show a login history, ``/user/joe/prefs`` to be
+a page where joe can edit his personal preferences, etc. The
+``_q_lookup()`` function would then be ::
+
+ def _q_lookup (request, name):
+ return UserUI(request, name)
+
+and the UserUI class, which implements the web interface to user
+objects, might look like ::
+
+ class UserUI:
+ _q_exports = ['history', 'prefs']
+
+ def __init__ (self, request, name):
+ if not request.session.user.is_admin():
+ raise AccessError("permission denied")
+ self.user = get_database().get_user(name)
+ if self.user is None:
+ raise TraversalError("no such user: %r" % name)
+
+ def _q_index (self, request):
+ # ... generate summary page ...
+
+ def history (self, request):
+ # ... generate history page ...
+
+ def prefs (self, request):
+ # ... generate prefs-editing page ...
+
+``_q_resolve(name)``
+--------------------
+
+``_q_resolve()`` looks a bit like ``_q_lookup()``, but is intended for
+a different purpose. Quixote applications can be slow to start up
+because they have to import a large number of Python and PTL modules.
+``_q_resolve()`` is a hook that lets time-consuming imports
+be postponed until the code is actually needed
+
+``name`` is a string containing the next component of the path.
+``_q_resolve()`` should do whatever imports are necessary and return a
+module that will be traversed further. (Nothing enforces that this
+function return a module, so you could also return other types, such
+as a class instance, a callable object, or even a string) if the last
+component of the path is being resolved. Given ``_q_resolve()``'s
+memoization feature, though, returning a module is the most useful
+thing to do.)
+
+``_q_resolve()`` is only ever called for names that are in
+``_q_exports`` and that don't already exist in the containing
+namespace. It is not passed the request object, so its return value
+can't depend on the client in any way. Calls are also memoized; after
+being called the object returned will be added to the containing
+namespace, so ``_q_resolve()`` will be called at most once for a given
+name.
+
+Most commonly, ``_q_resolve()`` will look something like this::
+
+ _q_exports = [..., 'expensive', ...]
+
+ def _q_resolve(name):
+ if name == 'expensive':
+ from otherpackage import expensive
+ return expensive
+
+Let's say this function is in ``app.ui``. The first time
+``/expensive`` is accessed, ``_q_resolve('expensive')`` is called, the
+``otherpackage.expensive`` module is returned and traversal continues.
+The imported module is also saved as ``app.ui.expensive``, so future
+references to ``/expensive`` won't need to invoke the ``_q_resolve()``
+hook.
+
+``_q_exception_handler(request, exception)``
+--------------------------------------------
+
+Quixote will display a default error page when a ``PublishError``
+exception is raised. Before displaying the default page, Quixote will
+search back through the list of namespaces traversed looking for an
+object with a ``_q_exception_handler`` attribute. That attribute is
+expected to be a function and is called with the request and exception
+instance as arguments and should return the error page (e.g. a
+string). If the handler doesn't want to handle a particular error it
+can re-raise it and the next nearest handler will be found. If no
+``_q_exception_handler`` is found, the default Quixote handler is
+used.
+
+
+$Id: programming.txt 23893 2004-04-06 19:31:20Z nascheme $
Added: packages/quixote1/branches/upstream/current/doc/session-mgmt.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/session-mgmt.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/session-mgmt.html (added)
+++ packages/quixote1/branches/upstream/current/doc/session-mgmt.html Mon May 8 19:16:16 2006
@@ -1,0 +1,335 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Quixote Session Management</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="quixote-session-management">
+<h1 class="title">Quixote Session Management</h1>
+<p>HTTP was originally designed as a stateless protocol, meaning that every
+request for a document or image was conducted in a separate TCP
+connection, and that there was no way for a web server to tell if two
+separate requests actually come from the same user. It's no longer
+necessarily true that every request is conducted in a separate TCP
+connection, but HTTP is still fundamentally stateless. However, there
+are many applications where it is desirable or even essential to
+establish a "session" for each user, ie. where all requests performed by
+that user are somehow tied together on the server.</p>
+<p>HTTP cookies were invented to address this requirement, and they are
+still the best solution for establishing sessions on top of HTTP. Thus,
+Quixote's session management mechanism is cookie-based. (The most
+common alternative is to generate long, complicated URLs with an
+embedded session identifier. Since Quixote views the URL as a
+fundamental part of the web user interface, a URL-based session
+management scheme would be un-Quixotic.)</p>
+<p>For further reading: the standard for cookies that is approximately
+implemented by most current browsers is RFC 2109; the latest version of
+the standard is RFC 2965. Those RFCs can be found here:</p>
+<blockquote>
+<p><a class="reference" href="ftp://ftp.isi.edu/in-notes/rfc2109.txt">ftp://ftp.isi.edu/in-notes/rfc2109.txt</a></p>
+<p><a class="reference" href="ftp://ftp.isi.edu/in-notes/rfc2965.txt">ftp://ftp.isi.edu/in-notes/rfc2965.txt</a></p>
+</blockquote>
+<p>In a nutshell, session management with Quixote works like this:</p>
+<ul>
+<li><p class="first">when a user-agent first requests a page from a Quixote application
+that implements session management, Quixote creates a Session object
+and generates a session ID (a random 64-bit number). The Session
+object is attached to the current HTTPRequest object, so that
+application code involved in processing this request has access to
+the Session object.</p>
+</li>
+<li><p class="first">if, at the end of processing that request, the application code has
+stored any information in the Session object, Quixote saves the
+session in its SessionManager object for use by future requests and
+sends a session cookie, called <tt class="literal"><span class="pre">QX_session</span></tt> by default, to the user.
+The session cookie contains the session ID encoded as a hexadecimal
+string, and is included in the response headers, eg.</p>
+<pre class="literal-block">
+Set-Cookie: QX_session="928F82A9B8FA92FD"
+</pre>
+<p>(You can instruct Quixote to specify the domain and path for
+URLs to which this cookie should be sent.)</p>
+</li>
+<li><p class="first">the user agent stores this cookie for future requests</p>
+</li>
+<li><p class="first">the next time the user agent requests a resource that matches the
+cookie's domain and path, it includes the <tt class="literal"><span class="pre">QX_session</span></tt> cookie
+previously generated by Quixote in the request headers, eg.:</p>
+<pre class="literal-block">
+Cookie: QX_session="928F82A9B8FA92FD"
+</pre>
+</li>
+<li><p class="first">while processing the request, Quixote decodes the session ID and
+looks up the corresponding Session object in its SessionManager. If
+there is no such session, the session cookie is bogus or
+out-of-date, so Quixote raises SessionError; ultimately the user
+gets an error page. Otherwise, the Session object is attached to
+the HTTPRequest object that is available to all application code
+used to process the request.</p>
+</li>
+</ul>
+<p>There are two caveats to keep in mind before proceeding, one major and
+one minor:</p>
+<ul class="simple">
+<li>Quixote's standard Session and SessionManager class do not
+implement any sort of persistence, meaning that all sessions
+disappear when the process handling web requests terminates.
+Thus, session management is completely useless with a plain
+CGI driver script unless you add some persistence to the mix;
+see "Session persistence" below for information.</li>
+<li>Quixote never expires sessions; if you want user sessions to
+be cleaned up after a period of inactivity, you will have to
+write code to do it yourself.</li>
+</ul>
+<div class="section" id="session-management-demo">
+<h1><a name="session-management-demo">Session management demo</a></h1>
+<p>There's a simple demo of Quixote's session management in
+<tt class="literal"><span class="pre">demo/session_demo.cgi</span></tt> and <tt class="literal"><span class="pre">demo/session.ptl</span></tt>. The demo implements
+a simple session persistence scheme (each session is written to a
+separate pickle file in <tt class="literal"><span class="pre">/tmp/quixote-session-demo</span></tt>), so running it
+through CGI is just fine.</p>
+<p>I'll assume that you've added a rewrite rule so that requests for
+<tt class="literal"><span class="pre">/qsdemo/</span></tt> are handled by <tt class="literal"><span class="pre">session_demo.cgi</span></tt>, similar to the
+rewriting for <tt class="literal"><span class="pre">/qdemo/</span></tt> described in web-server.txt. Once that's
+done, point your browser at</p>
+<pre class="literal-block">
+http://<hostname>/qsdemo/
+</pre>
+<p>and play around.</p>
+<p>This particular application uses sessions to keep track of just two
+things: the user's identity and the number of requests made in this
+session. The first is addressed by Quixote's standard Session class --
+every Session object has a <tt class="literal"><span class="pre">user</span></tt> attribute, which you can use for
+anything you like. In the session demo, we simply store a string, the
+user's name, which is entered by the user.</p>
+<p>Tracking the number of requests is a bit more interesting: from the
+DemoSession class in session_demo.cgi:</p>
+<pre class="literal-block">
+def __init__ (self, request, id):
+ Session.__init__(self, request, id)
+ self.num_requests = 0
+
+def start_request (self, request):
+ Session.start_request(self, request)
+ self.num_requests += 1
+</pre>
+<p>When the session is created, we initialize the request counter; and when
+we start processing each request, we increment it.</p>
+<p>Using the session information in the application code is simple. For
+example, here's the PTL code that checks if the user has logged in
+(identified herself) yet, and generates a login form if not:</p>
+<pre class="literal-block">
+session = request.session
+if session.user is None:
+ '''
+ <p>You haven\'t introduced yourself yet.<br>
+ Please tell me your name:
+ '''
+ login_form()
+</pre>
+<p>(The <tt class="literal"><span class="pre">login_form()</span></tt> template just emits a simple HTML form -- see
+<tt class="literal"><span class="pre">demo/session.ptl</span></tt> for full source.)</p>
+<p>If the user has already identified herself, then she doesn't need to do
+so again -- so the other branch of that <tt class="literal"><span class="pre">if</span></tt> statement simply prints a
+friendly greeting:</p>
+<pre class="literal-block">
+else:
+ ('<p>Hello, %s. Good to see you again.</p>\n'
+ % html_quote(session.user))
+</pre>
+<p>Note that we must quote the user's name, because they are free to enter
+anything they please, including special HTML characters like <tt class="literal"><span class="pre">&</span></tt> or
+<tt class="literal"><span class="pre"><</span></tt>.</p>
+<p>Of course, <tt class="literal"><span class="pre">session.user</span></tt> will never be set if we don't set it
+ourselves. The code that processes the login form is just this (from
+<tt class="literal"><span class="pre">login()</span></tt> in <tt class="literal"><span class="pre">demo/session.ptl</span></tt>):</p>
+<pre class="literal-block">
+if request.form:
+ user = request.form.get("name")
+ if not user:
+ raise QueryError("no user name supplied")
+
+ session.user = user
+</pre>
+<p>This is obviously a very simple application -- we're not doing any
+verification of the user's input. We have no user database, no
+passwords, and no limitations on what constitutes a "user name". A real
+application would have all of these, as well as a way for users to add
+themselves to the user database -- ie. register with your web site.</p>
+</div>
+<div class="section" id="configuring-the-session-cookie">
+<h1><a name="configuring-the-session-cookie">Configuring the session cookie</a></h1>
+<p>Quixote allows you to configure several aspects of the session cookie
+that it exchanges with clients. First, you can set the name of the
+cookie; this is important if you have multiple independent Quixote
+applications running on the same server. For example, the config file
+for the first application might have</p>
+<pre class="literal-block">
+SESSION_COOKIE_NAME = "foo_session"
+</pre>
+<p>and the second application might have</p>
+<pre class="literal-block">
+SESSION_COOKIE_NAME = "bar_session"
+</pre>
+<p>Next, you can use <tt class="literal"><span class="pre">SESSION_COOKIE_DOMAIN</span></tt> and <tt class="literal"><span class="pre">SESSION_COOKIE_PATH</span></tt>
+to set the cookie attributes that control which requests the cookie is
+included with. By default, these are both <tt class="literal"><span class="pre">None</span></tt>, which instructs
+Quixote to send the cookie without <tt class="literal"><span class="pre">Domain</span></tt> or <tt class="literal"><span class="pre">Path</span></tt> qualifiers.
+For example, if the client requests <tt class="literal"><span class="pre">/foo/bar/</span></tt> from
+www.example.com, and Quixote decides that it must set the session
+cookie in the response to that request, then the server would send</p>
+<pre class="literal-block">
+Set-Cookie: QX_session="928F82A9B8FA92FD"
+</pre>
+<p>in the response headers. Since no domain or path were specified with
+that cookie, the browser will only include the cookie with requests to
+www.example.com for URIs that start with <tt class="literal"><span class="pre">/foo/bar/</span></tt>.</p>
+<p>If you want to ensure that your session cookie is included with all
+requests to www.example.com, you should set <tt class="literal"><span class="pre">SESSION_COOKIE_PATH</span></tt> in your
+config file:</p>
+<pre class="literal-block">
+SESSION_COOKIE_PATH = "/"
+</pre>
+<p>which will cause Quixote to set the cookie like this:</p>
+<pre class="literal-block">
+Set-Cookie: QX_session="928F82A9B8FA92FD"; Path="/"
+</pre>
+<p>which will instruct the browser to include that cookie with <em>all</em>
+requests to www.example.com.</p>
+<p>However, think carefully about what you set <tt class="literal"><span class="pre">SESSION_COOKIE_PATH</span></tt> to
+-- eg. if you set it to "/", but all of your Quixote code is under "/q/"
+in your server's URL-space, then your user's session cookies could be
+unnecessarily exposed. On shared servers where you don't control all of
+the code, this is especially dangerous; be sure to use (eg.)</p>
+<pre class="literal-block">
+SESSION_COOKIE_PATH = "/q/"
+</pre>
+<p>on such servers. The trailing slash is important; without it, your
+session cookies will be sent to URIs like <tt class="literal"><span class="pre">/qux</span></tt> and <tt class="literal"><span class="pre">/qix</span></tt>, even if
+you don't control those URIs.</p>
+<p>If you want to share the cookie across servers in your domain,
+eg. www1.example.com and www2.example.com, you'll also need to set
+<tt class="literal"><span class="pre">SESSION_COOKIE_DOMAIN</span></tt>:</p>
+<blockquote>
+SESSION_COOKIE_DOMAIN = ".example.com"</blockquote>
+<p>Finally, note that the <tt class="literal"><span class="pre">SESSION_COOKIE_*</span></tt> configuration variables
+<em>only</em> affect Quixote's session cookie; if you set your own cookies
+using the <tt class="literal"><span class="pre">HTTPResponse.set_cookie()</span></tt> method, then the cookie sent to
+the client is completely determined by that <tt class="literal"><span class="pre">set_cookie()</span></tt> call.</p>
+<p>See RFCs 2109 and 2965 for more information on the rules browsers are
+supposed to follow for including cookies with HTTP requests.</p>
+</div>
+<div class="section" id="writing-the-session-class">
+<h1><a name="writing-the-session-class">Writing the session class</a></h1>
+<p>You will almost certainly have to write a custom session class for your
+application by subclassing Quixote's standard Session class. Every
+custom session class has two essential responsibilities:</p>
+<ul class="simple">
+<li>initialize the attributes that will be used by your application</li>
+<li>override the <tt class="literal"><span class="pre">has_info()</span></tt> method, so the session manager knows when
+it must save your session object</li>
+</ul>
+<p>The first one is fairly obvious and just good practice. The second is
+essential, and not at all obvious. The has_info() method exists because
+SessionManager does not automatically hang on to all session objects;
+this is a defence against clients that ignore cookies, making your
+session manager create lots of session objects that are just used once.
+As long as those session objects are not saved, the burden imposed by
+these clients is not too bad -- at least they aren't sucking up your
+memory, or bogging down the database that you save session data to.
+Thus, the session manager uses has_info() to know if it should hang on
+to a session object or not: if a session has information that must be
+saved, the session manager saves it and sends a session cookie to the
+client.</p>
+<p>For development/testing work, it's fine to say that your session objects
+should always be saved:</p>
+<pre class="literal-block">
+def has_info (self):
+ return 1
+</pre>
+<p>The opposite extreme is to forget to override <tt class="literal"><span class="pre">has_info()</span></tt> altogether,
+in which case session management most likely won't work: unless you
+tickle the Session object such that the base <tt class="literal"><span class="pre">has_info()</span></tt> method
+returns true, the session manager won't save the sessions that it
+creates, and Quixote will never drop a session cookie on the client.</p>
+<p>In a real application, you need to think carefully about what data to
+store in your sessions, and how <tt class="literal"><span class="pre">has_info()</span></tt> should react to the
+presence of that data. If you try and track something about every
+single visitor to your site, sooner or later one of those a
+broken/malicious client that ignores cookies and <tt class="literal"><span class="pre">robots.txt</span></tt> will
+come along and crawl your entire site, wreaking havoc on your Quixote
+application (or the database underlying it).</p>
+</div>
+<div class="section" id="session-persistence">
+<h1><a name="session-persistence">Session persistence</a></h1>
+<p>Keeping session data across requests is all very nice, but in the real
+world you want that data to survive across process termination. With
+CGI, this is essential, since each process serves exactly one request
+and then terminates. With other execution mechanisms, though, it's
+still important -- you don't want to lose all your session data just
+because your long-lived server process was restarted, or your server
+machine was rebooted.</p>
+<p>However, every application is different, so Quixote doesn't provide any
+built-in mechanism for session persistence. Instead, it provides a
+number of hooks, most in the SessionManager class, that let you plug in
+your preferred persistence mechanism.</p>
+<p>The first and most important hook is in the SessionManager constructor:
+you can provide an alternate mapping object that SessionManager will use
+to store session objects in. By default, SessionManager uses an
+ordinary dictionary; if you provide a mapping object that implements
+persistence, then your session data will automatically persist across
+processes. For example, you might use the standard 'shelve' module,
+which provides a mapping object on top of a DBM or Berkeley DB file:</p>
+<pre class="literal-block">
+import shelve
+sessions = shelve.open("/tmp/quixote-sessions")
+session_mgr = SessionManager(session_mapping=sessions)
+</pre>
+<p>For a persistent mapping implementation that doesn't require any
+external libraries, see the DirMapping class in
+<tt class="literal"><span class="pre">demo/session_demo.cgi</span></tt>.</p>
+<p>If you use one of these relatively simple persistent mapping types,
+you'll also need to override <tt class="literal"><span class="pre">is_dirty()</span></tt> in your Session class.
+That's in addition to overriding <tt class="literal"><span class="pre">has_info()</span></tt>, which determines if a
+session object is <em>ever</em> saved; <tt class="literal"><span class="pre">is_dirty()</span></tt> is only called on
+sessions that have already been added to the session mapping, to see if
+they need to be "re-added". The default implementation always returns
+false, because once an object has been added to a normal dictionary,
+there's no need to add it again. However, with simple persistent
+mapping types like shelve and DirMapping, you need to store the object
+again each time it changes. Thus, <tt class="literal"><span class="pre">is_dirty()</span></tt> should return true if
+the session object needs to be re-written. For a simple, naive, but
+inefficient implementation, making is_dirty an alias for <tt class="literal"><span class="pre">has_info()</span></tt>
+will work -- that just means that once the session has been written
+once, it will be re-written on every request. (This is what DemoSession
+in <tt class="literal"><span class="pre">demo/session_demo.cgi</span></tt> does.)</p>
+<p>The third and final part of the persistence interface only applies if
+you are using a transactional persistence mechanism, such as ZODB or an
+industrial-strength relational database. In that case, you need a place
+to commit or abort the transaction that contains pending changes to the
+current session. SessionManager provides two methods for you to
+override: <tt class="literal"><span class="pre">abort_changes()</span></tt> and <tt class="literal"><span class="pre">commit_changes()</span></tt>.
+<tt class="literal"><span class="pre">abort_changes()</span></tt> is called by SessionPublisher whenever a request
+crashes, ie. whenever your application raises an exception other than
+PublishError. <tt class="literal"><span class="pre">commit_changes()</span></tt> is called for requests that complete
+successfully, or that raise a PublishError exception. They are defined
+as follows:</p>
+<pre class="literal-block">
+def abort_changes (self, session):
+ """abort_changes(session : Session)"""
+
+def commit_changes (self, session):
+ """commit_changes(session : Session)"""
+</pre>
+<p>Obviously, you'll have to write your own SessionManager subclass if you
+need to take advantage of these hooks for transactional session
+persistence.</p>
+<p>$Id: session-mgmt.txt 20217 2003-01-16 20:51:53Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/session-mgmt.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/session-mgmt.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/session-mgmt.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/session-mgmt.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,352 @@
+Quixote Session Management
+==========================
+
+HTTP was originally designed as a stateless protocol, meaning that every
+request for a document or image was conducted in a separate TCP
+connection, and that there was no way for a web server to tell if two
+separate requests actually come from the same user. It's no longer
+necessarily true that every request is conducted in a separate TCP
+connection, but HTTP is still fundamentally stateless. However, there
+are many applications where it is desirable or even essential to
+establish a "session" for each user, ie. where all requests performed by
+that user are somehow tied together on the server.
+
+HTTP cookies were invented to address this requirement, and they are
+still the best solution for establishing sessions on top of HTTP. Thus,
+Quixote's session management mechanism is cookie-based. (The most
+common alternative is to generate long, complicated URLs with an
+embedded session identifier. Since Quixote views the URL as a
+fundamental part of the web user interface, a URL-based session
+management scheme would be un-Quixotic.)
+
+For further reading: the standard for cookies that is approximately
+implemented by most current browsers is RFC 2109; the latest version of
+the standard is RFC 2965. Those RFCs can be found here:
+
+ ftp://ftp.isi.edu/in-notes/rfc2109.txt
+
+ ftp://ftp.isi.edu/in-notes/rfc2965.txt
+
+In a nutshell, session management with Quixote works like this:
+
+* when a user-agent first requests a page from a Quixote application
+ that implements session management, Quixote creates a Session object
+ and generates a session ID (a random 64-bit number). The Session
+ object is attached to the current HTTPRequest object, so that
+ application code involved in processing this request has access to
+ the Session object.
+
+* if, at the end of processing that request, the application code has
+ stored any information in the Session object, Quixote saves the
+ session in its SessionManager object for use by future requests and
+ sends a session cookie, called ``QX_session`` by default, to the user.
+ The session cookie contains the session ID encoded as a hexadecimal
+ string, and is included in the response headers, eg. ::
+
+ Set-Cookie: QX_session="928F82A9B8FA92FD"
+
+ (You can instruct Quixote to specify the domain and path for
+ URLs to which this cookie should be sent.)
+
+* the user agent stores this cookie for future requests
+
+* the next time the user agent requests a resource that matches the
+ cookie's domain and path, it includes the ``QX_session`` cookie
+ previously generated by Quixote in the request headers, eg.::
+
+ Cookie: QX_session="928F82A9B8FA92FD"
+
+* while processing the request, Quixote decodes the session ID and
+ looks up the corresponding Session object in its SessionManager. If
+ there is no such session, the session cookie is bogus or
+ out-of-date, so Quixote raises SessionError; ultimately the user
+ gets an error page. Otherwise, the Session object is attached to
+ the HTTPRequest object that is available to all application code
+ used to process the request.
+
+There are two caveats to keep in mind before proceeding, one major and
+one minor:
+
+* Quixote's standard Session and SessionManager class do not
+ implement any sort of persistence, meaning that all sessions
+ disappear when the process handling web requests terminates.
+ Thus, session management is completely useless with a plain
+ CGI driver script unless you add some persistence to the mix;
+ see "Session persistence" below for information.
+
+* Quixote never expires sessions; if you want user sessions to
+ be cleaned up after a period of inactivity, you will have to
+ write code to do it yourself.
+
+
+Session management demo
+-----------------------
+
+There's a simple demo of Quixote's session management in
+``demo/session_demo.cgi`` and ``demo/session.ptl``. The demo implements
+a simple session persistence scheme (each session is written to a
+separate pickle file in ``/tmp/quixote-session-demo``), so running it
+through CGI is just fine.
+
+I'll assume that you've added a rewrite rule so that requests for
+``/qsdemo/`` are handled by ``session_demo.cgi``, similar to the
+rewriting for ``/qdemo/`` described in web-server.txt. Once that's
+done, point your browser at ::
+
+ http://<hostname>/qsdemo/
+
+and play around.
+
+This particular application uses sessions to keep track of just two
+things: the user's identity and the number of requests made in this
+session. The first is addressed by Quixote's standard Session class --
+every Session object has a ``user`` attribute, which you can use for
+anything you like. In the session demo, we simply store a string, the
+user's name, which is entered by the user.
+
+Tracking the number of requests is a bit more interesting: from the
+DemoSession class in session_demo.cgi::
+
+ def __init__ (self, request, id):
+ Session.__init__(self, request, id)
+ self.num_requests = 0
+
+ def start_request (self, request):
+ Session.start_request(self, request)
+ self.num_requests += 1
+
+When the session is created, we initialize the request counter; and when
+we start processing each request, we increment it.
+
+Using the session information in the application code is simple. For
+example, here's the PTL code that checks if the user has logged in
+(identified herself) yet, and generates a login form if not::
+
+ session = request.session
+ if session.user is None:
+ '''
+ <p>You haven\'t introduced yourself yet.<br>
+ Please tell me your name:
+ '''
+ login_form()
+
+(The ``login_form()`` template just emits a simple HTML form -- see
+``demo/session.ptl`` for full source.)
+
+If the user has already identified herself, then she doesn't need to do
+so again -- so the other branch of that ``if`` statement simply prints a
+friendly greeting::
+
+ else:
+ ('<p>Hello, %s. Good to see you again.</p>\n'
+ % html_quote(session.user))
+
+Note that we must quote the user's name, because they are free to enter
+anything they please, including special HTML characters like ``&`` or
+``<``.
+
+Of course, ``session.user`` will never be set if we don't set it
+ourselves. The code that processes the login form is just this (from
+``login()`` in ``demo/session.ptl``)::
+
+ if request.form:
+ user = request.form.get("name")
+ if not user:
+ raise QueryError("no user name supplied")
+
+ session.user = user
+
+This is obviously a very simple application -- we're not doing any
+verification of the user's input. We have no user database, no
+passwords, and no limitations on what constitutes a "user name". A real
+application would have all of these, as well as a way for users to add
+themselves to the user database -- ie. register with your web site.
+
+
+Configuring the session cookie
+------------------------------
+
+Quixote allows you to configure several aspects of the session cookie
+that it exchanges with clients. First, you can set the name of the
+cookie; this is important if you have multiple independent Quixote
+applications running on the same server. For example, the config file
+for the first application might have ::
+
+ SESSION_COOKIE_NAME = "foo_session"
+
+and the second application might have ::
+
+ SESSION_COOKIE_NAME = "bar_session"
+
+Next, you can use ``SESSION_COOKIE_DOMAIN`` and ``SESSION_COOKIE_PATH``
+to set the cookie attributes that control which requests the cookie is
+included with. By default, these are both ``None``, which instructs
+Quixote to send the cookie without ``Domain`` or ``Path`` qualifiers.
+For example, if the client requests ``/foo/bar/`` from
+www.example.com, and Quixote decides that it must set the session
+cookie in the response to that request, then the server would send ::
+
+ Set-Cookie: QX_session="928F82A9B8FA92FD"
+
+in the response headers. Since no domain or path were specified with
+that cookie, the browser will only include the cookie with requests to
+www.example.com for URIs that start with ``/foo/bar/``.
+
+If you want to ensure that your session cookie is included with all
+requests to www.example.com, you should set ``SESSION_COOKIE_PATH`` in your
+config file::
+
+ SESSION_COOKIE_PATH = "/"
+
+which will cause Quixote to set the cookie like this::
+
+ Set-Cookie: QX_session="928F82A9B8FA92FD"; Path="/"
+
+which will instruct the browser to include that cookie with *all*
+requests to www.example.com.
+
+However, think carefully about what you set ``SESSION_COOKIE_PATH`` to
+-- eg. if you set it to "/", but all of your Quixote code is under "/q/"
+in your server's URL-space, then your user's session cookies could be
+unnecessarily exposed. On shared servers where you don't control all of
+the code, this is especially dangerous; be sure to use (eg.) ::
+
+ SESSION_COOKIE_PATH = "/q/"
+
+on such servers. The trailing slash is important; without it, your
+session cookies will be sent to URIs like ``/qux`` and ``/qix``, even if
+you don't control those URIs.
+
+If you want to share the cookie across servers in your domain,
+eg. www1.example.com and www2.example.com, you'll also need to set
+``SESSION_COOKIE_DOMAIN``:
+
+ SESSION_COOKIE_DOMAIN = ".example.com"
+
+Finally, note that the ``SESSION_COOKIE_*`` configuration variables
+*only* affect Quixote's session cookie; if you set your own cookies
+using the ``HTTPResponse.set_cookie()`` method, then the cookie sent to
+the client is completely determined by that ``set_cookie()`` call.
+
+See RFCs 2109 and 2965 for more information on the rules browsers are
+supposed to follow for including cookies with HTTP requests.
+
+
+Writing the session class
+-------------------------
+
+You will almost certainly have to write a custom session class for your
+application by subclassing Quixote's standard Session class. Every
+custom session class has two essential responsibilities:
+
+* initialize the attributes that will be used by your application
+
+* override the ``has_info()`` method, so the session manager knows when
+ it must save your session object
+
+The first one is fairly obvious and just good practice. The second is
+essential, and not at all obvious. The has_info() method exists because
+SessionManager does not automatically hang on to all session objects;
+this is a defence against clients that ignore cookies, making your
+session manager create lots of session objects that are just used once.
+As long as those session objects are not saved, the burden imposed by
+these clients is not too bad -- at least they aren't sucking up your
+memory, or bogging down the database that you save session data to.
+Thus, the session manager uses has_info() to know if it should hang on
+to a session object or not: if a session has information that must be
+saved, the session manager saves it and sends a session cookie to the
+client.
+
+For development/testing work, it's fine to say that your session objects
+should always be saved::
+
+ def has_info (self):
+ return 1
+
+The opposite extreme is to forget to override ``has_info()`` altogether,
+in which case session management most likely won't work: unless you
+tickle the Session object such that the base ``has_info()`` method
+returns true, the session manager won't save the sessions that it
+creates, and Quixote will never drop a session cookie on the client.
+
+In a real application, you need to think carefully about what data to
+store in your sessions, and how ``has_info()`` should react to the
+presence of that data. If you try and track something about every
+single visitor to your site, sooner or later one of those a
+broken/malicious client that ignores cookies and ``robots.txt`` will
+come along and crawl your entire site, wreaking havoc on your Quixote
+application (or the database underlying it).
+
+
+Session persistence
+-------------------
+
+Keeping session data across requests is all very nice, but in the real
+world you want that data to survive across process termination. With
+CGI, this is essential, since each process serves exactly one request
+and then terminates. With other execution mechanisms, though, it's
+still important -- you don't want to lose all your session data just
+because your long-lived server process was restarted, or your server
+machine was rebooted.
+
+However, every application is different, so Quixote doesn't provide any
+built-in mechanism for session persistence. Instead, it provides a
+number of hooks, most in the SessionManager class, that let you plug in
+your preferred persistence mechanism.
+
+The first and most important hook is in the SessionManager constructor:
+you can provide an alternate mapping object that SessionManager will use
+to store session objects in. By default, SessionManager uses an
+ordinary dictionary; if you provide a mapping object that implements
+persistence, then your session data will automatically persist across
+processes. For example, you might use the standard 'shelve' module,
+which provides a mapping object on top of a DBM or Berkeley DB file::
+
+ import shelve
+ sessions = shelve.open("/tmp/quixote-sessions")
+ session_mgr = SessionManager(session_mapping=sessions)
+
+For a persistent mapping implementation that doesn't require any
+external libraries, see the DirMapping class in
+``demo/session_demo.cgi``.
+
+If you use one of these relatively simple persistent mapping types,
+you'll also need to override ``is_dirty()`` in your Session class.
+That's in addition to overriding ``has_info()``, which determines if a
+session object is *ever* saved; ``is_dirty()`` is only called on
+sessions that have already been added to the session mapping, to see if
+they need to be "re-added". The default implementation always returns
+false, because once an object has been added to a normal dictionary,
+there's no need to add it again. However, with simple persistent
+mapping types like shelve and DirMapping, you need to store the object
+again each time it changes. Thus, ``is_dirty()`` should return true if
+the session object needs to be re-written. For a simple, naive, but
+inefficient implementation, making is_dirty an alias for ``has_info()``
+will work -- that just means that once the session has been written
+once, it will be re-written on every request. (This is what DemoSession
+in ``demo/session_demo.cgi`` does.)
+
+The third and final part of the persistence interface only applies if
+you are using a transactional persistence mechanism, such as ZODB or an
+industrial-strength relational database. In that case, you need a place
+to commit or abort the transaction that contains pending changes to the
+current session. SessionManager provides two methods for you to
+override: ``abort_changes()`` and ``commit_changes()``.
+``abort_changes()`` is called by SessionPublisher whenever a request
+crashes, ie. whenever your application raises an exception other than
+PublishError. ``commit_changes()`` is called for requests that complete
+successfully, or that raise a PublishError exception. They are defined
+as follows::
+
+ def abort_changes (self, session):
+ """abort_changes(session : Session)"""
+
+ def commit_changes (self, session):
+ """commit_changes(session : Session)"""
+
+Obviously, you'll have to write your own SessionManager subclass if you
+need to take advantage of these hooks for transactional session
+persistence.
+
+
+$Id: session-mgmt.txt 20217 2003-01-16 20:51:53Z akuchlin $
Added: packages/quixote1/branches/upstream/current/doc/static-files.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/static-files.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/static-files.html (added)
+++ packages/quixote1/branches/upstream/current/doc/static-files.html Mon May 8 19:16:16 2006
@@ -1,0 +1,55 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Examples of serving static files</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="examples-of-serving-static-files">
+<h1 class="title">Examples of serving static files</h1>
+<p>The <tt class="literal"><span class="pre">quixote.util</span></tt> module includes classes for making files and
+directories available as Quixote resources. Here are some examples.</p>
+<div class="section" id="publishing-a-single-file">
+<h1><a name="publishing-a-single-file">Publishing a Single File</a></h1>
+<p>The <tt class="literal"><span class="pre">StaticFile</span></tt> class makes an individual filesystem file (possibly
+a symbolic link) available. You can also specify the MIME type and
+encoding of the file; if you don't specify this, the MIME type will be
+guessed using the standard Python <tt class="literal"><span class="pre">mimetypes.guess_type()</span></tt> function.
+The default action is to not follow symbolic links, but this behaviour
+can be changed using the <tt class="literal"><span class="pre">follow_symlinks</span></tt> parameter.</p>
+<p>The following example publishes a file with the URL <tt class="literal"><span class="pre">.../stylesheet_css</span></tt>:</p>
+<pre class="literal-block">
+# 'stylesheet_css' must be in the _q_exports list
+_q_exports = [ ..., 'stylesheet_css', ...]
+
+stylesheet_css = StaticFile(
+ "/htdocs/legacy_app/stylesheet.css",
+ follow_symlinks=1, mime_type="text/css")
+</pre>
+<p>If you want the URL of the file to have a <tt class="literal"><span class="pre">.css</span></tt> extension, you use
+the external to internal name mapping feature of <tt class="literal"><span class="pre">_q_exports</span></tt>. For
+example:</p>
+<pre class="literal-block">
+_q_exports = [ ..., ('stylesheet.css', 'stylesheet_css'), ...]
+</pre>
+</div>
+<div class="section" id="publishing-a-directory">
+<h1><a name="publishing-a-directory">Publishing a Directory</a></h1>
+<p>Publishing a directory is similar. The <tt class="literal"><span class="pre">StaticDirectory</span></tt> class
+makes a complete filesystem directory available. Again, the default
+behaviour is to not follow symlinks. You can also request that the
+<tt class="literal"><span class="pre">StaticDirectory</span></tt> object cache information about the files in
+memory so that it doesn't try to guess the MIME type on every hit.</p>
+<p>This example publishes the <tt class="literal"><span class="pre">notes/</span></tt> directory:</p>
+<pre class="literal-block">
+_q_exports = [ ..., 'notes', ...]
+
+notes = StaticDirectory("/htdocs/legacy_app/notes")
+</pre>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/static-files.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/static-files.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/static-files.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/static-files.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,51 @@
+Examples of serving static files
+================================
+
+The ``quixote.util`` module includes classes for making files and
+directories available as Quixote resources. Here are some examples.
+
+
+Publishing a Single File
+------------------------
+
+The ``StaticFile`` class makes an individual filesystem file (possibly
+a symbolic link) available. You can also specify the MIME type and
+encoding of the file; if you don't specify this, the MIME type will be
+guessed using the standard Python ``mimetypes.guess_type()`` function.
+The default action is to not follow symbolic links, but this behaviour
+can be changed using the ``follow_symlinks`` parameter.
+
+The following example publishes a file with the URL ``.../stylesheet_css``::
+
+ # 'stylesheet_css' must be in the _q_exports list
+ _q_exports = [ ..., 'stylesheet_css', ...]
+
+ stylesheet_css = StaticFile(
+ "/htdocs/legacy_app/stylesheet.css",
+ follow_symlinks=1, mime_type="text/css")
+
+
+If you want the URL of the file to have a ``.css`` extension, you use
+the external to internal name mapping feature of ``_q_exports``. For
+example::
+
+ _q_exports = [ ..., ('stylesheet.css', 'stylesheet_css'), ...]
+
+
+
+Publishing a Directory
+----------------------
+
+Publishing a directory is similar. The ``StaticDirectory`` class
+makes a complete filesystem directory available. Again, the default
+behaviour is to not follow symlinks. You can also request that the
+``StaticDirectory`` object cache information about the files in
+memory so that it doesn't try to guess the MIME type on every hit.
+
+This example publishes the ``notes/`` directory::
+
+ _q_exports = [ ..., 'notes', ...]
+
+ notes = StaticDirectory("/htdocs/legacy_app/notes")
+
+
Added: packages/quixote1/branches/upstream/current/doc/upgrading.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/upgrading.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/upgrading.html (added)
+++ packages/quixote1/branches/upstream/current/doc/upgrading.html Mon May 8 19:16:16 2006
@@ -1,0 +1,239 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Upgrading code from older versions of Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="upgrading-code-from-older-versions-of-quixote">
+<h1 class="title">Upgrading code from older versions of Quixote</h1>
+<p>This document lists backward-incompatible changes in Quixote, and
+explains how to update application code to work with the newer
+version.</p>
+<div class="section" id="changes-from-0-6-1-to-1-0">
+<h1><a name="changes-from-0-6-1-to-1-0">Changes from 0.6.1 to 1.0</a></h1>
+<div class="section" id="sessions">
+<h2><a name="sessions">Sessions</a></h2>
+<p>A leading underscore was removed from the <tt class="literal"><span class="pre">Session</span></tt> attributes
+<tt class="literal"><span class="pre">__remote_address</span></tt>, <tt class="literal"><span class="pre">__creation_time</span></tt>, and <tt class="literal"><span class="pre">__access_time</span></tt>. If
+you have pickled <tt class="literal"><span class="pre">Session</span></tt> objects you will need to upgrade them
+somehow. Our preferred method is to write a script that unpickles each
+object, renames the attributes and then re-pickles it.</p>
+</div>
+</div>
+<div class="section" id="changes-from-0-6-to-0-6-1">
+<h1><a name="changes-from-0-6-to-0-6-1">Changes from 0.6 to 0.6.1</a></h1>
+<div class="section" id="q-exception-handler-now-called-if-exception-while-traversing">
+<h2><a name="q-exception-handler-now-called-if-exception-while-traversing"><tt class="literal"><span class="pre">_q_exception_handler</span></tt> now called if exception while traversing</a></h2>
+<p><tt class="literal"><span class="pre">_q_exception_handler</span></tt> hooks will now be called if an exception is
+raised during the traversal process. Quixote 0.6 had a bug that caused
+<tt class="literal"><span class="pre">_q_exception_handler</span></tt> hooks to only be called if an exception was
+raised after the traversal completed.</p>
+</div>
+</div>
+<div class="section" id="changes-from-0-5-to-0-6">
+<h1><a name="changes-from-0-5-to-0-6">Changes from 0.5 to 0.6</a></h1>
+<div class="section" id="q-getname-renamed-to-q-lookup">
+<h2><a name="q-getname-renamed-to-q-lookup"><tt class="literal"><span class="pre">_q_getname</span></tt> renamed to <tt class="literal"><span class="pre">_q_lookup</span></tt></a></h2>
+<p>The <tt class="literal"><span class="pre">_q_getname</span></tt> special function was renamed to <tt class="literal"><span class="pre">_q_lookup</span></tt>,
+because that name gives a clearer impression of the function's
+purpose. In 0.6, <tt class="literal"><span class="pre">_q_getname</span></tt> still works but will trigger a
+warning.</p>
+</div>
+<div class="section" id="form-framework-changes">
+<h2><a name="form-framework-changes">Form Framework Changes</a></h2>
+<p>The <tt class="literal"><span class="pre">quixote.form.form</span></tt> module was changed from a .ptl file to a .py
+file. You should delete or move the existing <tt class="literal"><span class="pre">quixote/</span></tt> directory
+in <tt class="literal"><span class="pre">site-packages</span></tt> before running <tt class="literal"><span class="pre">setup.py</span></tt>, or at least delete
+the old <tt class="literal"><span class="pre">form.ptl</span></tt> and <tt class="literal"><span class="pre">form.ptlc</span></tt> files.</p>
+<p>The widget and form classes in the <tt class="literal"><span class="pre">quixote.form</span></tt> package now return
+<tt class="literal"><span class="pre">htmltext</span></tt> instances. Applications that use forms and widgets will
+likely have to be changed to use the <tt class="literal"><span class="pre">[html]</span></tt> template type to avoid
+over-escaping of HTML special characters.</p>
+<p>Also, the constructor arguments to <tt class="literal"><span class="pre">SelectWidget</span></tt> and its subclasses have
+changed. This only affects applications that use the form framework
+located in the <tt class="literal"><span class="pre">quixote.form</span></tt> package.</p>
+<p>In Quixote 0.5, the <tt class="literal"><span class="pre">SelectWidget</span></tt> constructor had this signature:</p>
+<pre class="literal-block">
+def __init__ (self, name, value=None,
+ allowed_values=None,
+ descriptions=None,
+ size=None,
+ sort=0):
+</pre>
+<p><tt class="literal"><span class="pre">allowed_values</span></tt> was the list of objects that the user could choose,
+and <tt class="literal"><span class="pre">descriptions</span></tt> was a list of strings that would actually be
+shown to the user in the generated HTML.</p>
+<p>In Quixote 0.6, the signature has changed slightly:</p>
+<pre class="literal-block">
+def __init__ (self, name, value=None,
+ allowed_values=None,
+ descriptions=None,
+ options=None,
+ size=None,
+ sort=0):
+</pre>
+<p>The <tt class="literal"><span class="pre">quote</span></tt> argument is gone, and the <tt class="literal"><span class="pre">options</span></tt> argument has been
+added. If an <tt class="literal"><span class="pre">options</span></tt> argument is provided, <tt class="literal"><span class="pre">allowed_values</span></tt>
+and <tt class="literal"><span class="pre">descriptions</span></tt> must not be supplied.</p>
+<p>The <tt class="literal"><span class="pre">options</span></tt> argument, if present, must be a list of tuples with
+1,2, or 3 elements, of the form <tt class="literal"><span class="pre">(value:any,</span> <span class="pre">description:any,</span>
+<span class="pre">key:string)</span></tt>.</p>
+<blockquote>
+<ul class="simple">
+<li><tt class="literal"><span class="pre">value</span></tt> is the object that will be returned if the user chooses
+this item, and must always be supplied.</li>
+<li><tt class="literal"><span class="pre">description</span></tt> is a string or htmltext instance which will be
+shown to the user in the generated HTML. It will be passed
+through the htmlescape() functions, so for an ordinary string
+special characters such as '&' will be converted to '&amp;'.
+htmltext instances will be left as they are.</li>
+<li>If supplied, <tt class="literal"><span class="pre">key</span></tt> will be used in the value attribute
+of the option element (<tt class="literal"><span class="pre"><option</span> <span class="pre">value="..."></span></tt>).
+If not supplied, keys will be generated; <tt class="literal"><span class="pre">value</span></tt> is checked for a
+<tt class="literal"><span class="pre">_p_oid</span></tt> attribute and if present, that string is used;
+otherwise the description is used.</li>
+</ul>
+</blockquote>
+<p>In the common case, most applications won't have to change anything,
+though the ordering of selection items may change due to the
+difference in how keys are generated.</p>
+</div>
+<div class="section" id="file-upload-changes">
+<h2><a name="file-upload-changes">File Upload Changes</a></h2>
+<p>Quixote 0.6 introduces new support for HTTP upload requests. Any HTTP
+request with a Content-Type of "multipart/form-data" -- which is
+generally only used for uploads -- is now represented by
+HTTPUploadRequest, a subclass of HTTPRequest, and the uploaded files
+themselves are represented by Upload objects.</p>
+<p>Whenever an HTTP request has a Content-Type of "multipart/form-data",
+an instance of HTTPUploadRequest is created instead of HTTPRequest.
+Some of the fields in the request are presumably uploaded files and
+might be quite large, so HTTPUploadRequest will read all of the fields
+supplied in the request body and write them out to temporary files;
+the temporary files are written in the directory specified by the
+UPLOAD_DIR configuration variable.</p>
+<p>Once the temporary files have been written, the HTTPUploadRequest
+object is passed to a function or PTL template, just like an ordinary
+request. The difference between HTTPRequest and HTTPUploadRequest
+is that all of the form variables are represented as Upload objects.
+Upload objects have three attributes:</p>
+<dl>
+<dt><tt class="literal"><span class="pre">orig_filename</span></tt></dt>
+<dd>the filename supplied by the browser.</dd>
+<dt><tt class="literal"><span class="pre">base_filename</span></tt></dt>
+<dd>a stripped-down version of orig_filename with unsafe characters removed.
+This could be used when writing uploaded data to a permanent location.</dd>
+<dt><tt class="literal"><span class="pre">tmp_filename</span></tt></dt>
+<dd>the path of the temporary file containing the uploaded data for this field.</dd>
+</dl>
+<p>Consult upload.txt for more information about handling file uploads.</p>
+</div>
+<div class="section" id="refactored-publisher-class">
+<h2><a name="refactored-publisher-class">Refactored <cite>Publisher</cite> Class</a></h2>
+<p>Various methods in the <cite>Publisher</cite> class were rearranged. If your
+application subclasses Publisher, you may need to change your code
+accordingly.</p>
+<blockquote>
+<ul>
+<li><p class="first"><tt class="literal"><span class="pre">parse_request()</span></tt> no longer creates the HTTPRequest object;
+instead a new method, <tt class="literal"><span class="pre">create_request()</span></tt>, handles this,
+and can be overridden as required.</p>
+<p>As a result, the method signature has changed from
+<tt class="literal"><span class="pre">parse_request(stdin,</span> <span class="pre">env)</span></tt> to <tt class="literal"><span class="pre">parse_request(request)</span></tt>.</p>
+</li>
+<li><p class="first">The <tt class="literal"><span class="pre">Publisher.publish()</span></tt> method now catches exceptions raised
+by <tt class="literal"><span class="pre">parse_request()</span></tt>.</p>
+</li>
+</ul>
+</blockquote>
+</div>
+</div>
+<div class="section" id="changes-from-0-4-to-0-5">
+<h1><a name="changes-from-0-4-to-0-5">Changes from 0.4 to 0.5</a></h1>
+<div class="section" id="session-management-changes">
+<h2><a name="session-management-changes">Session Management Changes</a></h2>
+<p>The Quixote session management interface underwent lots of change and
+cleanup with Quixote 0.5. It was previously undocumented (apart from
+docstrings in the code), so we thought that this was a good opportunity
+to clean up the interface. Nevertheless, those brave souls who got
+session management working just by reading the code are in for a bit of
+suffering; this brief note should help clarify things. The definitive
+documentation for session management is session-mgmt.txt -- you should
+start there.</p>
+<div class="section" id="attribute-renamings-and-pickled-objects">
+<h3><a name="attribute-renamings-and-pickled-objects">Attribute renamings and pickled objects</a></h3>
+<p>Most attributes of the standard Session class were made private in order
+to reduce collisions with subclasses. The downside is that pickled
+Session objects will break. You might want to (temporarily) modify
+session.py and add this method to Session:</p>
+<pre class="literal-block">
+def __setstate__ (self, dict):
+ # Update for attribute renamings made in rev. 1.51.2.3
+ # (between Quixote 0.4.7 and 0.5).
+ self.__dict__.update(dict)
+ if hasattr(self, 'remote_address'):
+ self.__remote_address = self.remote_address
+ del self.remote_address
+ if hasattr(self, 'creation_time'):
+ self.__creation_time = self.creation_time
+ del self.creation_time
+ if hasattr(self, 'access_time'):
+ self.__access_time = self.access_time
+ del self.access_time
+ if hasattr(self, 'form_tokens'):
+ self._form_tokens = self.form_tokens
+ del self.form_tokens
+</pre>
+<p>However, if your sessions were pickled via ZODB, this may not work. (It
+didn't work for us.) In that case, you'll have to add something like
+this to your class that inherits from both ZODB's Persistent and
+Quixote's Session:</p>
+<pre class="literal-block">
+def __setstate__ (self, dict):
+ # Blechhh! This doesn't work if I put it in Quixote's
+ # session.py, so I have to second-guess how Python
+ # treats "__" attribute names.
+ self.__dict__.update(dict)
+ if hasattr(self, 'remote_address'):
+ self._Session__remote_address = self.remote_address
+ del self.remote_address
+ if hasattr(self, 'creation_time'):
+ self._Session__creation_time = self.creation_time
+ del self.creation_time
+ if hasattr(self, 'access_time'):
+ self._Session__access_time = self.access_time
+ del self.access_time
+ if hasattr(self, 'form_tokens'):
+ self._form_tokens = self.form_tokens
+ del self.form_tokens
+</pre>
+<p>It's not pretty, but it worked for us.</p>
+</div>
+<div class="section" id="cookie-domains-and-paths">
+<h3><a name="cookie-domains-and-paths">Cookie domains and paths</a></h3>
+<p>The session cookie config variables -- <tt class="literal"><span class="pre">COOKIE_NAME</span></tt>,
+<tt class="literal"><span class="pre">COOKIE_DOMAIN</span></tt>, and <tt class="literal"><span class="pre">COOKIE_PATH</span></tt> -- have been renamed to
+<tt class="literal"><span class="pre">SESSION_COOKIE_*</span></tt> for clarity.</p>
+<p>If you previously set the config variable <tt class="literal"><span class="pre">COOKIE_DOMAIN</span></tt> to the name
+of your server, this is most likely no longer necessary -- it's now fine
+to leave <tt class="literal"><span class="pre">SESSION_COOKIE_DOMAIN</span></tt> unset (ie. <tt class="literal"><span class="pre">None</span></tt>), which
+ultimately means browsers will only include the session cookie in
+requests to the same server that sent it to them in the first place.</p>
+<p>If you previously set <tt class="literal"><span class="pre">COOKIE_PATH</span></tt>, then you should probably preserve
+your setting as <tt class="literal"><span class="pre">SESSION_COOKIE_PATH</span></tt>. The default of <tt class="literal"><span class="pre">None</span></tt> means
+that browsers will only send session cookies with requests for URIs
+under the URI that originally resulted in the session cookie being sent.
+See session-mgmt.txt and RFCs 2109 and 2965.</p>
+<p>If you previously set <tt class="literal"><span class="pre">COOKIE_NAME</span></tt>, change it to
+<tt class="literal"><span class="pre">SESSION_COOKIE_NAME</span></tt>.</p>
+<p>$Id: upgrading.txt 23961 2004-04-12 15:51:38Z nascheme $</p>
+</div>
+</div>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/upgrading.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/upgrading.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/upgrading.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/upgrading.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,256 @@
+Upgrading code from older versions of Quixote
+=============================================
+
+This document lists backward-incompatible changes in Quixote, and
+explains how to update application code to work with the newer
+version.
+
+Changes from 0.6.1 to 1.0
+-------------------------
+
+Sessions
+********
+
+A leading underscore was removed from the ``Session`` attributes
+``__remote_address``, ``__creation_time``, and ``__access_time``. If
+you have pickled ``Session`` objects you will need to upgrade them
+somehow. Our preferred method is to write a script that unpickles each
+object, renames the attributes and then re-pickles it.
+
+
+
+Changes from 0.6 to 0.6.1
+-------------------------
+
+``_q_exception_handler`` now called if exception while traversing
+*****************************************************************
+
+``_q_exception_handler`` hooks will now be called if an exception is
+raised during the traversal process. Quixote 0.6 had a bug that caused
+``_q_exception_handler`` hooks to only be called if an exception was
+raised after the traversal completed.
+
+
+
+Changes from 0.5 to 0.6
+-----------------------
+
+``_q_getname`` renamed to ``_q_lookup``
+***************************************
+
+The ``_q_getname`` special function was renamed to ``_q_lookup``,
+because that name gives a clearer impression of the function's
+purpose. In 0.6, ``_q_getname`` still works but will trigger a
+warning.
+
+
+Form Framework Changes
+**********************
+
+The ``quixote.form.form`` module was changed from a .ptl file to a .py
+file. You should delete or move the existing ``quixote/`` directory
+in ``site-packages`` before running ``setup.py``, or at least delete
+the old ``form.ptl`` and ``form.ptlc`` files.
+
+The widget and form classes in the ``quixote.form`` package now return
+``htmltext`` instances. Applications that use forms and widgets will
+likely have to be changed to use the ``[html]`` template type to avoid
+over-escaping of HTML special characters.
+
+Also, the constructor arguments to ``SelectWidget`` and its subclasses have
+changed. This only affects applications that use the form framework
+located in the ``quixote.form`` package.
+
+In Quixote 0.5, the ``SelectWidget`` constructor had this signature::
+
+ def __init__ (self, name, value=None,
+ allowed_values=None,
+ descriptions=None,
+ size=None,
+ sort=0):
+
+``allowed_values`` was the list of objects that the user could choose,
+and ``descriptions`` was a list of strings that would actually be
+shown to the user in the generated HTML.
+
+In Quixote 0.6, the signature has changed slightly::
+
+ def __init__ (self, name, value=None,
+ allowed_values=None,
+ descriptions=None,
+ options=None,
+ size=None,
+ sort=0):
+
+The ``quote`` argument is gone, and the ``options`` argument has been
+added. If an ``options`` argument is provided, ``allowed_values``
+and ``descriptions`` must not be supplied.
+
+The ``options`` argument, if present, must be a list of tuples with
+1,2, or 3 elements, of the form ``(value:any, description:any,
+key:string)``.
+
+ * ``value`` is the object that will be returned if the user chooses
+ this item, and must always be supplied.
+
+ * ``description`` is a string or htmltext instance which will be
+ shown to the user in the generated HTML. It will be passed
+ through the htmlescape() functions, so for an ordinary string
+ special characters such as '&' will be converted to '&'.
+ htmltext instances will be left as they are.
+
+ * If supplied, ``key`` will be used in the value attribute
+ of the option element (``<option value="...">``).
+ If not supplied, keys will be generated; ``value`` is checked for a
+ ``_p_oid`` attribute and if present, that string is used;
+ otherwise the description is used.
+
+In the common case, most applications won't have to change anything,
+though the ordering of selection items may change due to the
+difference in how keys are generated.
+
+
+File Upload Changes
+*******************
+
+Quixote 0.6 introduces new support for HTTP upload requests. Any HTTP
+request with a Content-Type of "multipart/form-data" -- which is
+generally only used for uploads -- is now represented by
+HTTPUploadRequest, a subclass of HTTPRequest, and the uploaded files
+themselves are represented by Upload objects.
+
+Whenever an HTTP request has a Content-Type of "multipart/form-data",
+an instance of HTTPUploadRequest is created instead of HTTPRequest.
+Some of the fields in the request are presumably uploaded files and
+might be quite large, so HTTPUploadRequest will read all of the fields
+supplied in the request body and write them out to temporary files;
+the temporary files are written in the directory specified by the
+UPLOAD_DIR configuration variable.
+
+Once the temporary files have been written, the HTTPUploadRequest
+object is passed to a function or PTL template, just like an ordinary
+request. The difference between HTTPRequest and HTTPUploadRequest
+is that all of the form variables are represented as Upload objects.
+Upload objects have three attributes:
+
+``orig_filename``
+ the filename supplied by the browser.
+``base_filename``
+ a stripped-down version of orig_filename with unsafe characters removed.
+ This could be used when writing uploaded data to a permanent location.
+``tmp_filename``
+ the path of the temporary file containing the uploaded data for this field.
+
+Consult upload.txt for more information about handling file uploads.
+
+
+Refactored `Publisher` Class
+****************************
+
+Various methods in the `Publisher` class were rearranged. If your
+application subclasses Publisher, you may need to change your code
+accordingly.
+
+ * ``parse_request()`` no longer creates the HTTPRequest object;
+ instead a new method, ``create_request()``, handles this,
+ and can be overridden as required.
+
+ As a result, the method signature has changed from
+ ``parse_request(stdin, env)`` to ``parse_request(request)``.
+
+ * The ``Publisher.publish()`` method now catches exceptions raised
+ by ``parse_request()``.
+
+
+Changes from 0.4 to 0.5
+-----------------------
+
+Session Management Changes
+**************************
+
+The Quixote session management interface underwent lots of change and
+cleanup with Quixote 0.5. It was previously undocumented (apart from
+docstrings in the code), so we thought that this was a good opportunity
+to clean up the interface. Nevertheless, those brave souls who got
+session management working just by reading the code are in for a bit of
+suffering; this brief note should help clarify things. The definitive
+documentation for session management is session-mgmt.txt -- you should
+start there.
+
+
+Attribute renamings and pickled objects
++++++++++++++++++++++++++++++++++++++++
+
+Most attributes of the standard Session class were made private in order
+to reduce collisions with subclasses. The downside is that pickled
+Session objects will break. You might want to (temporarily) modify
+session.py and add this method to Session::
+
+ def __setstate__ (self, dict):
+ # Update for attribute renamings made in rev. 1.51.2.3
+ # (between Quixote 0.4.7 and 0.5).
+ self.__dict__.update(dict)
+ if hasattr(self, 'remote_address'):
+ self.__remote_address = self.remote_address
+ del self.remote_address
+ if hasattr(self, 'creation_time'):
+ self.__creation_time = self.creation_time
+ del self.creation_time
+ if hasattr(self, 'access_time'):
+ self.__access_time = self.access_time
+ del self.access_time
+ if hasattr(self, 'form_tokens'):
+ self._form_tokens = self.form_tokens
+ del self.form_tokens
+
+However, if your sessions were pickled via ZODB, this may not work. (It
+didn't work for us.) In that case, you'll have to add something like
+this to your class that inherits from both ZODB's Persistent and
+Quixote's Session::
+
+ def __setstate__ (self, dict):
+ # Blechhh! This doesn't work if I put it in Quixote's
+ # session.py, so I have to second-guess how Python
+ # treats "__" attribute names.
+ self.__dict__.update(dict)
+ if hasattr(self, 'remote_address'):
+ self._Session__remote_address = self.remote_address
+ del self.remote_address
+ if hasattr(self, 'creation_time'):
+ self._Session__creation_time = self.creation_time
+ del self.creation_time
+ if hasattr(self, 'access_time'):
+ self._Session__access_time = self.access_time
+ del self.access_time
+ if hasattr(self, 'form_tokens'):
+ self._form_tokens = self.form_tokens
+ del self.form_tokens
+
+It's not pretty, but it worked for us.
+
+
+Cookie domains and paths
+++++++++++++++++++++++++
+
+The session cookie config variables -- ``COOKIE_NAME``,
+``COOKIE_DOMAIN``, and ``COOKIE_PATH`` -- have been renamed to
+``SESSION_COOKIE_*`` for clarity.
+
+If you previously set the config variable ``COOKIE_DOMAIN`` to the name
+of your server, this is most likely no longer necessary -- it's now fine
+to leave ``SESSION_COOKIE_DOMAIN`` unset (ie. ``None``), which
+ultimately means browsers will only include the session cookie in
+requests to the same server that sent it to them in the first place.
+
+If you previously set ``COOKIE_PATH``, then you should probably preserve
+your setting as ``SESSION_COOKIE_PATH``. The default of ``None`` means
+that browsers will only send session cookies with requests for URIs
+under the URI that originally resulted in the session cookie being sent.
+See session-mgmt.txt and RFCs 2109 and 2965.
+
+If you previously set ``COOKIE_NAME``, change it to
+``SESSION_COOKIE_NAME``.
+
+
+$Id: upgrading.txt 23961 2004-04-12 15:51:38Z nascheme $
+
Added: packages/quixote1/branches/upstream/current/doc/upload.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/upload.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/upload.html (added)
+++ packages/quixote1/branches/upstream/current/doc/upload.html Mon May 8 19:16:16 2006
@@ -1,0 +1,163 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>HTTP Upload with Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="http-upload-with-quixote">
+<h1 class="title">HTTP Upload with Quixote</h1>
+<p>Starting with Quixote 0.5.1, Quixote has a new mechanism for handling
+HTTP upload requests. The bad news is that Quixote applications that
+already handle file uploads will have to change; the good news is that
+the new way is much simpler, saner, and more efficient.</p>
+<p>As (vaguely) specified by RFC 1867, HTTP upload requests are implemented
+by transmitting requests with a Content-Type header of
+<tt class="literal"><span class="pre">multipart/form-data</span></tt>. (Normal HTTP form-processing requests have a
+Content-Type of <tt class="literal"><span class="pre">application/x-www-form-urlencoded</span></tt>.) Since this type
+of request is generally only used for file uploads, Quixote 0.5.1
+introduced a new class for dealing with it: HTTPUploadRequest, a
+subclass of HTTPRequest.</p>
+<div class="section" id="upload-form">
+<h1><a name="upload-form">Upload Form</a></h1>
+<p>Here's how it works: first, you create a form that will be encoded
+according to RFC 1867, ie. with <tt class="literal"><span class="pre">multipart/form-data</span></tt>. You can put
+any ordinary form elements there, but for a file upload to take place,
+you need to supply at least one <tt class="literal"><span class="pre">file</span></tt> form element. Here's an
+example:</p>
+<pre class="literal-block">
+def upload_form [html] (request):
+ '''
+ <form enctype="multipart/form-data"
+ method="POST"
+ action="receive">
+ Your name:<br>
+ <input type="text" name="name"><br>
+ File to upload:<br>
+ <input type="file" name="upload"><br>
+ <input type="submit" value="Upload">
+ </form>
+ '''
+</pre>
+<p>(You can use Quixote's widget classes to construct the non-<tt class="literal"><span class="pre">file</span></tt> form
+elements, but the Form class currently doesn't know about the
+<tt class="literal"><span class="pre">enctype</span></tt> attribute, so it's not much use here. Also, you can supply
+multiple <tt class="literal"><span class="pre">file</span></tt> widgets to upload multiple files simultaneously.)</p>
+<p>The user fills out this form as usual; most browsers let the user either
+enter a filename or select a file from a dialog box. But when the form
+is submitted, the browser creates an HTTP request that is different from
+other HTTP requests in two ways:</p>
+<ul class="simple">
+<li>it's encoded according to RFC 1867, i.e. as a MIME message where each
+sub-part is one form variable (this is irrelevant to you -- Quixote's
+HTTPUploadRequest takes care of the details)</li>
+<li>it's arbitrarily large -- even for very large and complicated HTML
+forms, the HTTP request is usually no more than a few hundred bytes.
+With file upload, the uploaded file is included right in the request,
+so the HTTP request is as large as the upload, plus a bit of overhead.</li>
+</ul>
+</div>
+<div class="section" id="how-quixote-handles-the-upload-request">
+<h1><a name="how-quixote-handles-the-upload-request">How Quixote Handles the Upload Request</a></h1>
+<p>When Quixote sees an HTTP request with a Content-Type of
+<tt class="literal"><span class="pre">multipart/form-data</span></tt>, it creates an HTTPUploadRequest object instead
+of the usual HTTPRequest. (This happens even if there's not an uploaded
+file in the request -- Quixote doesn't know this when the request object
+is created, and <tt class="literal"><span class="pre">multipart/form-data</span></tt> requests are oddballs that are
+better handled by a completely separate class, whether they actually
+include an upload or not.) This is the <tt class="literal"><span class="pre">request</span></tt> object that will be
+passed to your form-handling function or template, eg.</p>
+<pre class="literal-block">
+def receive [html] (request):
+ print request
+</pre>
+<p>should print an HTTPUploadRequest object to the debug log, assuming that
+<tt class="literal"><span class="pre">receive()</span></tt> is being invoked as a result of the above form.</p>
+<p>However, since upload requests can be arbitrarily large, it might be
+some time before Quixote actually calls <tt class="literal"><span class="pre">receive()</span></tt>. And Quixote has
+to interact with the real world in a number of ways in order to parse
+the request, so there are a number of opportunities for things to go
+wrong. In particular, whenever Quixote sees a file upload variable in
+the request, it:</p>
+<ul class="simple">
+<li>checks that the <tt class="literal"><span class="pre">UPLOAD_DIR</span></tt> configuration variable was defined.
+If not, it raises ConfigError.</li>
+<li>ensures that <tt class="literal"><span class="pre">UPLOAD_DIR</span></tt> exists, and creates it if not. (It's
+created with the mode specified by <tt class="literal"><span class="pre">UPLOAD_DIR_MODE</span></tt>, which defaults
+to <tt class="literal"><span class="pre">0755</span></tt>. I have no idea what this should be on Windows.) If this
+fails, your application will presumably crash with an OSError.</li>
+<li>opens a temporary file in <tt class="literal"><span class="pre">UPLOAD_DIR</span></tt> and write the contents
+of the uploaded file to it. Either opening or writing could fail
+with IOError.</li>
+</ul>
+<p>Furthermore, if there are any problems parsing the request body -- which
+could be the result of either a broken/malicious client or of a bug in
+HTTPUploadRequest -- then Quixote raises RequestError.</p>
+<p>These errors are treated the same as any other exception Quixote
+encounters: RequestError (which is a subclass of PublishError) is
+transformed into a "400 Invalid request" HTTP response, and the others
+become some form of "internal server error" response, with traceback
+optionally shown to the user, emailed to you, etc.</p>
+</div>
+<div class="section" id="processing-the-upload-request">
+<h1><a name="processing-the-upload-request">Processing the Upload Request</a></h1>
+<p>If Quixote successfully parses the upload request, then it passes a
+<tt class="literal"><span class="pre">request</span></tt> object to some function or PTL template that you supply, as
+usual. Of course, that <tt class="literal"><span class="pre">request</span></tt> object will be an instance of
+HTTPUploadRequest rather than HTTPRequest, but that doesn't make much
+difference to you. You can access form variables, cookies, etc. just as
+you usually do. The only difference is that form variables associated
+with uploaded files are represented as Upload objects. Here's an
+example that goes with the above upload form:</p>
+<pre class="literal-block">
+def receive [html] (request):
+ name = request.form.get("name")
+ if name:
+ "<p>Thanks, %s!</p>\n" % name
+
+ upload = request.form.get("upload")
+ size = os.stat(upload.tmp_filename)[stat.ST_SIZE]
+ if not upload.base_filename or size == 0:
+ "<p>You appear not to have uploaded anything.</p>\n"
+ else:
+ '''\
+ <p>You just uploaded <code>%s</code> (%d bytes)<br>
+ which is temporarily stored in <code>%s</code>.</p>
+ ''' % (upload.base_filename, size, upload.tmp_filename)
+</pre>
+<p>Upload objects provide three attributes of interest:</p>
+<dl>
+<dt><tt class="literal"><span class="pre">orig_filename</span></tt></dt>
+<dd>the complete filename supplied by the user-agent in the request that
+uploaded this file. Depending on the browser, this might have the
+complete path of the original file on the client system, in the client
+system's syntax -- eg. <tt class="literal"><span class="pre">C:\foo\bar\upload_this</span></tt> or
+<tt class="literal"><span class="pre">/foo/bar/upload_this</span></tt> or <tt class="literal"><span class="pre">foo:bar:upload_this</span></tt>.</dd>
+<dt><tt class="literal"><span class="pre">base_filename</span></tt></dt>
+<dd>the base component of orig_filename, shorn of MS-DOS, Mac OS, and Unix
+path components and with "unsafe" characters replaced with
+underscores. (The "safe" characters are <tt class="literal"><span class="pre">A-Z</span></tt>, <tt class="literal"><span class="pre">a-z</span></tt>, <tt class="literal"><span class="pre">0-9</span></tt>,
+<tt class="literal"><span class="pre">-</span> <span class="pre">@</span> <span class="pre">&</span> <span class="pre">+</span> <span class="pre">=</span> <span class="pre">_</span> <span class="pre">.</span></tt>, and space. Thus, this is "safe" in the sense that
+it's OK to create a filename with any of those characters on Unix, Mac
+OS, and Windows, <em>not</em> in the sense that you can use the filename in
+an HTML document without quoting it!)</dd>
+<dt><tt class="literal"><span class="pre">tmp_filename</span></tt></dt>
+<dd>where you'll actually find the file on the current system</dd>
+</dl>
+<p>Thus, you could open the file directly using <tt class="literal"><span class="pre">tmp_filename</span></tt>, or move
+it to a permanent location using <tt class="literal"><span class="pre">tmp_filename</span></tt> and <tt class="literal"><span class="pre">base_filename</span></tt>
+-- whatever.</p>
+</div>
+<div class="section" id="upload-demo">
+<h1><a name="upload-demo">Upload Demo</a></h1>
+<p>The above upload form and form-processor are available, in a slightly
+different form, in <tt class="literal"><span class="pre">demo/upload.cgi</span></tt>. Install that file to your usual
+<tt class="literal"><span class="pre">cgi-bin</span></tt> directory and play around.</p>
+<p>$Id: upload.txt 20217 2003-01-16 20:51:53Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/upload.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/upload.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/upload.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/upload.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,168 @@
+HTTP Upload with Quixote
+========================
+
+Starting with Quixote 0.5.1, Quixote has a new mechanism for handling
+HTTP upload requests. The bad news is that Quixote applications that
+already handle file uploads will have to change; the good news is that
+the new way is much simpler, saner, and more efficient.
+
+As (vaguely) specified by RFC 1867, HTTP upload requests are implemented
+by transmitting requests with a Content-Type header of
+``multipart/form-data``. (Normal HTTP form-processing requests have a
+Content-Type of ``application/x-www-form-urlencoded``.) Since this type
+of request is generally only used for file uploads, Quixote 0.5.1
+introduced a new class for dealing with it: HTTPUploadRequest, a
+subclass of HTTPRequest.
+
+
+Upload Form
+-----------
+
+Here's how it works: first, you create a form that will be encoded
+according to RFC 1867, ie. with ``multipart/form-data``. You can put
+any ordinary form elements there, but for a file upload to take place,
+you need to supply at least one ``file`` form element. Here's an
+example::
+
+ def upload_form [html] (request):
+ '''
+ <form enctype="multipart/form-data"
+ method="POST"
+ action="receive">
+ Your name:<br>
+ <input type="text" name="name"><br>
+ File to upload:<br>
+ <input type="file" name="upload"><br>
+ <input type="submit" value="Upload">
+ </form>
+ '''
+
+(You can use Quixote's widget classes to construct the non-``file`` form
+elements, but the Form class currently doesn't know about the
+``enctype`` attribute, so it's not much use here. Also, you can supply
+multiple ``file`` widgets to upload multiple files simultaneously.)
+
+The user fills out this form as usual; most browsers let the user either
+enter a filename or select a file from a dialog box. But when the form
+is submitted, the browser creates an HTTP request that is different from
+other HTTP requests in two ways:
+
+* it's encoded according to RFC 1867, i.e. as a MIME message where each
+ sub-part is one form variable (this is irrelevant to you -- Quixote's
+ HTTPUploadRequest takes care of the details)
+
+* it's arbitrarily large -- even for very large and complicated HTML
+ forms, the HTTP request is usually no more than a few hundred bytes.
+ With file upload, the uploaded file is included right in the request,
+ so the HTTP request is as large as the upload, plus a bit of overhead.
+
+
+How Quixote Handles the Upload Request
+--------------------------------------
+
+When Quixote sees an HTTP request with a Content-Type of
+``multipart/form-data``, it creates an HTTPUploadRequest object instead
+of the usual HTTPRequest. (This happens even if there's not an uploaded
+file in the request -- Quixote doesn't know this when the request object
+is created, and ``multipart/form-data`` requests are oddballs that are
+better handled by a completely separate class, whether they actually
+include an upload or not.) This is the ``request`` object that will be
+passed to your form-handling function or template, eg. ::
+
+ def receive [html] (request):
+ print request
+
+should print an HTTPUploadRequest object to the debug log, assuming that
+``receive()`` is being invoked as a result of the above form.
+
+However, since upload requests can be arbitrarily large, it might be
+some time before Quixote actually calls ``receive()``. And Quixote has
+to interact with the real world in a number of ways in order to parse
+the request, so there are a number of opportunities for things to go
+wrong. In particular, whenever Quixote sees a file upload variable in
+the request, it:
+
+* checks that the ``UPLOAD_DIR`` configuration variable was defined.
+ If not, it raises ConfigError.
+
+* ensures that ``UPLOAD_DIR`` exists, and creates it if not. (It's
+ created with the mode specified by ``UPLOAD_DIR_MODE``, which defaults
+ to ``0755``. I have no idea what this should be on Windows.) If this
+ fails, your application will presumably crash with an OSError.
+
+* opens a temporary file in ``UPLOAD_DIR`` and write the contents
+ of the uploaded file to it. Either opening or writing could fail
+ with IOError.
+
+Furthermore, if there are any problems parsing the request body -- which
+could be the result of either a broken/malicious client or of a bug in
+HTTPUploadRequest -- then Quixote raises RequestError.
+
+These errors are treated the same as any other exception Quixote
+encounters: RequestError (which is a subclass of PublishError) is
+transformed into a "400 Invalid request" HTTP response, and the others
+become some form of "internal server error" response, with traceback
+optionally shown to the user, emailed to you, etc.
+
+
+Processing the Upload Request
+-----------------------------
+
+If Quixote successfully parses the upload request, then it passes a
+``request`` object to some function or PTL template that you supply, as
+usual. Of course, that ``request`` object will be an instance of
+HTTPUploadRequest rather than HTTPRequest, but that doesn't make much
+difference to you. You can access form variables, cookies, etc. just as
+you usually do. The only difference is that form variables associated
+with uploaded files are represented as Upload objects. Here's an
+example that goes with the above upload form::
+
+ def receive [html] (request):
+ name = request.form.get("name")
+ if name:
+ "<p>Thanks, %s!</p>\n" % name
+
+ upload = request.form.get("upload")
+ size = os.stat(upload.tmp_filename)[stat.ST_SIZE]
+ if not upload.base_filename or size == 0:
+ "<p>You appear not to have uploaded anything.</p>\n"
+ else:
+ '''\
+ <p>You just uploaded <code>%s</code> (%d bytes)<br>
+ which is temporarily stored in <code>%s</code>.</p>
+ ''' % (upload.base_filename, size, upload.tmp_filename)
+
+Upload objects provide three attributes of interest:
+
+``orig_filename``
+ the complete filename supplied by the user-agent in the request that
+ uploaded this file. Depending on the browser, this might have the
+ complete path of the original file on the client system, in the client
+ system's syntax -- eg. ``C:\foo\bar\upload_this`` or
+ ``/foo/bar/upload_this`` or ``foo:bar:upload_this``.
+
+``base_filename``
+ the base component of orig_filename, shorn of MS-DOS, Mac OS, and Unix
+ path components and with "unsafe" characters replaced with
+ underscores. (The "safe" characters are ``A-Z``, ``a-z``, ``0-9``,
+ ``- @ & + = _ .``, and space. Thus, this is "safe" in the sense that
+ it's OK to create a filename with any of those characters on Unix, Mac
+ OS, and Windows, *not* in the sense that you can use the filename in
+ an HTML document without quoting it!)
+
+``tmp_filename``
+ where you'll actually find the file on the current system
+
+Thus, you could open the file directly using ``tmp_filename``, or move
+it to a permanent location using ``tmp_filename`` and ``base_filename``
+-- whatever.
+
+
+Upload Demo
+-----------
+
+The above upload form and form-processor are available, in a slightly
+different form, in ``demo/upload.cgi``. Install that file to your usual
+``cgi-bin`` directory and play around.
+
+$Id: upload.txt 20217 2003-01-16 20:51:53Z akuchlin $
Added: packages/quixote1/branches/upstream/current/doc/web-server.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/web-server.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/web-server.html (added)
+++ packages/quixote1/branches/upstream/current/doc/web-server.html Mon May 8 19:16:16 2006
@@ -1,0 +1,240 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Web Server Configuration for Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="web-server-configuration-for-quixote">
+<h1 class="title">Web Server Configuration for Quixote</h1>
+<p>For a simple Quixote installation, there are two things you have to get
+right:</p>
+<ul class="simple">
+<li>installation of the Quixote modules to Python's library (the
+trick here is that the <tt class="literal"><span class="pre">quixote</span></tt> package must be visible to the user
+that CGI scripts run as, not necessarily to you as an interactive
+command-line user)</li>
+<li>configuration of your web server to run Quixote driver scripts
+(such as demo.cgi)</li>
+</ul>
+<p>This document is concerned with the second of these.</p>
+<div class="section" id="which-web-servers">
+<h1><a name="which-web-servers">Which web servers?</a></h1>
+<p>We are only familiar with Apache, and we develop Quixote for use under
+Apache. However, Quixote doesn't rely on any Apache-specific tricks;
+if you can execute CGI scripts, then you can run Quixote applications
+(although they'll run a lot faster with mod_scgi or FastCGI). If you
+can redirect arbitrary URLs to a CGI script and preserve parts of the
+URL as an add-on to the script name (with <tt class="literal"><span class="pre">PATH_INFO</span></tt>), then you can
+run Quixote applications in the ideal manner, ie. with superfluous
+implementation details hidden from the user.</p>
+</div>
+<div class="section" id="which-operating-systems">
+<h1><a name="which-operating-systems">Which operating systems?</a></h1>
+<p>We are mainly familiar with Unix, and develop and deploy Quixote under
+Linux. However, we've had several reports of people using Quixote under
+Windows, more-or-less successfully. There are still a few Unix-isms in
+the code, but they are being rooted out in favour of portability.</p>
+<p>Remember that your system is only as secure as its weakest link.
+Quixote can't help you write secure web applications on an inherently
+insecure operating system.</p>
+</div>
+<div class="section" id="basic-cgi-configuration">
+<h1><a name="basic-cgi-configuration">Basic CGI configuration</a></h1>
+<p>Throughout this document, I'm going to assume that:</p>
+<ul class="simple">
+<li>CGI scripts live in the <tt class="literal"><span class="pre">/www/cgi-bin</span></tt> directory of your web server,
+and have the extension <tt class="literal"><span class="pre">.cgi</span></tt></li>
+<li>HTTP requests for <tt class="literal"><span class="pre">/cgi-bin/foo.cgi</span></tt> will result in the execution
+of <tt class="literal"><span class="pre">/www/cgi-bin/foo.cgi</span></tt> (for various values of <tt class="literal"><span class="pre">foo</span></tt>)</li>
+<li>if the web server is instructed to serve an executable file
+<tt class="literal"><span class="pre">bar.cgi</span></tt>, the file is treated as a CGI script</li>
+</ul>
+<p>With Apache, these configuration directives will do the trick:</p>
+<pre class="literal-block">
+AddHandler cgi-script .cgi
+ScriptAlias /cgi-bin/ /www/cgi-bin/
+</pre>
+<p>Consult the Apache documentation for other ways of configuring CGI
+script execution.</p>
+<p>For other web servers, consult your server's documentation.</p>
+</div>
+<div class="section" id="installing-driver-scripts">
+<h1><a name="installing-driver-scripts">Installing driver scripts</a></h1>
+<p>Given the above configuration, installing a Quixote driver script is the
+same as installing any other CGI script: copy it to <tt class="literal"><span class="pre">/www/cgi-bin</span></tt> (or
+whatever). To install the Quixote demo's driver script:</p>
+<pre class="literal-block">
+cp -p demo/demo.cgi /www/cgi-bin
+</pre>
+<p>(The <tt class="literal"><span class="pre">-p</span></tt> option ensures that <tt class="literal"><span class="pre">cp</span></tt> preserves the file mode, so that
+it remains executable.)</p>
+</div>
+<div class="section" id="url-rewriting">
+<h1><a name="url-rewriting">URL rewriting</a></h1>
+<p>With the above configuration, users need to use URLs like</p>
+<pre class="literal-block">
+http://www.example.com/cgi-bin/demo.cgi
+</pre>
+<p>to access the Quixote demo (or other Quixote applications installed in
+the same way). This works, but it's ugly and unnecessarily exposes
+implementation details.</p>
+<p>In our view, it's preferable to give each Quixote application its own
+chunk of URL-space -- a "virtual directory" if you like. For example,
+you might want</p>
+<pre class="literal-block">
+http://www.example.com/qdemo
+</pre>
+<p>to handle the Quixote demo.</p>
+<p>With Apache, this is quite easy, as long as mod_rewrite is compiled,
+loaded, and enabled. (Building and loading Apache modules is beyond the
+scope of this document; consult the Apache documentation.)</p>
+<p>To enable the rewrite engine, use the</p>
+<pre class="literal-block">
+RewriteEngine on
+</pre>
+<p>directive. If you have virtual hosts, make sure to repeat this for each
+<tt class="literal"><span class="pre"><VirtualHost></span></tt> section of your config file.</p>
+<p>The rewrite rule to use in this case is</p>
+<pre class="literal-block">
+RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+</pre>
+<p>This is <em>not</em> a redirect; this is all handled with one HTTP
+request/response cycle, and the user never sees <tt class="literal"><span class="pre">/cgi-bin/demo.cgi</span></tt> in
+a URL.</p>
+<p>Note that requests for <tt class="literal"><span class="pre">/qdemo/</span></tt> and <tt class="literal"><span class="pre">/qdemo</span></tt> are <em>not</em> the same; in
+particular, with the above rewrite rule, the former will succeed and the
+latter will not. (Look at the regex again if you don't believe me:
+<tt class="literal"><span class="pre">/qdemo</span></tt> doesn't match the regex, so <tt class="literal"><span class="pre">demo.cgi</span></tt> is never invoked.)</p>
+<p>The solution for <tt class="literal"><span class="pre">/qdemo</span></tt> is the same as if it corresponded to a
+directory in your document tree: redirect it to <tt class="literal"><span class="pre">/qdemo/</span></tt>. Apache
+(and, presumably, other web servers) does this automatically for "real"
+directories; however, <tt class="literal"><span class="pre">/qdemo/</span></tt> is just a directory-like chunk of
+URL-space, so either you or Quixote have to take care of the redirect.</p>
+<p>It's almost certainly faster for you to take care of it in the web
+server's configuration. With Apache, simply insert this directive
+<em>before</em> the above rewrite rule:</p>
+<pre class="literal-block">
+RewriteRule ^/qdemo$ /qdemo/ [redirect=permanent]
+</pre>
+<p>If, for some reason, you are unwilling or unable to instruct your web
+server to perform this redirection, Quixote will do it for you.
+However, you have to make sure that the <tt class="literal"><span class="pre">/qdemo</span></tt> URL is handled by
+Quixote. Change the rewrite rule to:</p>
+<pre class="literal-block">
+RewriteRule ^/qdemo(/.*)?$ /www/cgi-bin/demo.cgi$1 [last]
+</pre>
+<p>Now a request for <tt class="literal"><span class="pre">/qdemo</span></tt> will be handled by Quixote, and it will
+generate a redirect to <tt class="literal"><span class="pre">/qdemo/</span></tt>. If you're using a CGI driver
+script, this will be painfully slow, but it will work.</p>
+<p>For redirecting and rewriting URLs with other web servers, consult your
+server's documentation.</p>
+</div>
+<div class="section" id="long-running-processes">
+<h1><a name="long-running-processes">Long-running processes</a></h1>
+<p>For serious web applications, CGI is unacceptably slow. For a CGI-based
+Quixote application, you have to start a Python interpreter, load the
+Quixote modules, and load your application's modules before you can
+start working. For sophisticated, database-backed applications, you'll
+probably have to open a new database connection as well for every hit.</p>
+<p>Small wonder so many high-performance alternatives to CGI exist. (The
+main advantages of CGI are that it is widely supported and easy to
+develop with. Even for large Quixote applications, running in CGI mode
+is nice in development because you don't have to kill a long-running
+driver script every time the code changes.) Currently, Quixote supports
+three such alternatives: mod_scgi, FastCGI, and mod_python.</p>
+</div>
+<div class="section" id="mod-scgi-configuration">
+<h1><a name="mod-scgi-configuration">mod_scgi configuration</a></h1>
+<p>SCGI is a CGI replacement written by Neil Schemenauer, one of
+Quixote's developers, and is similar to FastCGI but is designed to be
+easier to implement. mod_scgi simply forwards requests to an
+already-running SCGI server on a different TCP port, and doesn't try
+to start or stop processes, leaving that up to the SCGI server.</p>
+<p>The SCGI code is available from <a class="reference" href="http://www.mems-exchange.org/software/scgi/">http://www.mems-exchange.org/software/scgi/</a> .
+It contains a Python module, scgi.quixote_handler, that will publish a
+Quixote-based application via SCGI. Here's an example script to publish
+an application:</p>
+<pre class="literal-block">
+#!/usr/bin/python
+from scgi.quixote_handler import QuixoteHandler, main
+from quixote.publisher import SessionPublisher
+
+class MyAppHandler(QuixoteHandler):
+ publisher_class = SessionPublisher
+ root_namespace = "myapp.ui"
+ prefix = ""
+
+if __name__ == '__main__':
+ main(MyAppHandler)
+</pre>
+<p>When run, this script will take various command-line arguments. <tt class="literal"><span class="pre">-p</span>
+<span class="pre"><port></span></tt> specifies the TCP port that the SCGI server will listen to.
+The following Apache directive will direct requests to an SCGI server
+running on port 3001:</p>
+<pre class="literal-block">
+<Location />
+ SCGIServer 127.0.0.1 3001
+ SCGIHandler On
+</Location>
+</pre>
+</div>
+<div class="section" id="fastcgi-configuration">
+<h1><a name="fastcgi-configuration">FastCGI configuration</a></h1>
+<p>If your web server supports FastCGI, you can significantly speed up your
+Quixote applications with a simple change to your configuration. You
+don't have to change your code at all (unless it makes assumptions about
+how many requests are handled by each process). (See
+<a class="reference" href="http://www.fastcgi.com/">http://www.fastcgi.com/</a> for more information on FastCGI.)</p>
+<p>To use FastCGI with Apache, you'll need to download mod_fastcgi from
+<a class="reference" href="http://www.fastcgi.com/">http://www.fastcgi.com/</a> and add it to your Apache installation.</p>
+<p>Configuring a FastCGI driver script is best done after reading the fine
+documentation for mod_fastcgi at
+<a class="reference" href="http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html">http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html</a></p>
+<p>However, if you just want to try it with the Quixote demo to see if it
+works, add this directive to your Apache configuration:</p>
+<pre class="literal-block">
+AddHandler fastcgi-script .fcgi
+</pre>
+<p>and rename demo.cgi to demo.fcgi. If you're using a URL rewrite to map
+requests for (eg.) <tt class="literal"><span class="pre">/qdemo</span></tt> to <tt class="literal"><span class="pre">/www/cgi-bin/demo.cgi</span></tt>, be sure to
+change the rewrite -- it should now point to <tt class="literal"><span class="pre">/www/cgi-bin/demo.fcgi</span></tt>.</p>
+<p>After the first access to <tt class="literal"><span class="pre">demo.fcgi</span></tt> (or <tt class="literal"><span class="pre">/qdemo/</span></tt> with the
+modified rewrite rule), the demo should be noticeably faster. You
+should also see a <tt class="literal"><span class="pre">demo.fcgi</span></tt> process running if you do <tt class="literal"><span class="pre">ps</span> <span class="pre">-le</span></tt>
+(<tt class="literal"><span class="pre">ps</span> <span class="pre">-aux</span></tt> on BSD-ish systems, or maybe <tt class="literal"><span class="pre">ps</span> <span class="pre">aux</span></tt>). (On my 800 MHz
+Athlon machine, there are slight but perceptible delays navigating the
+Quixote demo in CGI mode. In FastCGI mode, the delay between pages is
+no longer perceptible -- navigation is instantaneous.) The larger your
+application is, the more code it loads, and the more work it does at
+startup, the bigger a win FastCGI will be for you.</p>
+</div>
+<div class="section" id="mod-python-configuration">
+<h1><a name="mod-python-configuration">mod_python configuration</a></h1>
+<p>mod_python is an Apache module for embedding a Python interpreter into
+the Apache server. To use mod_python as the interface layer between
+Apache and Quixote, add something like this to your httpd.conf:</p>
+<pre class="literal-block">
+LoadModule python_module /usr/lib/apache/1.3/mod_python.so
+<LocationMatch "^/qdemo(/|$)">
+ SetHandler python-program
+ PythonHandler quixote.mod_python_handler
+ PythonOption quixote-root-namespace quixote.demo
+ PythonInterpreter quixote.demo
+ PythonDebug On
+</LocationMatch>
+</pre>
+<p>This will attach URLs starting with <tt class="literal"><span class="pre">/qdemo</span></tt> to the Quixote demo.
+When you use mod_python, there's no need for rewrite rules (because of
+the pattern in the <tt class="literal"><span class="pre">LocationMatch</span></tt> directive), and no need for a
+driver script.</p>
+<p>mod_python support was contributed to Quixote by Erno Kuusela
+<<a class="reference" href="mailto:erno@iki.fi">erno@iki.fi</a>>.</p>
+<p>$Id: web-server.txt 21999 2003-07-14 15:24:42Z nascheme $</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/web-server.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/web-server.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/web-server.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/web-server.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,267 @@
+Web Server Configuration for Quixote
+====================================
+
+For a simple Quixote installation, there are two things you have to get
+right:
+
+* installation of the Quixote modules to Python's library (the
+ trick here is that the ``quixote`` package must be visible to the user
+ that CGI scripts run as, not necessarily to you as an interactive
+ command-line user)
+
+* configuration of your web server to run Quixote driver scripts
+ (such as demo.cgi)
+
+This document is concerned with the second of these.
+
+
+Which web servers?
+------------------
+
+We are only familiar with Apache, and we develop Quixote for use under
+Apache. However, Quixote doesn't rely on any Apache-specific tricks;
+if you can execute CGI scripts, then you can run Quixote applications
+(although they'll run a lot faster with mod_scgi or FastCGI). If you
+can redirect arbitrary URLs to a CGI script and preserve parts of the
+URL as an add-on to the script name (with ``PATH_INFO``), then you can
+run Quixote applications in the ideal manner, ie. with superfluous
+implementation details hidden from the user.
+
+
+Which operating systems?
+------------------------
+
+We are mainly familiar with Unix, and develop and deploy Quixote under
+Linux. However, we've had several reports of people using Quixote under
+Windows, more-or-less successfully. There are still a few Unix-isms in
+the code, but they are being rooted out in favour of portability.
+
+Remember that your system is only as secure as its weakest link.
+Quixote can't help you write secure web applications on an inherently
+insecure operating system.
+
+
+Basic CGI configuration
+-----------------------
+
+Throughout this document, I'm going to assume that:
+
+* CGI scripts live in the ``/www/cgi-bin`` directory of your web server,
+ and have the extension ``.cgi``
+
+* HTTP requests for ``/cgi-bin/foo.cgi`` will result in the execution
+ of ``/www/cgi-bin/foo.cgi`` (for various values of ``foo``)
+
+* if the web server is instructed to serve an executable file
+ ``bar.cgi``, the file is treated as a CGI script
+
+With Apache, these configuration directives will do the trick::
+
+ AddHandler cgi-script .cgi
+ ScriptAlias /cgi-bin/ /www/cgi-bin/
+
+Consult the Apache documentation for other ways of configuring CGI
+script execution.
+
+For other web servers, consult your server's documentation.
+
+
+Installing driver scripts
+-------------------------
+
+Given the above configuration, installing a Quixote driver script is the
+same as installing any other CGI script: copy it to ``/www/cgi-bin`` (or
+whatever). To install the Quixote demo's driver script::
+
+ cp -p demo/demo.cgi /www/cgi-bin
+
+(The ``-p`` option ensures that ``cp`` preserves the file mode, so that
+it remains executable.)
+
+
+URL rewriting
+-------------
+
+With the above configuration, users need to use URLs like ::
+
+ http://www.example.com/cgi-bin/demo.cgi
+
+to access the Quixote demo (or other Quixote applications installed in
+the same way). This works, but it's ugly and unnecessarily exposes
+implementation details.
+
+In our view, it's preferable to give each Quixote application its own
+chunk of URL-space -- a "virtual directory" if you like. For example,
+you might want ::
+
+ http://www.example.com/qdemo
+
+to handle the Quixote demo.
+
+With Apache, this is quite easy, as long as mod_rewrite is compiled,
+loaded, and enabled. (Building and loading Apache modules is beyond the
+scope of this document; consult the Apache documentation.)
+
+To enable the rewrite engine, use the ::
+
+ RewriteEngine on
+
+directive. If you have virtual hosts, make sure to repeat this for each
+``<VirtualHost>`` section of your config file.
+
+The rewrite rule to use in this case is ::
+
+ RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+
+This is *not* a redirect; this is all handled with one HTTP
+request/response cycle, and the user never sees ``/cgi-bin/demo.cgi`` in
+a URL.
+
+Note that requests for ``/qdemo/`` and ``/qdemo`` are *not* the same; in
+particular, with the above rewrite rule, the former will succeed and the
+latter will not. (Look at the regex again if you don't believe me:
+``/qdemo`` doesn't match the regex, so ``demo.cgi`` is never invoked.)
+
+The solution for ``/qdemo`` is the same as if it corresponded to a
+directory in your document tree: redirect it to ``/qdemo/``. Apache
+(and, presumably, other web servers) does this automatically for "real"
+directories; however, ``/qdemo/`` is just a directory-like chunk of
+URL-space, so either you or Quixote have to take care of the redirect.
+
+It's almost certainly faster for you to take care of it in the web
+server's configuration. With Apache, simply insert this directive
+*before* the above rewrite rule::
+
+ RewriteRule ^/qdemo$ /qdemo/ [redirect=permanent]
+
+If, for some reason, you are unwilling or unable to instruct your web
+server to perform this redirection, Quixote will do it for you.
+However, you have to make sure that the ``/qdemo`` URL is handled by
+Quixote. Change the rewrite rule to::
+
+ RewriteRule ^/qdemo(/.*)?$ /www/cgi-bin/demo.cgi$1 [last]
+
+Now a request for ``/qdemo`` will be handled by Quixote, and it will
+generate a redirect to ``/qdemo/``. If you're using a CGI driver
+script, this will be painfully slow, but it will work.
+
+For redirecting and rewriting URLs with other web servers, consult your
+server's documentation.
+
+
+Long-running processes
+----------------------
+
+For serious web applications, CGI is unacceptably slow. For a CGI-based
+Quixote application, you have to start a Python interpreter, load the
+Quixote modules, and load your application's modules before you can
+start working. For sophisticated, database-backed applications, you'll
+probably have to open a new database connection as well for every hit.
+
+Small wonder so many high-performance alternatives to CGI exist. (The
+main advantages of CGI are that it is widely supported and easy to
+develop with. Even for large Quixote applications, running in CGI mode
+is nice in development because you don't have to kill a long-running
+driver script every time the code changes.) Currently, Quixote supports
+three such alternatives: mod_scgi, FastCGI, and mod_python.
+
+
+mod_scgi configuration
+----------------------
+
+SCGI is a CGI replacement written by Neil Schemenauer, one of
+Quixote's developers, and is similar to FastCGI but is designed to be
+easier to implement. mod_scgi simply forwards requests to an
+already-running SCGI server on a different TCP port, and doesn't try
+to start or stop processes, leaving that up to the SCGI server.
+
+The SCGI code is available from http://www.mems-exchange.org/software/scgi/ .
+It contains a Python module, scgi.quixote_handler, that will publish a
+Quixote-based application via SCGI. Here's an example script to publish
+an application::
+
+ #!/usr/bin/python
+ from scgi.quixote_handler import QuixoteHandler, main
+ from quixote.publisher import SessionPublisher
+
+ class MyAppHandler(QuixoteHandler):
+ publisher_class = SessionPublisher
+ root_namespace = "myapp.ui"
+ prefix = ""
+
+ if __name__ == '__main__':
+ main(MyAppHandler)
+
+When run, this script will take various command-line arguments. ``-p
+<port>`` specifies the TCP port that the SCGI server will listen to.
+The following Apache directive will direct requests to an SCGI server
+running on port 3001::
+
+ <Location />
+ SCGIServer 127.0.0.1 3001
+ SCGIHandler On
+ </Location>
+
+
+FastCGI configuration
+---------------------
+
+If your web server supports FastCGI, you can significantly speed up your
+Quixote applications with a simple change to your configuration. You
+don't have to change your code at all (unless it makes assumptions about
+how many requests are handled by each process). (See
+http://www.fastcgi.com/ for more information on FastCGI.)
+
+To use FastCGI with Apache, you'll need to download mod_fastcgi from
+http://www.fastcgi.com/ and add it to your Apache installation.
+
+Configuring a FastCGI driver script is best done after reading the fine
+documentation for mod_fastcgi at
+http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html
+
+However, if you just want to try it with the Quixote demo to see if it
+works, add this directive to your Apache configuration::
+
+ AddHandler fastcgi-script .fcgi
+
+and rename demo.cgi to demo.fcgi. If you're using a URL rewrite to map
+requests for (eg.) ``/qdemo`` to ``/www/cgi-bin/demo.cgi``, be sure to
+change the rewrite -- it should now point to ``/www/cgi-bin/demo.fcgi``.
+
+After the first access to ``demo.fcgi`` (or ``/qdemo/`` with the
+modified rewrite rule), the demo should be noticeably faster. You
+should also see a ``demo.fcgi`` process running if you do ``ps -le``
+(``ps -aux`` on BSD-ish systems, or maybe ``ps aux``). (On my 800 MHz
+Athlon machine, there are slight but perceptible delays navigating the
+Quixote demo in CGI mode. In FastCGI mode, the delay between pages is
+no longer perceptible -- navigation is instantaneous.) The larger your
+application is, the more code it loads, and the more work it does at
+startup, the bigger a win FastCGI will be for you.
+
+
+mod_python configuration
+------------------------
+
+mod_python is an Apache module for embedding a Python interpreter into
+the Apache server. To use mod_python as the interface layer between
+Apache and Quixote, add something like this to your httpd.conf::
+
+ LoadModule python_module /usr/lib/apache/1.3/mod_python.so
+ <LocationMatch "^/qdemo(/|$)">
+ SetHandler python-program
+ PythonHandler quixote.mod_python_handler
+ PythonOption quixote-root-namespace quixote.demo
+ PythonInterpreter quixote.demo
+ PythonDebug On
+ </LocationMatch>
+
+This will attach URLs starting with ``/qdemo`` to the Quixote demo.
+When you use mod_python, there's no need for rewrite rules (because of
+the pattern in the ``LocationMatch`` directive), and no need for a
+driver script.
+
+mod_python support was contributed to Quixote by Erno Kuusela
+<erno at iki.fi>.
+
+
+$Id: web-server.txt 21999 2003-07-14 15:24:42Z nascheme $
Added: packages/quixote1/branches/upstream/current/doc/web-services.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/web-services.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/web-services.html (added)
+++ packages/quixote1/branches/upstream/current/doc/web-services.html Mon May 8 19:16:16 2006
@@ -1,0 +1,187 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Implementing Web Services with Quixote</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="implementing-web-services-with-quixote">
+<h1 class="title">Implementing Web Services with Quixote</h1>
+<p>This document will show you how to implement Web services using
+Quixote.</p>
+<div class="section" id="an-xml-rpc-service">
+<h1><a name="an-xml-rpc-service">An XML-RPC Service</a></h1>
+<p>XML-RPC is the simplest protocol commonly used to expose a Web
+service. In XML-RPC, there are a few basic data types such as
+integers, floats, strings, and dates, and a few aggregate types such
+as arrays and structs. The xmlrpclib module, part of the Python 2.2
+standard library and available separately from
+<a class="reference" href="http://www.pythonware.com/products/xmlrpc/">http://www.pythonware.com/products/xmlrpc/</a>, converts between Python's
+standard data types and the XML-RPC data types.</p>
+<table class="table" frame="border" rules="all">
+<colgroup>
+<col width="40%" />
+<col width="60%" />
+</colgroup>
+<tbody valign="top">
+<tr><td>XML-RPC Type</td>
+<td>Python Type or Class</td>
+</tr>
+<tr><td><int></td>
+<td>int</td>
+</tr>
+<tr><td><double></td>
+<td>float</td>
+</tr>
+<tr><td><string></td>
+<td>string</td>
+</tr>
+<tr><td><array></td>
+<td>list</td>
+</tr>
+<tr><td><struct></td>
+<td>dict</td>
+</tr>
+<tr><td><boolean></td>
+<td>xmlrpclib.Boolean</td>
+</tr>
+<tr><td><base64></td>
+<td>xmlrpclib.Binary</td>
+</tr>
+<tr><td><dateTime></td>
+<td>xmlrpclib.DateTime</td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="section" id="making-xml-rpc-calls">
+<h1><a name="making-xml-rpc-calls">Making XML-RPC Calls</a></h1>
+<p>Making an XML-RPC call using xmlrpclib is easy. An XML-RPC server
+lives at a particular URL, so the first step is to create an
+xmlrpclib.ServerProxy object pointing at that URL.</p>
+<pre class="literal-block">
+>>> import xmlrpclib
+>>> s = xmlrpclib.ServerProxy(
+ 'http://www.stuffeddog.com/speller/speller-rpc.cgi')
+</pre>
+<p>Now you can simply make a call to the spell-checking service offered
+by this server:</p>
+<pre class="literal-block">
+>>> s.speller.spellCheck('my speling isnt gud', {})
+[{'word': 'speling', 'suggestions': ['apeling', 'spelding',
+ 'spelling', 'sperling', 'spewing', 'spiling'], 'location': 4},
+{'word': 'isnt', 'suggestions': [``isn't'', 'ist'], 'location': 12}]
+>>>
+</pre>
+<p>This call results in the following XML being sent:</p>
+<pre class="literal-block">
+<?xml version='1.0'?>
+<methodCall>
+ <methodName>speller.spellCheck</methodName>
+ <params>
+ <param>
+ <value><string>my speling isnt gud</string></value>
+ </param>
+ <param>
+ <value><struct></struct></value>
+ </param>
+ </params>
+</methodCall>
+</pre>
+</div>
+<div class="section" id="writing-a-quixote-service">
+<h1><a name="writing-a-quixote-service">Writing a Quixote Service</a></h1>
+<p>In the quixote.util module, Quixote provides a function,
+<tt class="literal"><span class="pre">xmlrpc(request,</span> <span class="pre">func)</span></tt>, that processes the body of an XML-RPC
+request. <tt class="literal"><span class="pre">request</span></tt> is the HTTPRequest object that Quixote passes to
+every function it invokes. <tt class="literal"><span class="pre">func</span></tt> is a user-supplied function that
+receives the name of the XML-RPC method being called and a tuple
+containing the method's parameters. If there's a bug in the function
+you supply and it raises an exception, the <tt class="literal"><span class="pre">xmlrpc()</span></tt> function will
+catch the exception and return a <tt class="literal"><span class="pre">Fault</span></tt> to the remote caller.</p>
+<p>Here's an example of implementing a simple XML-RPC handler with a
+single method, <tt class="literal"><span class="pre">get_time()</span></tt>, that simply returns the current
+time. The first task is to expose a URL for accessing the service.</p>
+<pre class="literal-block">
+from quixote.util import xmlrpc
+
+_q_exports = ['rpc']
+
+def rpc (request):
+ return xmlrpc(request, rpc_process)
+
+def rpc_process (meth, params):
+ ...
+</pre>
+<p>When the above code is placed in the __init__.py file for the Python
+package corresponding to your Quixote application, it exposes the URL
+<tt class="literal"><span class="pre">http://<hostname>/rpc</span></tt> as the access point for the XML-RPC service.</p>
+<p>Next, we need to fill in the contents of the <tt class="literal"><span class="pre">rpc_process()</span></tt>
+function:</p>
+<pre class="literal-block">
+import time
+
+def rpc_process (meth, params):
+ if meth == 'get_time':
+ # params is ignored
+ now = time.gmtime(time.time())
+ return xmlrpclib.DateTime(now)
+ else:
+ raise RuntimeError, "Unknown XML-RPC method: %r" % meth
+</pre>
+<p><tt class="literal"><span class="pre">rpc_process()</span></tt> receives the method name and the parameters, and its
+job is to run the right code for the method, returning a result that
+will be marshalled into XML-RPC. The body of <tt class="literal"><span class="pre">rpc_process()</span></tt> will
+therefore usually be an <tt class="literal"><span class="pre">if</span></tt> statement that checks the name of the
+method, and calls another function to do the actual work. In this case,
+<tt class="literal"><span class="pre">get_time()</span></tt> is very simple so the two lines of code it requires are
+simply included in the body of <tt class="literal"><span class="pre">rpc_process()</span></tt>.</p>
+<p>If the method name doesn't belong to a supported method, execution
+will fall through to the <tt class="literal"><span class="pre">else</span></tt> clause, which will raise a
+RuntimeError exception. Quixote's <tt class="literal"><span class="pre">xmlrpc()</span></tt> will catch this
+exception and report it to the caller as an XML-RPC fault, with the
+error code set to 1.</p>
+<p>As you add additional XML-RPC services, the <tt class="literal"><span class="pre">if</span></tt> statement in
+<tt class="literal"><span class="pre">rpc_process()</span></tt> will grow more branches. You might be tempted to pass
+the method name to <tt class="literal"><span class="pre">getattr()</span></tt> to select a method from a module or
+class. That would work, too, and avoids having a continually growing
+set of branches, but you should be careful with this and be sure that
+there are no private methods that a remote caller could access. I
+generally prefer to have the <tt class="literal"><span class="pre">if...</span> <span class="pre">elif...</span> <span class="pre">elif...</span> <span class="pre">else</span></tt> blocks, for
+three reasons: 1) adding another branch isn't much work, 2) it's
+explicit about the supported method names, and 3) there won't be any
+security holes in doing so.</p>
+<p>An alternative approach is to have a dictionary mapping method names
+to the corresponding functions and restrict the legal method names
+to the keys of this dictionary:</p>
+<pre class="literal-block">
+def echo (*params):
+ # Just returns the parameters it's passed
+ return params
+
+def get_time ():
+ now = time.gmtime(time.time())
+ return xmlrpclib.DateTime(now)
+
+methods = {'echo' : echo,
+ 'get_time' : get_time}
+
+def rpc_process (meth, params):
+ func = methods.get[meth]
+ if methods.has_key(meth):
+ # params is ignored
+ now = time.gmtime(time.time())
+ return xmlrpclib.DateTime(now)
+ else:
+ raise RuntimeError, "Unknown XML-RPC method: %r" % meth
+</pre>
+<p>This approach works nicely when there are many methods and the
+<tt class="literal"><span class="pre">if...elif...else</span></tt> statement would be unworkably long.</p>
+<p>$Id: web-services.txt 21603 2003-05-09 19:17:04Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/web-services.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/web-services.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/web-services.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/web-services.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,165 @@
+Implementing Web Services with Quixote
+======================================
+
+This document will show you how to implement Web services using
+Quixote.
+
+
+An XML-RPC Service
+------------------
+
+XML-RPC is the simplest protocol commonly used to expose a Web
+service. In XML-RPC, there are a few basic data types such as
+integers, floats, strings, and dates, and a few aggregate types such
+as arrays and structs. The xmlrpclib module, part of the Python 2.2
+standard library and available separately from
+http://www.pythonware.com/products/xmlrpc/, converts between Python's
+standard data types and the XML-RPC data types.
+
+============== =====================
+XML-RPC Type Python Type or Class
+-------------- ---------------------
+<int> int
+<double> float
+<string> string
+<array> list
+<struct> dict
+<boolean> xmlrpclib.Boolean
+<base64> xmlrpclib.Binary
+<dateTime> xmlrpclib.DateTime
+============== =====================
+
+
+Making XML-RPC Calls
+--------------------
+
+Making an XML-RPC call using xmlrpclib is easy. An XML-RPC server
+lives at a particular URL, so the first step is to create an
+xmlrpclib.ServerProxy object pointing at that URL. ::
+
+ >>> import xmlrpclib
+ >>> s = xmlrpclib.ServerProxy(
+ 'http://www.stuffeddog.com/speller/speller-rpc.cgi')
+
+Now you can simply make a call to the spell-checking service offered
+by this server::
+
+ >>> s.speller.spellCheck('my speling isnt gud', {})
+ [{'word': 'speling', 'suggestions': ['apeling', 'spelding',
+ 'spelling', 'sperling', 'spewing', 'spiling'], 'location': 4},
+ {'word': 'isnt', 'suggestions': [``isn't'', 'ist'], 'location': 12}]
+ >>>
+
+This call results in the following XML being sent::
+
+ <?xml version='1.0'?>
+ <methodCall>
+ <methodName>speller.spellCheck</methodName>
+ <params>
+ <param>
+ <value><string>my speling isnt gud</string></value>
+ </param>
+ <param>
+ <value><struct></struct></value>
+ </param>
+ </params>
+ </methodCall>
+
+
+Writing a Quixote Service
+-------------------------
+
+In the quixote.util module, Quixote provides a function,
+``xmlrpc(request, func)``, that processes the body of an XML-RPC
+request. ``request`` is the HTTPRequest object that Quixote passes to
+every function it invokes. ``func`` is a user-supplied function that
+receives the name of the XML-RPC method being called and a tuple
+containing the method's parameters. If there's a bug in the function
+you supply and it raises an exception, the ``xmlrpc()`` function will
+catch the exception and return a ``Fault`` to the remote caller.
+
+Here's an example of implementing a simple XML-RPC handler with a
+single method, ``get_time()``, that simply returns the current
+time. The first task is to expose a URL for accessing the service. ::
+
+ from quixote.util import xmlrpc
+
+ _q_exports = ['rpc']
+
+ def rpc (request):
+ return xmlrpc(request, rpc_process)
+
+ def rpc_process (meth, params):
+ ...
+
+When the above code is placed in the __init__.py file for the Python
+package corresponding to your Quixote application, it exposes the URL
+``http://<hostname>/rpc`` as the access point for the XML-RPC service.
+
+Next, we need to fill in the contents of the ``rpc_process()``
+function::
+
+ import time
+
+ def rpc_process (meth, params):
+ if meth == 'get_time':
+ # params is ignored
+ now = time.gmtime(time.time())
+ return xmlrpclib.DateTime(now)
+ else:
+ raise RuntimeError, "Unknown XML-RPC method: %r" % meth
+
+``rpc_process()`` receives the method name and the parameters, and its
+job is to run the right code for the method, returning a result that
+will be marshalled into XML-RPC. The body of ``rpc_process()`` will
+therefore usually be an ``if`` statement that checks the name of the
+method, and calls another function to do the actual work. In this case,
+``get_time()`` is very simple so the two lines of code it requires are
+simply included in the body of ``rpc_process()``.
+
+If the method name doesn't belong to a supported method, execution
+will fall through to the ``else`` clause, which will raise a
+RuntimeError exception. Quixote's ``xmlrpc()`` will catch this
+exception and report it to the caller as an XML-RPC fault, with the
+error code set to 1.
+
+As you add additional XML-RPC services, the ``if`` statement in
+``rpc_process()`` will grow more branches. You might be tempted to pass
+the method name to ``getattr()`` to select a method from a module or
+class. That would work, too, and avoids having a continually growing
+set of branches, but you should be careful with this and be sure that
+there are no private methods that a remote caller could access. I
+generally prefer to have the ``if... elif... elif... else`` blocks, for
+three reasons: 1) adding another branch isn't much work, 2) it's
+explicit about the supported method names, and 3) there won't be any
+security holes in doing so.
+
+An alternative approach is to have a dictionary mapping method names
+to the corresponding functions and restrict the legal method names
+to the keys of this dictionary::
+
+ def echo (*params):
+ # Just returns the parameters it's passed
+ return params
+
+ def get_time ():
+ now = time.gmtime(time.time())
+ return xmlrpclib.DateTime(now)
+
+ methods = {'echo' : echo,
+ 'get_time' : get_time}
+
+ def rpc_process (meth, params):
+ func = methods.get[meth]
+ if methods.has_key(meth):
+ # params is ignored
+ now = time.gmtime(time.time())
+ return xmlrpclib.DateTime(now)
+ else:
+ raise RuntimeError, "Unknown XML-RPC method: %r" % meth
+
+This approach works nicely when there are many methods and the
+``if...elif...else`` statement would be unworkably long.
+
+
+$Id: web-services.txt 21603 2003-05-09 19:17:04Z akuchlin $
Added: packages/quixote1/branches/upstream/current/doc/widgets.html
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/widgets.html?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/widgets.html (added)
+++ packages/quixote1/branches/upstream/current/doc/widgets.html Mon May 8 19:16:16 2006
@@ -1,0 +1,503 @@
+<?xml version="1.0" encoding="us-ascii" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
+<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
+<title>Quixote Widget Classes</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="quixote-widget-classes">
+<h1 class="title">Quixote Widget Classes</h1>
+<p>[This is reference documentation. If you haven't yet read "Lesson 5:
+widgets" of demo.txt, you should go and do so now. This document also
+assumes you have a good understanding of HTML forms and form elements.
+If not, you could do worse than pick up a copy of <em>HTML: The Definitive
+Guide</em> by Chuck Musciano & Bill Kennedy (O'Reilly). I usually keep it
+within arm's reach.]</p>
+<p>Web forms are built out of form elements: string input, select lists,
+checkboxes, submit buttons, and so forth. Quixote provides a family of
+classes for handling these form elements, or widgets, in the
+quixote.form.widget module. The class hierarchy is:</p>
+<pre class="literal-block">
+Widget [A]
+|
++--StringWidget
+| |
+| +--PasswordWidget
+| |
+| +--NumberWidget [*] [A]
+| |
+| +-FloatWidget [*]
+| +-IntWidget [*]
+|
++--TextWidget
+|
++--CheckboxWidget
+|
++--SelectWidget [A]
+| |
+| +--SingleSelectWidget
+| | |
+| | +-RadiobuttonsWidget
+| | |
+| | +-OptionSelectWidget [*]
+| |
+| +--MultipleSelectWidget
+|
++--SubmitButtonWidget
+|
++--HiddenWidget
+|
++--ListWidget [*]
+
+[*] Widget classes that do not correspond exactly with a particular
+ HTML form element
+[A] Abstract classes
+</pre>
+<div class="section" id="widget-the-base-class">
+<h1><a name="widget-the-base-class">Widget: the base class</a></h1>
+<p>Widget is the abstract base class for the widget hierarchy. It provides
+the following facilities:</p>
+<ul class="simple">
+<li>widget name (<tt class="literal"><span class="pre">name</span></tt> attribute, <tt class="literal"><span class="pre">set_name()</span></tt> method)</li>
+<li>widget value (<tt class="literal"><span class="pre">value</span></tt> attribute, <tt class="literal"><span class="pre">set_value()</span></tt> and <tt class="literal"><span class="pre">clear()</span></tt> methods)</li>
+<li><tt class="literal"><span class="pre">__str__()</span></tt> and <tt class="literal"><span class="pre">__repr__()</span></tt> methods</li>
+<li>some facilities for writing composite widget classes</li>
+</ul>
+<p>The Widget constructor signature is:</p>
+<pre class="literal-block">
+Widget(name : string, value : any = None)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">name</span></tt></dt>
+<dd>the name of the widget. For non-compound widgets (ie. everything in
+the above class hierarchy), this will be used as the "name"
+attribute for the main HTML tag that defines the form element.</dd>
+<dt><tt class="literal"><span class="pre">value</span></tt></dt>
+<dd>the current value of the form element. The type of 'value' depends
+on the widget class. Most widget classes support only a single
+type, eg. StringWidget always deals with strings and IntWidget with
+integers. The SelectWidget classes are different; see the
+descriptions below for details.</dd>
+</dl>
+</div>
+<div class="section" id="common-widget-methods">
+<h1><a name="common-widget-methods">Common widget methods</a></h1>
+<p>The Widget base class also provides a couple of useful
+methods:</p>
+<dl>
+<dt><tt class="literal"><span class="pre">set_name(name:string)</span></tt></dt>
+<dd>use this to change the widget name supplied to the constructor.
+Unless you know what you're doing, you should do this before
+rendering or parsing the widget.</dd>
+<dt><tt class="literal"><span class="pre">set_value(value:any)</span></tt></dt>
+<dd>use this to set the widget value; this is the same as supplying
+a value to the constructor (and the same type rules apply, ie.
+the type of 'value' depends on the widget class).</dd>
+<dt><tt class="literal"><span class="pre">clear()</span></tt></dt>
+<dd>clear the widget's current value. Equivalent to
+<tt class="literal"><span class="pre">widget.set_value(None)</span></tt>.</dd>
+</dl>
+<p>The following two methods will be used on every widget object you
+create; if you write your own widget classes, you will almost certainly
+have to define both of these:</p>
+<dl>
+<dt><tt class="literal"><span class="pre">render(request:HTTPRequest)</span></tt> <span class="classifier-delimiter">:</span> <span class="classifier"><tt class="literal"><span class="pre">string</span></tt></span></dt>
+<dd>return a chunk of HTML that implements the form element
+corresponding to this widget.</dd>
+<dt><tt class="literal"><span class="pre">parse(request:HTTPRequest)</span></tt> <span class="classifier-delimiter">:</span> <span class="classifier"><tt class="literal"><span class="pre">any</span></tt></span></dt>
+<dd>extract the form value for this widget from <tt class="literal"><span class="pre">request.form</span></tt>, parse it
+according to the rules for this widget class, and return the
+resulting value. The return value depends on the widget class, and
+will be of the same type as the value passed to the constructor
+and/or <tt class="literal"><span class="pre">set_value()</span></tt>.</dd>
+</dl>
+</div>
+<div class="section" id="stringwidget">
+<h1><a name="stringwidget">StringWidget</a></h1>
+<p>Used for short, single-line string input with no validation (ie. any
+string will be accepted.) Generates an <tt class="literal"><span class="pre"><input</span> <span class="pre">type="text"></span></tt> form
+element.</p>
+<div class="section" id="constructor">
+<h2><a name="constructor">Constructor</a></h2>
+<pre class="literal-block">
+StringWidget(name : string,
+ value : string = None,
+ size : int = None,
+ maxlength : int = None)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">size</span></tt></dt>
+<dd>used as the <tt class="literal"><span class="pre">size</span></tt> attribute of the generated <tt class="literal"><span class="pre"><input></span></tt> tag;
+controls the physical size of the input field.</dd>
+<dt><tt class="literal"><span class="pre">maxlength</span></tt></dt>
+<dd>used as the <tt class="literal"><span class="pre">maxlength</span></tt> attribute; controls the maximum amount
+of input.</dd>
+</dl>
+</div>
+<div class="section" id="examples">
+<h2><a name="examples">Examples</a></h2>
+<pre class="literal-block">
+>>> StringWidget("foo", value="hello").render(request)
+'<input name="foo" type="text" value="hello">'
+
+>>> StringWidget("foo", size=10, maxlength=20).render(request)
+'<input name="foo" type="text" size="10" maxlength="20">'
+</pre>
+</div>
+</div>
+<div class="section" id="passwordwidget">
+<h1><a name="passwordwidget">PasswordWidget</a></h1>
+<p>PasswordWidget is identical to StringWidget except for the type of the
+HTML form element: <tt class="literal"><span class="pre">password</span></tt> instead of <tt class="literal"><span class="pre">text</span></tt>.</p>
+</div>
+<div class="section" id="textwidget">
+<h1><a name="textwidget">TextWidget</a></h1>
+<p>Used for multi-line text input. The value is a single string with
+newlines right where the browser supplied them. (<tt class="literal"><span class="pre">\r\n</span></tt>, if present,
+is converted to <tt class="literal"><span class="pre">\n</span></tt>.) Generates a <tt class="literal"><span class="pre"><textarea></span></tt> form element.</p>
+<div class="section" id="id1">
+<h2><a name="id1">Constructor</a></h2>
+<pre class="literal-block">
+TextWidget(name : string,
+ value : string = None,
+ cols : int = None,
+ rows : int = None,
+ wrap : string = "physical")
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">cols</span></tt>, <tt class="literal"><span class="pre">rows</span></tt></dt>
+<dd>number of columns/rows in the textarea</dd>
+<dt><tt class="literal"><span class="pre">wrap</span></tt></dt>
+<dd>controls how the browser wraps text and includes newlines in the
+submitted form value; consult an HTML book for details.</dd>
+</dl>
+</div>
+</div>
+<div class="section" id="checkboxwidget">
+<h1><a name="checkboxwidget">CheckboxWidget</a></h1>
+<p>Used for single boolean (on/off) value. The value you supply can be
+anything, since Python has a boolean interpretation for all values; the
+value returned by <tt class="literal"><span class="pre">parse()</span></tt> will always be 0 or 1 (but you shouldn't
+rely on that!). Generates an <tt class="literal"><span class="pre"><input</span> <span class="pre">type="checkbox"></span></tt> form element.</p>
+<div class="section" id="id2">
+<h2><a name="id2">Constructor</a></h2>
+<pre class="literal-block">
+CheckboxWidget(name : string,
+ value : boolean = false)
+</pre>
+</div>
+<div class="section" id="id3">
+<h2><a name="id3">Examples</a></h2>
+<pre class="literal-block">
+>>> CheckboxWidget("foo", value=0).render(request)
+'<input name="foo" type="checkbox" value="yes">'
+
+>>> CheckboxWidget("foo", value="you bet").render(request)
+'<input name="foo" type="checkbox" value="yes" checked>'
+</pre>
+</div>
+</div>
+<div class="section" id="radiobuttonswidget">
+<h1><a name="radiobuttonswidget">RadiobuttonsWidget</a></h1>
+<p>Used for a <em>set</em> of related radiobuttons, ie. several <tt class="literal"><span class="pre"><input</span>
+<span class="pre">type="radio"></span></tt> tags with the same name and different values. The set
+of values are supplied to the constructor as <tt class="literal"><span class="pre">allowed_values</span></tt>, which
+may be a list of any Python objects (not just strings). The current
+value must be either <tt class="literal"><span class="pre">None</span></tt> (the default) or one of the values in
+<tt class="literal"><span class="pre">allowed_values</span></tt>; if you supply a <tt class="literal"><span class="pre">value</span></tt> not in <tt class="literal"><span class="pre">allowed_values</span></tt>,
+it will be ignored. <tt class="literal"><span class="pre">parse()</span></tt> will return either <tt class="literal"><span class="pre">None</span></tt> or one of
+the values in <tt class="literal"><span class="pre">allowed_values</span></tt>.</p>
+<div class="section" id="id4">
+<h2><a name="id4">Constructor</a></h2>
+<pre class="literal-block">
+RadiobuttonsWidget(name : string,
+ value : any = None,
+ allowed_values : [any] = None,
+ descriptions : [string] = map(str, allowed_values),
+ quote : boolean = true,
+ delim : string = "\n")
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">allowed_values</span></tt></dt>
+<dd><p class="first">specifies how many <tt class="literal"><span class="pre"><input</span> <span class="pre">type="radio"></span></tt> tags to generate and the
+values for each. Eg. <tt class="literal"><span class="pre">allowed_values=["foo",</span> <span class="pre">"bar"]</span></tt> will result in
+(roughly):</p>
+<pre class="last literal-block">
+<input type="radio" value="foo">
+<input type="radio" value="bar">
+</pre>
+</dd>
+<dt><tt class="literal"><span class="pre">descriptions</span></tt></dt>
+<dd>the text that will actually be shown to the user in the web page
+that includes this widget. Handy when the elements of
+<tt class="literal"><span class="pre">allowed_values</span></tt> are too terse, or don't have a meaningful
+<tt class="literal"><span class="pre">str()</span></tt>, or you want to add some additional cues for the user. If
+not supplied, <tt class="literal"><span class="pre">map(str,</span> <span class="pre">allowed_values)</span></tt> is used, with the
+exception that <tt class="literal"><span class="pre">None</span></tt> in <tt class="literal"><span class="pre">allowed_values</span></tt> becomes <tt class="literal"><span class="pre">""</span></tt> (the
+empty string) in <tt class="literal"><span class="pre">descriptions</span></tt>. If supplied, <tt class="literal"><span class="pre">descriptions</span></tt>
+must be the same length as <tt class="literal"><span class="pre">allowed_values</span></tt>.</dd>
+<dt><tt class="literal"><span class="pre">quote</span></tt></dt>
+<dd>if true (the default), the elements of 'descriptions' will be
+HTML-quoted (using <tt class="literal"><span class="pre">quixote.html.html_quote()</span></tt>) when the widget is
+rendered. This is essential if you might have characters like
+<tt class="literal"><span class="pre">&</span></tt> or <tt class="literal"><span class="pre"><</span></tt> in your descriptions. However, you'll want to set
+<tt class="literal"><span class="pre">quote</span></tt> to false if you are deliberately including HTML markup
+in your descriptions.</dd>
+<dt><tt class="literal"><span class="pre">delim</span></tt></dt>
+<dd>the delimiter to separate the radiobuttons with when rendering
+the whole widget. The default ensures that your HTML is readable
+(by putting each <tt class="literal"><span class="pre"><input></span></tt> tag on a separate line), and that there
+is horizontal whitespace between each radiobutton.</dd>
+</dl>
+</div>
+<div class="section" id="id5">
+<h2><a name="id5">Examples</a></h2>
+<pre class="literal-block">
+>>> colours = ["red", "green", "blue", "pink"]
+>>> widget = RadiobuttonsWidget("foo", allowed_values=colours)
+>>> print widget.render(request)
+<input name="foo" type="radio" value="0">red</input>
+<input name="foo" type="radio" value="1">green</input>
+<input name="foo" type="radio" value="2">blue</input>
+<input name="foo" type="radio" value="3">pink</input>
+</pre>
+<p>(Note that the actual form values, ie. what the browser returns to the
+server, are always stringified indices into the 'allowed_values' list.
+This is irrelevant to you, since SingleSelectWidget takes care of
+converting <tt class="literal"><span class="pre">"1"</span></tt> to <tt class="literal"><span class="pre">1</span></tt> and looking up <tt class="literal"><span class="pre">allowed_values[1]</span></tt>.)</p>
+<pre class="literal-block">
+>>> values = [val1, val2, val3]
+>>> descs = ["thing <b>1</b>",
+ "thing <b>2</b>",
+ "thing <b>3</b>"]
+>>> widget = RadiobuttonsWidget("bar",
+ allowed_values=values,
+ descriptions=descs,
+ value=val3,
+ delim="<br>\n",
+ quote=0)
+>>> print widget.render(request)
+<input name="bar" type="radio" value="0">thing <b>1</b></input><br>
+<input name="bar" type="radio" value="1">thing <b>2</b></input><br>
+<input name="bar" type="radio" value="2" checked="checked">thing <b>3</b></input>
+</pre>
+</div>
+</div>
+<div class="section" id="singleselectwidget">
+<h1><a name="singleselectwidget">SingleSelectWidget</a></h1>
+<p>Used to select a single value from a list that's too long or ungainly
+for a set of radiobuttons. (Most browsers implement this as a scrolling
+list; UNIX versions of Netscape 4.x and earlier used a pop-up menu.)
+The value can be any Python object; <tt class="literal"><span class="pre">parse()</span></tt> will return either
+<tt class="literal"><span class="pre">None</span></tt> or one of the values you supply to the constructor as
+<tt class="literal"><span class="pre">allowed_values</span></tt>. Generates a <tt class="literal"><span class="pre"><select>...</select></span></tt> tag, with one
+<tt class="literal"><span class="pre"><option></span></tt> tag for each element of <tt class="literal"><span class="pre">allowed_values</span></tt>.</p>
+<div class="section" id="id6">
+<h2><a name="id6">Constructor</a></h2>
+<pre class="literal-block">
+SingleSelectWidget(name : string,
+ value : any = None,
+ allowed_values : [any] = None,
+ descriptions : [string] = map(str, allowed_values),
+ quote : boolean = true,
+ size : int = None)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">allowed_values</span></tt></dt>
+<dd>determines the set of <tt class="literal"><span class="pre"><option></span></tt> tags that will go inside the
+<tt class="literal"><span class="pre"><select></span></tt> tag; these can be any Python values (not just strings).
+<tt class="literal"><span class="pre">parse()</span></tt> will return either one of the <tt class="literal"><span class="pre">allowed_values</span></tt> or <tt class="literal"><span class="pre">None</span></tt>.
+If you supply a <tt class="literal"><span class="pre">value</span></tt> that is not in <tt class="literal"><span class="pre">allowed_values</span></tt>, it
+will be ignored.</dd>
+<dt><tt class="literal"><span class="pre">descriptions</span></tt></dt>
+<dd>(same as RadiobuttonsWidget above)</dd>
+<dt><tt class="literal"><span class="pre">quote</span></tt></dt>
+<dd>(same as RadiobuttonsWidget above)</dd>
+<dt><tt class="literal"><span class="pre">size</span></tt></dt>
+<dd>corresponds to the <tt class="literal"><span class="pre">size</span></tt> attribute of the <tt class="literal"><span class="pre"><select></span></tt> tag: ask
+the browser to show a select list with <tt class="literal"><span class="pre">size</span></tt> items visible.
+Not always respected by the browser; consult an HTML book.</dd>
+</dl>
+</div>
+<div class="section" id="id7">
+<h2><a name="id7">Examples</a></h2>
+<pre class="literal-block">
+>>> widget = SingleSelectWidget("foo",
+ allowed_values=["abc", 123, 5.5])
+>>> print widget.render(request)
+<select name="foo">
+<option value="0">abc
+<option value="1">123
+<option value="2">5.5
+</select>
+
+>>> widget = SingleSelectWidget("bar",
+ value=val2,
+ allowed_values=[val1, val2, val3],
+ descriptions=["foo", "bar", "foo & bar"],
+ size=3)
+>>> print widget.render(request)
+<select name="bar" size="3">
+<option value="0">foo
+<option selected value="1">bar
+<option value="2">foo &amp; bar
+</select>
+</pre>
+</div>
+</div>
+<div class="section" id="multipleselectwidget">
+<h1><a name="multipleselectwidget">MultipleSelectWidget</a></h1>
+<p>Used to select multiple values from a list. Everything is just like
+SingleSelectWidget, except that <tt class="literal"><span class="pre">value</span></tt> can be a list of objects
+selected from <tt class="literal"><span class="pre">allowed_values</span></tt> (in which case every object in <tt class="literal"><span class="pre">value</span></tt>
+will initially be selected). Generates a <tt class="literal"><span class="pre"><select</span> <span class="pre">multiple>...</select></span></tt>
+tag, with one <tt class="literal"><span class="pre"><option></span></tt> tag for each element of <tt class="literal"><span class="pre">allowed_values</span></tt>.</p>
+<div class="section" id="id8">
+<h2><a name="id8">Constructor</a></h2>
+<pre class="literal-block">
+MultipleSelectWidget(name : string,
+ value : any | [any] = None,
+ allowed_values : [any] = None,
+ descriptions : [string] = map(str, allowed_values),
+ quote : boolean = true,
+ size : int = None)
+</pre>
+</div>
+</div>
+<div class="section" id="submitbuttonwidget">
+<h1><a name="submitbuttonwidget">SubmitButtonWidget</a></h1>
+<p>Used for generating submit buttons. Note that HTML submit buttons are
+rather weird, and Quixote preserves this weirdness -- the Widget classes
+are meant to be a fairly thin wrapper around HTML form elements, after
+all.</p>
+<p>In particular, the widget value for a submit button controls two things:
+what the user sees in their browser (the text in the button) and what
+the browser returns as the value for that form element. You can't
+control the two separately, as you can with radiobuttons or selection
+widgets.</p>
+<p>Also, SubmitButtonWidget is the only widget with an optional <tt class="literal"><span class="pre">name</span></tt>.
+In many simple forms, all you care about is the fact that the form was
+submitted -- which submit button the user used doesn't matter.</p>
+<div class="section" id="id9">
+<h2><a name="id9">Constructor</a></h2>
+<pre class="literal-block">
+SubmitButtonWidget(name : string = None,
+ value : string = None)
+</pre>
+<dl>
+<dt><tt class="literal"><span class="pre">value</span></tt></dt>
+<dd>the text that will be shown in the user's browser, <em>and</em> the
+value that will be returned for this form element (widget)
+if the user selects this submit button.</dd>
+</dl>
+</div>
+<div class="section" id="id10">
+<h2><a name="id10">Examples</a></h2>
+<blockquote>
+<pre class="doctest-block">
+>>> SubmitButtonWidget(value="Submit Form").render(request)
+'<input type="submit" value="Submit Form">'
+</pre>
+</blockquote>
+</div>
+</div>
+<div class="section" id="hiddenwidget">
+<h1><a name="hiddenwidget">HiddenWidget</a></h1>
+<p>Used to generate HTML hidden widgets, which can be useful for carrying
+around non-sensitive application state. (The Quixote form framework
+uses hidden widgets for form tokens as a measure against cross-site
+request forgery [CSRF] attacks. So by "sensitive" I mean "information
+which should not be revealed", rather than "security-related". If you
+wouldn't put it in a cookie or in email, don't put it in a hidden form
+element.)</p>
+<div class="section" id="id11">
+<h2><a name="id11">Constructor</a></h2>
+<pre class="literal-block">
+HiddenWidget(name : string,
+ value : string)
+</pre>
+</div>
+<div class="section" id="id12">
+<h2><a name="id12">Examples</a></h2>
+<pre class="literal-block">
+>>> HiddenWidget("form_id", "2452345135").render(request)
+'<input type="hidden" name="form_id" value="2452345135">'
+</pre>
+</div>
+</div>
+<div class="section" id="intwidget">
+<h1><a name="intwidget">IntWidget</a></h1>
+<p>The first derived widget class: this is a subclass of StringWidget
+specifically for entering integer values. As such, this is the first
+widget class we've covered that can reject certain user input. (The
+selection widgets all have to validate their input in case of broken or
+malicious clients, but they just drop bogus values.) If the user enters
+a string that Python's built-in <tt class="literal"><span class="pre">int()</span></tt> can't convert to an integer,
+IntWidget's <tt class="literal"><span class="pre">parse()</span></tt> method raises FormValueError (also defined in
+the quixote.form.widget module). This exception is handled by Quixote's
+form framework, but if you're using widget objects on their own, you'll
+have to handle it yourself.</p>
+<p><tt class="literal"><span class="pre">IntWidget.parse()</span></tt> always returns an integer or <tt class="literal"><span class="pre">None</span></tt>.</p>
+<div class="section" id="id13">
+<h2><a name="id13">Constructor</a></h2>
+<pre class="literal-block">
+IntWidget(name : string,
+ value : int = None,
+ size : int = None,
+ maxlength : int = None)
+</pre>
+<p>Constructor arguments are as for StringWidget, except that <tt class="literal"><span class="pre">value</span></tt>
+must be an integer (or <tt class="literal"><span class="pre">None</span></tt>). Note that <tt class="literal"><span class="pre">size</span></tt> and
+<tt class="literal"><span class="pre">maxlength</span></tt> have exactly the same meaning: they control the size of
+the input widget and the maximum number of characters of input.</p>
+<p>[Examples]</p>
+<blockquote>
+<pre class="doctest-block">
+>>> IntWidget("num", value=37, size=5).render(request)
+'<input type="string" name="num" value="37" size="5">'
+</pre>
+</blockquote>
+</div>
+</div>
+<div class="section" id="floatwidget">
+<h1><a name="floatwidget">FloatWidget</a></h1>
+<p>FloatWidget is identical to IntWidget, except:</p>
+<ul class="simple">
+<li><tt class="literal"><span class="pre">value</span></tt> must be a float</li>
+<li><tt class="literal"><span class="pre">parse()</span></tt> returns a float or <tt class="literal"><span class="pre">None</span></tt></li>
+<li><tt class="literal"><span class="pre">parse()</span></tt> raises FormValueError if the string entered by the
+user cannot be converted by Python's built-in <tt class="literal"><span class="pre">float()</span></tt> function</li>
+</ul>
+</div>
+<div class="section" id="optionselectwidget">
+<h1><a name="optionselectwidget">OptionSelectWidget</a></h1>
+<p>OptionSelectWidget is simply a SingleSelectWidget that uses a bit of
+Javascript to automatically submit the current form as soon as the user
+selects a value. This is useful for very simple one-element forms where
+you don't want to bother with a submit button, or for very complex forms
+where you need to revamp the user interface based on a user's selection.
+Your form-processing code could then detect that style of form
+submission, and regenerate a slightly different form for the user. (Or
+you could treat it as a full-blown form submission, if the only widget
+of interest is the OptionSelectWidget.)</p>
+<p>For example, if you're asking a user for their address, some of the
+details will vary depending on which country they're in. You might make
+the country widget an OptionSelectWidget: if the user selects "Canada",
+you'll ask them for a province and a postal code; if they select "United
+States", you ask for a state and a zip code; and so forth. (I don't
+really recommend a user interface that works this way: you'll spend way
+too much time getting the details right ["How many states does Australia
+have again?"], and you're bound to get something wrong -- there are over
+200 countries in the world, after all.)</p>
+<p>Be warned that since OptionSelectWidget relies on Javascript to work,
+using it makes immediately makes your application less portable and more
+fragile. One thing to avoid: form elements with a name of <tt class="literal"><span class="pre">submit</span></tt>,
+since that masks the Javascript function called by OptionSelectWidget.</p>
+<p>$Id: widgets.txt 20217 2003-01-16 20:51:53Z akuchlin $</p>
+</div>
+</div>
+</body>
+</html>
Added: packages/quixote1/branches/upstream/current/doc/widgets.txt
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/doc/widgets.txt?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/doc/widgets.txt (added)
+++ packages/quixote1/branches/upstream/current/doc/widgets.txt Mon May 8 19:16:16 2006
@@ -1,0 +1,524 @@
+Quixote Widget Classes
+======================
+
+[This is reference documentation. If you haven't yet read "Lesson 5:
+widgets" of demo.txt, you should go and do so now. This document also
+assumes you have a good understanding of HTML forms and form elements.
+If not, you could do worse than pick up a copy of *HTML: The Definitive
+Guide* by Chuck Musciano & Bill Kennedy (O'Reilly). I usually keep it
+within arm's reach.]
+
+Web forms are built out of form elements: string input, select lists,
+checkboxes, submit buttons, and so forth. Quixote provides a family of
+classes for handling these form elements, or widgets, in the
+quixote.form.widget module. The class hierarchy is::
+
+ Widget [A]
+ |
+ +--StringWidget
+ | |
+ | +--PasswordWidget
+ | |
+ | +--NumberWidget [*] [A]
+ | |
+ | +-FloatWidget [*]
+ | +-IntWidget [*]
+ |
+ +--TextWidget
+ |
+ +--CheckboxWidget
+ |
+ +--SelectWidget [A]
+ | |
+ | +--SingleSelectWidget
+ | | |
+ | | +-RadiobuttonsWidget
+ | | |
+ | | +-OptionSelectWidget [*]
+ | |
+ | +--MultipleSelectWidget
+ |
+ +--SubmitButtonWidget
+ |
+ +--HiddenWidget
+ |
+ +--ListWidget [*]
+
+ [*] Widget classes that do not correspond exactly with a particular
+ HTML form element
+ [A] Abstract classes
+
+
+Widget: the base class
+----------------------
+
+Widget is the abstract base class for the widget hierarchy. It provides
+the following facilities:
+
+* widget name (``name`` attribute, ``set_name()`` method)
+* widget value (``value`` attribute, ``set_value()`` and ``clear()`` methods)
+* ``__str__()`` and ``__repr__()`` methods
+* some facilities for writing composite widget classes
+
+The Widget constructor signature is::
+
+ Widget(name : string, value : any = None)
+
+``name``
+ the name of the widget. For non-compound widgets (ie. everything in
+ the above class hierarchy), this will be used as the "name"
+ attribute for the main HTML tag that defines the form element.
+
+``value``
+ the current value of the form element. The type of 'value' depends
+ on the widget class. Most widget classes support only a single
+ type, eg. StringWidget always deals with strings and IntWidget with
+ integers. The SelectWidget classes are different; see the
+ descriptions below for details.
+
+
+Common widget methods
+---------------------
+
+The Widget base class also provides a couple of useful
+methods:
+
+``set_name(name:string)``
+ use this to change the widget name supplied to the constructor.
+ Unless you know what you're doing, you should do this before
+ rendering or parsing the widget.
+
+``set_value(value:any)``
+ use this to set the widget value; this is the same as supplying
+ a value to the constructor (and the same type rules apply, ie.
+ the type of 'value' depends on the widget class).
+
+``clear()``
+ clear the widget's current value. Equivalent to
+ ``widget.set_value(None)``.
+
+The following two methods will be used on every widget object you
+create; if you write your own widget classes, you will almost certainly
+have to define both of these:
+
+``render(request:HTTPRequest)`` : ``string``
+ return a chunk of HTML that implements the form element
+ corresponding to this widget.
+
+``parse(request:HTTPRequest)`` : ``any``
+ extract the form value for this widget from ``request.form``, parse it
+ according to the rules for this widget class, and return the
+ resulting value. The return value depends on the widget class, and
+ will be of the same type as the value passed to the constructor
+ and/or ``set_value()``.
+
+
+StringWidget
+------------
+
+Used for short, single-line string input with no validation (ie. any
+string will be accepted.) Generates an ``<input type="text">`` form
+element.
+
+Constructor
+~~~~~~~~~~~
+::
+
+ StringWidget(name : string,
+ value : string = None,
+ size : int = None,
+ maxlength : int = None)
+
+``size``
+ used as the ``size`` attribute of the generated ``<input>`` tag;
+ controls the physical size of the input field.
+
+``maxlength``
+ used as the ``maxlength`` attribute; controls the maximum amount
+ of input.
+
+Examples
+~~~~~~~~
+::
+
+ >>> StringWidget("foo", value="hello").render(request)
+ '<input name="foo" type="text" value="hello">'
+
+ >>> StringWidget("foo", size=10, maxlength=20).render(request)
+ '<input name="foo" type="text" size="10" maxlength="20">'
+
+
+PasswordWidget
+--------------
+
+PasswordWidget is identical to StringWidget except for the type of the
+HTML form element: ``password`` instead of ``text``.
+
+
+TextWidget
+----------
+
+Used for multi-line text input. The value is a single string with
+newlines right where the browser supplied them. (``\r\n``, if present,
+is converted to ``\n``.) Generates a ``<textarea>`` form element.
+
+Constructor
+~~~~~~~~~~~
+::
+
+ TextWidget(name : string,
+ value : string = None,
+ cols : int = None,
+ rows : int = None,
+ wrap : string = "physical")
+
+``cols``, ``rows``
+ number of columns/rows in the textarea
+``wrap``
+ controls how the browser wraps text and includes newlines in the
+ submitted form value; consult an HTML book for details.
+
+
+CheckboxWidget
+--------------
+
+Used for single boolean (on/off) value. The value you supply can be
+anything, since Python has a boolean interpretation for all values; the
+value returned by ``parse()`` will always be 0 or 1 (but you shouldn't
+rely on that!). Generates an ``<input type="checkbox">`` form element.
+
+Constructor
+~~~~~~~~~~~
+::
+
+ CheckboxWidget(name : string,
+ value : boolean = false)
+
+Examples
+~~~~~~~~
+::
+
+ >>> CheckboxWidget("foo", value=0).render(request)
+ '<input name="foo" type="checkbox" value="yes">'
+
+ >>> CheckboxWidget("foo", value="you bet").render(request)
+ '<input name="foo" type="checkbox" value="yes" checked>'
+
+
+RadiobuttonsWidget
+------------------
+
+Used for a *set* of related radiobuttons, ie. several ``<input
+type="radio">`` tags with the same name and different values. The set
+of values are supplied to the constructor as ``allowed_values``, which
+may be a list of any Python objects (not just strings). The current
+value must be either ``None`` (the default) or one of the values in
+``allowed_values``; if you supply a ``value`` not in ``allowed_values``,
+it will be ignored. ``parse()`` will return either ``None`` or one of
+the values in ``allowed_values``.
+
+Constructor
+~~~~~~~~~~~
+::
+
+ RadiobuttonsWidget(name : string,
+ value : any = None,
+ allowed_values : [any] = None,
+ descriptions : [string] = map(str, allowed_values),
+ quote : boolean = true,
+ delim : string = "\n")
+
+``allowed_values``
+ specifies how many ``<input type="radio">`` tags to generate and the
+ values for each. Eg. ``allowed_values=["foo", "bar"]`` will result in
+ (roughly)::
+
+ <input type="radio" value="foo">
+ <input type="radio" value="bar">
+
+``descriptions``
+ the text that will actually be shown to the user in the web page
+ that includes this widget. Handy when the elements of
+ ``allowed_values`` are too terse, or don't have a meaningful
+ ``str()``, or you want to add some additional cues for the user. If
+ not supplied, ``map(str, allowed_values)`` is used, with the
+ exception that ``None`` in ``allowed_values`` becomes ``""`` (the
+ empty string) in ``descriptions``. If supplied, ``descriptions``
+ must be the same length as ``allowed_values``.
+
+``quote``
+ if true (the default), the elements of 'descriptions' will be
+ HTML-quoted (using ``quixote.html.html_quote()``) when the widget is
+ rendered. This is essential if you might have characters like
+ ``&`` or ``<`` in your descriptions. However, you'll want to set
+ ``quote`` to false if you are deliberately including HTML markup
+ in your descriptions.
+
+``delim``
+ the delimiter to separate the radiobuttons with when rendering
+ the whole widget. The default ensures that your HTML is readable
+ (by putting each ``<input>`` tag on a separate line), and that there
+ is horizontal whitespace between each radiobutton.
+
+Examples
+~~~~~~~~
+::
+
+ >>> colours = ["red", "green", "blue", "pink"]
+ >>> widget = RadiobuttonsWidget("foo", allowed_values=colours)
+ >>> print widget.render(request)
+ <input name="foo" type="radio" value="0">red</input>
+ <input name="foo" type="radio" value="1">green</input>
+ <input name="foo" type="radio" value="2">blue</input>
+ <input name="foo" type="radio" value="3">pink</input>
+
+(Note that the actual form values, ie. what the browser returns to the
+server, are always stringified indices into the 'allowed_values' list.
+This is irrelevant to you, since SingleSelectWidget takes care of
+converting ``"1"`` to ``1`` and looking up ``allowed_values[1]``.)
+
+::
+
+ >>> values = [val1, val2, val3]
+ >>> descs = ["thing <b>1</b>",
+ "thing <b>2</b>",
+ "thing <b>3</b>"]
+ >>> widget = RadiobuttonsWidget("bar",
+ allowed_values=values,
+ descriptions=descs,
+ value=val3,
+ delim="<br>\n",
+ quote=0)
+ >>> print widget.render(request)
+ <input name="bar" type="radio" value="0">thing <b>1</b></input><br>
+ <input name="bar" type="radio" value="1">thing <b>2</b></input><br>
+ <input name="bar" type="radio" value="2" checked="checked">thing <b>3</b></input>
+
+
+SingleSelectWidget
+------------------
+
+Used to select a single value from a list that's too long or ungainly
+for a set of radiobuttons. (Most browsers implement this as a scrolling
+list; UNIX versions of Netscape 4.x and earlier used a pop-up menu.)
+The value can be any Python object; ``parse()`` will return either
+``None`` or one of the values you supply to the constructor as
+``allowed_values``. Generates a ``<select>...</select>`` tag, with one
+``<option>`` tag for each element of ``allowed_values``.
+
+Constructor
+~~~~~~~~~~~
+::
+
+ SingleSelectWidget(name : string,
+ value : any = None,
+ allowed_values : [any] = None,
+ descriptions : [string] = map(str, allowed_values),
+ quote : boolean = true,
+ size : int = None)
+
+``allowed_values``
+ determines the set of ``<option>`` tags that will go inside the
+ ``<select>`` tag; these can be any Python values (not just strings).
+ ``parse()`` will return either one of the ``allowed_values`` or ``None``.
+ If you supply a ``value`` that is not in ``allowed_values``, it
+ will be ignored.
+
+``descriptions``
+ (same as RadiobuttonsWidget above)
+
+``quote``
+ (same as RadiobuttonsWidget above)
+
+``size``
+ corresponds to the ``size`` attribute of the ``<select>`` tag: ask
+ the browser to show a select list with ``size`` items visible.
+ Not always respected by the browser; consult an HTML book.
+
+Examples
+~~~~~~~~
+::
+
+ >>> widget = SingleSelectWidget("foo",
+ allowed_values=["abc", 123, 5.5])
+ >>> print widget.render(request)
+ <select name="foo">
+ <option value="0">abc
+ <option value="1">123
+ <option value="2">5.5
+ </select>
+
+ >>> widget = SingleSelectWidget("bar",
+ value=val2,
+ allowed_values=[val1, val2, val3],
+ descriptions=["foo", "bar", "foo & bar"],
+ size=3)
+ >>> print widget.render(request)
+ <select name="bar" size="3">
+ <option value="0">foo
+ <option selected value="1">bar
+ <option value="2">foo & bar
+ </select>
+
+
+MultipleSelectWidget
+--------------------
+
+Used to select multiple values from a list. Everything is just like
+SingleSelectWidget, except that ``value`` can be a list of objects
+selected from ``allowed_values`` (in which case every object in ``value``
+will initially be selected). Generates a ``<select multiple>...</select>``
+tag, with one ``<option>`` tag for each element of ``allowed_values``.
+
+Constructor
+~~~~~~~~~~~
+::
+
+ MultipleSelectWidget(name : string,
+ value : any | [any] = None,
+ allowed_values : [any] = None,
+ descriptions : [string] = map(str, allowed_values),
+ quote : boolean = true,
+ size : int = None)
+
+
+SubmitButtonWidget
+------------------
+
+Used for generating submit buttons. Note that HTML submit buttons are
+rather weird, and Quixote preserves this weirdness -- the Widget classes
+are meant to be a fairly thin wrapper around HTML form elements, after
+all.
+
+In particular, the widget value for a submit button controls two things:
+what the user sees in their browser (the text in the button) and what
+the browser returns as the value for that form element. You can't
+control the two separately, as you can with radiobuttons or selection
+widgets.
+
+Also, SubmitButtonWidget is the only widget with an optional ``name``.
+In many simple forms, all you care about is the fact that the form was
+submitted -- which submit button the user used doesn't matter.
+
+Constructor
+~~~~~~~~~~~
+::
+
+ SubmitButtonWidget(name : string = None,
+ value : string = None)
+
+``value``
+ the text that will be shown in the user's browser, *and* the
+ value that will be returned for this form element (widget)
+ if the user selects this submit button.
+
+Examples
+~~~~~~~~
+
+ >>> SubmitButtonWidget(value="Submit Form").render(request)
+ '<input type="submit" value="Submit Form">'
+
+
+HiddenWidget
+------------
+
+Used to generate HTML hidden widgets, which can be useful for carrying
+around non-sensitive application state. (The Quixote form framework
+uses hidden widgets for form tokens as a measure against cross-site
+request forgery [CSRF] attacks. So by "sensitive" I mean "information
+which should not be revealed", rather than "security-related". If you
+wouldn't put it in a cookie or in email, don't put it in a hidden form
+element.)
+
+Constructor
+~~~~~~~~~~~
+::
+
+ HiddenWidget(name : string,
+ value : string)
+
+Examples
+~~~~~~~~
+::
+
+ >>> HiddenWidget("form_id", "2452345135").render(request)
+ '<input type="hidden" name="form_id" value="2452345135">'
+
+
+IntWidget
+---------
+
+The first derived widget class: this is a subclass of StringWidget
+specifically for entering integer values. As such, this is the first
+widget class we've covered that can reject certain user input. (The
+selection widgets all have to validate their input in case of broken or
+malicious clients, but they just drop bogus values.) If the user enters
+a string that Python's built-in ``int()`` can't convert to an integer,
+IntWidget's ``parse()`` method raises FormValueError (also defined in
+the quixote.form.widget module). This exception is handled by Quixote's
+form framework, but if you're using widget objects on their own, you'll
+have to handle it yourself.
+
+``IntWidget.parse()`` always returns an integer or ``None``.
+
+Constructor
+~~~~~~~~~~~
+::
+
+ IntWidget(name : string,
+ value : int = None,
+ size : int = None,
+ maxlength : int = None)
+
+Constructor arguments are as for StringWidget, except that ``value``
+must be an integer (or ``None``). Note that ``size`` and
+``maxlength`` have exactly the same meaning: they control the size of
+the input widget and the maximum number of characters of input.
+
+[Examples]
+
+ >>> IntWidget("num", value=37, size=5).render(request)
+ '<input type="string" name="num" value="37" size="5">'
+
+
+FloatWidget
+-----------
+
+FloatWidget is identical to IntWidget, except:
+
+* ``value`` must be a float
+* ``parse()`` returns a float or ``None``
+* ``parse()`` raises FormValueError if the string entered by the
+ user cannot be converted by Python's built-in ``float()`` function
+
+
+OptionSelectWidget
+------------------
+
+OptionSelectWidget is simply a SingleSelectWidget that uses a bit of
+Javascript to automatically submit the current form as soon as the user
+selects a value. This is useful for very simple one-element forms where
+you don't want to bother with a submit button, or for very complex forms
+where you need to revamp the user interface based on a user's selection.
+Your form-processing code could then detect that style of form
+submission, and regenerate a slightly different form for the user. (Or
+you could treat it as a full-blown form submission, if the only widget
+of interest is the OptionSelectWidget.)
+
+For example, if you're asking a user for their address, some of the
+details will vary depending on which country they're in. You might make
+the country widget an OptionSelectWidget: if the user selects "Canada",
+you'll ask them for a province and a postal code; if they select "United
+States", you ask for a state and a zip code; and so forth. (I don't
+really recommend a user interface that works this way: you'll spend way
+too much time getting the details right ["How many states does Australia
+have again?"], and you're bound to get something wrong -- there are over
+200 countries in the world, after all.)
+
+Be warned that since OptionSelectWidget relies on Javascript to work,
+using it makes immediately makes your application less portable and more
+fragile. One thing to avoid: form elements with a name of ``submit``,
+since that masks the Javascript function called by OptionSelectWidget.
+
+
+$Id: widgets.txt 20217 2003-01-16 20:51:53Z akuchlin $
Added: packages/quixote1/branches/upstream/current/errors.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/errors.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/errors.py (added)
+++ packages/quixote1/branches/upstream/current/errors.py Mon May 8 19:16:16 2006
@@ -1,0 +1,170 @@
+"""quixote.errors
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/errors.py $
+$Id: errors.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Exception classes used by Quixote
+"""
+from quixote.html import htmltext, htmlescape
+
+__revision__ = "$Id: errors.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+class PublishError(Exception):
+ """PublishError exceptions are raised due to some problem with the
+ data provided by the client and are raised during the publishing
+ process. Quixote will abort the current request and return an error
+ page to the client.
+
+ public_msg should be a user-readable message that reveals no
+ inner workings of your application; it will always be shown.
+
+ private_msg will only be shown if the config option SECURE_ERRORS is
+ false; Quixote uses this to give you more detail about why the error
+ occurred. You might want to use it for similar, application-specific
+ information. (SECURE_ERRORS should always be true in a production
+ environment, since these details about the inner workings of your
+ application could conceivably be useful to attackers.)
+
+ The formatting done by the Quixote versions of these exceptions is
+ very simple. Applications will probably wish to raise application
+ specific subclasses which do more sophisticated formatting or provide
+ a _q_except handler to format the exception.
+
+ """
+
+ status_code = 400 # bad request
+ title = "Publishing error"
+ description = "no description"
+
+ def __init__(self, public_msg=None, private_msg=None):
+ self.public_msg = public_msg
+ self.private_msg = private_msg # cleared if SECURE_ERRORS is true
+
+ def __str__(self):
+ return self.private_msg or self.public_msg or "???"
+
+ def format(self, request):
+ msg = htmlescape(self.title)
+ if not isinstance(self.title, htmltext):
+ msg = str(msg) # for backwards compatibility
+ if self.public_msg:
+ msg = msg + ": " + self.public_msg
+ if self.private_msg:
+ msg = msg + ": " + self.private_msg
+ return msg
+
+
+class TraversalError(PublishError):
+ """
+ Raised when a client attempts to access a resource that does not
+ exist or is otherwise unavailable to them (eg. a Python function
+ not listed in its module's _q_exports list).
+
+ path should be the path to the requested resource; if not
+ supplied, the current request object will be fetched and its
+ get_path() method called.
+ """
+
+ status_code = 404 # not found
+ title = "Page not found"
+ description = ("The requested link does not exist on this site. If "
+ "you arrived here by following a link from an external "
+ "page, please inform that page's maintainer.")
+
+ def __init__(self, public_msg=None, private_msg=None, path=None):
+ PublishError.__init__(self, public_msg, private_msg)
+ if path is None:
+ import quixote
+ path = quixote.get_request().get_path()
+ self.path = path
+
+ def format(self, request):
+ msg = htmlescape(self.title) + ": " + self.path
+ if not isinstance(self.title, htmltext):
+ msg = str(msg) # for backwards compatibility
+ if self.public_msg:
+ msg = msg + ": " + self.public_msg
+ if self.private_msg:
+ msg = msg + ": " + self.private_msg
+ return msg
+
+class RequestError(PublishError):
+ """
+ Raised when Quixote is unable to parse an HTTP request (or its CGI
+ representation). This is a lower-level error than QueryError -- it
+ either means that Quixote is not smart enough to handle the request
+ being passed to it, or the user-agent is broken and/or malicious.
+ """
+ status_code = 400
+ title = "Invalid request"
+ description = "Unable to parse HTTP request."
+
+
+class QueryError(PublishError):
+ """Should be raised if bad data was provided in the query part of a
+ URL or in the content of a POST request. What constitutes bad data is
+ solely application dependent (eg: letters in a form field when the
+ application expects a number).
+ """
+
+ status_code = 400
+ title = "Invalid query"
+ description = ("An error occurred while handling your request. The "
+ "query data provided as part of the request is invalid.")
+
+
+
+class AccessError(PublishError):
+ """Should be raised if the client does not have access to the
+ requested resource. Usually applications will raise this error from
+ an _q_access method.
+ """
+
+ status_code = 403
+ title = "Access denied"
+ description = ("An error occurred while handling your request. "
+ "Access to the requested resource was not permitted.")
+
+
+
+class SessionError(PublishError):
+ """Raised when a session cookie has an invalid session ID. This
+ could be either a broken/malicious client or an expired session.
+ """
+
+ status_code = 400
+ title = "Expired or invalid session"
+ description = ("Your session is invalid or has expired. "
+ "Please reload this page to start a new session.")
+
+ def __init__(self, public_msg=None, private_msg=None, session_id=None):
+ PublishError.__init__(self, public_msg, private_msg)
+ self.session_id = session_id
+
+ def format(self, request):
+ from quixote import get_session_manager
+ get_session_manager().revoke_session_cookie(request)
+ msg = PublishError.format(self, request)
+ if self.session_id:
+ msg = msg + ": " + self.session_id
+ return msg
+
+
+def default_exception_handler(request, exc):
+ """(request : HTTPRequest, exc : PublishError) -> string
+
+ Format a PublishError exception as a web page. This is the default
+ handler called if no '_q_exception_handler' function was found while
+ traversing the path.
+ """
+ return htmltext("""\
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"
+ "http://www.w3.org/TR/REC-html40/strict.dtd">
+ <html>
+ <head><title>Error: %s</title></head>
+ <body>
+ <p>%s</p>
+ <p>%s</p>
+ </body>
+ </html>
+ """) % (exc.title, exc.description, exc.format(request))
Added: packages/quixote1/branches/upstream/current/fcgi.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/fcgi.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/fcgi.py (added)
+++ packages/quixote1/branches/upstream/current/fcgi.py Mon May 8 19:16:16 2006
@@ -1,0 +1,462 @@
+#!/usr/local/bin/python1.5
+#------------------------------------------------------------------------
+# Copyright (c) 1998 by Total Control Software
+# All Rights Reserved
+#------------------------------------------------------------------------
+#
+# Module Name: fcgi.py
+#
+# Description: Handles communication with the FastCGI module of the
+# web server without using the FastCGI developers kit, but
+# will also work in a non-FastCGI environment, (straight CGI.)
+# This module was originally fetched from someplace on the
+# Net (I don't remember where and I can't find it now...) and
+# has been significantly modified to fix several bugs, be more
+# readable, more robust at handling large CGI data and return
+# document sizes, and also to fit the model that we had previously
+# used for FastCGI.
+#
+# WARNING: If you don't know what you are doing, don't tinker with this
+# module!
+#
+# Creation Date: 1/30/98 2:59:04PM
+#
+# License: This is free software. You may use this software for any
+# purpose including modification/redistribution, so long as
+# this header remains intact and that you do not claim any
+# rights of ownership or authorship of this software. This
+# software has been tested, but no warranty is expressed or
+# implied.
+#
+#------------------------------------------------------------------------
+
+__revision__ = "$Id: fcgi.py 20729 2003-02-12 22:00:03Z nascheme $"
+
+
+import os, sys, string, socket, errno, struct
+from cStringIO import StringIO
+import cgi
+
+#---------------------------------------------------------------------------
+
+# Set various FastCGI constants
+# Maximum number of requests that can be handled
+FCGI_MAX_REQS=1
+FCGI_MAX_CONNS = 1
+
+# Supported version of the FastCGI protocol
+FCGI_VERSION_1 = 1
+
+# Boolean: can this application multiplex connections?
+FCGI_MPXS_CONNS=0
+
+# Record types
+FCGI_BEGIN_REQUEST = 1 ; FCGI_ABORT_REQUEST = 2 ; FCGI_END_REQUEST = 3
+FCGI_PARAMS = 4 ; FCGI_STDIN = 5 ; FCGI_STDOUT = 6
+FCGI_STDERR = 7 ; FCGI_DATA = 8 ; FCGI_GET_VALUES = 9
+FCGI_GET_VALUES_RESULT = 10
+FCGI_UNKNOWN_TYPE = 11
+FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
+
+# Types of management records
+ManagementTypes = [FCGI_GET_VALUES]
+
+FCGI_NULL_REQUEST_ID = 0
+
+# Masks for flags component of FCGI_BEGIN_REQUEST
+FCGI_KEEP_CONN = 1
+
+# Values for role component of FCGI_BEGIN_REQUEST
+FCGI_RESPONDER = 1 ; FCGI_AUTHORIZER = 2 ; FCGI_FILTER = 3
+
+# Values for protocolStatus component of FCGI_END_REQUEST
+FCGI_REQUEST_COMPLETE = 0 # Request completed nicely
+FCGI_CANT_MPX_CONN = 1 # This app can't multiplex
+FCGI_OVERLOADED = 2 # New request rejected; too busy
+FCGI_UNKNOWN_ROLE = 3 # Role value not known
+
+
+error = 'fcgi.error'
+
+
+#---------------------------------------------------------------------------
+
+# The following function is used during debugging; it isn't called
+# anywhere at the moment
+
+def error(msg):
+ "Append a string to /tmp/err"
+ errf = open('/tmp/err', 'a+')
+ errf.write(msg+'\n')
+ errf.close()
+
+#---------------------------------------------------------------------------
+
+class record:
+ "Class representing FastCGI records"
+ def __init__(self):
+ self.version = FCGI_VERSION_1
+ self.recType = FCGI_UNKNOWN_TYPE
+ self.reqId = FCGI_NULL_REQUEST_ID
+ self.content = ""
+
+ #----------------------------------------
+ def readRecord(self, sock, unpack=struct.unpack):
+ (self.version, self.recType, self.reqId, contentLength,
+ paddingLength) = unpack(">BBHHBx", sock.recv(8))
+
+ content = ""
+ while len(content) < contentLength:
+ content = content + sock.recv(contentLength - len(content))
+ self.content = content
+
+ if paddingLength != 0:
+ padding = sock.recv(paddingLength)
+
+ # Parse the content information
+ if self.recType == FCGI_BEGIN_REQUEST:
+ (self.role, self.flags) = unpack(">HB", content[:3])
+
+ elif self.recType == FCGI_UNKNOWN_TYPE:
+ self.unknownType = ord(content[0])
+
+ elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
+ self.values = {}
+ pos = 0
+ while pos < len(content):
+ name, value, pos = readPair(content, pos)
+ self.values[name] = value
+
+ elif self.recType == FCGI_END_REQUEST:
+ (self.appStatus, self.protocolStatus) = unpack(">IB", content[0:5])
+
+ #----------------------------------------
+ def writeRecord(self, sock, pack=struct.pack):
+ content = self.content
+ if self.recType == FCGI_BEGIN_REQUEST:
+ content = pack(">HBxxxxx", self.role, self.flags)
+
+ elif self.recType == FCGI_UNKNOWN_TYPE:
+ content = pack(">Bxxxxxx", self.unknownType)
+
+ elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
+ content = ""
+ for i in self.values.keys():
+ content = content + writePair(i, self.values[i])
+
+ elif self.recType == FCGI_END_REQUEST:
+ content = pack(">IBxxx", self.appStatus, self.protocolStatus)
+
+ cLen = len(content)
+ eLen = (cLen + 7) & (0xFFFF - 7) # align to an 8-byte boundary
+ padLen = eLen - cLen
+
+ hdr = pack(">BBHHBx", self.version, self.recType, self.reqId, cLen,
+ padLen)
+
+ ##debug.write('Sending fcgi record: %s\n' % repr(content[:50]) )
+ sock.send(hdr + content + padLen*'\000')
+
+#---------------------------------------------------------------------------
+
+_lowbits = ~(1L << 31) # everything but the 31st bit
+
+def readPair(s, pos):
+ nameLen = ord(s[pos]) ; pos = pos+1
+ if nameLen & 128:
+ pos = pos + 3
+ nameLen = int(struct.unpack(">I", s[pos-4:pos])[0] & _lowbits)
+ valueLen = ord(s[pos]) ; pos = pos+1
+ if valueLen & 128:
+ pos = pos + 3
+ valueLen = int(struct.unpack(">I", s[pos-4:pos])[0] & _lowbits)
+ return ( s[pos:pos+nameLen], s[pos+nameLen:pos+nameLen+valueLen],
+ pos+nameLen+valueLen )
+
+#---------------------------------------------------------------------------
+
+_highbit = (1L << 31)
+
+def writePair(name, value):
+ l = len(name)
+ if l < 128:
+ s = chr(l)
+ else:
+ s = struct.pack(">I", l | _highbit)
+ l = len(value)
+ if l < 128:
+ s = s + chr(l)
+ else:
+ s = s + struct.pack(">I", l | _highbit)
+ return s + name + value
+
+#---------------------------------------------------------------------------
+
+def HandleManTypes(r, conn):
+ if r.recType == FCGI_GET_VALUES:
+ r.recType = FCGI_GET_VALUES_RESULT
+ v = {}
+ vars = {'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
+ 'FCGI_MAX_REQS' : FCGI_MAX_REQS,
+ 'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
+ for i in r.values.keys():
+ if vars.has_key(i): v[i] = vars[i]
+ r.values = vars
+ r.writeRecord(conn)
+
+#---------------------------------------------------------------------------
+#---------------------------------------------------------------------------
+
+
+_isFCGI = 1 # assume it is until we find out for sure
+
+def isFCGI():
+ return _isFCGI
+
+
+
+#---------------------------------------------------------------------------
+
+
+_init = None
+_sock = None
+
+class FCGI:
+ def __init__(self):
+ self.haveFinished = 0
+ if _init == None:
+ _startup()
+ if not _isFCGI:
+ self.haveFinished = 1
+ self.inp = sys.__stdin__
+ self.out = sys.__stdout__
+ self.err = sys.__stderr__
+ self.env = os.environ
+ return
+
+ if os.environ.has_key('FCGI_WEB_SERVER_ADDRS'):
+ good_addrs = string.split(os.environ['FCGI_WEB_SERVER_ADDRS'], ',')
+ good_addrs = map(string.strip, good_addrs) # Remove whitespace
+ else:
+ good_addrs = None
+
+ self.conn, addr = _sock.accept()
+ stdin, data = "", ""
+ self.env = {}
+ self.requestId = 0
+ remaining = 1
+
+ # Check if the connection is from a legal address
+ if good_addrs != None and addr not in good_addrs:
+ raise error, 'Connection from invalid server!'
+
+ while remaining:
+ r = record()
+ r.readRecord(self.conn)
+
+ if r.recType in ManagementTypes:
+ HandleManTypes(r, self.conn)
+
+ elif r.reqId == 0:
+ # Oh, poopy. It's a management record of an unknown
+ # type. Signal the error.
+ r2 = record()
+ r2.recType = FCGI_UNKNOWN_TYPE
+ r2.unknownType = r.recType
+ r2.writeRecord(self.conn)
+ continue # Charge onwards
+
+ # Ignore requests that aren't active
+ elif r.reqId != self.requestId and r.recType != FCGI_BEGIN_REQUEST:
+ continue
+
+ # If we're already doing a request, ignore further BEGIN_REQUESTs
+ elif r.recType == FCGI_BEGIN_REQUEST and self.requestId != 0:
+ continue
+
+ # Begin a new request
+ if r.recType == FCGI_BEGIN_REQUEST:
+ self.requestId = r.reqId
+ if r.role == FCGI_AUTHORIZER: remaining = 1
+ elif r.role == FCGI_RESPONDER: remaining = 2
+ elif r.role == FCGI_FILTER: remaining = 3
+
+ elif r.recType == FCGI_PARAMS:
+ if r.content == "":
+ remaining = remaining-1
+ else:
+ for i in r.values.keys():
+ self.env[i] = r.values[i]
+
+ elif r.recType == FCGI_STDIN:
+ if r.content == "":
+ remaining = remaining-1
+ else:
+ stdin = stdin+r.content
+
+ elif r.recType == FCGI_DATA:
+ if r.content == "":
+ remaining = remaining-1
+ else:
+ data = data+r.content
+ # end of while remaining:
+
+ self.inp = StringIO(stdin)
+ self.err = StringIO()
+ self.out = StringIO()
+ self.data = StringIO(data)
+
+ def __del__(self):
+ self.Finish()
+
+ def Finish(self, status=0):
+ if not self.haveFinished:
+ self.haveFinished = 1
+
+ self.err.seek(0,0)
+ self.out.seek(0,0)
+
+ ##global debug
+ ##debug = open("/tmp/quixote-debug.log", "a+")
+ ##debug.write("fcgi.FCGI.Finish():\n")
+
+ r = record()
+ r.recType = FCGI_STDERR
+ r.reqId = self.requestId
+ data = self.err.read()
+ ##debug.write(" sending stderr (%s)\n" % `self.err`)
+ ##debug.write(" data = %s\n" % `data`)
+ while data:
+ chunk, data = self.getNextChunk(data)
+ ##debug.write(" chunk, data = %s, %s\n" % (`chunk`, `data`))
+ r.content = chunk
+ r.writeRecord(self.conn)
+ r.content = ""
+ r.writeRecord(self.conn) # Terminate stream
+
+ r.recType = FCGI_STDOUT
+ data = self.out.read()
+ ##debug.write(" sending stdout (%s)\n" % `self.out`)
+ ##debug.write(" data = %s\n" % `data`)
+ while data:
+ chunk, data = self.getNextChunk(data)
+ r.content = chunk
+ r.writeRecord(self.conn)
+ r.content = ""
+ r.writeRecord(self.conn) # Terminate stream
+
+ r = record()
+ r.recType = FCGI_END_REQUEST
+ r.reqId = self.requestId
+ r.appStatus = status
+ r.protocolStatus = FCGI_REQUEST_COMPLETE
+ r.writeRecord(self.conn)
+ self.conn.close()
+
+ #debug.close()
+
+
+ def getFieldStorage(self):
+ method = 'GET'
+ if self.env.has_key('REQUEST_METHOD'):
+ method = string.upper(self.env['REQUEST_METHOD'])
+ if method == 'GET':
+ return cgi.FieldStorage(environ=self.env, keep_blank_values=1)
+ else:
+ return cgi.FieldStorage(fp=self.inp,
+ environ=self.env,
+ keep_blank_values=1)
+
+ def getNextChunk(self, data):
+ chunk = data[:8192]
+ data = data[8192:]
+ return chunk, data
+
+
+Accept = FCGI # alias for backwards compatibility
+#---------------------------------------------------------------------------
+
+def _startup():
+ global _isFCGI, _init, _sock
+ # This function won't work on Windows at all.
+ if sys.platform[:3] == 'win':
+ _isFCGI = 0
+ return
+
+ _init = 1
+ try:
+ s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
+ socket.SOCK_STREAM)
+ s.getpeername()
+ except socket.error, (err, errmsg):
+ if err != errno.ENOTCONN: # must be a non-fastCGI environment
+ _isFCGI = 0
+ return
+
+ _sock = s
+
+
+#---------------------------------------------------------------------------
+
+def _test():
+ counter = 0
+ try:
+ while isFCGI():
+ req = Accept()
+ counter = counter+1
+
+ try:
+ fs = req.getFieldStorage()
+ size = string.atoi(fs['size'].value)
+ doc = ['*' * size]
+ except:
+ doc = ['<HTML><HEAD>'
+ '<TITLE>FCGI TestApp</TITLE>'
+ '</HEAD>\n<BODY>\n']
+ doc.append('<H2>FCGI TestApp</H2><P>')
+ doc.append('<b>request count</b> = %d<br>' % counter)
+ doc.append('<b>pid</b> = %s<br>' % os.getpid())
+ if req.env.has_key('CONTENT_LENGTH'):
+ cl = string.atoi(req.env['CONTENT_LENGTH'])
+ doc.append('<br><b>POST data (%s):</b><br><pre>' % cl)
+ keys = fs.keys()
+ keys.sort()
+ for k in keys:
+ val = fs[k]
+ if type(val) == type([]):
+ doc.append(' <b>%-15s :</b> %s\n'
+ % (k, val))
+ else:
+ doc.append(' <b>%-15s :</b> %s\n'
+ % (k, val.value))
+ doc.append('</pre>')
+
+
+ doc.append('<P><HR><P><pre>')
+ keys = req.env.keys()
+ keys.sort()
+ for k in keys:
+ doc.append('<b>%-20s :</b> %s\n' % (k, req.env[k]))
+ doc.append('\n</pre><P><HR>\n')
+ doc.append('</BODY></HTML>\n')
+
+
+ doc = string.join(doc, '')
+ req.out.write('Content-length: %s\r\n'
+ 'Content-type: text/html\r\n'
+ 'Cache-Control: no-cache\r\n'
+ '\r\n'
+ % len(doc))
+ req.out.write(doc)
+
+ req.Finish()
+ except:
+ import traceback
+ f = open('traceback', 'w')
+ traceback.print_exc( file = f )
+# f.write('%s' % doc)
+
+if __name__ == '__main__':
+ #import pdb
+ #pdb.run('_test()')
+ _test()
Added: packages/quixote1/branches/upstream/current/form/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/form/__init__.py Mon May 8 19:16:16 2006
@@ -1,0 +1,37 @@
+"""quixote.form
+
+The web interface framework, consisting of Form and Widget base classes
+(and a bunch of standard widget classes recognized by Form).
+Application developers will typically create a Form subclass for each
+form in their application; each form object will contain a number
+of widget objects. Custom widgets can be created by inheriting
+and/or composing the standard widget classes.
+"""
+
+# created 2000/09/19 - 22, GPW
+
+__revision__ = "$Id: __init__.py 22253 2003-08-13 20:15:01Z rmasse $"
+
+from quixote.form.form import Form, register_widget_class, FormTokenWidget
+from quixote.form.widget import Widget, StringWidget, FileWidget, \
+ PasswordWidget, TextWidget, CheckboxWidget, RadiobuttonsWidget, \
+ SingleSelectWidget, SelectWidget, OptionSelectWidget, \
+ MultipleSelectWidget, ListWidget, SubmitButtonWidget, HiddenWidget, \
+ FloatWidget, IntWidget, CollapsibleListWidget, FormValueError
+
+# Register the standard widget classes
+register_widget_class(StringWidget)
+register_widget_class(FileWidget)
+register_widget_class(PasswordWidget)
+register_widget_class(TextWidget)
+register_widget_class(CheckboxWidget)
+register_widget_class(RadiobuttonsWidget)
+register_widget_class(SingleSelectWidget)
+register_widget_class(OptionSelectWidget)
+register_widget_class(MultipleSelectWidget)
+register_widget_class(ListWidget)
+register_widget_class(SubmitButtonWidget)
+register_widget_class(HiddenWidget)
+register_widget_class(FloatWidget)
+register_widget_class(IntWidget)
+register_widget_class(CollapsibleListWidget)
Added: packages/quixote1/branches/upstream/current/form/form.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form/form.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form/form.py (added)
+++ packages/quixote1/branches/upstream/current/form/form.py Mon May 8 19:16:16 2006
@@ -1,0 +1,535 @@
+"""quixote.form.form
+
+Provides the Form class and bureaucracy for registering widget classes.
+(The standard widget classes are registered automatically.)
+"""
+
+__revision__ = "$Id: form.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+from types import StringType
+from quixote import get_session, get_publisher
+from quixote.html import url_quote, htmltag, htmltext, nl2br, TemplateIO
+from quixote.form.widget import FormValueError, HiddenWidget
+
+
+class FormTokenWidget (HiddenWidget):
+ def render(self, request):
+ self.value = get_session().create_form_token()
+ return HiddenWidget.render(self, request)
+
+
+JAVASCRIPT_MARKUP = htmltext('''\
+<script type="text/javascript">
+<!--
+%s
+// -->
+</script>
+''')
+
+class Form:
+ """
+ A form is the major element of an interactive web page. A form
+ consists of the following:
+ * widgets (input/interaction elements)
+ * text
+ * layout
+ * code to process the form
+
+ All four of these are the responsibility of Form classes.
+ Typically, you will create one Form subclass for each form in your
+ application. Thanks to the separation of responsibilities here,
+ it's not too hard to structure things so that a given form is
+ rendered and/or processed somewhat differently depending on context.
+ That separation is as follows:
+ * the constructor declares what widgets are in the form, and
+ any static text that is always associated with those widgets
+ (in particular, a widget title and "hint" text)
+ * the 'render()' method combines the widgets and their associated
+ text to create a (1-D) stream of HTML that represents the
+ (2-D) web page that will be presented to the user
+ * the 'process()' method parses the user input values from the form
+ and validates them
+ * the 'action()' method takes care of finishing whatever action
+ was requested by the user submitting the form -- commit
+ a database transaction, update session flags, redirect the
+ user to a new page, etc.
+
+ This class provides a default 'process()' method that just parses
+ each widget, storing any error messages for display on the next
+ 'render()', and returns the results (if the form parses
+ successfully) in a dictionary.
+
+ This class also provides a default 'render()' method that lays out
+ widgets and text in a 3-column table: the first column is the widget
+ title, the second column is the widget itself, and the third column is
+ any hint and/or error text associated with the widget. Also provided
+ are methods that can be used to construct this table a row at a time,
+ so you can use this layout for most widgets, but escape from it for
+ oddities.
+
+ Instance attributes:
+ widgets : { widget_name:string : widget:Widget }
+ dictionary of all widgets in the form
+ widget_order : [Widget]
+ same widgets as 'widgets', but ordered (because order matters)
+ submit_buttons : [SubmitButtonWidget]
+ the submit button widgets in the form
+
+ error : { widget_name:string : error_message:string }
+ hint : { widget_name:string : hint_text:string }
+ title : { widget_name:string : widget_title:string }
+ required : { widget_name:string : boolean }
+
+ """
+
+ TOKEN_NAME = "_form_id" # name of hidden token widget
+
+ def __init__(self, method="post", enctype=None, use_tokens=1):
+
+ if method not in ("post", "get"):
+ raise ValueError("Form method must be 'post' or 'get', "
+ "not %r" % method)
+ self.method = method
+
+ if enctype is not None and enctype not in (
+ "application/x-www-form-urlencoded", "multipart/form-data"):
+ raise ValueError, ("Form enctype must be "
+ "'application/x-www-form-urlencoded' or "
+ "'multipart/form-data', not %r" % enctype)
+ self.enctype = enctype
+
+ # The first major component of a form: its widgets. We want
+ # both easy access and order, so we have a dictionary and a list
+ # of the same objects. The dictionary is keyed on widget name.
+ # These are populated by the 'add_*_widget()' methods.
+ self.widgets = {}
+ self.widget_order = []
+ self.submit_buttons = []
+ self.cancel_url = None
+
+ # The second major component: text. It's up to the 'render()'
+ # method to figure out how to lay these out; the standard
+ # 'render()' does so in a fairly sensible way that should work
+ # for most of our forms. These are also populated by the
+ # 'add_*_widget()' methods.
+ self.error = {}
+ self.hint = {}
+ self.title = {}
+ self.required = {}
+
+ config = get_publisher().config
+ if self.method == "post" and use_tokens and config.form_tokens:
+ # unique token for each form, this prevents many cross-site
+ # attacks and prevents a form from being submitted twice
+ self.add_widget(FormTokenWidget, self.TOKEN_NAME)
+ self.use_form_tokens = 1
+ else:
+ self.use_form_tokens = 0
+
+ # Subclasses should override this method to specify the actual
+ # widgets in this form -- typically this consists of a series of
+ # calls to 'add_widget()', which updates the data structures we
+ # just defined.
+
+
+ # -- Layout (rendering) methods ------------------------------------
+
+ # The third major component of a web form is layout. These methods
+ # combine text and widgets in a 1-D stream of HTML, or in a 2-D web
+ # page (depending on your level of abstraction).
+
+ def render(self, request, action_url):
+ # render(request : HTTPRequest,
+ # action_url : string)
+ # -> HTML text
+ #
+ # Render a form as HTML.
+ assert type(action_url) in (StringType, htmltext)
+ r = TemplateIO(html=1)
+ r += self._render_start(request, action_url,
+ enctype=self.enctype, method=self.method)
+ r += self._render_body(request)
+ r += self._render_finish(request)
+ return r.getvalue()
+
+ def _render_start(self, request, action,
+ enctype=None, method='post', name=None):
+ r = TemplateIO(html=1)
+ r += htmltag('form', enctype=enctype, method=method,
+ action=action, name=name)
+ r += self._render_hidden_widgets(request)
+ return r.getvalue()
+
+ def _render_finish(self, request):
+ r = TemplateIO(html=1)
+ r += htmltext('</form>')
+ r += self._render_javascript(request)
+ return r.getvalue()
+
+ def _render_sep(self, text, line=1):
+ return htmltext('<tr><td colspan="3">%s<strong><big>%s'
+ '</big></strong></td></tr>') % \
+ (line and htmltext('<hr>') or '', text)
+
+ def _render_error(self, error):
+ if error:
+ return htmltext('<font color="red">%s</font><br />') % nl2br(error)
+ else:
+ return ''
+
+ def _render_hint(self, hint):
+ if hint:
+ return htmltext('<em>%s</em>') % hint
+ else:
+ return ''
+
+ def _render_widget_row(self, request, widget):
+ if widget.widget_type == 'hidden':
+ return ''
+ title = self.title[widget.name] or ''
+ if self.required.get(widget.name):
+ title = title + htmltext(' *')
+ r = TemplateIO(html=1)
+ r += htmltext('<tr><th colspan="3" align="left">')
+ r += title
+ r += htmltext('</th></tr>'
+ '<tr><td> </td><td>')
+ r += widget.render(request)
+ r += htmltext('</td><td>')
+ r += self._render_error(self.error.get(widget.name))
+ r += self._render_hint(self.hint.get(widget.name))
+ r += htmltext('</td></tr>')
+ return r.getvalue()
+
+ def _render_hidden_widgets(self, request):
+ r = TemplateIO(html=1)
+ for widget in self.widget_order:
+ if widget.widget_type == 'hidden':
+ r += widget.render(request)
+ r += self._render_error(self.error.get(widget.name))
+ return r.getvalue()
+
+ def _render_submit_buttons(self, request, ncols=3):
+ r = TemplateIO(html=1)
+ r += htmltext('<tr><td colspan="%d">\n') % ncols
+ for button in self.submit_buttons:
+ r += button.render(request)
+ r += htmltext('</td></tr>')
+ return r.getvalue()
+
+ def _render_visible_widgets(self, request):
+ r = TemplateIO(html=1)
+ for widget in self.widget_order:
+ r += self._render_widget_row(request, widget)
+ return r.getvalue()
+
+ def _render_error_notice(self, request):
+ if self.error:
+ r = htmltext('<tr><td colspan="3">'
+ '<font color="red"><strong>Warning:</strong></font> '
+ 'there were errors processing your form. '
+ 'See below for details.'
+ '</td></tr>')
+ else:
+ r = ''
+ return r
+
+ def _render_required_notice(self, request):
+ if filter(None, self.required.values()):
+ r = htmltext('<tr><td colspan="3">'
+ '<b>*</b> = <em>required field</em>'
+ '</td></tr>')
+ else:
+ r = ''
+ return r
+
+ def _render_body(self, request):
+ r = TemplateIO(html=1)
+ r += htmltext('<table>')
+ r += self._render_error_notice(request)
+ r += self._render_required_notice(request)
+ r += self._render_visible_widgets(request)
+ r += self._render_submit_buttons(request)
+ r += htmltext('</table>')
+ return r.getvalue()
+
+ def _render_javascript(self, request):
+ """Render javacript code for the form, if any.
+ Insert code lexically sorted by code_id
+ """
+ javascript_code = request.response.javascript_code
+ if javascript_code:
+ form_code = []
+ code_ids = javascript_code.keys()
+ code_ids.sort()
+ for code_id in code_ids:
+ code = javascript_code[code_id]
+ if code:
+ form_code.append(code)
+ javascript_code[code_id] = ''
+ if form_code:
+ return JAVASCRIPT_MARKUP % htmltext(''.join(form_code))
+ return ''
+
+
+ # -- Processing methods --------------------------------------------
+
+ # The fourth and final major component: code to process the form.
+ # The standard 'process()' method just parses every widget and
+ # returns a { field_name : field_value } dictionary as 'values'.
+
+ def process(self, request):
+ """process(request : HTTPRequest) -> values : { string : any }
+
+ Process the form data, validating all input fields (widgets).
+ If any errors in input fields, adds error messages to the
+ 'error' attribute (so that future renderings of the form will
+ include the errors). Returns a dictionary mapping widget names to
+ parsed values.
+ """
+ self.error.clear()
+
+ values = {}
+ for widget in self.widget_order:
+ try:
+ val = widget.parse(request)
+ except FormValueError, exc:
+ self.error[widget.name] = exc.msg
+ else:
+ values[widget.name] = val
+
+ return values
+
+ def action(self, request, submit, values):
+ """action(request : HTTPRequest, submit : string,
+ values : { string : any }) -> string
+
+ Carry out the action required by a form submission. 'submit' is the
+ name of submit button used to submit the form. 'values' is the
+ dictionary of parsed values from 'process()'. Note that error
+ checking cannot be done here -- it must done in the 'process()'
+ method.
+ """
+ raise NotImplementedError, "sub-classes must implement 'action()'"
+
+ def handle(self, request):
+ """handle(request : HTTPRequest) -> string
+
+ Master method for handling forms. It should be called after
+ initializing a form. Controls form action based on a request. You
+ probably should override 'process' and 'action' instead of
+ overriding this method.
+ """
+ action_url = self.get_action_url(request)
+ if not self.form_submitted(request):
+ return self.render(request, action_url)
+ submit = self.get_submit_button(request)
+ if submit == "cancel":
+ return request.redirect(self.cancel_url)
+ values = self.process(request)
+ if submit == "":
+ # The form was submitted by unknown submit button, assume that
+ # the submission was required to update the layout of the form.
+ # Clear the errors and re-render the form.
+ self.error.clear()
+ return self.render(request, action_url)
+
+ if self.use_form_tokens:
+ # before calling action() ensure that there is a valid token
+ # present
+ token = values.get(self.TOKEN_NAME)
+ if not request.session.has_form_token(token):
+ if not self.error:
+ # if there are other errors then don't show the token
+ # error, the form needs to be resubmitted anyhow
+ self.error[self.TOKEN_NAME] = (
+ "The form you have submitted is invalid. It has "
+ "already been submitted or has expired. Please "
+ "review and resubmit the form.")
+ else:
+ request.session.remove_form_token(token)
+
+ if self.error:
+ return self.render(request, action_url)
+ else:
+ return self.action(request, submit, values)
+
+
+ # -- Convenience methods -------------------------------------------
+
+ def form_submitted(self, request):
+ """form_submitted(request : HTTPRequest) -> boolean
+
+ Return true if a form was submitted in the current request.
+ """
+ return len(request.form) > 0
+
+ def get_action_url(self, request):
+ action_url = url_quote(request.get_path())
+ query = request.get_environ("QUERY_STRING")
+ if query:
+ action_url += "?" + query
+ return action_url
+
+ def get_submit_button(self, request):
+ """get_submit_button(request : HTTPRequest) -> string | None
+
+ Get the name of the submit button that was used to submit the
+ current form. If the browser didn't include this information in
+ the request, use the first submit button registered.
+ """
+ for button in self.submit_buttons:
+ if request.form.has_key(button.name):
+ return button.name
+ else:
+ if request.form and self.submit_buttons:
+ return ""
+ else:
+ return None
+
+ def get_widget(self, widget_name):
+ return self.widgets.get(widget_name)
+
+ def parse_widget(self, name, request):
+ """parse_widget(name : string, request : HTTPRequest) -> any
+
+ Parse the value of named widget. If any parse errors, store the
+ error message (in self.error) for use in the next rendering of
+ the form and return None; otherwise, return the value parsed
+ from the widget (whose type depends on the widget type).
+ """
+ try:
+ return self.widgets[name].parse(request)
+ except FormValueError, exc:
+ self.error[name] = str(exc)
+ return None
+
+ def store_value(self, widget_name, request, target,
+ mode="modifier",
+ key=None,
+ missing_error=None):
+ """store_value(widget_name : string,
+ request : HTTPRequest,
+ target : instance | dict,
+ mode : string = "modifier",
+ key : string = widget_name,
+ missing_error : string = None)
+
+ Parse a widget and, if it parsed successfully, store its value
+ in 'target'. The value is stored in 'target' by name 'key';
+ if 'key' is not supplied, it defaults to 'widget_name'.
+ How the value is stored depends on 'mode':
+ * modifier: call a modifier method, eg. if 'key' is "foo",
+ call 'target.set_foo(value)'
+ * direct: direct attribute update, eg. if 'key' is
+ "foo" do "target.foo = value"
+ * dict: dictionary update, eg. if 'key' is "foo" do
+ "target['foo'] = value"
+
+ If 'missing_error' is supplied, use it as an error message if
+ the field doesn't have a value -- ie. supplying 'missing_error'
+ means this field is required.
+ """
+ value = self.parse_widget(widget_name, request)
+ if (value is None or value == "") and missing_error:
+ self.error[widget_name] = missing_error
+ return None
+
+ if key is None:
+ key = widget_name
+ if mode == "modifier":
+ # eg. turn "name" into "target.set_name", and
+ # call it like "target.set_name(value)"
+ mod = getattr(target, "set_" + key)
+ mod(value)
+ elif mode == "direct":
+ if not hasattr(target, key):
+ raise AttributeError, \
+ ("target object %s doesn't have attribute %s" %
+ (`target`, key))
+ setattr(target, key, value)
+ elif mode == "dict":
+ target[key] = value
+ else:
+ raise ValueError, "unknown update mode %s" % `mode`
+
+ def clear_widget(self, widget_name):
+ self.widgets[widget_name].clear()
+
+ def get_widget_value(self, widget_name):
+ return self.widgets[widget_name].value
+
+ def set_widget_value(self, widget_name, value):
+ self.widgets[widget_name].set_value(value)
+
+
+ # -- Form population methods ---------------------------------------
+
+ def add_widget(self, widget_type, name, value=None,
+ title=None, hint=None, required=0, **args):
+ """add_widget(widget_type : string | Widget,
+ name : string,
+ value : any = None,
+ title : string = None,
+ hint : string = None,
+ required : boolean = 0,
+ ...) -> Widget
+
+ Create a new Widget object and add it to the form. The widget
+ class used depends on 'widget_type', and the expected type of
+ 'value' also depends on the widget class. Any extra keyword
+ args are passed to the widget constructor.
+
+ Returns the new Widget.
+ """
+ if self.widgets.has_key(name):
+ raise ValueError, "form already has '%s' variable" % name
+ klass = get_widget_class(widget_type)
+ new_widget = apply(klass, (name, value), args)
+
+ self.widgets[name] = new_widget
+ self.widget_order.append(new_widget)
+ self.title[name] = title
+ self.hint[name] = hint
+ self.required[name] = required
+ return new_widget
+
+ def add_submit_button(self, name, value):
+ global _widget_class
+ if self.widgets.has_key(name):
+ raise ValueError, "form already has '%s' variable" % name
+ new_widget = _widget_class['submit_button'](name, value)
+
+ self.widgets[name] = new_widget
+ self.submit_buttons.append(new_widget)
+
+ def add_cancel_button(self, caption, url):
+ if not isinstance(url, (StringType, htmltext)):
+ raise TypeError, "url must be a string (got %r)" % url
+ self.add_submit_button("cancel", caption)
+ self.cancel_url = url
+
+# class Form
+
+
+_widget_class = {}
+
+def register_widget_class(klass, widget_type=None):
+ global _widget_class
+ if widget_type is None:
+ widget_type = klass.widget_type
+ assert widget_type is not None, "widget_type must be defined"
+ _widget_class[widget_type] = klass
+
+def get_widget_class(widget_type):
+ global _widget_class
+ if callable(widget_type):
+ # Presumably someone passed a widget class object to
+ # Widget.create_subwidget() or Form.add_widget() --
+ # don't bother with the widget class registry at all.
+ return widget_type
+ else:
+ try:
+ return _widget_class[widget_type]
+ except KeyError:
+ raise ValueError("unknown widget type %r" % widget_type)
Added: packages/quixote1/branches/upstream/current/form/widget.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form/widget.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form/widget.py (added)
+++ packages/quixote1/branches/upstream/current/form/widget.py Mon May 8 19:16:16 2006
@@ -1,0 +1,845 @@
+"""quixote.form.widget
+
+Provides the basic web widget classes: Widget itself, plus StringWidget,
+TextWidget, CheckboxWidget, etc.
+"""
+
+# created 2000/09/20, GPW
+
+__revision__ = "$Id: widget.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import struct
+from types import FloatType, IntType, ListType, StringType, TupleType
+from quixote import get_request
+from quixote.html import htmltext, htmlescape, htmltag, ValuelessAttr
+from quixote.upload import Upload
+
+
+class FormValueError (Exception):
+ """Raised whenever a widget has problems parsing its value."""
+
+ def __init__(self, msg):
+ self.msg = msg
+
+
+ def __str__(self):
+ return str(self.msg)
+
+
+class Widget:
+ """Abstract base class for web widgets. The key elements
+ of a web widget are:
+ - name
+ - widget type (how the widget looks/works in the browser)
+ - value
+
+ The name and value are instance attributes (because they're specific to
+ a particular widget in a particular context); widget type is a
+ class attributes.
+
+ Instance attributes:
+ name : string
+ value : any
+
+ Feel free to access these directly; to set them, use the 'set_*()'
+ modifier methods.
+ """
+
+ # Subclasses must define. 'widget_type' is just a string, e.g.
+ # "string", "text", "checkbox".
+ widget_type = None
+
+ def __init__(self, name, value=None):
+ assert self.__class__ is not Widget, "abstract class"
+ self.set_name(name)
+ self.set_value(value)
+
+
+ def __repr__(self):
+ return "<%s at %x: %s>" % (self.__class__.__name__,
+ id(self),
+ self.name)
+
+
+ def __str__(self):
+ return "%s: %s" % (self.widget_type, self.name)
+
+
+ def set_name(self, name):
+ self.name = name
+
+
+ def set_value(self, value):
+ self.value = value
+
+
+ def clear(self):
+ self.value = None
+
+ # -- Subclasses must implement these -------------------------------
+
+ def render(self, request):
+ """render(request) -> HTML text"""
+ raise NotImplementedError
+
+
+ def parse(self, request):
+ """parse(request) -> any"""
+ value = request.form.get(self.name)
+ if type(value) is StringType and value.strip():
+ self.value = value
+ else:
+ self.value = None
+
+ return self.value
+
+ # -- Convenience methods for subclasses ----------------------------
+
+ # This one's really only for composite widgets; lives here until
+ # we have a demonstrated need for a CompositeWidget class.
+ def get_subwidget_name(self, name):
+ return "%s$%s" % (self.name, name)
+
+
+ def create_subwidget(self, widget_type, widget_name, value=None, **args):
+ from quixote.form.form import get_widget_class
+ klass = get_widget_class(widget_type)
+ name = self.get_subwidget_name(widget_name)
+ return apply(klass, (name, value), args)
+
+# class Widget
+
+# -- Fundamental widget types ------------------------------------------
+# These correspond to the standard types of input tag in HTML:
+# text StringWidget
+# password PasswordWidget
+# radio RadiobuttonWidget
+# checkbox CheckboxWidget
+#
+# and also to the other basic form elements:
+# <textarea> TextWidget
+# <select> SingleSelectWidget
+# <select multiple>
+# MultipleSelectWidget
+
+class StringWidget (Widget):
+ """Widget for entering a single string: corresponds to
+ '<input type="text">' in HTML.
+
+ Instance attributes:
+ value : string
+ size : int
+ maxlength : int
+ """
+
+ widget_type = "string"
+
+ # This lets PasswordWidget be a trivial subclass
+ html_type = "text"
+
+ def __init__(self, name, value=None,
+ size=None, maxlength=None):
+ Widget.__init__(self, name, value)
+ self.size = size
+ self.maxlength = maxlength
+
+
+ def render(self, request, **attributes):
+ return htmltag("input", xml_end=1,
+ type=self.html_type,
+ name=self.name,
+ size=self.size,
+ maxlength=self.maxlength,
+ value=self.value,
+ **attributes)
+
+
+class FileWidget (StringWidget):
+ """Trivial subclass of StringWidget for uploading files.
+
+ Instance attributes: none
+ """
+ widget_type = "file"
+ html_type = "file"
+
+ def parse(self, request):
+ """parse(request) -> any"""
+ value = request.form.get(self.name)
+ if isinstance(value, Upload):
+ self.value = value
+ else:
+ self.value = None
+ return self.value
+
+
+class PasswordWidget (StringWidget):
+ """Trivial subclass of StringWidget for entering passwords (different
+ widget type because HTML does it that way).
+
+ Instance attributes: none
+ """
+
+ widget_type = "password"
+ html_type = "password"
+
+
+class TextWidget (Widget):
+ """Widget for entering a long, multi-line string; corresponds to
+ the HTML "<textarea>" tag.
+
+ Instance attributes:
+ value : string
+ cols : int
+ rows : int
+ wrap : string
+ (see an HTML book for details on text widget wrap options)
+ css_class : string
+ """
+
+ widget_type = "text"
+
+ def __init__(self, name, value=None, cols=None, rows=None, wrap=None,
+ css_class=None):
+ Widget.__init__(self, name, value)
+ self.cols = cols
+ self.rows = rows
+ self.wrap = wrap
+ self.css_class = css_class
+
+ def render(self, request):
+ return (htmltag("textarea", name=self.name,
+ cols=self.cols,
+ rows=self.rows,
+ wrap=self.wrap,
+ css_class=self.css_class) +
+ htmlescape(self.value or "") +
+ htmltext("</textarea>"))
+
+
+ def parse(self, request):
+ value = Widget.parse(self, request)
+ if value:
+ value = value.replace("\r\n", "\n")
+ self.value = value
+ return self.value
+
+
+class CheckboxWidget (Widget):
+ """Widget for a single checkbox: corresponds to "<input
+ type=checkbox>". Do not put multiple CheckboxWidgets with the same
+ name in the same form.
+
+ Instance attributes:
+ value : boolean
+ """
+
+ widget_type = "checkbox"
+
+ def render(self, request):
+ return htmltag("input", xml_end=1,
+ type="checkbox",
+ name=self.name,
+ value="yes",
+ checked=self.value and ValuelessAttr or None)
+
+
+ def parse(self, request):
+ self.value = request.form.has_key(self.name)
+ return self.value
+
+
+class SelectWidget (Widget):
+ """Widget for single or multiple selection; corresponds to
+ <select name=...>
+ <option value="Foo">Foo</option>
+ ...
+ </select>
+
+ Instance attributes:
+ options : [ (value:any, description:any, key:string) ]
+ value : any
+ The value is None or an element of dict(options.values()).
+ size : int
+ The number of options that should be presented without scrolling.
+ """
+
+ # NB. 'widget_type' not set here because this is an abstract class: it's
+ # set by subclasses SingleSelectWidget and MultipleSelectWidget.
+
+ def __init__(self, name, value=None,
+ allowed_values=None,
+ descriptions=None,
+ options=None,
+ size=None,
+ sort=0,
+ verify_selection=1):
+ assert self.__class__ is not SelectWidget, "abstract class"
+ self.options = []
+ # if options passed, cannot pass allowed_values or descriptions
+ if allowed_values is not None:
+ assert options is None, (
+ 'cannot pass both allowed_values and options')
+ assert allowed_values, (
+ 'cannot pass empty allowed_values list')
+ self.set_allowed_values(allowed_values, descriptions, sort)
+ elif options is not None:
+ assert descriptions is None, (
+ 'cannot pass both options and descriptions')
+ assert options, (
+ 'cannot pass empty options list')
+ self.set_options(options, sort)
+ self.set_name(name)
+ self.set_value(value)
+ self.size = size
+ self.verify_selection = verify_selection
+
+
+ def get_allowed_values(self):
+ return [item[0] for item in self.options]
+
+
+ def get_descriptions(self):
+ return [item[1] for item in self.options]
+
+
+ def set_value(self, value):
+ self.value = None
+ for object, description, key in self.options:
+ if value == object:
+ self.value = value
+ break
+
+
+ def _generate_keys(self, values, descriptions):
+ """Called if no keys were provided. Try to generate a set of keys
+ that will be consistent between rendering and parsing.
+ """
+ # try to use ZODB object IDs
+ keys = []
+ for value in values:
+ if value is None:
+ oid = ""
+ else:
+ oid = getattr(value, "_p_oid", None)
+ if not oid:
+ break
+ hi, lo = struct.unpack(">LL", oid)
+ oid = "%x" % ((hi << 32) | lo)
+ keys.append(oid)
+ else:
+ # found OID for every value
+ return keys
+ # can't use OIDs, try using descriptions
+ used_keys = {}
+ keys = map(str, descriptions)
+ for key in keys:
+ if used_keys.has_key(key):
+ raise ValueError, "duplicated descriptions (provide keys)"
+ used_keys[key] = 1
+ return keys
+
+
+ def set_options(self, options, sort=0):
+ """(options: [objects:any], sort=0)
+ or
+ (options: [(object:any, description:any)], sort=0)
+ or
+ (options: [(object:any, description:any, key:any)], sort=0)
+ """
+
+ """
+ Set the options list. The list of options can be a list of objects, in
+ which case the descriptions default to map(htmlescape, objects)
+ applying htmlescape() to each description and
+ key.
+ If keys are provided they must be distinct. If the sort keyword
+ argument is true, sort the options by case-insensitive lexicographic
+ order of descriptions, except that options with value None appear
+ before others.
+ """
+ if options:
+ first = options[0]
+ values = []
+ descriptions = []
+ keys = []
+ if type(first) is TupleType:
+ if len(first) == 2:
+ for value, description in options:
+ values.append(value)
+ descriptions.append(description)
+ elif len(first) == 3:
+ for value, description, key in options:
+ values.append(value)
+ descriptions.append(description)
+ keys.append(str(key))
+ else:
+ raise ValueError, 'invalid options %r' % options
+ else:
+ values = descriptions = options
+
+ if not keys:
+ keys = self._generate_keys(values, descriptions)
+
+ options = zip(values, descriptions, keys)
+
+ if sort:
+ def make_sort_key(option):
+ value, description, key = option
+ if value is None:
+ return ('', option)
+ else:
+ return (str(description).lower(), option)
+ doptions = map(make_sort_key, options)
+ doptions.sort()
+ options = [item[1] for item in doptions]
+ self.options = options
+
+
+ def parse_single_selection(self, parsed_key):
+ for value, description, key in self.options:
+ if key == parsed_key:
+ return value
+ else:
+ if self.verify_selection:
+ raise FormValueError, "invalid value selected"
+ else:
+ return self.options[0][0]
+
+
+ def set_allowed_values(self, allowed_values, descriptions=None, sort=0):
+ """(allowed_values:[any], descriptions:[any], sort:boolean=0)
+
+ Set the options for this widget. The allowed_values and descriptions
+ parameters must be sequences of the same length. The sort option
+ causes the options to be sorted using case-insensitive lexicographic
+ order of descriptions, except that options with value None appear
+ before others.
+ """
+ if descriptions is None:
+ self.set_options(allowed_values, sort)
+ else:
+ assert len(descriptions) == len(allowed_values)
+ self.set_options(zip(allowed_values, descriptions), sort)
+
+
+ def is_selected(self, value):
+ return value == self.value
+
+
+ def render(self, request):
+ if self.widget_type == "multiple_select":
+ multiple = ValuelessAttr
+ else:
+ multiple = None
+ if self.widget_type == "option_select":
+ onchange = "submit()"
+ else:
+ onchange = None
+ tags = [htmltag("select", name=self.name,
+ multiple=multiple, onchange=onchange,
+ size=self.size)]
+ for object, description, key in self.options:
+ if self.is_selected(object):
+ selected = ValuelessAttr
+ else:
+ selected = None
+ if description is None:
+ description = ""
+ r = htmltag("option", value=key, selected=selected)
+ tags.append(r + htmlescape(description) + htmltext('</option>'))
+ tags.append(htmltext("</select>"))
+ return htmltext("\n").join(tags)
+
+
+class SingleSelectWidget (SelectWidget):
+ """Widget for single selection.
+ """
+
+ widget_type = "single_select"
+
+ def parse(self, request):
+ parsed_key = request.form.get(self.name)
+ self.value = None
+ if parsed_key:
+ if type(parsed_key) is ListType:
+ raise FormValueError, "cannot select multiple values"
+ self.value = self.parse_single_selection(parsed_key)
+ return self.value
+
+
+class RadiobuttonsWidget (SingleSelectWidget):
+ """Widget for a *set* of related radiobuttons -- all have the
+ same name, but different values (and only one of those values
+ is returned by the whole group).
+
+ Instance attributes:
+ delim : string = None
+ string to emit between each radiobutton in the group. If
+ None, a single newline is emitted.
+ """
+
+ widget_type = "radiobuttons"
+
+ def __init__(self, name, value=None,
+ allowed_values=None,
+ descriptions=None,
+ options=None,
+ delim=None):
+ SingleSelectWidget.__init__(self, name, value, allowed_values,
+ descriptions, options)
+ if delim is None:
+ self.delim = "\n"
+ else:
+ self.delim = delim
+
+
+ def render(self, request):
+ tags = []
+ for object, description, key in self.options:
+ if self.is_selected(object):
+ checked = ValuelessAttr
+ else:
+ checked = None
+ r = htmltag("input", xml_end=True,
+ type="radio",
+ name=self.name,
+ value=key,
+ checked=checked)
+ tags.append(r + htmlescape(description))
+ return htmlescape(self.delim).join(tags)
+
+
+class MultipleSelectWidget (SelectWidget):
+ """Widget for multiple selection.
+
+ Instance attributes:
+ value : [any]
+ for multipe selects, the value is None or a list of
+ elements from dict(self.options).values()
+ """
+
+ widget_type = "multiple_select"
+
+ def set_value(self, value):
+ allowed_values = self.get_allowed_values()
+ if value in allowed_values:
+ self.value = [ value ]
+ elif type(value) in (ListType, TupleType):
+ self.value = [ element
+ for element in value
+ if element in allowed_values ] or None
+ else:
+ self.value = None
+
+
+ def is_selected(self, value):
+ if self.value is None:
+ return value is None
+ else:
+ return value in self.value
+
+
+ def parse(self, request):
+ parsed_keys = request.form.get(self.name)
+ self.value = None
+ if parsed_keys:
+ if type(parsed_keys) is ListType:
+ self.value = [value
+ for value, description, key in self.options
+ if key in parsed_keys] or None
+ else:
+ self.value = [self.parse_single_selection(parsed_keys)]
+ return self.value
+
+
+class SubmitButtonWidget (Widget):
+ """
+ Instance attributes:
+ value : boolean
+ """
+
+ widget_type = "submit_button"
+
+ def __init__(self, name=None, value=None):
+ Widget.__init__(self, name, value)
+
+
+ def render(self, request):
+ value = (self.value and htmlescape(self.value) or None)
+ return htmltag("input", xml_end=1, type="submit",
+ name=self.name, value=value)
+
+
+ def parse(self, request):
+ return request.form.get(self.name)
+
+
+ def is_submitted(self):
+ return self.parse(get_request())
+
+
+class HiddenWidget (Widget):
+ """
+ Instance attributes:
+ value : string
+ """
+
+ widget_type = "hidden"
+
+ def render(self, request):
+ if self.value is None:
+ value = None
+ else:
+ value = htmlescape(self.value)
+ return htmltag("input", xml_end=1,
+ type="hidden",
+ name=self.name,
+ value=value)
+
+
+ def set_current_value(self, value):
+ self.value = value
+ request = get_request()
+ if request.form:
+ request.form[self.name] = value
+
+
+ def get_current_value(self):
+ request = get_request()
+ if request.form:
+ return self.parse(request)
+ else:
+ return self.value
+
+# -- Derived widget types ----------------------------------------------
+# (these don't correspond to fundamental widget types in HTML,
+# so they're separated)
+
+class NumberWidget (StringWidget):
+ """
+ Instance attributes: none
+ """
+
+ # Parameterize the number type (either float or int) through
+ # these class attributes:
+ type_object = None # eg. int, float
+ type_error = None # human-readable error message
+ type_converter = None # eg. int(), float()
+
+ def __init__(self, name,
+ value=None,
+ size=None, maxlength=None):
+ assert self.__class__ is not NumberWidget, "abstract class"
+ assert value is None or type(value) is self.type_object, (
+ "form value '%s' not a %s: got %r" % (name,
+ self.type_object,
+ value))
+ StringWidget.__init__(self, name, value, size, maxlength)
+
+
+ def parse(self, request):
+ value = StringWidget.parse(self, request)
+ if value:
+ try:
+ self.value = self.type_converter(value)
+ except ValueError:
+ raise FormValueError, self.type_error
+ return self.value
+
+
+class FloatWidget (NumberWidget):
+ """
+ Instance attributes:
+ value : float
+ """
+
+ widget_type = "float"
+ type_object = FloatType
+ type_converter = float
+ type_error = "must be a number"
+
+
+class IntWidget (NumberWidget):
+ """
+ Instance attributes:
+ value : int
+ """
+
+ widget_type = "int"
+ type_object = IntType
+ type_converter = int
+ type_error = "must be an integer"
+
+
+class OptionSelectWidget (SingleSelectWidget):
+ """Widget for single selection with automatic submission and early
+ parsing. This widget parses the request when it is created. This
+ allows its value to be used to decide what other widgets need to be
+ created in a form. It's a powerful feature but it can be hard to
+ understand what's going on.
+
+ Instance attributes:
+ value : any
+ """
+
+ widget_type = "option_select"
+
+ def __init__(self, *args, **kwargs):
+ SingleSelectWidget.__init__(self, *args, **kwargs)
+
+ request = get_request()
+ if request.form:
+ SingleSelectWidget.parse(self, request)
+ if self.value is None:
+ self.value = self.options[0][0]
+
+
+ def render(self, request):
+ return (SingleSelectWidget.render(self, request) +
+ htmltext('<noscript>'
+ '<input type="submit" name="" value="apply" />'
+ '</noscript>'))
+
+
+ def parse(self, request):
+ return self.value
+
+
+ def get_current_option(self):
+ return self.value
+
+
+class ListWidget (Widget):
+ """Widget for lists of objects.
+
+ Instance attributes:
+ value : [any]
+ """
+
+ widget_type = "list"
+
+ def __init__(self, name, value=None,
+ element_type=None,
+ element_name="row",
+ **args):
+ assert value is None or type(value) is ListType, (
+ "form value '%s' not a list: got %r" % (name, value))
+ assert type(element_name) in (StringType, htmltext), (
+ "form value '%s' element_name not a string: "
+ "got %r" % (name, element_name))
+
+ Widget.__init__(self, name, value)
+
+ if element_type is None:
+ self.element_type = "string"
+ else:
+ self.element_type = element_type
+ self.args = args
+
+ self.added_elements_widget = self.create_subwidget(
+ "hidden", "added_elements")
+
+ added_elements = int(self.added_elements_widget.get_current_value() or
+ '1')
+
+ self.add_button = self.create_subwidget(
+ "submit_button", "add_element",
+ value="Add %s" % element_name)
+
+ if self.add_button.is_submitted():
+ added_elements += 1
+ self.added_elements_widget.set_current_value(str(added_elements))
+
+ self.element_widgets = []
+ self.element_count = 0
+
+ if self.value is not None:
+ for element in self.value:
+ self.add_element(element)
+
+ for index in range(added_elements):
+ self.add_element()
+
+ def add_element(self, value=None):
+ self.element_widgets.append(
+ self.create_subwidget(self.element_type,
+ "element_%d" % self.element_count,
+ value=value,
+ **self.args))
+ self.element_count += 1
+
+ def render(self, request):
+ tags = []
+ for element_widget in self.element_widgets:
+ tags.append(element_widget.render(request))
+ tags.append(self.add_button.render(request))
+ tags.append(self.added_elements_widget.render(request))
+ return htmltext('<br />\n').join(tags)
+
+ def parse(self, request):
+ self.value = []
+ for element_widget in self.element_widgets:
+ value = element_widget.parse(request)
+ if value is not None:
+ self.value.append(value)
+ self.value = self.value or None
+ return self.value
+
+
+
+class CollapsibleListWidget (ListWidget):
+ """Widget for lists of objects with associated delete buttons.
+
+ CollapsibleListWidget behaves like ListWidget except that each element
+ is rendered with an associated delete button. Pressing the delete
+ button will cause the associated element name to be added to a hidden
+ widget that remembers all deletions until the form is submitted.
+ Only elements that are not marked as deleted will be rendered and
+ ultimately added to the value of the widget.
+
+ Instance attributes:
+ value : [any]
+ """
+
+ widget_type = "collapsible_list"
+
+ def __init__(self, name, value=None, element_name="row", **args):
+ self.name = name
+ self.element_name = element_name
+ self.deleted_elements_widget = self.create_subwidget(
+ "hidden", "deleted_elements")
+ self.element_delete_buttons = []
+ self.deleted_elements = (
+ self.deleted_elements_widget.get_current_value() or '')
+ ListWidget.__init__(self, name, value=value,
+ element_name=element_name,
+ **args)
+
+ def add_element(self, value=None):
+ element_widget_name = "element_%d" % self.element_count
+ if self.deleted_elements.find(element_widget_name) == -1:
+ delete_button = self.create_subwidget(
+ "submit_button", "delete_" + element_widget_name,
+ value="Delete %s" % self.element_name)
+ if delete_button.is_submitted():
+ self.element_count += 1
+ self.deleted_elements += element_widget_name
+ self.deleted_elements_widget.set_current_value(
+ self.deleted_elements)
+ else:
+ self.element_delete_buttons.append(delete_button)
+ ListWidget.add_element(self, value=value)
+ else:
+ self.element_count += 1
+
+ def render(self, request):
+ tags = []
+ for element_widget, element_delete_button in zip(
+ self.element_widgets, self.element_delete_buttons):
+ if self.deleted_elements.find(element_widget.name) == -1:
+ tags.append(element_widget.render(request) +
+ element_delete_button.render(request))
+ tags.append(self.add_button.render(request))
+ tags.append(self.added_elements_widget.render(request))
+ tags.append(self.deleted_elements_widget.render(request))
+ return htmltext('<br />\n').join(tags)
Added: packages/quixote1/branches/upstream/current/form2/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/form2/__init__.py Mon May 8 19:16:16 2006
@@ -1,0 +1,18 @@
+"""$URL: svn+ssh://svn/repos/trunk/quixote/form2/__init__.py $
+$Id: __init__.py 23720 2004-03-17 16:45:59Z nascheme $
+
+The web interface framework, consisting of Form and Widget base classes
+(and a bunch of standard widget classes recognized by Form).
+Application developers will typically create a Form instance each
+form in their application; each form object will contain a number
+of widget objects. Custom widgets can be created by inheriting
+and/or composing the standard widget classes.
+"""
+
+from quixote.form2.form import Form, FormTokenWidget
+from quixote.form2.widget import Widget, StringWidget, FileWidget, \
+ PasswordWidget, TextWidget, CheckboxWidget, RadiobuttonsWidget, \
+ SingleSelectWidget, SelectWidget, OptionSelectWidget, \
+ MultipleSelectWidget, SubmitWidget, HiddenWidget, \
+ FloatWidget, IntWidget, subname, WidgetValueError, CompositeWidget, \
+ WidgetList
Added: packages/quixote1/branches/upstream/current/form2/compatibility.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/compatibility.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/compatibility.py (added)
+++ packages/quixote1/branches/upstream/current/form2/compatibility.py Mon May 8 19:16:16 2006
@@ -1,0 +1,97 @@
+'''$URL: svn+ssh://svn/repos/trunk/quixote/form2/compatibility.py $
+$Id: compatibility.py 25234 2004-09-30 17:36:19Z nascheme $
+
+A Form subclass that provides close to the same API as the old form
+class (useful for transitioning existing forms).
+'''
+
+from quixote.form2 import Form as _Form, Widget, StringWidget, FileWidget, \
+ PasswordWidget, TextWidget, CheckboxWidget, RadiobuttonsWidget, \
+ SingleSelectWidget, SelectWidget, OptionSelectWidget, \
+ MultipleSelectWidget, SubmitWidget, HiddenWidget, \
+ FloatWidget, IntWidget
+from quixote.html import url_quote
+
+_widget_names = {
+ "string" : StringWidget,
+ "file" : FileWidget,
+ "password" : PasswordWidget,
+ "text" : TextWidget,
+ "checkbox" : CheckboxWidget,
+ "single_select" : SingleSelectWidget,
+ "radiobuttons" : RadiobuttonsWidget,
+ "multiple_select" : MultipleSelectWidget,
+ "submit_button" : SubmitWidget,
+ "hidden" : HiddenWidget,
+ "float" : FloatWidget,
+ "int" : IntWidget,
+ "option_select" : OptionSelectWidget,
+}
+
+
+class Form(_Form):
+ def __init__(self, *args, **kwargs):
+ _Form.__init__(self, *args, **kwargs)
+ self.cancel_url = None
+
+ def add_widget(self, widget_class, name, value=None,
+ title=None, hint=None, required=0, **kwargs):
+ try:
+ widget_class = _widget_names[widget_class]
+ except KeyError:
+ pass
+ self.add(widget_class, name, value=value, title=title, hint=hint,
+ required=required, **kwargs)
+
+ def add_submit_button(self, name, value):
+ self.add_submit(name, value)
+
+ def add_cancel_button(self, caption, url):
+ self.add_submit("cancel", caption)
+ self.cancel_url = url
+
+ def get_action_url(self, request):
+ action_url = url_quote(request.get_path())
+ query = request.get_environ("QUERY_STRING")
+ if query:
+ action_url += "?" + query
+ return action_url
+
+ def render(self, request, action_url=None):
+ if action_url:
+ self.action_url = action_url
+ return _Form.render(self)
+
+ def process(self, request):
+ values = {}
+ for name, widget in self._names.items():
+ values[name] = widget.parse(request)
+ return values
+
+ def action(self, request, submit, values):
+ raise NotImplementedError, "sub-classes must implement 'action()'"
+
+ def handle(self, request):
+ """handle(request : HTTPRequest) -> string
+
+ Master method for handling forms. It should be called after
+ initializing a form. Controls form action based on a request. You
+ probably should override 'process' and 'action' instead of
+ overriding this method.
+ """
+ if not self.is_submitted():
+ return self.render(request, self.action_url)
+ submit = self.get_submit()
+ if submit == "cancel":
+ return request.redirect(self.cancel_url)
+ values = self.process(request)
+ if submit == True:
+ # The form was submitted by an unregistered submit button, assume
+ # that the submission was required to update the layout of the form.
+ self.clear_errors()
+ return self.render(request, self.action_url)
+
+ if self.has_errors():
+ return self.render(request, self.action_url)
+ else:
+ return self.action(request, submit, values)
Added: packages/quixote1/branches/upstream/current/form2/css.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/css.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/css.py (added)
+++ packages/quixote1/branches/upstream/current/form2/css.py Mon May 8 19:16:16 2006
@@ -1,0 +1,76 @@
+
+BASIC_FORM_CSS = """\
+form.quixote div.title {
+ font-weight: bold;
+}
+
+form.quixote br.submit,
+form.quixote br.widget,
+br.quixoteform {
+ clear: left;
+}
+
+form.quixote div.submit br.widget {
+ display: none;
+}
+
+form.quixote div.widget {
+ float: left;
+ padding: 4px;
+ padding-right: 1em;
+ margin-bottom: 6px;
+}
+
+/* pretty forms (attribute selector hides from broken browsers (e.g. IE) */
+form.quixote[action] {
+ float: left;
+}
+
+form.quixote[action] > div.widget {
+ float: none;
+}
+
+form.quixote[action] > br.widget {
+ display: none;
+}
+
+form.quixote div.widget div.widget {
+ padding: 0;
+ margin-bottom: 0;
+}
+
+form.quixote div.SubmitWidget {
+ float: left
+}
+
+form.quixote div.content {
+ margin-left: 0.6em; /* indent content */
+}
+
+form.quixote div.content div.content {
+ margin-left: 0; /* indent content only for top-level widgets */
+}
+
+form.quixote div.error {
+ color: #c00;
+ font-size: small;
+ margin-top: .1em;
+}
+
+form.quixote div.hint {
+ font-size: small;
+ font-style: italic;
+ margin-top: .1em;
+}
+
+form.quixote div.errornotice {
+ color: #c00;
+ padding: 0.5em;
+ margin: 0.5em;
+}
+
+form.quixote div.FormTokenWidget,
+form.quixote.div.HiddenWidget {
+ display: none;
+}
+"""
Added: packages/quixote1/branches/upstream/current/form2/form.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/form.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/form.py (added)
+++ packages/quixote1/branches/upstream/current/form2/form.py Mon May 8 19:16:16 2006
@@ -1,0 +1,373 @@
+"""$URL: svn+ssh://svn/repos/trunk/quixote/form2/form.py $
+$Id: form.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Provides the Form class and related classes. Forms are a convenient
+way of building HTML forms that are composed of Widget objects.
+"""
+
+from quixote import get_request, get_session, get_publisher
+from quixote.html import url_quote, htmltag, htmltext, TemplateIO
+from quixote.form2.widget import HiddenWidget, StringWidget, TextWidget, \
+ CheckboxWidget, SingleSelectWidget, RadiobuttonsWidget, \
+ MultipleSelectWidget, ResetWidget, SubmitWidget, FloatWidget, \
+ IntWidget
+
+
+try:
+ True, False, bool
+except NameError:
+ True = 1
+ False = 0
+ def bool(v):
+ return not not v
+
+
+class FormTokenWidget(HiddenWidget):
+
+ def _parse(self, request):
+ token = request.form.get(self.name)
+ session = get_session()
+ if not session.has_form_token(token):
+ self.error = 'invalid'
+ else:
+ session.remove_form_token(token)
+
+ def render_error(self, error):
+ return ''
+
+ def render(self):
+ self.value = get_session().create_form_token()
+ return HiddenWidget.render(self)
+
+
+class Form:
+ """
+ Provides a high-level mechanism for collecting and processing user
+ input that is based on HTML forms.
+
+ Instance attributes:
+ widgets : [Widget]
+ widgets that are not subclasses of SubmitWidget or HiddenWidget
+ submit_widgets : [SubmitWidget]
+ subclasses of SubmitWidget, normally rendered at the end of the
+ form
+ hidden_widgets : [HiddenWidget]
+ subclasses of HiddenWidget, normally rendered at the beginning
+ of the form
+ _names : { name:string : Widget }
+ names used in the form and the widgets associated with them
+ """
+
+ TOKEN_NAME = "_form_id" # name of hidden token widget
+
+ JAVASCRIPT_MARKUP = htmltext('<script type="text/javascript">\n'
+ '<!--\n'
+ '%s\n'
+ '// -->\n'
+ '</script>\n')
+
+ def __init__(self,
+ name=None,
+ method="post",
+ action_url=None,
+ enctype=None,
+ use_tokens=True,
+ attrs=None):
+
+ if method not in ("post", "get"):
+ raise ValueError("Form method must be 'post' or 'get', "
+ "not %r" % method)
+ self.name = name
+ self.method = method
+ self.action_url = action_url or self._get_default_action_url()
+ if not attrs:
+ attrs = {'class': 'quixote'}
+ elif 'class' not in attrs:
+ attrs = attrs.copy()
+ attrs['class'] = 'quixote'
+ self.attrs = attrs
+ self.widgets = []
+ self.submit_widgets = []
+ self.hidden_widgets = []
+ self._names = {}
+
+ if enctype is not None and enctype not in (
+ "application/x-www-form-urlencoded", "multipart/form-data"):
+ raise ValueError, ("Form enctype must be "
+ "'application/x-www-form-urlencoded' or "
+ "'multipart/form-data', not %r" % enctype)
+ self.enctype = enctype
+
+ if use_tokens and self.method == "post":
+ config = get_publisher().config
+ if config.form_tokens:
+ # unique token for each form, this prevents many cross-site
+ # attacks and prevents a form from being submitted twice
+ self.add(FormTokenWidget, self.TOKEN_NAME, value=None)
+
+ def _get_default_action_url(self):
+ request = get_request()
+ action_url = url_quote(request.get_path())
+ query = request.get_environ("QUERY_STRING")
+ if query:
+ action_url += "?" + query
+ return action_url
+
+ # -- Form data access methods --------------------------------------
+
+ def __getitem__(self, name):
+ """(name:string) -> any
+ Return a widget's value. Raises KeyError if widget named 'name'
+ does not exist.
+ """
+ try:
+ return self._names[name].parse()
+ except KeyError:
+ raise KeyError, 'no widget named %r' % name
+
+ def has_key(self, name):
+ """Return true if the widget named 'name' is in the form."""
+ return self._names.has_key(name)
+
+ def get(self, name, default=None):
+ """(name:string, default=None) -> any
+ Return a widget's value. Returns 'default' if widget named 'name'
+ does not exist.
+ """
+ widget = self._names.get(name)
+ if widget is not None:
+ return widget.parse()
+ else:
+ return default
+
+ def get_widget(self, name):
+ """(name:string) -> Widget | None
+ Return the widget named 'name'. Returns None if the widget does
+ not exist.
+ """
+ return self._names.get(name)
+
+ def get_submit_widgets(self):
+ """() -> [SubmitWidget]
+ """
+ return self.submit_widgets
+
+ def get_all_widgets(self):
+ """() -> [Widget]
+ Return all the widgets that have been added to the form. Note that
+ this while this list includes submit widgets and hidden widgets, it
+ does not include sub-widgets (e.g. widgets that are part of
+ CompositeWidgets)
+ """
+ return self._names.values()
+
+ # -- Form processing and error checking ----------------------------
+
+ def is_submitted(self):
+ """() -> bool
+
+ Return true if a form was submitted. If the form method is 'POST'
+ and the page was not requested using 'POST', then the form is not
+ considered to be submitted. If the form method is 'GET' then the
+ form is considered submitted if there is any form data in the
+ request.
+ """
+ request = get_request()
+ if self.method == 'post':
+ if request.get_method() == 'POST':
+ return True
+ else:
+ return False
+ else:
+ return bool(request.form)
+
+ def has_errors(self):
+ """() -> bool
+
+ Ensure that all components of the form have parsed themselves. Return
+ true if any of them have errors.
+ """
+ request = get_request()
+ has_errors = False
+ if self.is_submitted():
+ for widget in self.get_all_widgets():
+ if widget.has_error(request=request):
+ has_errors = True
+ return has_errors
+
+ def clear_errors(self):
+ """Ensure that all components of the form have parsed themselves.
+ Clear any errors that might have occured during parsing.
+ """
+ request = get_request()
+ for widget in self.get_all_widgets():
+ widget.clear_error(request)
+
+ def get_submit(self):
+ """() -> string | bool
+
+ Get the name of the submit button that was used to submit the
+ current form. If the form is submitted but not by any known
+ SubmitWidget then return True. Otherwise, return False.
+ """
+ request = get_request()
+ for button in self.submit_widgets:
+ if button.parse(request):
+ return button.name
+ else:
+ if self.is_submitted():
+ return True
+ else:
+ return False
+
+ def set_error(self, name, error):
+ """(name : string, error : string)
+ Set the error attribute of the widget named 'name'.
+ """
+ widget = self._names.get(name)
+ if not widget:
+ raise KeyError, "unknown name %r" % name
+ widget.set_error(error)
+
+ # -- Form population methods ---------------------------------------
+
+ def add(self, widget_class, name, *args, **kwargs):
+ if self._names.has_key(name):
+ raise ValueError, "form already has '%s' widget" % name
+ widget = widget_class(name, *args, **kwargs)
+ self._names[name] = widget
+ if isinstance(widget, SubmitWidget):
+ self.submit_widgets.append(widget) # will be rendered at end
+ elif isinstance(widget, HiddenWidget):
+ self.hidden_widgets.append(widget) # will be render at beginning
+ else:
+ self.widgets.append(widget)
+
+ # convenience methods
+
+ def add_submit(self, name, value=None, **kwargs):
+ self.add(SubmitWidget, name, value, **kwargs)
+
+ def add_reset(self, name, value=None, **kwargs):
+ self.add(ResetWidget, name, value, **kwargs)
+
+ def add_hidden(self, name, value=None, **kwargs):
+ self.add(HiddenWidget, name, value, **kwargs)
+
+ def add_string(self, name, value=None, **kwargs):
+ self.add(StringWidget, name, value, **kwargs)
+
+ def add_text(self, name, value=None, **kwargs):
+ self.add(TextWidget, name, value, **kwargs)
+
+ def add_checkbox(self, name, value=None, **kwargs):
+ self.add(CheckboxWidget, name, value, **kwargs)
+
+ def add_single_select(self, name, value=None, **kwargs):
+ self.add(SingleSelectWidget, name, value, **kwargs)
+
+ def add_multiple_select(self, name, value=None, **kwargs):
+ self.add(MultipleSelectWidget, name, value, **kwargs)
+
+ def add_radiobuttons(self, name, value=None, **kwargs):
+ self.add(RadiobuttonsWidget, name, value, **kwargs)
+
+ def add_float(self, name, value=None, **kwargs):
+ self.add(FloatWidget, name, value, **kwargs)
+
+ def add_int(self, name, value=None, **kwargs):
+ self.add(IntWidget, name, value, **kwargs)
+
+
+ # -- Layout (rendering) methods ------------------------------------
+
+ def render(self):
+ """() -> HTML text
+ Render a form as HTML.
+ """
+ r = TemplateIO(html=True)
+ r += self._render_start()
+ r += self._render_body()
+ r += self._render_finish()
+ return r.getvalue()
+
+ def _render_start(self):
+ r = TemplateIO(html=True)
+ r += htmltag('form', name=self.name, method=self.method,
+ enctype=self.enctype, action=self.action_url,
+ **self.attrs)
+ r += self._render_hidden_widgets()
+ return r.getvalue()
+
+ def _render_finish(self):
+ r = TemplateIO(html=True)
+ r += htmltext('</form><br class="quixoteform" />')
+ code = get_request().response.javascript_code
+ if code:
+ r += self._render_javascript(code)
+ return r.getvalue()
+
+ def _render_widgets(self):
+ r = TemplateIO(html=True)
+ for widget in self.widgets:
+ r += widget.render()
+ return r.getvalue()
+
+ def _render_hidden_widgets(self):
+ r = TemplateIO(html=True)
+ for widget in self.hidden_widgets:
+ r += widget.render()
+ return r.getvalue()
+
+ def _render_submit_widgets(self):
+ r = TemplateIO(html=True)
+ if self.submit_widgets:
+ r += htmltext('<div class="submit">')
+ for widget in self.submit_widgets:
+ r += widget.render()
+ r += htmltext('</div><br class="submit" />')
+ return r.getvalue()
+
+ def _render_error_notice(self):
+ token_widget = self.get_widget(self.TOKEN_NAME)
+ if token_widget is not None and token_widget.has_error():
+ # form tokens are enabled but the token data in the request
+ # does not match anything in the session. It could be an
+ # a cross-site attack but most likely the back button has
+ # be used
+ return htmltext('<div class="errornotice">'
+ 'The form you have submitted is invalid. Most '
+ 'likely it has been successfully submitted once '
+ 'already. Please review the the form data '
+ 'and submit the form again.'
+ '</div>')
+ else:
+ return htmltext('<div class="errornotice">'
+ 'There were errors processing your form. '
+ 'See below for details.'
+ '</div>')
+
+ def _render_javascript(self, javascript_code):
+ """Render javacript code for the form. Insert code lexically
+ sorted by code_id.
+ """
+ form_code = []
+ code_ids = javascript_code.keys()
+ code_ids.sort()
+ for code_id in code_ids:
+ code = javascript_code[code_id]
+ if code:
+ form_code.append(code)
+ javascript_code[code_id] = ''
+ if form_code:
+ return self.JAVASCRIPT_MARKUP % htmltext(''.join(form_code))
+ else:
+ return ''
+
+ def _render_body(self):
+ r = TemplateIO(html=True)
+ if self.has_errors():
+ r += self._render_error_notice()
+ r += self._render_widgets()
+ r += self._render_submit_widgets()
+ return r.getvalue()
Added: packages/quixote1/branches/upstream/current/form2/widget.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/form2/widget.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/form2/widget.py (added)
+++ packages/quixote1/branches/upstream/current/form2/widget.py Mon May 8 19:16:16 2006
@@ -1,0 +1,954 @@
+"""$URL: svn+ssh://svn/repos/trunk/quixote/form2/widget.py $
+$Id: widget.py 25244 2004-10-01 14:05:13Z dbinger $
+
+Provides the basic web widget classes: Widget itself, plus StringWidget,
+TextWidget, CheckboxWidget, etc.
+"""
+
+import struct
+from types import FloatType, IntType, ListType, StringType, TupleType
+from quixote import get_request
+from quixote.html import htmltext, htmlescape, htmltag, TemplateIO
+from quixote.upload import Upload
+
+try:
+ True, False
+except NameError:
+ True = 1
+ False = 0
+
+
+def subname(prefix, name):
+ """Create a unique name for a sub-widget or sub-component."""
+ # $ is nice because it's valid as part of a Javascript identifier
+ return "%s$%s" % (prefix, name)
+
+
+def merge_attrs(base, overrides):
+ """({string: any}, {string: any}) -> {string: any}
+ """
+ items = []
+ if base:
+ items.extend(base.items())
+ if overrides:
+ items.extend(overrides.items())
+ attrs = {}
+ for name, val in items:
+ if name.endswith('_'):
+ name = name[:-1]
+ attrs[name] = val
+ return attrs
+
+
+class WidgetValueError(Exception):
+ """May be raised a widget has problems parsing its value."""
+
+ def __init__(self, msg):
+ self.msg = msg
+
+ def __str__(self):
+ return str(self.msg)
+
+
+class Widget:
+ """Abstract base class for web widgets.
+
+ Instance attributes:
+ name : string
+ value : any
+ error : string
+ title : string
+ hint : string
+ required : bool
+ attrs : {string: any}
+ _parsed : bool
+
+ Feel free to access these directly; to set them, use the 'set_*()'
+ modifier methods.
+ """
+
+ def __init__(self, name, value=None, title="", hint="", required=False,
+ render_br=True, attrs=None, **kwattrs):
+ assert self.__class__ is not Widget, "abstract class"
+ self.name = name
+ self.value = value
+ self.error = None
+ self.title = title
+ self.hint = hint
+ self.required = required
+ self.render_br = render_br
+ self.attrs = merge_attrs(attrs, kwattrs)
+ self._parsed = False
+
+ def __repr__(self):
+ return "<%s at %x: %s>" % (self.__class__.__name__,
+ id(self),
+ self.name)
+
+ def __str__(self):
+ return "%s: %s" % (self.__class__.__name__, self.name)
+
+ def get_name(self):
+ return self.name
+
+ def set_value(self, value):
+ self.value = value
+
+ def set_error(self, error):
+ self.error = error
+
+ def get_error(self, request=None):
+ self.parse(request=request)
+ return self.error
+
+ def has_error(self, request=None):
+ return bool(self.get_error(request=request))
+
+ def clear_error(self, request=None):
+ self.parse(request=request)
+ self.error = None
+
+ def set_title(self, title):
+ self.title = title
+
+ def get_title(self):
+ return self.title
+
+ def set_hint(self, hint):
+ self.hint = hint
+
+ def get_hint(self):
+ return self.hint
+
+ def is_required(self):
+ return self.required
+
+ def parse(self, request=None):
+ if not self._parsed:
+ self._parsed = True
+ if request is None:
+ request = get_request()
+ if request.form or request.get_method() == 'POST':
+ try:
+ self._parse(request)
+ except WidgetValueError, exc:
+ self.set_error(str(exc))
+ if (self.required and self.value is None and
+ not self.has_error()):
+ self.set_error('required')
+ return self.value
+
+ def _parse(self, request):
+ # subclasses may override but this is not part of the public API
+ value = request.form.get(self.name)
+ if type(value) is StringType and value.strip():
+ self.value = value
+ else:
+ self.value = None
+
+ def render_title(self, title):
+ if title:
+ if self.required:
+ title += htmltext('<span class="required">*</span>')
+ return htmltext('<div class="title">%s</div>') % title
+ else:
+ return ''
+
+ def render_hint(self, hint):
+ if hint:
+ return htmltext('<div class="hint">%s</div>') % hint
+ else:
+ return ''
+
+ def render_error(self, error):
+ if error:
+ return htmltext('<div class="error">%s</div>') % error
+ else:
+ return ''
+
+ def render(self):
+ r = TemplateIO(html=True)
+ classnames = '%s widget' % self.__class__.__name__
+ r += htmltext('<div class="%s">') % classnames
+ r += self.render_title(self.get_title())
+ r += htmltext('<div class="content">')
+ r += self.render_content()
+ r += self.render_hint(self.get_hint())
+ r += self.render_error(self.get_error())
+ r += htmltext('</div>')
+ r += htmltext('</div>')
+ if self.render_br:
+ r += htmltext('<br class="%s" />') % classnames
+ r += htmltext('\n')
+ return r.getvalue()
+
+ def render_content(self):
+ raise NotImplementedError
+
+# class Widget
+
+# -- Fundamental widget types ------------------------------------------
+# These correspond to the standard types of input tag in HTML:
+# text StringWidget
+# password PasswordWidget
+# radio RadiobuttonsWidget
+# checkbox CheckboxWidget
+#
+# and also to the other basic form elements:
+# <textarea> TextWidget
+# <select> SingleSelectWidget
+# <select multiple>
+# MultipleSelectWidget
+
+class StringWidget(Widget):
+ """Widget for entering a single string: corresponds to
+ '<input type="text">' in HTML.
+
+ Instance attributes:
+ value : string
+ """
+
+ # This lets PasswordWidget be a trivial subclass
+ HTML_TYPE = "text"
+
+ def render_content(self):
+ return htmltag("input", xml_end=True,
+ type=self.HTML_TYPE,
+ name=self.name,
+ value=self.value,
+ **self.attrs)
+
+
+class FileWidget(StringWidget):
+ """Subclass of StringWidget for uploading files.
+
+ Instance attributes: none
+ """
+
+ HTML_TYPE = "file"
+
+ def _parse(self, request):
+ parsed_value = request.form.get(self.name)
+ if isinstance(parsed_value, Upload):
+ self.value = parsed_value
+ else:
+ self.value = None
+
+
+class PasswordWidget(StringWidget):
+ """Trivial subclass of StringWidget for entering passwords (different
+ widget type because HTML does it that way).
+
+ Instance attributes: none
+ """
+
+ HTML_TYPE = "password"
+
+
+class TextWidget(Widget):
+ """Widget for entering a long, multi-line string; corresponds to
+ the HTML "<textarea>" tag.
+
+ Instance attributes:
+ value : string
+ """
+
+ def _parse(self, request):
+ Widget._parse(self, request)
+ if self.value and self.value.find("\r\n") >= 0:
+ self.value = self.value.replace("\r\n", "\n")
+
+ def render_content(self):
+ return (htmltag("textarea", name=self.name, **self.attrs) +
+ htmlescape(self.value or "") +
+ htmltext("</textarea>"))
+
+
+class CheckboxWidget(Widget):
+ """Widget for a single checkbox: corresponds to "<input
+ type=checkbox>". Do not put multiple CheckboxWidgets with the same
+ name in the same form.
+
+ Instance attributes:
+ value : boolean
+ """
+
+ def _parse(self, request):
+ self.value = request.form.has_key(self.name)
+
+ def render_content(self):
+ return htmltag("input", xml_end=True,
+ type="checkbox",
+ name=self.name,
+ value="yes",
+ checked=self.value and "checked" or None,
+ **self.attrs)
+
+
+
+class SelectWidget(Widget):
+ """Widget for single or multiple selection; corresponds to
+ <select name=...>
+ <option value="Foo">Foo</option>
+ ...
+ </select>
+
+ Instance attributes:
+ options : [ (value:any, description:any, key:string) ]
+ value : any
+ The value is None or an element of dict(options.values()).
+ """
+
+ def __init__(self, name, value=None, options=None, sort=False,
+ verify_selection=True, **kwargs):
+ assert self.__class__ is not SelectWidget, "abstract class"
+ Widget.__init__(self, name, value, **kwargs)
+ self.options = []
+ if not options:
+ raise ValueError, "a non-empty list of 'options' is required"
+ else:
+ self.set_options(options, sort)
+ self.verify_selection = verify_selection
+
+ def get_allowed_values(self):
+ return [item[0] for item in self.options]
+
+ def get_descriptions(self):
+ return [item[1] for item in self.options]
+
+ def set_value(self, value):
+ self.value = None
+ for object, description, key in self.options:
+ if value == object:
+ self.value = value
+ break
+
+ def _generate_keys(self, values, descriptions):
+ """Called if no keys were provided. Try to generate a set of keys
+ that will be consistent between rendering and parsing.
+ """
+ # try to use ZODB object IDs
+ keys = []
+ for value in values:
+ if value is None:
+ oid = ""
+ else:
+ oid = getattr(value, "_p_oid", None)
+ if not oid:
+ break
+ hi, lo = struct.unpack(">LL", oid)
+ oid = "%x" % ((hi << 32) | lo)
+ keys.append(oid)
+ else:
+ # found OID for every value
+ return keys
+ # can't use OIDs, try using descriptions
+ used_keys = {}
+ keys = map(str, descriptions)
+ for key in keys:
+ if used_keys.has_key(key):
+ raise ValueError, "duplicated descriptions (provide keys)"
+ used_keys[key] = 1
+ return keys
+
+ def set_options(self, options, sort=False):
+ """(options: [objects:any], sort=False)
+ or
+ (options: [(object:any, description:any)], sort=False)
+ or
+ (options: [(object:any, description:any, key:any)], sort=False)
+ """
+
+ """
+ Set the options list. The list of options can be a list of objects, in
+ which case the descriptions default to map(htmlescape, objects)
+ applying htmlescape() to each description and
+ key.
+ If keys are provided they must be distinct. If the sort keyword
+ argument is true, sort the options by case-insensitive lexicographic
+ order of descriptions, except that options with value None appear
+ before others.
+ """
+ if options:
+ first = options[0]
+ values = []
+ descriptions = []
+ keys = []
+ if type(first) is TupleType:
+ if len(first) == 2:
+ for value, description in options:
+ values.append(value)
+ descriptions.append(description)
+ elif len(first) == 3:
+ for value, description, key in options:
+ values.append(value)
+ descriptions.append(description)
+ keys.append(str(key))
+ else:
+ raise ValueError, 'invalid options %r' % options
+ else:
+ values = descriptions = options
+
+ if not keys:
+ keys = self._generate_keys(values, descriptions)
+
+ options = zip(values, descriptions, keys)
+
+ if sort:
+ def make_sort_key(option):
+ value, description, key = option
+ if value is None:
+ return ('', option)
+ else:
+ return (str(description).lower(), option)
+ doptions = map(make_sort_key, options)
+ doptions.sort()
+ options = [item[1] for item in doptions]
+ self.options = options
+
+ def _parse_single_selection(self, parsed_key, default=None):
+ for value, description, key in self.options:
+ if key == parsed_key:
+ return value
+ else:
+ if self.verify_selection:
+ self.error = "invalid value selected"
+ return default
+ elif self.options:
+ return self.options[0][0]
+ else:
+ return default
+
+ def set_allowed_values(self, allowed_values, descriptions=None,
+ sort=False):
+ """(allowed_values:[any], descriptions:[any], sort:boolean=False)
+
+ Set the options for this widget. The allowed_values and descriptions
+ parameters must be sequences of the same length. The sort option
+ causes the options to be sorted using case-insensitive lexicographic
+ order of descriptions, except that options with value None appear
+ before others.
+ """
+ if descriptions is None:
+ self.set_options(allowed_values, sort)
+ else:
+ assert len(descriptions) == len(allowed_values)
+ self.set_options(zip(allowed_values, descriptions), sort)
+
+ def is_selected(self, value):
+ return value == self.value
+
+ def render_content(self):
+ tags = [htmltag("select", name=self.name, **self.attrs)]
+ for object, description, key in self.options:
+ if self.is_selected(object):
+ selected = 'selected'
+ else:
+ selected = None
+ if description is None:
+ description = ""
+ r = htmltag("option", value=key, selected=selected)
+ tags.append(r + htmlescape(description) + htmltext('</option>'))
+ tags.append(htmltext("</select>"))
+ return htmltext("\n").join(tags)
+
+
+class SingleSelectWidget(SelectWidget):
+ """Widget for single selection.
+ """
+
+ SELECT_TYPE = "single_select"
+
+ def _parse(self, request):
+ parsed_key = request.form.get(self.name)
+ if parsed_key:
+ if type(parsed_key) is ListType:
+ self.error = "cannot select multiple values"
+ else:
+ self.value = self._parse_single_selection(parsed_key)
+ else:
+ self.value = None
+
+
+class RadiobuttonsWidget(SingleSelectWidget):
+ """Widget for a *set* of related radiobuttons -- all have the
+ same name, but different values (and only one of those values
+ is returned by the whole group).
+
+ Instance attributes:
+ delim : string = None
+ string to emit between each radiobutton in the group. If
+ None, a single newline is emitted.
+ """
+
+ SELECT_TYPE = "radiobuttons"
+
+ def __init__(self, name, value=None, options=None, delim=None, **kwargs):
+ SingleSelectWidget.__init__(self, name, value, options=options,
+ **kwargs)
+ if delim is None:
+ self.delim = "\n"
+ else:
+ self.delim = delim
+
+ def render_content(self):
+ tags = []
+ for object, description, key in self.options:
+ if self.is_selected(object):
+ checked = 'checked'
+ else:
+ checked = None
+ r = htmltag("input", xml_end=True,
+ type="radio",
+ name=self.name,
+ value=key,
+ checked=checked,
+ **self.attrs)
+ tags.append(r + htmlescape(description))
+ return htmlescape(self.delim).join(tags)
+
+
+class MultipleSelectWidget(SelectWidget):
+ """Widget for multiple selection.
+
+ Instance attributes:
+ value : [any]
+ for multipe selects, the value is None or a list of
+ elements from dict(self.options).values()
+ """
+
+ SELECT_TYPE = "multiple_select"
+
+ def __init__(self, name, value=None, options=None, **kwargs):
+ SelectWidget.__init__(self, name, value, options=options,
+ multiple='multiple', **kwargs)
+
+ def set_value(self, value):
+ allowed_values = self.get_allowed_values()
+ if value in allowed_values:
+ self.value = [ value ]
+ elif type(value) in (ListType, TupleType):
+ self.value = [ element
+ for element in value
+ if element in allowed_values ] or None
+ else:
+ self.value = None
+
+ def is_selected(self, value):
+ if self.value is None:
+ return value is None
+ else:
+ return value in self.value
+
+ def _parse(self, request):
+ parsed_keys = request.form.get(self.name)
+ if parsed_keys:
+ if type(parsed_keys) is ListType:
+ self.value = [value
+ for value, description, key in self.options
+ if key in parsed_keys] or None
+ else:
+ _marker = []
+ value = self._parse_single_selection(parsed_keys, _marker)
+ if value is _marker:
+ self.value = None
+ else:
+ self.value = [value]
+ else:
+ self.value = None
+
+
+class ButtonWidget(Widget):
+ """
+ Instance attributes:
+ label : string
+ value : boolean
+ """
+
+ HTML_TYPE = "button"
+
+ def __init__(self, name, value=None, **kwargs):
+ Widget.__init__(self, name, value=None, **kwargs)
+ self.set_label(value)
+
+ def set_label(self, label):
+ self.label = label
+
+ def get_label(self):
+ return self.label
+
+ def render_content(self):
+ # slightly different behavior here, we always render the
+ # tag using the 'value' passed in as a parameter. 'self.value'
+ # is a boolean that is true if the button's name appears
+ # in the request.
+ value = (self.label and htmlescape(self.label) or None)
+ return htmltag("input", xml_end=True, type=self.HTML_TYPE,
+ name=self.name, value=value, **self.attrs)
+
+ def _parse(self, request):
+ self.value = request.form.has_key(self.name)
+
+
+class SubmitWidget(ButtonWidget):
+ HTML_TYPE = "submit"
+
+class ResetWidget(SubmitWidget):
+ HTML_TYPE = "reset"
+
+
+class HiddenWidget(Widget):
+ """
+ Instance attributes:
+ value : string
+ """
+
+ def set_error(self, error):
+ if error is not None:
+ raise TypeError, 'error not allowed on hidden widgets'
+
+ def render_content(self):
+ if self.value is None:
+ value = None
+ else:
+ value = htmlescape(self.value)
+ return htmltag("input", xml_end=True,
+ type="hidden",
+ name=self.name,
+ value=value,
+ **self.attrs)
+
+ def render(self):
+ return self.render_content() # Input elements of type hidden have no decoration.
+
+# -- Derived widget types ----------------------------------------------
+# (these don't correspond to fundamental widget types in HTML,
+# so they're separated)
+
+class NumberWidget(StringWidget):
+ """
+ Instance attributes: none
+ """
+
+ # Parameterize the number type (either float or int) through
+ # these class attributes:
+ TYPE_OBJECT = None # eg. int, float
+ TYPE_ERROR = None # human-readable error message
+ TYPE_CONVERTER = None # eg. int(), float()
+
+ def __init__(self, name, value=None, **kwargs):
+ assert self.__class__ is not NumberWidget, "abstract class"
+ assert value is None or type(value) is self.TYPE_OBJECT, (
+ "form value '%s' not a %s: got %r" % (name,
+ self.TYPE_OBJECT,
+ value))
+ StringWidget.__init__(self, name, value, **kwargs)
+
+ def _parse(self, request):
+ StringWidget._parse(self, request)
+ if self.value is not None:
+ try:
+ self.value = self.TYPE_CONVERTER(self.value)
+ except ValueError:
+ self.error = self.TYPE_ERROR
+
+
+class FloatWidget(NumberWidget):
+ """
+ Instance attributes:
+ value : float
+ """
+ TYPE_OBJECT = FloatType
+ TYPE_CONVERTER = float
+ TYPE_ERROR = "must be a number"
+
+
+class IntWidget(NumberWidget):
+ """
+ Instance attributes:
+ value : int
+ """
+ TYPE_OBJECT = IntType
+ TYPE_CONVERTER = int
+ TYPE_ERROR = "must be an integer"
+
+
+class OptionSelectWidget(SingleSelectWidget):
+ """Widget for single selection with automatic submission. Parse
+ will always return a value from it's options, even if the form is
+ not submitted. This allows its value to be used to decide what
+ other widgets need to be created in a form. It's a powerful
+ feature but it can be hard to understand what's going on.
+
+ Instance attributes:
+ value : any
+ """
+
+ SELECT_TYPE = "option_select"
+
+ def __init__(self, name, value=None, options=None, **kwargs):
+ SingleSelectWidget.__init__(self, name, value, options=options,
+ onchange='submit()', **kwargs)
+
+ def parse(self, request=None):
+ if not self._parsed:
+ if request is None:
+ request = get_request()
+ self._parse(request)
+ self._parsed = True
+ return self.value
+
+ def _parse(self, request):
+ parsed_key = request.form.get(self.name)
+ if parsed_key:
+ if type(parsed_key) is ListType:
+ self.error = "cannot select multiple values"
+ else:
+ self.value = self._parse_single_selection(parsed_key)
+ elif self.value is None:
+ self.value = self.options[0][0]
+
+ def render_content(self):
+ return (SingleSelectWidget.render_content(self) +
+ htmltext('<noscript>'
+ '<input type="submit" name="" value="apply" />'
+ '</noscript>'))
+
+
+class CompositeWidget(Widget):
+ """
+ Instance attributes:
+ widgets : [Widget]
+ _names : {name:string : Widget}
+ """
+ def __init__(self, name, value=None, **kwargs):
+ Widget.__init__(self, name, value, **kwargs)
+ self.widgets = []
+ self._names = {}
+
+ def _parse(self, request):
+ for widget in self.widgets:
+ widget.parse(request)
+
+ def __getitem__(self, name):
+ return self._names[name].parse()
+
+ def get(self, name):
+ widget = self._names.get(name)
+ if widget:
+ return widget.parse()
+ return None
+
+ def get_widget(self, name):
+ return self._names.get(name)
+
+ def get_widgets(self):
+ return self.widgets
+
+ def clear_error(self, request=None):
+ Widget.clear_error(self, request)
+ for widget in self.widgets:
+ widget.clear_error(request)
+
+ def set_widget_error(self, name, error):
+ self._names[name].set_error(error)
+
+ def has_error(self, request=None):
+ has_error = False
+ if Widget.has_error(self, request=request):
+ has_error = True
+ for widget in self.widgets:
+ if widget.has_error(request=request):
+ has_error = True
+ return has_error
+
+ def add(self, widget_class, name, *args, **kwargs):
+ if self._names.has_key(name):
+ raise ValueError, 'the name %r is already used' % name
+ widget = widget_class(subname(self.name, name), *args, **kwargs)
+ self._names[name] = widget
+ self.widgets.append(widget)
+
+ def render_content(self):
+ r = TemplateIO(html=True)
+ for widget in self.get_widgets():
+ r += widget.render()
+ return r.getvalue()
+
+
+class WidgetList(CompositeWidget):
+ """A variable length list of widgets. There is only one
+ title and hint but each element of the list can have its own
+ error. You can also set an error on the WidgetList itself (e.g. as a
+ result of higher-level processing).
+
+ Instance attributes:
+ element_names : [string]
+ """
+
+ def __init__(self, name, value=None,
+ element_type=StringWidget,
+ element_kwargs={},
+ element_name="row", **kwargs):
+ assert value is None or type(value) is list, (
+ "value '%s' not a list: got %r" % (name, value))
+ assert issubclass(element_type, Widget), (
+ "value '%s' element_type not a Widget: "
+ "got %r" % (name, element_type))
+ assert type(element_kwargs) is dict, (
+ "value '%s' element_kwargs not a dict: "
+ "got %r" % (name, element_kwargs))
+ assert type(element_name) in (str, htmltext), (
+ "value '%s' element_name not a string: "
+ "got %r" % (name, element_name))
+
+ CompositeWidget.__init__(self, name, value, **kwargs)
+ self.element_names = []
+
+ self.add(HiddenWidget, 'added_elements')
+ added_elements_widget = self.get_widget('added_elements')
+
+
+ def add_element(value=None):
+ name = "element%d" % len(self.element_names)
+ self.add(element_type, name, value=value, **element_kwargs)
+ self.element_names.append(name)
+
+ # Add element widgets for initial value
+ if value is not None:
+ for element_value in value:
+ add_element(value=element_value)
+
+ # Add at least one additional element widget
+ num_added = int(added_elements_widget.parse() or 1)
+ for i in range(num_added):
+ add_element()
+
+ # Add submit to add more element widgets
+ self.add(SubmitWidget, 'add_element', value='Add %s' % element_name)
+ if self.get('add_element'):
+ add_element()
+ num_added += 1
+ added_elements_widget.set_value(num_added)
+
+ def _parse(self, request):
+ values = []
+ for name in self.element_names:
+ value = self.get(name)
+ if value is not None:
+ values.append(value)
+ self.value = values or None
+
+ def render_content(self):
+ r = TemplateIO(html=True)
+ add_element_widget = self.get_widget('add_element')
+ for widget in self.get_widgets():
+ if widget is add_element_widget:
+ continue
+ r += widget.render()
+ r += add_element_widget.render()
+ return r.getvalue()
+
+ def render(self):
+ r = TemplateIO(html=True)
+ r += self.render_title(self.get_title())
+ add_element_widget = self.get_widget('add_element')
+ for widget in self.get_widgets():
+ if widget is add_element_widget:
+ continue
+ r += widget.render()
+ r += add_element_widget.render()
+ r += self.render_hint(self.get_hint())
+ return r.getvalue()
+
+
+class WidgetDict(CompositeWidget):
+ """A variable length dict of widgets. There is only one
+ title and hint but each element of the list can have its own
+ error. You can also set an error on the WidgetList itself (e.g. as a
+ result of higher-level processing).
+
+ Instance attributes:
+ element_names : [string]
+ """
+
+ def __init__(self, name, value=None, title='', hint='',
+ element_key_type=StringWidget,
+ element_value_type=StringWidget,
+ element_key_kwargs={},
+ element_value_kwargs={},
+ element_name='row', **kwargs):
+ assert value is None or type(value) is dict, (
+ 'value %r not a dict: got %r' % (name, value))
+ assert issubclass(element_key_type, Widget), (
+ "value '%s' element_key_type not a Widget: "
+ "got %r" % (name, element_key_type))
+ assert issubclass(element_value_type, Widget), (
+ "value '%s' element_value_type not a Widget: "
+ "got %r" % (name, element_value_type))
+ assert type(element_key_kwargs) is dict, (
+ "value '%s' element_key_kwargs not a dict: "
+ "got %r" % (name, element_key_kwargs))
+ assert type(element_value_kwargs) is dict, (
+ "value '%s' element_value_kwargs not a dict: "
+ "got %r" % (name, element_value_kwargs))
+ assert type(element_name) in (str, htmltext), (
+ 'value %r element_name not a string: '
+ 'got %r' % (name, element_name))
+
+ CompositeWidget.__init__(self, name, value, **kwargs)
+ self.element_names = []
+
+ self.add(HiddenWidget, 'added_elements')
+ added_elements_widget = self.get_widget('added_elements')
+
+ def add_element(key=None, value=None):
+ name = 'element%d' % len(self.element_names)
+ self.add(element_key_type, name + 'key',
+ value=key, render_br=False, **element_key_kwargs)
+ self.add(element_value_type, name + 'value',
+ value=value, **element_value_kwargs)
+ self.element_names.append(name)
+
+ # Add element widgets for initial value
+ if value is not None:
+ for key, element_value in value.items():
+ add_element(key=key, value=element_value)
+
+ # Add at least one additional element widget
+ num_added = int(added_elements_widget.parse() or 1)
+ for i in range(num_added):
+ add_element()
+
+ # Add submit to add more element widgets
+ self.add(SubmitWidget, 'add_element', value='Add %s' % element_name)
+ if self.get('add_element'):
+ add_element()
+ num_added += 1
+ added_elements_widget.set_value(num_added)
+
+ def _parse(self, request):
+ values = {}
+ for name in self.element_names:
+ key = self.get(name + 'key')
+ value = self.get(name + 'value')
+ if key and value:
+ values[key] = value
+ self.value = values or None
+
+ def render_content(self):
+ r = TemplateIO(html=True)
+ for name in self.element_names:
+ if name in ('add_element', 'added_elements'):
+ continue
+ key_widget = self.get_widget(name + 'key')
+ value_widget = self.get_widget(name + 'value')
+ r += htmltext('%s<div class="widget">: </div>%s') % (
+ key_widget.render(),
+ value_widget.render())
+ if self.render_br:
+ r += htmltext('<br clear="left" class="widget" />')
+ r += htmltext('\n')
+ r += self.get_widget('add_element').render()
+ r += self.get_widget('added_elements').render()
+ return r.getvalue()
Added: packages/quixote1/branches/upstream/current/html.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/html.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/html.py (added)
+++ packages/quixote1/branches/upstream/current/html.py Mon May 8 19:16:16 2006
@@ -1,0 +1,185 @@
+"""Various functions for dealing with HTML.
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/html.py $
+$Id: html.py 25234 2004-09-30 17:36:19Z nascheme $
+
+These functions are fairly simple but it is critical that they be
+used correctly. Many security problems are caused by quoting errors
+(cross site scripting is one example). The HTML and XML standards on
+www.w3c.org and www.xml.com should be studied, especially the sections
+on character sets, entities, attribute and values.
+
+htmltext and htmlescape
+-----------------------
+
+This type and function are meant to be used with [html] PTL template type.
+The htmltext type designates data that does not need to be escaped and the
+htmlescape() function calls str() on the argment, escapes the resulting
+string and returns a htmltext instance. htmlescape() does nothing to
+htmltext instances.
+
+
+html_quote
+----------
+
+Use for quoting data that will be used within attribute values or as
+element contents (if the [html] template type is not being used).
+Examples:
+
+ '<title>%s</title>' % html_quote(title)
+ '<input type="hidden" value="%s" />' % html_quote(data)
+ '<a href="%s">something</a>' % html_quote(url)
+
+Note that the \" character should be used to surround attribute values.
+
+
+url_quote
+---------
+
+Use for quoting data to be included as part of a URL, for example:
+
+ input = "foo bar"
+ ...
+ '<a href="/search?keyword=%s">' % url_quote(input)
+
+Note that URLs are usually used as attribute values and should be quoted
+using html_quote. For example:
+
+ url = 'http://example.com/?a=1©=0'
+ ...
+ '<a href="%s">do something</a>' % html_quote(url)
+
+If html_quote is not used, old browsers would treat "©" as an entity
+reference and replace it with the copyright character. XML processors should
+treat it as an invalid entity reference.
+
+"""
+
+__revision__ = "$Id: html.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import urllib
+from types import UnicodeType
+
+try:
+ # faster C implementation
+ from quixote._c_htmltext import htmltext, htmlescape, _escape_string, \
+ TemplateIO
+except ImportError:
+ from quixote._py_htmltext import htmltext, htmlescape, _escape_string, \
+ TemplateIO
+
+ValuelessAttr = ["valueless_attr"] # magic singleton object
+
+def htmltag(tag, xml_end=0, css_class=None, **attrs):
+ """Create a HTML tag.
+ """
+ r = ["<%s" % tag]
+ if css_class is not None:
+ attrs['class'] = css_class
+ for (attr, val) in attrs.items():
+ if val is ValuelessAttr:
+ val = attr
+ if val is not None:
+ r.append(' %s="%s"' % (attr, _escape_string(str(val))))
+ if xml_end:
+ r.append(" />")
+ else:
+ r.append(">")
+ return htmltext("".join(r))
+
+
+def href(url, text, title=None, **attrs):
+ return (htmltag("a", href=url, title=title, **attrs) +
+ htmlescape(text) +
+ htmltext("</a>"))
+
+
+def nl2br(value):
+ """nl2br(value : any) -> htmltext
+
+ Insert <br /> tags before newline characters.
+ """
+ text = htmlescape(value)
+ return htmltext(text.s.replace('\n', '<br />\n'))
+
+
+def url_quote(value, fallback=None):
+ """url_quote(value : any [, fallback : string]) -> string
+
+ Quotes 'value' for use in a URL; see urllib.quote(). If value is None,
+ then the behavior depends on the fallback argument. If it is not
+ supplied then an error is raised. Otherwise, the fallback value is
+ returned unquoted.
+ """
+ if value is None:
+ if fallback is None:
+ raise ValueError, "value is None and no fallback supplied"
+ else:
+ return fallback
+ if isinstance(value, UnicodeType):
+ value = value.encode('iso-8859-1')
+ else:
+ value = str(value)
+ return urllib.quote(value)
+
+
+#
+# The rest of this module is for Quixote applications that were written
+# before 'htmltext'. If you are writing a new application, ignore them.
+#
+
+def html_quote(value, fallback=None):
+ """html_quote(value : any [, fallback : string]) -> str
+
+ Quotes 'value' for use in an HTML page. The special characters &,
+ <, > are replaced by SGML entities. If value is None, then the
+ behavior depends on the fallback argument. If it is not supplied
+ then an error is raised. Otherwise, the fallback value is returned
+ unquoted.
+ """
+ if value is None:
+ if fallback is None:
+ raise ValueError, "value is None and no fallback supplied"
+ else:
+ return fallback
+ elif isinstance(value, UnicodeType):
+ value = value.encode('iso-8859-1')
+ else:
+ value = str(value)
+ value = value.replace("&", "&") # must be done first
+ value = value.replace("<", "<")
+ value = value.replace(">", ">")
+ value = value.replace('"', """)
+ return value
+
+
+def value_quote(value):
+ """Quote HTML attribute values. This function is of marginal
+ utility since html_quote can be used.
+
+ XHTML 1.0 requires that all values be quoted. weblint claims
+ that some clients don't understand single quotes. For compatibility
+ with HTML, XHTML 1.0 requires that ampersands be encoded.
+ """
+ assert value is not None, "can't pass None to value_quote"
+ value = str(value).replace('&', '&')
+ value = value.replace('"', '"')
+ return '"%s"' % value
+
+
+def link(url, text, title=None, name=None, **kwargs):
+ return render_tag("a", href=url, title=title, name=name,
+ **kwargs) + str(text) + "</a>"
+
+
+def render_tag(tag, xml_end=0, **attrs):
+ r = "<%s" % tag
+ for (attr, val) in attrs.items():
+ if val is ValuelessAttr:
+ r += ' %s="%s"' % (attr, attr)
+ elif val is not None:
+ r += " %s=%s" % (attr, value_quote(val))
+ if xml_end:
+ r += " />"
+ else:
+ r += ">"
+ return r
Added: packages/quixote1/branches/upstream/current/http_request.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/http_request.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/http_request.py (added)
+++ packages/quixote1/branches/upstream/current/http_request.py Mon May 8 19:16:16 2006
@@ -1,0 +1,522 @@
+"""quixote.http_request
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/http_request.py $
+$Id: http_request.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Provides the HTTPRequest class and related code for parsing HTTP
+requests, such as the FileUpload class.
+
+Derived from Zope's HTTPRequest module (hence the different
+copyright and license from the rest of Quixote).
+"""
+
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+__revision__ = "$Id: http_request.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import re
+import time
+import urlparse, urllib
+from cgi import FieldStorage
+from types import ListType
+
+from quixote.http_response import HTTPResponse
+from quixote.html import html_quote
+
+
+# Various regexes for parsing specific bits of HTTP, all from RFC 2616.
+
+# These are needed by 'get_encoding()', to parse the "Accept-Encoding"
+# header. LWS is linear whitespace; the latter two assume that LWS
+# has been removed.
+_http_lws_re = re.compile("(\r\n)?[ \t]+")
+_http_list_re = re.compile(r",+")
+_http_encoding_re = re.compile(r"([^;]+)(;q=([\d.]+))?$")
+
+# These are needed by 'guess_browser_version()', for parsing the
+# "User-Agent" header.
+# token = 1*<any CHAR except CTLs or separators>
+# CHAR = any 7-bit US ASCII character (0-127)
+# separators are ( ) < > @ , ; : \ " / [ ] ? = { }
+#
+# The user_agent RE is a simplification; it only looks for one "product",
+# possibly followed by a comment.
+_http_token_pat = r'[^\x00-\x20\(\)\<\>\@\,\;\:\\\"\/\[\]\?\=\{\}\x7F-\xFF]+'
+_http_product_pat = r'(%s)(?:/(%s))?' % (_http_token_pat, _http_token_pat)
+_http_product_re = re.compile(_http_product_pat)
+_comment_delim_re = re.compile(r';\s*')
+
+
+def get_content_type(environ):
+ ctype = environ.get("CONTENT_TYPE")
+ if ctype:
+ return ctype.split(";")[0]
+ else:
+ return None
+
+
+class HTTPRequest:
+ """
+ Model a single HTTP request and all associated data: environment
+ variables, form variables, cookies, etc.
+
+ To access environment variables associated with the request, use
+ get_environ(): eg. request.get_environ('SERVER_PORT', 80).
+
+ To access form variables, use get_form_var(), eg.
+ request.get_form_var("name").
+
+ To access cookies, use get_cookie().
+
+ Various bits and pieces of the requested URL can be accessed with
+ get_url(), get_path(), get_server()
+
+ The HTTPResponse object corresponding to this request is available
+ in the 'response' attribute. This is rarely needed: eg. to send an
+ error response, you should raise one of the exceptions in errors.py;
+ to send a redirect, you should use the request's redirect() method,
+ which lets you specify relative URLs. However, if you need to tweak
+ the response object in other ways, you can do so via 'response'.
+ Just keep in mind that Quixote discards the original response object
+ when handling an exception.
+ """
+
+ def __init__(self, stdin, environ, content_type=None):
+ self.stdin = stdin
+ self.environ = environ
+ if content_type is None:
+ self.content_type = get_content_type(environ)
+ else:
+ self.content_type = content_type
+ self.form = {}
+ self.session = None
+ self.response = HTTPResponse()
+ self.start_time = None
+
+ # The strange treatment of SERVER_PORT_SECURE is because IIS
+ # sets this environment variable to "0" for non-SSL requests
+ # (most web servers -- well, Apache at least -- simply don't set
+ # it in that case).
+ if (environ.get('HTTPS', 'off').lower() == 'on' or
+ environ.get('SERVER_PORT_SECURE', '0') != '0'):
+ self.scheme = "https"
+ else:
+ self.scheme = "http"
+
+ k = self.environ.get('HTTP_COOKIE', '')
+ if k:
+ self.cookies = parse_cookie(k)
+ else:
+ self.cookies = {}
+
+ # IIS breaks PATH_INFO because it leaves in the path to
+ # the script, so SCRIPT_NAME is "/cgi-bin/q.py" and PATH_INFO
+ # is "/cgi-bin/q.py/foo/bar". The following code fixes
+ # PATH_INFO to the expected value "/foo/bar".
+ web_server = environ.get('SERVER_SOFTWARE', 'unknown')
+ if web_server.find('Microsoft-IIS') != -1:
+ script = environ['SCRIPT_NAME']
+ path = environ['PATH_INFO']
+ if path.startswith(script):
+ path = path[len(script):]
+ self.environ['PATH_INFO'] = path
+
+ def add_form_value(self, key, value):
+ if self.form.has_key(key):
+ found = self.form[key]
+ if type(found) is ListType:
+ found.append(value)
+ else:
+ found = [found, value]
+ self.form[key] = found
+ else:
+ self.form[key] = value
+
+ def process_inputs(self):
+ """Process request inputs.
+ """
+ self.start_time = time.time()
+ if self.get_method() != 'GET':
+ # Avoid consuming the contents of stdin unless we're sure
+ # there's actually form data.
+ if self.content_type == "multipart/form-data":
+ raise RuntimeError(
+ "cannot handle multipart/form-data requests")
+ elif self.content_type == "application/x-www-form-urlencoded":
+ fp = self.stdin
+ else:
+ return
+ else:
+ fp = None
+
+ fs = FieldStorage(fp=fp, environ=self.environ, keep_blank_values=1)
+ if fs.list:
+ for item in fs.list:
+ self.add_form_value(item.name, item.value)
+
+ def get_header(self, name, default=None):
+ """get_header(name : string, default : string = None) -> string
+
+ Return the named HTTP header, or an optional default argument
+ (or None) if the header is not found. Note that both original
+ and CGI-ified header names are recognized, e.g. 'Content-Type',
+ 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE' should all return the
+ Content-Type header, if available.
+ """
+ environ = self.environ
+ name = name.replace("-", "_").upper()
+ val = environ.get(name)
+ if val is not None:
+ return val
+ if name[:5] != 'HTTP_':
+ name = 'HTTP_' + name
+ return environ.get(name, default)
+
+ def get_cookie(self, cookie_name, default=None):
+ return self.cookies.get(cookie_name, default)
+
+ def get_form_var(self, var_name, default=None):
+ return self.form.get(var_name, default)
+
+ def get_method(self):
+ """Returns the HTTP method for this request
+ """
+ return self.environ.get('REQUEST_METHOD', 'GET')
+
+ def formiter(self):
+ return self.form.iteritems()
+
+ def get_scheme(self):
+ return self.scheme
+
+ # The following environment variables are useful for reconstructing
+ # the original URL, all of which are specified by CGI 1.1:
+ #
+ # SERVER_NAME "www.example.com"
+ # SCRIPT_NAME "/q"
+ # PATH_INFO "/debug/dump_sessions"
+ # QUERY_STRING "session_id=10.27.8.40...."
+
+ def get_server(self):
+ """get_server() -> string
+
+ Return the server name with an optional port number, eg.
+ "www.example.com" or "foo.bar.com:8000".
+ """
+ http_host = self.environ.get("HTTP_HOST")
+ if http_host:
+ return http_host
+ server_name = self.environ["SERVER_NAME"].strip()
+ server_port = self.environ.get("SERVER_PORT")
+ if (not server_port or
+ (self.get_scheme() == "http" and server_port == "80") or
+ (self.get_scheme() == "https" and server_port == "443")):
+ return server_name
+ else:
+ return server_name + ":" + server_port
+
+ def get_path(self, n=0):
+ """get_path(n : int = 0) -> string
+
+ Return the path of the current request, chopping off 'n' path
+ components from the right. Eg. if the path is "/bar/baz/qux",
+ n=0 would return "/bar/baz/qux" and n=2 would return "/bar".
+ Note that the query string, if any, is not included.
+
+ A path with a trailing slash should just be considered as having
+ an empty last component. Eg. if the path is "/bar/baz/", then:
+ get_path(0) == "/bar/baz/"
+ get_path(1) == "/bar/baz"
+ get_path(2) == "/bar"
+
+ If 'n' is negative, then components from the left of the path
+ are returned. Continuing the above example,
+ get_path(-1) = "/bar"
+ get_path(-2) = "/bar/baz"
+ get_path(-3) = "/bar/baz/"
+
+ Raises ValueError if absolute value of n is larger than the number of
+ path components."""
+
+ path_info = self.environ.get('PATH_INFO', '')
+ path = self.environ['SCRIPT_NAME'] + path_info
+ if n == 0:
+ return path
+ else:
+ path_comps = path.split('/')
+ if abs(n) > len(path_comps)-1:
+ raise ValueError, "n=%d too big for path '%s'" % (n, path)
+ if n > 0:
+ return '/'.join(path_comps[:-n])
+ elif n < 0:
+ return '/'.join(path_comps[:-n+1])
+ else:
+ assert 0, "Unexpected value for n (%s)" % n
+
+ def get_url(self, n=0):
+ """get_url(n : int = 0) -> string
+
+ Return the URL of the current request, chopping off 'n' path
+ components from the right. Eg. if the URL is
+ "http://foo.com/bar/baz/qux", n=2 would return
+ "http://foo.com/bar". Does not include the query string (if
+ any).
+ """
+ return "%s://%s%s" % (self.get_scheme(), self.get_server(),
+ urllib.quote(self.get_path(n)))
+
+ def get_environ(self, key, default=None):
+ """get_environ(key : string) -> string
+
+ Fetch a CGI environment variable from the request environment.
+ See http://hoohoo.ncsa.uiuc.edu/cgi/env.html
+ for the variables specified by the CGI standard.
+ """
+ return self.environ.get(key, default)
+
+ def get_encoding(self, encodings):
+ """get_encoding(encodings : [string]) -> string
+
+ Parse the "Accept-encoding" header. 'encodings' is a list of
+ encodings supported by the server sorted in order of preference.
+ The return value is one of 'encodings' or None if the client
+ does not accept any of the encodings.
+ """
+ accept_encoding = self.get_header("accept-encoding") or ""
+ found_encodings = self._parse_pref_header(accept_encoding)
+ if found_encodings:
+ for encoding in encodings:
+ if found_encodings.has_key(encoding):
+ return encoding
+ return None
+
+ def get_accepted_types(self):
+ """get_accepted_types() : {string:float}
+ Return a dictionary mapping MIME types the client will accept
+ to the corresponding quality value (1.0 if no value was specified).
+ """
+ accept_types = self.environ.get('HTTP_ACCEPT', "")
+ return self._parse_pref_header(accept_types)
+
+
+ def _parse_pref_header(self, S):
+ """_parse_pref_header(S:string) : {string:float}
+ Parse a list of HTTP preferences (content types, encodings) and
+ return a dictionary mapping strings to the quality value.
+ """
+
+ found = {}
+ # remove all linear whitespace
+ S = _http_lws_re.sub("", S)
+ for coding in _http_list_re.split(S):
+ m = _http_encoding_re.match(coding)
+ if m:
+ encoding = m.group(1).lower()
+ q = m.group(3) or 1.0
+ try:
+ q = float(q)
+ except ValueError:
+ continue
+ if encoding == "*":
+ continue # stupid, ignore it
+ if q > 0:
+ found[encoding] = q
+ return found
+
+
+ def dump_html(self):
+ row_fmt=('<tr valign="top"><th align="left">%s</th><td>%s</td></tr>')
+ lines = ["<h3>form</h3>",
+ "<table>"]
+
+ for k,v in self.form.items():
+ lines.append(row_fmt % (html_quote(k), html_quote(v)))
+ lines += ["</table>",
+ "<h3>cookies</h3>",
+ "<table>"]
+ for k,v in self.cookies.items():
+ lines.append(row_fmt % (html_quote(k), html_quote(v)))
+
+ lines += ["</table>",
+ "<h3>environ</h3>"
+ "<table>"]
+ for k,v in self.environ.items():
+ lines.append(row_fmt % (html_quote(k), html_quote(str(v))))
+ lines.append("</table>")
+
+ return "\n".join(lines)
+
+ def dump(self):
+ result=[]
+ row='%-15s %s'
+
+ result.append("Form:")
+ L = self.form.items() ; L.sort()
+ for k,v in L:
+ result.append(row % (k,v))
+
+ result.append("")
+ result.append("Cookies:")
+ L = self.cookies.items() ; L.sort()
+ for k,v in L:
+ result.append(row % (k,v))
+
+
+ result.append("")
+ result.append("Environment:")
+ L = self.environ.items() ; L.sort()
+ for k,v in L:
+ result.append(row % (k,v))
+ return "\n".join(result)
+
+ def guess_browser_version(self):
+ """guess_browser_version() -> (name : string, version : string)
+
+ Examine the User-agent request header to try to figure out what
+ the current browser is. Returns either (name, version) where
+ each element is a string, (None, None) if we couldn't parse the
+ User-agent header at all, or (name, None) if we got the name but
+ couldn't figure out the version.
+
+ Handles Microsoft's little joke of pretending to be Mozilla,
+ eg. if the "User-Agent" header is
+ Mozilla/5.0 (compatible; MSIE 5.5)
+ returns ("MSIE", "5.5"). Konqueror does the same thing, and
+ it's handled the same way.
+ """
+ ua = self.get_header('user-agent')
+ if ua is None:
+ return (None, None)
+
+ # The syntax for "User-Agent" in RFC 2616 is fairly simple:
+ #
+ # User-Agent = "User-Agent" ":" 1*( product | comment )
+ # product = token ["/" product-version ]
+ # product-version = token
+ # comment = "(" *( ctext | comment ) ")"
+ # ctext = <any TEXT excluding "(" and ")">
+ # token = 1*<any CHAR except CTLs or tspecials>
+ # tspecials = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" |
+ # "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" |
+ # "}" | SP | HT
+ #
+ # This function handles the most-commonly-used subset of this syntax,
+ # namely
+ # User-Agent = "User-Agent" ":" product 1*SP [comment]
+ # ie. one product string followed by an optional comment;
+ # anything after that first comment is ignored. This should be
+ # enough to distinguish Mozilla/Netscape, MSIE, Opera, and
+ # Konqueror.
+
+ m = _http_product_re.match(ua)
+ if not m:
+ import sys
+ sys.stderr.write("couldn't parse User-Agent header: %r\n" % ua)
+ return (None, None)
+
+ name, version = m.groups()
+ ua = ua[m.end():].lstrip()
+
+ if ua.startswith('('):
+ # we need to handle nested comments since MSIE uses them
+ depth = 1
+ chars = []
+ for c in ua[1:]:
+ if c == '(':
+ depth += 1
+ elif c == ')':
+ depth -= 1
+ if depth == 0:
+ break
+ elif depth == 1:
+ # nested comments are discarded
+ chars.append(c)
+ comment = ''.join(chars)
+ else:
+ comment = ''
+ if comment:
+ comment_chunks = _comment_delim_re.split(comment)
+ else:
+ comment_chunks = []
+
+ if ("compatible" in comment_chunks and
+ len(comment_chunks) > 1 and comment_chunks[1]):
+ # A-ha! Someone is kidding around, pretending to be what
+ # they are not. Most likely MSIE masquerading as Mozilla,
+ # but lots of other clients (eg. Konqueror) do the same.
+ real_ua = comment_chunks[1]
+ if "/" in real_ua:
+ (name, version) = real_ua.split("/", 1)
+ else:
+ if real_ua.startswith("MSIE") and ' ' in real_ua:
+ (name, version) = real_ua.split(" ", 1)
+ else:
+ name = real_ua
+ version = None
+ return (name, version)
+
+ # Either nobody is pulling our leg, or we didn't find anything
+ # that looks vaguely like a user agent in the comment. So use
+ # what we found outside the comment, ie. what the spec says we
+ # should use (sigh).
+ return (name, version)
+
+ # guess_browser_version ()
+
+ def redirect(self, location, permanent=0):
+ """redirect(location : string, permanent : boolean = false)
+ -> string
+
+ Create a redirection response. If the location is relative, then it
+ will automatically be made absolute. The return value is an HTML
+ document indicating the new URL (useful if the client browser does
+ not honor the redirect).
+ """
+ location = urlparse.urljoin(self.get_url(), location)
+ return self.response.redirect(location, permanent)
+
+
+_qparm_re = re.compile(r'([\0- ]*'
+ r'([^\0- ;,=\"]+)="([^"]*)"'
+ r'([\0- ]*[;,])?[\0- ]*)')
+_parm_re = re.compile(r'([\0- ]*'
+ r'([^\0- ;,="]+)=([^\0- ;,"]*)'
+ r'([\0- ]*[;,])?[\0- ]*)')
+
+def parse_cookie(text):
+ result = {}
+
+ pos = 0
+ while 1:
+ mq = _qparm_re.match(text, pos)
+ m = _parm_re.match(text, pos)
+ if mq is not None:
+ # Match quoted correct cookies
+ name = mq.group(2)
+ value = mq.group(3)
+ pos = mq.end()
+ elif m is not None:
+ # Match evil MSIE cookies ;)
+ name = m.group(2)
+ value = m.group(3)
+ pos = m.end()
+ else:
+ # this may be an invalid cookie.
+ # We'll simply bail without raising an error
+ # if the cookie is invalid.
+ return result
+
+ if not result.has_key(name):
+ result[name] = value
+
+ return result
Added: packages/quixote1/branches/upstream/current/http_response.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/http_response.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/http_response.py (added)
+++ packages/quixote1/branches/upstream/current/http_response.py Mon May 8 19:16:16 2006
@@ -1,0 +1,421 @@
+"""quixote.http_response
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/http_response.py $
+$Id: http_response.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Provides the HTTPResponse class.
+
+Derived from Zope's HTTPResponse module (hence the different
+copyright and license from the rest of Quixote).
+"""
+
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+__revision__ = "$Id: http_response.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import time
+from rfc822 import formatdate
+from types import StringType, IntType
+
+status_reasons = {
+ 100: 'Continue',
+ 101: 'Switching Protocols',
+ 102: 'Processing',
+ 200: 'OK',
+ 201: 'Created',
+ 202: 'Accepted',
+ 203: 'Non-Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 207: 'Multi-Status',
+ 300: 'Multiple Choices',
+ 301: 'Moved Permanently',
+ 302: 'Moved Temporarily',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 307: 'Temporary Redirect',
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 402: 'Payment Required',
+ 403: 'Forbidden',
+ 404: 'Not Found',
+ 405: 'Method Not Allowed',
+ 406: 'Not Acceptable',
+ 407: 'Proxy Authentication Required',
+ 408: 'Request Time-out',
+ 409: 'Conflict',
+ 410: 'Gone',
+ 411: 'Length Required',
+ 412: 'Precondition Failed',
+ 413: 'Request Entity Too Large',
+ 414: 'Request-URI Too Large',
+ 415: 'Unsupported Media Type',
+ 416: 'Requested range not satisfiable',
+ 417: 'Expectation Failed',
+ 422: 'Unprocessable Entity',
+ 423: 'Locked',
+ 424: 'Failed Dependency',
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Time-out',
+ 505: 'HTTP Version not supported',
+ 507: 'Insufficient Storage',
+}
+
+
+class HTTPResponse:
+ """
+ An object representation of an HTTP response.
+
+ The Response type encapsulates all possible responses to HTTP
+ requests. Responses are normally created by the Quixote publisher
+ or by the HTTPRequest class (every request must have a response,
+ after all).
+
+ Instance attributes:
+ status_code : int
+ HTTP response status code (integer between 100 and 599)
+ reason_phrase : string
+ the reason phrase that accompanies status_code (usually
+ set automatically by the set_status() method)
+ headers : { string : string }
+ most of the headers included with the response; every header set
+ by 'set_header()' goes here. Does not include "Status" or
+ "Set-Cookie" headers (unless someone uses set_header() to set
+ them, but that would be foolish).
+ body : string
+ the response body, None by default. If the body is never
+ set (ie. left as None), the response will not include
+ "Content-type" or "Content-length" headers. These headers
+ are set as soon as the body is set (with set_body()), even
+ if the body is an empty string.
+ buffered : bool
+ if false, response data will be flushed as soon as it is
+ written (the default is true). This is most useful for
+ responses that use the Stream() protocol. Note that whether the
+ client actually receives the partial response data is highly
+ dependent on the web server
+ cookies : { name:string : { attrname : value } }
+ collection of cookies to set in this response; it is expected
+ that the user-agent will remember the cookies and send them on
+ future requests. The cookie value is stored as the "value"
+ attribute. The other attributes are as specified by RFC 2109.
+ cache : int | None
+ the number of seconds the response may be cached. The default is 0,
+ meaning don't cache at all. This variable is used to set the HTTP
+ expires header. If set to None then the expires header will not be
+ added.
+ javascript_code : { string : string }
+ a collection of snippets of JavaScript code to be included in
+ the response. The collection is built by calling add_javascript(),
+ but actually including the code in the HTML document is somebody
+ else's problem.
+ """
+
+ def __init__(self, status=200, body=None):
+ """
+ Creates a new HTTP response.
+ """
+ self.set_status(status)
+ self.headers = {}
+
+ if body is not None:
+ self.set_body(body)
+ else:
+ self.body = None
+
+ self.cookies = {}
+ self.cache = 0
+ self.buffered = 1
+ self.javascript_code = None
+
+ def set_status(self, status, reason=None):
+ """set_status(status : int, reason : string = None)
+
+ Sets the HTTP status code of the response. 'status' must be an
+ integer in the range 100 .. 599. 'reason' must be a string; if
+ not supplied, the default reason phrase for 'status' will be
+ used. If 'status' is a non-standard status code, the generic
+ reason phrase for its group of status codes will be used; eg.
+ if status == 493, the reason for status 400 will be used.
+ """
+ if type(status) is not IntType:
+ raise TypeError, "status must be an integer"
+ if not (100 <= status <= 599):
+ raise ValueError, "status must be between 100 and 599"
+
+ self.status_code = status
+ if reason is None:
+ if status_reasons.has_key(status):
+ reason = status_reasons[status]
+ else:
+ # Eg. for generic 4xx failures, use the reason
+ # associated with status 400.
+ reason = status_reasons[status - (status % 100)]
+ else:
+ reason = str(reason)
+
+ self.reason_phrase = reason
+
+ def set_header(self, name, value):
+ """set_header(name : string, value : string)
+
+ Sets an HTTP return header "name" with value "value", clearing
+ the previous value set for the header, if one exists.
+ """
+ self.headers[name.lower()] = value
+
+ def get_header(self, name, default=None):
+ """get_header(name : string, default=None) -> value : string
+
+ Gets an HTTP return header "name". If none exists then 'default' is
+ returned.
+ """
+ return self.headers.get(name.lower(), default)
+
+ def set_content_type(self, ctype):
+ """set_content_type(ctype : string)
+
+ Set the "Content-type" header to the MIME type specified in ctype.
+ Shortcut for set_header("Content-type", ctype).
+ """
+ self.headers["content-type"] = ctype
+
+ def set_body(self, body):
+ """set_body(body : any)
+
+ Sets the return body equal to the argument "body". Also updates the
+ "Content-length" header if the length is of the body is known. If
+ the "Content-type" header has not yet been set, it is set to
+ "text/html".
+ """
+ if isinstance(body, Stream):
+ self.body = body
+ if body.length is not None:
+ self.set_header('content-length', body.length)
+ else:
+ self.body = str(body)
+ self.set_header('content-length', len(self.body))
+ if not self.headers.has_key('content-type'):
+ self.set_header('content-type', 'text/html; charset=iso-8859-1')
+
+ def expire_cookie(self, name, **attrs):
+ """
+ Cause an HTTP cookie to be removed from the browser
+
+ The response will include an HTTP header that will remove the cookie
+ corresponding to "name" on the client, if one exists. This is
+ accomplished by sending a new cookie with an expiration date
+ that has already passed. Note that some clients require a path
+ to be specified - this path must exactly match the path given
+ when creating the cookie. The path can be specified as a keyword
+ argument.
+ """
+ dict = {'max_age': 0, 'expires': 'Thu, 01-Jan-1970 00:00:00 GMT'}
+ dict.update(attrs)
+ self.set_cookie(name, "deleted", **dict)
+
+ def set_cookie(self, name, value, **attrs):
+ """set_cookie(name : string, value : string, **attrs)
+
+ Set an HTTP cookie on the browser.
+
+ The response will include an HTTP header that sets a cookie on
+ cookie-enabled browsers with a key "name" and value "value".
+ Cookie attributes such as "expires" and "domains" may be
+ supplied as keyword arguments; see RFC 2109 for a full list.
+ (For the "secure" attribute, use any true value.)
+
+ This overrides any previous value for this cookie. Any
+ previously-set attributes for the cookie are preserved, unless
+ they are explicitly overridden with keyword arguments to this
+ call.
+ """
+ cookies = self.cookies
+ if cookies.has_key(name):
+ cookie = cookies[name]
+ else:
+ cookie = cookies[name] = {}
+ cookie.update(attrs)
+ cookie['value'] = value
+
+ def add_javascript(self, code_id, code):
+ """Add javascript code to be included in the response.
+
+ code_id is used to ensure that the same piece of code is not
+ included twice. The caller must be careful to avoid
+ unintentional code_id and javascript identifier collisions.
+ Note that the response object only provides a mechanism for
+ collecting code -- actually including it in the HTML document
+ that is the response body is somebody else's problem. (For
+ an example, see Form._render_javascript().)
+ """
+ if self.javascript_code is None:
+ self.javascript_code = {code_id: code}
+ elif not self.javascript_code.has_key(code_id):
+ self.javascript_code[code_id] = code
+
+ def redirect(self, location, permanent=0):
+ """Cause a redirection without raising an error"""
+ if not isinstance(location, StringType):
+ raise TypeError, "location must be a string (got %s)" % `location`
+ # Ensure that location is a full URL
+ if location.find('://') == -1:
+ raise ValueError, "URL must include the server name"
+ if permanent:
+ status = 301
+ else:
+ status = 302
+ self.set_status(status)
+ self.headers['location'] = location
+ self.set_content_type('text/plain')
+ return "Your browser should have redirected you to %s" % location
+
+ def _gen_cookie_headers(self):
+ """_gen_cookie_headers() -> [string]
+
+ Build a list of "Set-Cookie" headers based on all cookies
+ set with 'set_cookie()', and return that list.
+ """
+ cookie_list = []
+ for (name, attrs) in self.cookies.items():
+
+ # Note that as of May 98, IE4 ignores cookies with
+ # quoted cookie attr values, so only the value part
+ # of name=value pairs may be quoted.
+
+ # 'chunks' is a list of "name=val" chunks; will be joined
+ # with "; " to create the "Set-cookie" header.
+ chunks = ['%s="%s"' % (name, attrs['value'])]
+
+ for (name, val) in attrs.items():
+ name = name.lower()
+ if val is None:
+ continue
+ if name in ('expires', 'domain', 'path', 'max_age', 'comment'):
+ name = name.replace('_', '-')
+ chunks.append("%s=%s" % (name, val))
+ elif name == 'secure' and val:
+ chunks.append("secure")
+
+ cookie_list.append(("Set-Cookie", ("; ".join(chunks))))
+
+ # Should really check size of cookies here!
+
+ return cookie_list
+
+ def generate_headers(self):
+ """generate_headers() -> [(name:string, value:string)]
+
+ Generate a list of headers to be returned as part of the response.
+ """
+ headers = []
+
+ # "Status" header must come first.
+ headers.append(("Status", "%03d %s" % (self.status_code,
+ self.reason_phrase)))
+
+ for name, value in self.headers.items():
+ headers.append((name.title(), value))
+
+ # All the "Set-Cookie" headers.
+ if self.cookies:
+ headers.extend(self._gen_cookie_headers())
+
+ # Date header
+ now = time.time()
+ if not self.headers.has_key("date"):
+ headers.append(("Date", formatdate(now)))
+
+ # Cache directives
+ if self.cache is None:
+ pass # don't mess with the expires header
+ elif not self.headers.has_key("expires"):
+ if self.cache > 0:
+ expire_date = formatdate(now + self.cache)
+ else:
+ expire_date = "-1" # allowed by HTTP spec and may work better
+ # with some clients
+ headers.append(("Expires", expire_date))
+
+ return headers
+
+
+ def write(self, file):
+ """write(file : file)
+
+ Write the HTTP response headers and body to 'file'. This is not
+ a complete HTTP response, as it doesn't start with a response
+ status line as specified by RFC 2616. It does, however, start
+ with a "Status" header as described by the CGI spec. It
+ is expected that this response is parsed by the web server
+ and turned into a complete HTTP response.
+ """
+ # XXX currently we write a response like this:
+ # Status: 200 OK
+ # Content-type: text/html; charset=iso-8859-1
+ # Content-length: 100
+ # Set-Cookie: foo=bar
+ # Set-Cookie: bar=baz
+ #
+ # <html><body>This is a document</body></html>
+ #
+ # which has to be interpreted by the web server to create
+ # a true HTTP response -- that is, this is for a
+ # "parsed header" CGI driver script.
+ #
+ # We should probably have provisions for operating in
+ # "non-parsed header" mode, where the CGI script is responsible
+ # for generating a complete HTTP response with no help from the
+ # server.
+ flush_output = not self.buffered and hasattr(file, 'flush')
+ for name, value in self.generate_headers():
+ file.write("%s: %s\r\n" % (name, value))
+ file.write("\r\n")
+ if self.body is not None:
+ if isinstance(self.body, Stream):
+ for chunk in self.body:
+ file.write(chunk)
+ if flush_output:
+ file.flush()
+ else:
+ file.write(self.body)
+ if flush_output:
+ file.flush()
+
+
+class Stream:
+ """
+ A wrapper around response data that can be streamed. The 'iterable'
+ argument must support the iteration protocol. Items returned by 'next()'
+ must be strings. Beware that exceptions raised while writing the stream
+ will not be handled gracefully.
+
+ Instance attributes:
+ iterable : any
+ an object that supports the iteration protocol. The items produced
+ by the stream must be strings.
+ length: int | None
+ the number of bytes that will be produced by the stream, None
+ if it is not known. Used to set the Content-Length header.
+ """
+ def __init__(self, iterable, length=None):
+ self.iterable = iterable
+ self.length = length
+
+ def __iter__(self):
+ return iter(self.iterable)
Added: packages/quixote1/branches/upstream/current/mod_python_handler.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/mod_python_handler.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/mod_python_handler.py (added)
+++ packages/quixote1/branches/upstream/current/mod_python_handler.py Mon May 8 19:16:16 2006
@@ -1,0 +1,81 @@
+"""quixote.mod_python_handler
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/mod_python_handler.py $
+$Id: mod_python_handler.py 25236 2004-09-30 18:25:52Z nascheme $
+
+mod_python handler for Quixote. See the "mod_python configuration"
+section of doc/web-server.txt for details.
+"""
+
+import sys
+from mod_python import apache
+from quixote import Publisher, enable_ptl
+from quixote.config import Config
+
+class ErrorLog:
+ def __init__(self, publisher):
+ self.publisher = publisher
+
+ def write(self, msg):
+ self.publisher.log(msg)
+
+ def close(self):
+ pass
+
+class ModPythonPublisher(Publisher):
+ def __init__(self, package, config=None):
+ Publisher.__init__(self, package, config)
+ self.error_log = self.__error_log = ErrorLog(self) # may be overwritten
+ self.setup_logs()
+ self.__apache_request = None
+
+ def log(self, msg):
+ if self.error_log is self.__error_log:
+ try:
+ self.__apache_request.log_error(msg)
+ except AttributeError:
+ apache.log_error(msg)
+ else:
+ Publisher.log(self, msg)
+
+ def publish_modpython(self, req):
+ """publish_modpython() -> None
+
+ Entry point from mod_python.
+ """
+ self.__apache_request = req
+ try:
+ self.publish(apache.CGIStdin(req),
+ apache.CGIStdout(req),
+ sys.stderr,
+ apache.build_cgi_env(req))
+
+ return apache.OK
+ finally:
+ self.__apache_request = None
+
+enable_ptl()
+
+name2publisher = {}
+
+def handler(req):
+ opts = req.get_options()
+ try:
+ package = opts['quixote-root-namespace']
+ except KeyError:
+ package = None
+
+ try:
+ configfile = opts['quixote-config-file']
+ config = Config()
+ config.read_file(configfile)
+ except KeyError:
+ config = None
+
+ if not package:
+ return apache.HTTP_INTERNAL_SERVER_ERROR
+
+ pub = name2publisher.get(package)
+ if pub is None:
+ pub = ModPythonPublisher(package, config)
+ name2publisher[package] = pub
+ return pub.publish_modpython(req)
Added: packages/quixote1/branches/upstream/current/ptl_compile.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/ptl_compile.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/ptl_compile.py (added)
+++ packages/quixote1/branches/upstream/current/ptl_compile.py Mon May 8 19:16:16 2006
@@ -1,0 +1,379 @@
+#!/www/python/bin/python
+#$HeadURL: svn+ssh://svn/repos/trunk/quixote/ptl_compile.py $
+#$Id: ptl_compile.py 25234 2004-09-30 17:36:19Z nascheme $
+
+"""
+Compile a PTL template.
+
+First the tokens "template" are replaced with "def". Next, the file is
+parsed into a parse tree. This tree is converted into a modified AST.
+It is during this state that the semantics are modified by adding extra
+nodes to the tree. Finally bytecode is generated using the compiler
+package.
+
+Note that script/module requires the compiler package.
+"""
+
+__revision__ = "$Id: ptl_compile.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import sys
+import os
+import stat
+import symbol
+import token
+import parser
+import re
+
+assert sys.hexversion >= 0x20000b1, 'PTL requires Python 2.0 or newer'
+
+from compiler import pycodegen, transformer, walk
+from compiler import ast
+from compiler.consts import OP_ASSIGN
+if sys.hexversion >= 0x20200b1:
+ from compiler import misc, syntax
+
+
+# magic names inserted into the code
+IO_MODULE = "quixote.html"
+IO_CLASS = "TemplateIO"
+IO_INSTANCE = "_q_output"
+HTML_TEMPLATE_PREFIX = "_q_html_template_"
+PLAIN_TEMPLATE_PREFIX = "_q_plain_template_"
+TEMPLATE_PREFIX = "_q_template_"
+MARKUP_MODULE = "quixote.html"
+MARKUP_CLASS = "htmltext"
+MARKUP_MANGLED_CLASS = "_q_htmltext"
+
+class TemplateTransformer(transformer.Transformer):
+
+ def __init__(self, *args, **kwargs):
+ transformer.Transformer.__init__(self, *args, **kwargs)
+ self.__template_type = [] # stack, "html", "plain" or None
+
+ def file_input(self, nodelist):
+ # Add a "from IO_MODULE import IO_CLASS" statement to the
+ # beginning of the module.
+ doc = None # self.get_docstring(nodelist, symbol.file_input)
+
+ io_imp = ast.From(IO_MODULE, [(IO_CLASS, None)])
+ markup_imp = ast.From(MARKUP_MODULE, [(MARKUP_CLASS, None)])
+ markup_assign = ast.Assign([ast.AssName(MARKUP_MANGLED_CLASS,
+ OP_ASSIGN)],
+ ast.Name(MARKUP_CLASS))
+
+ # Add an IO_INSTANCE binding for module level expressions (like
+ # doc strings). This instance will not be returned.
+ io_instance = ast.CallFunc(ast.Name(IO_CLASS), [])
+ io_assign_name = ast.AssName(IO_INSTANCE, OP_ASSIGN)
+ io_assign = ast.Assign([io_assign_name], io_instance)
+
+ stmts = [ io_imp, io_assign, markup_imp, markup_assign ]
+
+ for node in nodelist:
+ if node[0] != token.ENDMARKER and node[0] != token.NEWLINE:
+ self.com_append_stmt(stmts, node)
+
+ return ast.Module(doc, ast.Stmt(stmts))
+
+ def funcdef(self, nodelist):
+ if len(nodelist) == 6:
+ assert nodelist[0][0] == symbol.decorators
+ decorators = self.decorators(nodelist[0][1:])
+ else:
+ assert len(nodelist) == 5
+ decorators = None
+
+ lineno = nodelist[-4][2]
+ name = nodelist[-4][1]
+ args = nodelist[-3][2]
+
+ if not re.match('_q_((html|plain)_)?template_', name):
+ # just a normal function, let base class handle it
+ self.__template_type.append(None)
+ n = transformer.Transformer.funcdef(self, nodelist)
+
+ else:
+ if name.startswith(PLAIN_TEMPLATE_PREFIX):
+ name = name[len(PLAIN_TEMPLATE_PREFIX):]
+ template_type = "plain"
+ elif name.startswith(HTML_TEMPLATE_PREFIX):
+ name = name[len(HTML_TEMPLATE_PREFIX):]
+ template_type = "html"
+ elif name.startswith(TEMPLATE_PREFIX):
+ name = name[len(TEMPLATE_PREFIX):]
+ template_type = "plain"
+ else:
+ raise RuntimeError, 'unknown prefix on %s' % name
+
+ self.__template_type.append(template_type)
+
+ # Add "IO_INSTANCE = IO_CLASS()" statement at the beginning of
+ # the function and a "return IO_INSTANCE" at the end.
+ if args[0] == symbol.varargslist:
+ names, defaults, flags = self.com_arglist(args[1:])
+ else:
+ names = defaults = ()
+ flags = 0
+ doc = None # self.get_docstring(nodelist[-1])
+
+ # code for function
+ code = self.com_node(nodelist[-1])
+
+ # create an instance, assign to IO_INSTANCE
+ klass = ast.Name(IO_CLASS)
+ args = [ast.Const(template_type == "html")]
+ instance = ast.CallFunc(klass, args)
+ assign_name = ast.AssName(IO_INSTANCE, OP_ASSIGN)
+ assign = ast.Assign([assign_name], instance)
+
+ # return the IO_INSTANCE.getvalue(...)
+ func = ast.Getattr(ast.Name(IO_INSTANCE), "getvalue")
+ ret = ast.Return(ast.CallFunc(func, []))
+
+ # wrap original function code
+ code = ast.Stmt([assign, code, ret])
+
+ if sys.hexversion >= 0x20400a2:
+ n = ast.Function(decorators, name, names, defaults, flags, doc,
+ code)
+ else:
+ n = ast.Function(name, names, defaults, flags, doc, code)
+ n.lineno = lineno
+
+ self.__template_type.pop()
+ return n
+
+ def expr_stmt(self, nodelist):
+ if not self.__template_type or not self.__template_type[-1]:
+ return transformer.Transformer.expr_stmt(self, nodelist)
+
+ # Instead of discarding objects on the stack, call
+ # "IO_INSTANCE += obj".
+ exprNode = self.com_node(nodelist[-1])
+ if len(nodelist) == 1:
+ lval = ast.Name(IO_INSTANCE)
+ n = ast.AugAssign(lval, '+=', exprNode)
+ if hasattr(exprNode, 'lineno'):
+ n.lineno = exprNode.lineno
+ elif nodelist[1][0] == token.EQUAL:
+ nodes = [ ]
+ for i in range(0, len(nodelist) - 2, 2):
+ nodes.append(self.com_assign(nodelist[i], OP_ASSIGN))
+ n = ast.Assign(nodes, exprNode)
+ n.lineno = nodelist[1][2]
+ else:
+ lval = self.com_augassign(nodelist[0])
+ op = self.com_augassign_op(nodelist[1])
+ n = ast.AugAssign(lval, op[1], exprNode)
+ n.lineno = op[2]
+ return n
+
+ def atom_string(self, nodelist):
+ k = ''
+ for node in nodelist:
+ k = k + eval(node[1])
+ n = ast.Const(k)
+ if self.__template_type and self.__template_type[-1] == "html":
+ # change "foo" to _q_htmltext("foo")
+ n = ast.CallFunc(ast.Name(MARKUP_MANGLED_CLASS), [n])
+ return n
+
+
+_old_template_re = re.compile(r"^([ \t]*) template ([ \t]+)"
+ r" ([a-zA-Z_][a-zA-Z_0-9]*)" # name of template
+ r" ([ \t]*[\(\\])",
+ re.MULTILINE|re.VERBOSE)
+
+_template_re = re.compile(r"^([ \t]*) def (?:[ \t]+)" # def
+ r" ([a-zA-Z_][a-zA-Z_0-9]*)" # <name>
+ r" (?:[ \t]*) \[(plain|html)\] (?:[ \t]*)" # <type>
+ r" (?:[ \t]*[\(\\])", # (
+ re.MULTILINE|re.VERBOSE)
+
+def translate_tokens(buf):
+ """
+ Since we can't modify the parser in the builtin parser module we
+ must do token translation here. Luckily it does not affect line
+ numbers.
+
+ template foo(...): -> def _q_template__foo(...):
+
+ def foo [plain] (...): -> def _q_plain_template__foo(...):
+
+ def foo [html] (...): -> def _q_html_template__foo(...):
+
+ XXX This parser is too stupid. For example, it doesn't understand
+ triple quoted strings.
+ """
+ global _template_re
+
+ # handle new style template declarations
+ buf = _template_re.sub(r"\1def _q_\3_template_\2(", buf)
+
+ # change old style template to def
+ buf = _old_template_re.sub(r"\1def\2%s\3\4" % TEMPLATE_PREFIX, buf)
+
+ return buf
+
+
+if sys.hexversion >= 0x20300b1:
+ def parse(buf, filename='<string>'):
+ buf = translate_tokens(buf)
+ try:
+ return TemplateTransformer().parsesuite(buf)
+ except SyntaxError, e:
+ # set the filename attribute
+ raise SyntaxError(str(e), (filename, e.lineno, e.offset, e.text))
+
+else:
+ # The parser module in Python <= 2.2 can raise ParserError. Since
+ # the ParserError exception is basically useless, we use compile()
+ # to generate a better exception.
+ def parse(buf, filename='<string>'):
+ buf = translate_tokens(buf)
+ # compile() and parsermodule don't accept code that is missing a
+ # trailing newline. The Python interpreter seems to add a newline when
+ # importing modules so we match that behavior.
+ if buf[-1:] != '\n':
+ buf += "\n"
+ try:
+ return TemplateTransformer().parsesuite(buf)
+ except (parser.ParserError, SyntaxError):
+ import __builtin__
+ try:
+ __builtin__.compile(buf, filename, 'exec')
+ except SyntaxError, exc:
+ # Another hack to fix the filename attribute.
+ raise SyntaxError(str(exc), (filename, exc.lineno, exc.offset,
+ exc.text))
+
+
+PTL_EXT = ".ptl"
+PTLC_EXT = ".ptlc"
+if sys.hexversion >= 0x20300a1:
+ PTLC_MAGIC = "PTLC\x04\x00"
+elif sys.hexversion >= 0x20200b1:
+ PTLC_MAGIC = "PTLC\x03\x00"
+elif sys.hexversion >= 0x20100b1:
+ PTLC_MAGIC = "PTLC\x02\x00"
+elif sys.hexversion >= 0x20000b1:
+ PTLC_MAGIC = "PTLC\x01\x00"
+else:
+ raise RuntimeError, 'python too old'
+
+class Template(pycodegen.Module):
+
+ if sys.hexversion >= 0x20200b1:
+ def _get_tree(self):
+ tree = parse(self.source, self.filename)
+ misc.set_filename(self.filename, tree)
+ syntax.check(tree)
+ return tree
+ else:
+ def compile(self):
+ ast = parse(self.source, self.filename)
+ gen = pycodegen.ModuleCodeGenerator(self.filename)
+ walk(ast, gen, 1)
+ self.code = gen.getCode()
+
+ def dump(self, f):
+ import marshal
+ import stat
+
+ f.write(PTLC_MAGIC)
+ mtime = os.stat(self.filename)[stat.ST_MTIME]
+ marshal.dump(mtime, f)
+ marshal.dump(self.code, f)
+
+
+def compile_template(input, filename, output=None):
+ """compile_template(input, filename, output=None) -> code
+
+ Compile an open file. If output is not None then the code is written
+ with the magic template header. The code object is returned.
+ """
+ buf = input.read()
+ template = Template(buf, filename)
+ template.compile()
+ if output:
+ template.dump(output)
+ return template.code
+
+def compile(inputname, outputname):
+ """compile(inputname, outputname)
+
+ Compile a template file. The new template is writen to outputname.
+ """
+ input = open(inputname)
+ output = open(outputname, "wb")
+ try:
+ compile_template(input, inputname, output)
+ except:
+ # don't leave a corrupt .ptlc file around
+ output.close()
+ os.unlink(outputname)
+ raise
+
+def compile_dir(dir, maxlevels=10, force=0):
+ """Byte-compile all PTL modules in the given directory tree.
+ (Adapted from compile_dir in Python module: compileall.py)
+
+ Arguments (only dir is required):
+
+ dir: the directory to byte-compile
+ maxlevels: maximum recursion level (default 10)
+ force: if true, force compilation, even if timestamps are up-to-date
+ """
+ print 'Listing', dir, '...'
+ try:
+ names = os.listdir(dir)
+ except os.error:
+ print "Can't list", dir
+ names = []
+ names.sort()
+ success = 1
+ for name in names:
+ fullname = os.path.join(dir, name)
+ if os.path.isfile(fullname):
+ head, tail = name[:-4], name[-4:]
+ if tail == '.ptl':
+ cfile = fullname + 'c'
+ ftime = os.stat(fullname)[stat.ST_MTIME]
+ try:
+ ctime = os.stat(cfile)[stat.ST_MTIME]
+ except os.error: ctime = 0
+ if (ctime > ftime) and not force:
+ continue
+ print 'Compiling', fullname, '...'
+ try:
+ ok = compile(fullname, cfile)
+ except KeyboardInterrupt:
+ raise KeyboardInterrupt
+ except:
+ # XXX compile catches SyntaxErrors
+ if type(sys.exc_type) == type(''):
+ exc_type_name = sys.exc_type
+ else: exc_type_name = sys.exc_type.__name__
+ print 'Sorry:', exc_type_name + ':',
+ print sys.exc_value
+ success = 0
+ else:
+ if ok == 0:
+ success = 0
+ elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
+ os.path.isdir(fullname) and not os.path.islink(fullname)):
+ if not compile_dir(fullname, maxlevels - 1, force):
+ success = 0
+ return success
+
+def main():
+ args = sys.argv[1:]
+ if not args:
+ print "no files to compile"
+ else:
+ for filename in args:
+ path, ext = os.path.splitext(filename)
+ compile(filename, path + PTLC_EXT)
+
+if __name__ == "__main__":
+ main()
Added: packages/quixote1/branches/upstream/current/ptl_import.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/ptl_import.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/ptl_import.py (added)
+++ packages/quixote1/branches/upstream/current/ptl_import.py Mon May 8 19:16:16 2006
@@ -1,0 +1,156 @@
+"""quixote.ptl_import
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/ptl_import.py $
+$Id: ptl_import.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Import hooks; when installed, these hooks allow importing .ptl files
+as if they were Python modules.
+
+Note: there's some unpleasant incompatibility between ZODB's import
+trickery and the import hooks here. Bottom line: if you're using ZODB,
+import it *before* installing the Quixote/PTL import hooks.
+"""
+
+__revision__ = "$Id: ptl_import.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+
+import sys
+import os.path
+import imp, ihooks, new
+import marshal
+import stat
+import __builtin__
+
+from ptl_compile import compile_template, PTL_EXT, PTLC_EXT, PTLC_MAGIC
+
+assert sys.hexversion >= 0x20000b1, "need Python 2.0b1 or later"
+
+def _exec_module_code(code, name, filename):
+ if sys.modules.has_key(name):
+ mod = sys.modules[name] # necessary for reload()
+ else:
+ mod = new.module(name)
+ sys.modules[name] = mod
+ mod.__name__ = name
+ mod.__file__ = filename
+ exec code in mod.__dict__
+ return mod
+
+def _load_ptlc(name, filename, file=None):
+ if not file:
+ try:
+ file = open(filename, "rb")
+ except IOError:
+ return None
+ path, ext = os.path.splitext(filename)
+ ptl_filename = path + PTL_EXT
+ magic = file.read(len(PTLC_MAGIC))
+ if magic != PTLC_MAGIC:
+ return _load_ptl(name, ptl_filename)
+ ptlc_mtime = marshal.load(file)
+ try:
+ mtime = os.stat(ptl_filename)[stat.ST_MTIME]
+ except OSError:
+ mtime = ptlc_mtime
+ if mtime > ptlc_mtime:
+ return _load_ptl(name, ptl_filename)
+ code = marshal.load(file)
+ return _exec_module_code(code, name, filename)
+
+def _load_ptl(name, filename, file=None):
+ if not file:
+ try:
+ file = open(filename, "rb")
+ except IOError:
+ return None
+ path, ext = os.path.splitext(filename)
+ ptlc_filename = path + PTLC_EXT
+ try:
+ output = open(ptlc_filename, "wb")
+ except IOError, msg:
+ output = None
+ try:
+ code = compile_template(file, filename, output)
+ except:
+ # don't leave a corrupt .ptlc file around
+ if output:
+ output.close()
+ os.unlink(ptlc_filename)
+ raise
+ else:
+ if output:
+ output.close()
+ return _exec_module_code(code, name, filename)
+
+
+# Constant used to signal a PTL files
+PTLC_FILE = 128
+PTL_FILE = 129
+
+class PTLHooks(ihooks.Hooks):
+
+ def get_suffixes(self):
+ # add our suffixes
+ L = imp.get_suffixes()
+ return L + [(PTLC_EXT, 'rb', PTLC_FILE), (PTL_EXT, 'r', PTL_FILE)]
+
+class PTLLoader(ihooks.ModuleLoader):
+
+ def load_module(self, name, stuff):
+ file, filename, info = stuff
+ (suff, mode, type) = info
+
+ # If it's a PTL file, load it specially.
+ if type == PTLC_FILE:
+ return _load_ptlc(name, filename, file)
+
+ elif type == PTL_FILE:
+ return _load_ptl(name, filename, file)
+
+ else:
+ # Otherwise, use the default handler for loading
+ return ihooks.ModuleLoader.load_module( self, name, stuff)
+
+try:
+ import cimport
+except ImportError:
+ cimport = None
+
+class cModuleImporter(ihooks.ModuleImporter):
+ def __init__(self, loader=None):
+ self.loader = loader or ihooks.ModuleLoader()
+ cimport.set_loader(self.find_import_module)
+
+ def find_import_module(self, fullname, subname, path):
+ stuff = self.loader.find_module(subname, path)
+ if not stuff:
+ return None
+ return self.loader.load_module(fullname, stuff)
+
+ def install(self):
+ self.save_import_module = __builtin__.__import__
+ self.save_reload = __builtin__.reload
+ if not hasattr(__builtin__, 'unload'):
+ __builtin__.unload = None
+ self.save_unload = __builtin__.unload
+ __builtin__.__import__ = cimport.import_module
+ __builtin__.reload = cimport.reload_module
+ __builtin__.unload = self.unload
+
+_installed = 0
+
+def install():
+ global _installed
+ if not _installed:
+ hooks = PTLHooks()
+ loader = PTLLoader(hooks)
+ if cimport is not None:
+ importer = cModuleImporter(loader)
+ else:
+ importer = ihooks.ModuleImporter(loader)
+ ihooks.install(importer)
+ _installed = 1
+
+
+if __name__ == '__main__':
+ import ZODB
+ install()
Added: packages/quixote1/branches/upstream/current/ptlc_dump.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/ptlc_dump.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/ptlc_dump.py (added)
+++ packages/quixote1/branches/upstream/current/ptlc_dump.py Mon May 8 19:16:16 2006
@@ -1,0 +1,51 @@
+#! /usr/bin/env python
+"""
+Dump the information contained in a compiled PTL file. Based on the
+dumppyc.py script in the Tools/compiler directory of the Python
+distribution.
+"""
+
+__revision__ = "$Id: ptlc_dump.py 20217 2003-01-16 20:51:53Z akuchlin $"
+
+import marshal
+import dis
+import types
+
+from ptl_compile import PTLC_MAGIC
+
+def dump(obj):
+ print obj
+ for attr in dir(obj):
+ print "\t", attr, repr(getattr(obj, attr))
+
+def loadCode(path):
+ f = open(path)
+ magic = f.read(len(PTLC_MAGIC))
+ if magic != PTLC_MAGIC:
+ raise ValueError, 'bad .ptlc magic for file "%s"' % path
+ mtime = marshal.load(f)
+ co = marshal.load(f)
+ f.close()
+ return co
+
+def walk(co, match=None):
+ if match is None or co.co_name == match:
+ dump(co)
+ print
+ dis.dis(co)
+ for obj in co.co_consts:
+ if type(obj) == types.CodeType:
+ walk(obj, match)
+
+def main(filename, codename=None):
+ co = loadCode(filename)
+ walk(co, codename)
+
+if __name__ == "__main__":
+ import sys
+ if len(sys.argv) == 3:
+ filename, codename = sys.argv[1:]
+ else:
+ filename = sys.argv[1]
+ codename = None
+ main(filename, codename)
Added: packages/quixote1/branches/upstream/current/publish.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/publish.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/publish.py (added)
+++ packages/quixote1/branches/upstream/current/publish.py Mon May 8 19:16:16 2006
@@ -1,0 +1,902 @@
+"""quixote.publish
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/publish.py $
+$Id: publish.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Logic for publishing modules and objects on the Web.
+"""
+
+__revision__ = "$Id: publish.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import sys, os, traceback, cStringIO
+import time, types, socket, re, warnings
+import struct
+try:
+ import zlib # for COMPRESS_PAGES option
+except ImportError:
+ pass
+
+from quixote import errors
+from quixote.html import htmltext
+from quixote.http_request import HTTPRequest, get_content_type
+from quixote.http_response import HTTPResponse, Stream
+from quixote.upload import HTTPUploadRequest
+from quixote.sendmail import sendmail
+
+try:
+ import cgitb # Only available in Python 2.2
+except ImportError:
+ cgitb = None
+
+def _get_module(name):
+ """Get a module object by name."""
+ __import__(name)
+ module = sys.modules[name]
+ return module
+
+# Error message to dispay when DISPLAY_EXCEPTIONS in config file is not
+# true. Note that SERVER_ADMIN must be fetched from the environment and
+# plugged in here -- we can't do it now because the environment isn't
+# really setup for us yet if running as a FastCGI script.
+INTERNAL_ERROR_MESSAGE = """\
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"
+ "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html>
+<head><title>Internal Server Error</title></head>
+<body>
+<h1>Internal Server Error</h1>
+<p>An internal error occurred while handling your request.</p>
+
+<p>The server administrator should have been notified of the problem.
+You may wish to contact the server administrator (%s) and inform them of
+the time the error occurred, and anything you might have done to trigger
+the error.</p>
+
+<p>If you are the server administrator, more information may be
+available in either the server's error log or Quixote's error log.</p>
+</body>
+</html>
+"""
+
+class Publisher:
+ """
+ The core of Quixote and of any Quixote application. This class is
+ responsible for converting each HTTP request into a search of
+ Python's package namespace and, ultimately, a call of a Python
+ function/method/callable object.
+
+ Each invocation of a driver script should have one Publisher
+ instance that lives for as long as the driver script itself. Eg. if
+ your driver script is plain CGI, each Publisher instance will handle
+ exactly one HTTP request; if you have a FastCGI driver, then each
+ Publisher will handle every HTTP request handed to that driver
+ script process.
+
+ Instance attributes:
+ root_namespace : module | instance | class
+ the Python namespace that will be searched for objects to
+ fulfill each HTTP request
+ exit_now : boolean
+ used for internal state management. If true, the loop in
+ publish_cgi() will terminate at the end of the current request.
+ access_log : file
+ file to which every access will be logged; set by
+ setup_logs() (None if no access log)
+ error_log : file
+ file to which application errors (exceptions caught by Quixote,
+ as well as anything printed to stderr by application code) will
+ be logged; set by setup_logs(). Set to sys.stderr if no
+ ERROR_LOG setting in the application config file.
+ config : Config
+ holds all configuration info for this application. If the
+ application doesn't have a config file, uses the default values
+ from the quixote.config module.
+ _request : HTTPRequest
+ the HTTP request currently being processed.
+ namespace_stack : [ module | instance | class ]
+ """
+
+ def __init__(self, root_namespace, config=None):
+ from quixote.config import Config
+ global _publisher
+
+ if _publisher is not None:
+ raise RuntimeError, "only one instance of Publisher allowed"
+ _publisher = self
+
+ if type(root_namespace) is types.StringType:
+ self.root_namespace = _get_module(root_namespace)
+ else:
+ # Should probably check that root_namespace is really a
+ # namespace, ie. a module, class, or instance -- but it's
+ # tricky to know if something is really a class or instance
+ # (because of ExtensionClass), and who knows what other
+ # namespaces are lurking out there in the world?
+ self.root_namespace = root_namespace
+
+ # for PublishError exception handling
+ self.namespace_stack = [self.root_namespace]
+
+ self.exit_now = 0
+ self.access_log = None
+ self.error_log = sys.stderr # possibly overridden in setup_logs()
+ sys.stdout = self.error_log # print is handy for debugging
+
+ # Initialize default config object with all the default values from
+ # the config variables at the top of the config module, ie. if
+ # ERROR_LOG is set to "/var/log/quxiote-error.log", then
+ # config.ERROR_LOG will also be "/var/log/quixote-error.log". If
+ # application FCGI/CGI scripts need to override any of these
+ # defaults, they can do so by direct manipulation of the config
+ # object, or by reading a config file:
+ # app.read_config("myapp.conf")
+ if config is None:
+ self.config = Config()
+ else:
+ self.set_config(config)
+
+ self._request = None
+
+ def configure(self, **kwargs):
+ self.config.set_from_dict(kwargs)
+
+ def read_config(self, filename):
+ self.config.read_file(filename)
+
+ def set_config(self, config):
+ from quixote.config import Config
+ if not isinstance(config, Config):
+ raise TypeError, "'config' must be a Config instance"
+ self.config = config
+
+ def setup_logs(self):
+ """
+ Open all log files specified in the config file. Reassign
+ sys.stderr to go to the error log, and sys.stdout to go to
+ the debug log.
+ """
+
+ if self.config.access_log is not None:
+ try:
+ self.access_log = open(self.config.access_log, 'a', 1)
+ except IOError, exc:
+ sys.stderr.write("error opening access log %s: %s\n"
+ % (`self.config.access_log`, exc.strerror))
+
+ if self.config.error_log is not None:
+ try:
+ self.error_log = open(self.config.error_log, 'a', 1)
+ sys.stderr = self.error_log
+ except IOError, exc:
+ # leave self.error_log as it was, most likely sys.stderr
+ sys.stderr.write("error opening error log %s: %s\n"
+ % (`self.config.error_log`, exc.strerror))
+
+ if self.config.debug_log is not None:
+ try:
+ debug_log = open(self.config.debug_log, 'a', 1)
+ sys.stdout = debug_log
+ except IOError, exc:
+ sys.stderr.write("error opening debug log %s: %s\n"
+ % (`self.config.debug_log`, exc.strerror))
+
+
+ def shutdown_logs(self):
+ """
+ Close log files and restore sys.stdout and sys.stderr to their
+ original values.
+ """
+ if sys.stdout is sys.__stdout__:
+ raise RuntimeError, "'setup_logs()' never called"
+ if sys.stdout is not sys.stderr:
+ sys.stdout.close()
+ sys.stdout = sys.__stdout__
+ self.access_log.close()
+ if self.error_log is not sys.__stderr__:
+ self.error_log.close()
+ sys.stderr = sys.__stderr__
+
+ def log(self, msg):
+ """
+ Write an message to the error log with a time stamp.
+ """
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S",
+ time.localtime(time.time()))
+ self.error_log.write("[%s] %s\n" % (timestamp, msg))
+
+ debug = log # backwards compatibility
+
+ def create_request(self, stdin, env):
+ ctype = get_content_type(env)
+ if ctype == "multipart/form-data":
+ req = HTTPUploadRequest(stdin, env, content_type=ctype)
+ req.set_upload_dir(self.config.upload_dir,
+ self.config.upload_dir_mode)
+ return req
+ else:
+ return HTTPRequest(stdin, env, content_type=ctype)
+
+ def parse_request(self, request):
+ """Parse the request information waiting in 'request'.
+ """
+ request.process_inputs()
+
+ def start_request(self, request):
+ """Called at the start of each request. Overridden by
+ SessionPublisher to handle session details.
+ """
+ pass
+
+ def _set_request(self, request):
+ """Set the current request object.
+ """
+ self._request = request
+
+ def _clear_request(self):
+ """Unset the current request object.
+ """
+ self._request = None
+
+ def get_request(self):
+ """Return the current request object.
+ """
+ return self._request
+
+ def log_request(self, request):
+ """Log a request in the access_log file.
+ """
+ if self.access_log is not None:
+ if request.session:
+ user = request.session.user or "-"
+ else:
+ user = "-"
+ now = time.time()
+ seconds = now - request.start_time
+ timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(now))
+
+ env = request.environ
+
+ # Under Apache, REQUEST_URI == SCRIPT_NAME + PATH_INFO.
+ # Not everyone uses Apache, so we have to stick to
+ # environment variables in the CGI spec. Note that this
+ # relies on PATH_INFO under IIS being fixed by HTTPRequest,
+ # because IIS gets it wrong.
+ request_uri = env.get('SCRIPT_NAME') + env.get('PATH_INFO', '')
+ query = env.get('QUERY_STRING', '')
+ if query:
+ query = "?" + query
+ proto = env.get('SERVER_PROTOCOL')
+
+ self.access_log.write('%s %s %s %d "%s %s %s" %s %r %0.2fsec\n' %
+ (request.environ.get('REMOTE_ADDR'),
+ str(user),
+ timestamp,
+ os.getpid(),
+ request.get_method(),
+ request_uri + query,
+ proto,
+ request.response.status_code,
+ request.environ.get('HTTP_USER_AGENT', ''),
+ seconds
+ ))
+
+
+ def finish_successful_request(self, request):
+ """Called at the end of a successful request. Overridden by
+ SessionPublisher to handle session details."""
+ pass
+
+ def finish_interrupted_request(self, request, exc):
+ """
+ Called at the end of an interrupted request. Requests are
+ interrupted by raising a PublishError exception. This method
+ should return a string object which will be used as the result of
+ the request.
+
+ This method searches for the nearest namespace with a
+ _q_exception_handler attribute. That attribute is expected to be
+ a function and is called with the request and exception instance
+ as arguments and should return the error page (e.g. a string). If
+ the handler doesn't want to handle a particular error it can
+ re-raise it and the next nearest handler will be found. If no
+ _q_exception_handler is found, the default Quixote handler is
+ used.
+ """
+
+ # Throw away the existing response object and start a new one
+ # for the error document we're going to create here.
+ request.response = HTTPResponse()
+
+ # set response status code so every custom doesn't have to do it
+ request.response.set_status(exc.status_code)
+
+ if self.config.secure_errors and exc.private_msg:
+ exc.private_msg = None # hide it
+
+ # walk up stack and find handler for the exception
+ stack = self.namespace_stack[:]
+ while 1:
+ handler = None
+ while stack:
+ object = stack.pop()
+ if hasattr(object, "_q_exception_handler"):
+ handler = object._q_exception_handler
+ break
+ if handler is None:
+ handler = errors.default_exception_handler
+
+ try:
+ return handler(request, exc)
+ except errors.PublishError:
+ assert handler is not errors.default_exception_handler
+ continue # exception was re-raised or another exception occured
+
+
+ def finish_failed_request(self, request):
+ """
+ Called at the end of an failed request. Any exception (other
+ than PublishError) causes a request to fail. This method should
+ return a string object which will be used as the result of the
+ request.
+ """
+ # build new response to be safe
+ original_response = request.response
+ request.response = HTTPResponse()
+ #self.log("caught an error (%s), reporting it." %
+ # sys.exc_info()[1])
+
+ (exc_type, exc_value, tb) = sys.exc_info()
+ error_summary = traceback.format_exception_only(exc_type, exc_value)
+ error_summary = error_summary[0][0:-1] # de-listify and strip newline
+
+ plain_error_msg = self._generate_plaintext_error(request,
+ original_response,
+ exc_type, exc_value,
+ tb)
+
+ if not self.config.display_exceptions:
+ # DISPLAY_EXCEPTIONS is false, so return the most
+ # secure (and cryptic) page.
+ request.response.set_header("Content-Type", "text/html")
+ user_error_msg = self._generate_internal_error(request)
+ elif self.config.display_exceptions == 'html' and cgitb is not None:
+ # Generate a spiffy HTML display using cgitb
+ request.response.set_header("Content-Type", "text/html")
+ user_error_msg = self._generate_cgitb_error(request,
+ original_response,
+ exc_type, exc_value,
+ tb)
+ else:
+ # Generate a plaintext page containing the traceback
+ request.response.set_header("Content-Type", "text/plain")
+ user_error_msg = plain_error_msg
+
+ self.log("exception caught")
+ self.error_log.write(plain_error_msg)
+
+ if self.config.error_email:
+ self.mail_error(plain_error_msg, error_summary)
+
+ request.response.set_status(500)
+ return user_error_msg
+
+
+ def _generate_internal_error(self, request):
+ admin = request.environ.get('SERVER_ADMIN',
+ "<i>email address unknown</i>")
+ return INTERNAL_ERROR_MESSAGE % admin
+
+
+ def _generate_plaintext_error(self, request, original_response,
+ exc_type, exc_value, tb):
+ error_file = cStringIO.StringIO()
+
+ # format the traceback
+ traceback.print_exception(exc_type, exc_value, tb, file=error_file)
+
+ # include request and response dumps
+ error_file.write('\n')
+ error_file.write(request.dump())
+ error_file.write('\n')
+
+ return error_file.getvalue()
+
+
+ def _generate_cgitb_error(self, request, original_response,
+ exc_type, exc_value, tb):
+ error_file = cStringIO.StringIO()
+ hook = cgitb.Hook(file=error_file)
+ hook(exc_type, exc_value, tb)
+ error_file.write('<h2>Original Request</h2>')
+ error_file.write(request.dump_html())
+ error_file.write('<h2>Original Response</h2><pre>')
+ original_response.write(error_file)
+ error_file.write('</pre>')
+ return error_file.getvalue()
+
+
+ def mail_error(self, msg, error_summary):
+ """Send an email notifying someone of a traceback."""
+ sendmail('Quixote Traceback (%s)' % error_summary,
+ msg, [self.config.error_email],
+ from_addr=(self.config.error_email, socket.gethostname()))
+
+ def get_namespace_stack(self):
+ """get_namespace_stack() -> [ module | instance | class ]
+ """
+ return self.namespace_stack
+
+ def try_publish(self, request, path):
+ """try_publish(request : HTTPRequest, path : string) -> string
+
+ The master method that does all the work for a single request. Uses
+ traverse_url() to get a callable object. The object is called and
+ the output is returned. Exceptions are handled by the caller.
+ """
+
+ self.start_request(request)
+
+ # Initialize the publisher's namespace_stack
+ self.namespace_stack = []
+
+ # Traverse package to a (hopefully-) callable object
+ object = _traverse_url(self.root_namespace, path, request,
+ self.config.fix_trailing_slash,
+ self.namespace_stack)
+
+ # None means no output -- traverse_url() just issued a redirect.
+ if object is None:
+ return None
+
+ # Anything else must be either a string...
+ if isstring(object):
+ output = object
+
+ # ...or a callable.
+ elif callable(object):
+ try:
+ output = object(request)
+ except SystemExit:
+ output = "SystemExit exception caught, shutting down"
+ self.log(output)
+ self.exit_now = 1
+
+ if output is None:
+ raise RuntimeError, 'callable %s returned None' % repr(object)
+
+ # Uh-oh: 'object' is neither a string nor a callable.
+ else:
+ raise RuntimeError(
+ "object is neither callable nor a string: %s" % repr(object))
+
+
+ # The callable ran OK, commit any changes to the session
+ self.finish_successful_request(request)
+
+ return output
+
+ _GZIP_HEADER = ("\037\213" # magic
+ "\010" # compression method
+ "\000" # flags
+ "\000\000\000\000" # time, who cares?
+ "\002"
+ "\377")
+
+ _GZIP_THRESHOLD = 200 # responses smaller than this are not compressed
+
+ def compress_output(self, request, output):
+ encoding = request.get_encoding(["gzip", "x-gzip"])
+ n = len(output)
+ if n > self._GZIP_THRESHOLD and encoding:
+ co = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS,
+ zlib.DEF_MEM_LEVEL, 0)
+ chunks = [self._GZIP_HEADER,
+ co.compress(output),
+ co.flush(),
+ struct.pack("<ll", zlib.crc32(output), len(output))]
+ output = "".join(chunks)
+ #self.log("gzip (original size %d, ratio %.1f)" %
+ # (n, float(n)/len(output)))
+ request.response.set_header("Content-Encoding", encoding)
+ return output
+
+ def filter_output(self, request, output):
+ """Hook for post processing the output. Subclasses may wish to
+ override (e.g. check HTML syntax).
+ """
+ if (output and
+ self.config.compress_pages and
+ not isinstance(output, Stream)):
+ output = self.compress_output(request, str(output))
+ return output
+
+ def process_request(self, request, env):
+ """process_request(request : HTTPRequest, env : dict) : string
+
+ Process a single request, given an HTTPRequest object. The
+ try_publish() method will be called to do the work and
+ exceptions will be handled here.
+ """
+ self._set_request(request)
+ try:
+ self.parse_request(request)
+ output = self.try_publish(request, env.get('PATH_INFO', ''))
+ except errors.PublishError, exc:
+ # Exit the publishing loop and return a result right away.
+ output = self.finish_interrupted_request(request, exc)
+ except:
+ # Some other exception, generate error messages to the logs, etc.
+ output = self.finish_failed_request(request)
+ output = self.filter_output(request, output)
+ self.log_request(request)
+ return output
+
+ def publish(self, stdin, stdout, stderr, env):
+ """publish(stdin : file, stdout : file, stderr : file, env : dict)
+
+ Create an HTTPRequest object from the environment and from
+ standard input, process it, and write the response to standard
+ output.
+ """
+ request = self.create_request(stdin, env)
+ output = self.process_request(request, env)
+
+ # Output results from Response object
+ if output:
+ request.response.set_body(output)
+ try:
+ request.response.write(stdout)
+ except IOError, exc:
+ self.log('IOError caught while writing request (%s)' % exc)
+ self._clear_request()
+
+
+ def publish_cgi(self):
+ """publish_cgi()
+
+ Entry point from CGI scripts; it will execute the publish function
+ once and return.
+ """
+ if sys.platform == "win32":
+ # on Windows, stdin and stdout are in text mode by default
+ import msvcrt
+ msvcrt.setmode(sys.__stdin__.fileno(), os.O_BINARY)
+ msvcrt.setmode(sys.__stdout__.fileno(), os.O_BINARY)
+ self.publish(sys.__stdin__, sys.__stdout__, sys.__stderr__, os.environ)
+
+ def publish_fcgi(self):
+ """publish_fcgi()
+
+ Entry point from FCGI scripts; it will repeatedly do the publish()
+ function until there are no more requests. This should also work
+ for CGI scripts but it is not as portable as publish_cgi().
+ """
+ from quixote import fcgi
+ while fcgi.isFCGI() and not self.exit_now:
+ f = fcgi.FCGI()
+ self.publish(f.inp, f.out, f.err, f.env)
+ f.Finish()
+ if self.config.run_once:
+ break
+
+
+# class Publisher
+
+
+class SessionPublisher(Publisher):
+
+ def __init__(self, root_namespace, config=None, session_mgr=None):
+ from quixote.session import SessionManager
+ Publisher.__init__(self, root_namespace, config)
+ if session_mgr is None:
+ self.session_mgr = SessionManager()
+ else:
+ self.session_mgr = session_mgr
+
+ def set_session_manager(self, session_mgr):
+ self.session_mgr = session_mgr
+
+ def start_request(self, request):
+ # Get the session object and stick it onto the request
+ request.session = self.session_mgr.get_session(request)
+ request.session.start_request(request)
+
+ def finish_successful_request(self, request):
+ if request.session is not None:
+ request.session.finish_request(request)
+ self.session_mgr.maintain_session(request, request.session)
+ self.session_mgr.commit_changes(request.session)
+
+ def finish_interrupted_request(self, request, exc):
+ output = Publisher.finish_interrupted_request(self, request, exc)
+
+ # commit the current transaction so that any changes to the
+ # session objects are saved and are visible on the next HTTP
+ # hit. Remember, AccessError is a subclass of PublishError,
+ # so this code will be run for both typos in the URL and for
+ # the user not being logged in.
+ #
+ # The assumption here is that the UI code won't make changes
+ # to the core database before checking permissions and raising
+ # a PublishError; if you must do this (though it's hard to see
+ # why this would be necessary), you'll have to abort the
+ # current transaction, make your session changes, and then
+ # raise the PublishError.
+ #
+ # XXX We should really be able to commit session changes and
+ # database changes separately, but that requires ZODB
+ # incantations that we currently don't know.
+ self.session_mgr.commit_changes(request.session)
+
+ return output
+
+ def finish_failed_request(self, request):
+ if self.session_mgr:
+ self.session_mgr.abort_changes(request.session)
+ return Publisher.finish_failed_request(self, request)
+
+# class SessionPublisher
+
+_slash_pat = re.compile("//*")
+
+def _traverse_url(root_namespace, path, request, fix_trailing_slash,
+ namespace_stack):
+ """traverse_url(root_namespace : any, path : string,
+ request : HTTPRequest, fix_trailing_slash : bool,
+ namespace_stack : list) -> (object : any)
+
+ Perform traversal based on the provided path, starting at the root
+ object. It returns the script name and path info values for
+ the arrived-at object, along with the object itself and
+ a list of the namespaces traversed to get there.
+
+ It's expected that the final object is something callable like a
+ function or a method; intermediate objects along the way will
+ usually be packages or modules.
+
+ To prevent crackers from writing URLs that traverse private
+ objects, every package, module, or object along the way must have
+ a _q_exports attribute containing a list of publicly visible
+ names. Not having a _q_exports attribute is an error, though
+ having _q_exports be an empty list is OK. If a component of the path
+ isn't in _q_exports, that also produces an error.
+
+ Modifies the namespace_stack as it traverses the url, so that
+ any exceptions encountered along the way can be handled by the
+ nearest handler.
+ """
+
+ # If someone accesses a Quixote driver script without a trailing
+ # slash, we'll wind up here with an empty path. This won't
+ # work; relative references in the page generated by the root
+ # namespace's _q_index() will be off. Fix it by redirecting the
+ # user to the right URL; when the client follows the redirect,
+ # we'll wind up here again with path == '/'.
+ if (not path and fix_trailing_slash):
+ request.redirect(request.environ['SCRIPT_NAME'] + '/' ,
+ permanent=1)
+ return None
+
+ # replace repeated slashes with a single slash
+ if path.find("//") != -1:
+ path = _slash_pat.sub("/", path)
+
+ # split path apart; /foo/bar/baz -> ['foo', 'bar', 'baz']
+ # /foo/bar/ -> ['foo', 'bar', '']
+ path_components = path[1:].split('/')
+
+ # Traverse starting at the root
+ object = root_namespace
+ namespace_stack.append(object)
+
+ # Loop over the components of the path
+ for component in path_components:
+ if component == "":
+ # "/q/foo/" == "/q/foo/_q_index"
+ component = "_q_index"
+ object = _get_component(object, component, path, request,
+ namespace_stack)
+
+ if not (isstring(object) or callable(object)):
+ # We went through all the components of the path and ended up at
+ # something which isn't callable, like a module or an instance
+ # without a __call__ method.
+ if path[-1] != '/':
+ if not request.form and fix_trailing_slash:
+ # This is for the convenience of users who type in paths.
+ # Repair the path and redirect. This should not happen for
+ # URLs within the site.
+ request.redirect(request.get_path() + "/", permanent=1)
+ return None
+
+ else:
+ # Automatic redirects disabled or there is form data. If
+ # there is form data then the programmer is using the
+ # wrong path. A redirect won't work if the form data came
+ # from a POST anyhow.
+ raise errors.TraversalError(
+ "object is neither callable nor string "
+ "(missing trailing slash?)",
+ private_msg=repr(object),
+ path=path)
+ else:
+ raise errors.TraversalError(
+ "object is neither callable nor string",
+ private_msg=repr(object),
+ path=path)
+
+ return object
+
+
+def _lookup_export(name, exports):
+ """Search an exports list for a name. Returns the internal name for
+ 'name' or return None if 'name' is not in 'exports'.
+
+ Each element of the export list can be either a string or a 2-tuple
+ of strings that maps an external name into internal name. The
+ mapping is useful when the desired external name is not a valid
+ Python identifier.
+ """
+ for value in exports:
+ if value == name:
+ internal_name = name
+ break
+ elif type(value) is types.TupleType:
+ if value[0] == name:
+ internal_name = value[1] # internal name is different
+ break
+ else:
+ if name == '_q_index':
+ internal_name = name # _q_index does not need to be in exports list
+ else:
+ internal_name = None # not found in exports
+ return internal_name
+
+
+def _get_component(container, component, path, request, namespace_stack):
+ """Get one component of a path from a namespace.
+ """
+ # First security check: if the container doesn't even have an
+ # _q_exports list, fail now: all Quixote-traversable namespaces
+ # (modules, packages, instances) must have an export list!
+ if not hasattr(container, '_q_exports'):
+ raise errors.TraversalError(
+ private_msg="%r has no _q_exports list" % container)
+
+ # Second security check: call _q_access function if it's present.
+ if hasattr(container, '_q_access'):
+ # will raise AccessError if access failed
+ container._q_access(request)
+
+ # Third security check: make sure the current name component
+ # is in the export list or is '_q_index'. If neither
+ # condition is true, check for a _q_lookup() and call it.
+ # '_q_lookup()' translates an arbitrary string into an object
+ # that we continue traversing. (This is very handy; it lets
+ # you put user-space objects into your URL-space, eliminating
+ # the need for digging ID strings out of a query, or checking
+ # PATHINFO after Quixote's done with it. But it is a
+ # compromise to security: it opens up the traversal algorithm
+ # to arbitrary names not listed in _q_exports!) If
+ # _q_lookup() doesn't exist or is None, a TraversalError is
+ # raised.
+
+ # Check if component is in _q_exports. The elements in
+ # _q_exports can be strings or 2-tuples mapping external names
+ # to internal names.
+ if component in container._q_exports or component == '_q_index':
+ internal_name = component
+ else:
+ # check for an explicit external to internal mapping
+ for value in container._q_exports:
+ if type(value) is types.TupleType:
+ if value[0] == component:
+ internal_name = value[1]
+ break
+ else:
+ internal_name = None
+
+ if internal_name is None:
+ # Component is not in exports list.
+ object = None
+ if hasattr(container, "_q_lookup"):
+ object = container._q_lookup(request, component)
+ elif hasattr(container, "_q_getname"):
+ warnings.warn("_q_getname() on %s used; should "
+ "be replaced by _q_lookup()" % type(container))
+ object = container._q_getname(request, component)
+ if object is None:
+ raise errors.TraversalError(
+ private_msg="object %r has no attribute %r" % (
+ container,
+ component))
+
+ # From here on, you can assume that the internal_name is not None
+ elif hasattr(container, internal_name):
+ # attribute is in _q_exports and exists
+ object = getattr(container, internal_name)
+
+ elif internal_name == '_q_index':
+ if hasattr(container, "_q_lookup"):
+ object = container._q_lookup(request, "")
+ else:
+ raise errors.AccessError(
+ private_msg=("_q_index not found in %r" % container))
+
+ elif hasattr(container, "_q_resolve"):
+ object = container._q_resolve(internal_name)
+ if object is None:
+ raise RuntimeError, ("component listed in _q_exports, "
+ "but not returned by _q_resolve(%r)"
+ % internal_name)
+ else:
+ # Set the object, so _q_resolve won't need to be called again.
+ setattr(container, internal_name, object)
+
+ elif type(container) is types.ModuleType:
+ # try importing it as a sub-module. If we get an ImportError
+ # here we don't catch it. It means that something that
+ # doesn't exist was exported or an exception was raised from
+ # deeper in the code.
+ mod_name = container.__name__ + '.' + internal_name
+ object = _get_module(mod_name)
+
+ else:
+ # a non-existent attribute is in _q_exports,
+ # and the container is not a module. Give up.
+ raise errors.TraversalError(
+ private_msg=("%r in _q_exports list, "
+ "but not found in %r" % (component,
+ container)))
+
+ namespace_stack.append(object)
+ return object
+
+
+
+# Publisher singleton, only one of these per process.
+_publisher = None
+
+def get_publisher():
+ global _publisher
+ return _publisher
+
+def get_request():
+ global _publisher
+ return _publisher.get_request()
+
+def get_path(n=0):
+ global _publisher
+ return _publisher.get_request().get_path(n)
+
+def redirect(location, permanent=0):
+ global _publisher
+ return _publisher.get_request().redirect(location, permanent)
+
+def get_session():
+ global _publisher
+ return _publisher.get_request().session
+
+def get_session_manager():
+ global _publisher
+ return _publisher.session_mgr
+
+def get_user():
+ global _publisher
+ session = _publisher.get_request().session
+ if session is None:
+ return None
+ else:
+ return session.user
+
+
+if sys.hexversion >= 0x02020000: # Python 2.2 or greater
+ def isstring(x):
+ return isinstance(x, (str, unicode, htmltext))
+else:
+ if hasattr(types, 'UnicodeType'):
+ _string_types = (types.StringType, types.UnicodeType)
+ else:
+ _string_types = (types.StringType,)
+
+ def isstring(x):
+ return type(x) in _string_types
Added: packages/quixote1/branches/upstream/current/qx_distutils.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/qx_distutils.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/qx_distutils.py (added)
+++ packages/quixote1/branches/upstream/current/qx_distutils.py Mon May 8 19:16:16 2006
@@ -1,0 +1,57 @@
+"""quixote.qx_distutils
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/qx_distutils.py $
+$Id: qx_distutils.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Provides a version of the Distutils "build_py" command that knows about
+PTL files.
+
+This is installed with Quixote so other Quixote apps can use it in their
+setup scripts.
+"""
+
+# created 2001/08/28, Greg Ward (initially written for SPLAT!'s setup.py)
+
+__revision__ = "$Id: qx_distutils.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import os, string
+from glob import glob
+from types import StringType, ListType, TupleType
+from distutils.command.build_py import build_py
+
+# This bites -- way too much code had to be copied from
+# distutils/command/build.py just to add an extra file extension!
+
+class qx_build_py(build_py):
+
+ def find_package_modules(self, package, package_dir):
+ self.check_package(package, package_dir)
+ module_files = (glob(os.path.join(package_dir, "*.py")) +
+ glob(os.path.join(package_dir, "*.ptl")))
+ modules = []
+ setup_script = os.path.abspath(self.distribution.script_name)
+
+ for f in module_files:
+ abs_f = os.path.abspath(f)
+ if abs_f != setup_script:
+ module = os.path.splitext(os.path.basename(f))[0]
+ modules.append((package, module, f))
+ else:
+ self.debug_print("excluding %s" % setup_script)
+ return modules
+
+ def build_module(self, module, module_file, package):
+ if type(package) is StringType:
+ package = string.split(package, '.')
+ elif type(package) not in (ListType, TupleType):
+ raise TypeError, \
+ "'package' must be a string (dot-separated), list, or tuple"
+
+ # Now put the module source file into the "build" area -- this is
+ # easy, we just copy it somewhere under self.build_lib (the build
+ # directory for Python source).
+ outfile = self.get_module_outfile(self.build_lib, package, module)
+ if module_file.endswith(".ptl"): # XXX hack for PTL
+ outfile = outfile[0:outfile.rfind('.')] + ".ptl"
+ dir = os.path.dirname(outfile)
+ self.mkpath(dir)
+ return self.copy_file(module_file, outfile, preserve_mode=0)
Added: packages/quixote1/branches/upstream/current/sendmail.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/sendmail.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/sendmail.py (added)
+++ packages/quixote1/branches/upstream/current/sendmail.py Mon May 8 19:16:16 2006
@@ -1,0 +1,273 @@
+"""quixote.sendmail
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/sendmail.py $
+$Id: sendmail.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Tools for sending mail from Quixote applications.
+"""
+
+# created 2001/08/27, Greg Ward (with a long and complicated back-story)
+
+__revision__ = "$Id: sendmail.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import re
+from types import ListType, TupleType, StringType
+from smtplib import SMTP
+
+rfc822_specials_re = re.compile(r'[\(\)\<\>\@\,\;\:\\\"\.\[\]]')
+
+class RFC822Mailbox:
+ """
+ In RFC 822, a "mailbox" is either a bare e-mail address or a bare
+ e-mail address coupled with a chunk of text, most often someone's
+ name. Eg. the following are all "mailboxes" in the RFC 822 grammar:
+ luser at example.com
+ Joe Luser <luser at example.com>
+ Paddy O'Reilly <paddy at example.ie>
+ "Smith, John" <smith at example.com>
+ Dick & Jane <dickjane at example.net>
+ "Tom, Dick, & Harry" <tdh at example.org>
+
+ This class represents an (addr_spec, real_name) pair and takes care
+ of quoting the real_name according to RFC 822's rules for you.
+ Just use the format() method and it will spit out a properly-
+ quoted RFC 822 "mailbox".
+ """
+
+ def __init__(self, *args):
+ """RFC822Mailbox(addr_spec : string, name : string)
+ RFC822Mailbox(addr_spec : string)
+ RFC822Mailbox((addr_spec : string, name : string))
+ RFC822Mailbox((addr_spec : string))
+
+ Create a new RFC822Mailbox instance. The variety of call
+ signatures is purely for your convenience.
+ """
+ if (len(args) == 1 and type(args[0]) is TupleType):
+ args = args[0]
+
+ if len(args) == 1:
+ addr_spec = args[0]
+ real_name = None
+ elif len(args) == 2:
+ (addr_spec, real_name) = args
+ else:
+ raise TypeError(
+ "invalid number of arguments: "
+ "expected 1 or 2 strings or "
+ "a tuple of 1 or 2 strings")
+
+ self.addr_spec = addr_spec
+ self.real_name = real_name
+
+ def __str__(self):
+ return self.addr_spec
+
+ def __repr__(self):
+ return "<%s at %x: %s>" % (self.__class__.__name__, id(self), self)
+
+ def format(self):
+ if self.real_name and rfc822_specials_re.search(self.real_name):
+ return '"%s" <%s>' % (self.real_name.replace('"', '\\"'),
+ self.addr_spec)
+ elif self.real_name:
+ return '%s <%s>' % (self.real_name, self.addr_spec)
+
+ else:
+ return self.addr_spec
+
+
+def _ensure_mailbox(s):
+ """_ensure_mailbox(s : string |
+ (string,) |
+ (string, string) |
+ RFC822Mailbox |
+ None)
+ -> RFC822Mailbox | None
+
+ If s is a string, or a tuple of 1 or 2 strings, returns an
+ RFC822Mailbox encapsulating them as an addr_spec and real_name. If
+ s is already an RFC822Mailbox, returns s. If s is None, returns
+ None.
+ """
+ if s is None or isinstance(s, RFC822Mailbox):
+ return s
+ else:
+ return RFC822Mailbox(s)
+
+
+# Maximum number of recipients that will be explicitly listed in
+# any single message header. Eg. if MAX_HEADER_RECIPIENTS is 10,
+# there could be up to 10 "To" recipients and 10 "CC" recipients
+# explicitly listed in the message headers.
+MAX_HEADER_RECIPIENTS = 10
+
+def _add_recip_headers(headers, field_name, addrs):
+ if not addrs:
+ return
+ addrs = [addr.format() for addr in addrs]
+
+ if len(addrs) == 1:
+ headers.append("%s: %s" % (field_name, addrs[0]))
+ elif len(addrs) <= MAX_HEADER_RECIPIENTS:
+ headers.append("%s: %s," % (field_name, addrs[0]))
+ for addr in addrs[1:-1]:
+ headers.append(" %s," % addr)
+ headers.append(" %s" % addrs[-1])
+ else:
+ headers.append("%s: (long recipient list suppressed) : ;" % field_name)
+
+
+def sendmail(subject, msg_body, to_addrs,
+ from_addr=None, cc_addrs=None,
+ extra_headers=None,
+ smtp_sender=None, smtp_recipients=None,
+ config=None):
+ """sendmail(subject : string,
+ msg_body : string,
+ to_addrs : [email_address],
+ from_addr : email_address = config.MAIL_SENDER,
+ cc_addrs : [email_address] = None,
+ extra_headers : [string] = None,
+ smtp_sender : email_address = (derived from from_addr)
+ smtp_recipients : [email_address] = (derived from to_addrs),
+ config : quixote.config.Config = (current publisher's config)):
+
+ Send an email message to a list of recipients via a local SMTP
+ server. In normal use, you supply a list of primary recipient
+ e-mail addresses in 'to_addrs', an optional list of secondary
+ recipient addresses in 'cc_addrs', and a sender address in
+ 'from_addr'. sendmail() then constructs a message using those
+ addresses, 'subject', and 'msg_body', and mails the message to every
+ recipient address. (Specifically, it connects to the mail server
+ named in the MAIL_SERVER config variable -- default "localhost" --
+ and instructs the server to send the message to every recipient
+ address in 'to_addrs' and 'cc_addrs'.)
+
+ 'from_addr' is optional because web applications often have a common
+ e-mail sender address, such as "webmaster at example.com". Just set
+ the Quixote config variable MAIL_FROM, and it will be used as the
+ default sender (both header and envelope) for all e-mail sent by
+ sendmail().
+
+ E-mail addresses can be specified a number of ways. The most
+ efficient is to supply instances of RFC822Mailbox, which bundles a
+ bare e-mail address (aka "addr_spec" from the RFC 822 grammar) and
+ real name together in a readily-formattable object. You can also
+ supply an (addr_spec, real_name) tuple, or an addr_spec on its own.
+ The latter two are converted into RFC822Mailbox objects for
+ formatting, which is why it may be more efficient to construct
+ RFC822Mailbox objects yourself.
+
+ Thus, the following are all equivalent in terms of who gets the
+ message:
+ sendmail(to_addrs=["joe at example.com"], ...)
+ sendmail(to_addrs=[("joe at example.com", "Joe User")], ...)
+ sendmail(to_addrs=[RFC822Mailbox("joe at example.com", "Joe User")], ...)
+ ...although the "To" header will be slightly different. In the
+ first case, it will be
+ To: joe at example.com
+ while in the other two, it will be:
+ To: Joe User <joe at example.com>
+ which is a little more user-friendly.
+
+ In more advanced usage, you might wish to specify the SMTP sender
+ and recipient addresses separately. For example, if you want your
+ application to send mail to users that looks like it comes from a
+ real human being, but you don't want that human being to get the
+ bounce messages from the mailing, you might do this:
+ sendmail(to_addrs=user_list,
+ ...,
+ from_addr=("realuser at example.com", "A Real User"),
+ smtp_sender="postmaster at example.com")
+
+ End users will see mail from "A Real User <realuser at example.com>" in
+ their inbox, but bounces will go to postmaster at example.com.
+
+ One use of different header and envelope recipients is for
+ testing/debugging. If you want to test that your application is
+ sending the right mail to bigboss at example.com without filling
+ bigboss' inbox with dross, you might do this:
+ sendmail(to_addrs=["bigboss at example.com"],
+ ...,
+ smtp_recipients=["developers at example.com"])
+
+ This is so useful that it's a Quixote configuration option: just set
+ MAIL_DEBUG_ADDR to (eg.) "developers at example.com", and every message
+ that sendmail() would send out is diverted to the debug address.
+
+ Generally raises an exception on any SMTP errors; see smtplib (in
+ the standard library documentation) for details.
+ """
+ if config is None:
+ from quixote import get_publisher
+ config = get_publisher().config
+
+ if not isinstance(to_addrs, ListType):
+ raise TypeError("'to_addrs' must be a list")
+ if not (cc_addrs is None or isinstance(cc_addrs, ListType)):
+ raise TypeError("'cc_addrs' must be a list or None")
+
+ # Make sure we have a "From" address
+ if from_addr is None:
+ from_addr = config.mail_from
+ if from_addr is None:
+ raise RuntimeError(
+ "no from_addr supplied, and MAIL_FROM not set in config file")
+
+ # Ensure all of our addresses are really RFC822Mailbox objects.
+ from_addr = _ensure_mailbox(from_addr)
+ to_addrs = map(_ensure_mailbox, to_addrs)
+ if cc_addrs:
+ cc_addrs = map(_ensure_mailbox, cc_addrs)
+
+ # Start building the message headers.
+ headers = ["From: %s" % from_addr.format(),
+ "Subject: %s" % subject]
+ _add_recip_headers(headers, "To", to_addrs)
+
+ if cc_addrs:
+ _add_recip_headers(headers, "Cc", cc_addrs)
+
+ if extra_headers:
+ headers.extend(extra_headers)
+
+ if config.mail_debug_addr:
+ debug1 = ("[debug mode, message actually sent to %s]\n"
+ % config.mail_debug_addr)
+ if smtp_recipients:
+ debug2 = ("[original SMTP recipients: %s]\n"
+ % ", ".join(smtp_recipients))
+ else:
+ debug2 = ""
+
+ sep = ("-"*72) + "\n"
+ msg_body = debug1 + debug2 + sep + msg_body
+
+ smtp_recipients = [config.mail_debug_addr]
+
+ if smtp_sender is None:
+ smtp_sender = from_addr.addr_spec
+ else:
+ smtp_sender = _ensure_mailbox(smtp_sender).addr_spec
+
+ if smtp_recipients is None:
+ smtp_recipients = [addr.addr_spec for addr in to_addrs]
+ if cc_addrs:
+ smtp_recipients.extend([addr.addr_spec for addr in cc_addrs])
+ else:
+ smtp_recipients = [_ensure_mailbox(recip).addr_spec
+ for recip in smtp_recipients]
+
+ message = "\n".join(headers) + "\n\n" + msg_body
+
+ # Sanity checks
+ assert type(smtp_sender) is StringType, \
+ "smtp_sender not a string: %r" % (smtp_sender,)
+ assert (type(smtp_recipients) is ListType and
+ map(type, smtp_recipients) == [StringType]*len(smtp_recipients)), \
+ "smtp_recipients not a list of strings: %r" % (smtp_recipients,)
+ smtp = SMTP(config.mail_server)
+ smtp.sendmail(smtp_sender, smtp_recipients, message)
+ smtp.quit()
+
+# sendmail ()
Added: packages/quixote1/branches/upstream/current/server/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/server/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/server/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/server/__init__.py Mon May 8 19:16:16 2006
@@ -1,0 +1,10 @@
+"""quixote.server
+
+This package is for HTTP servers, built using one or another
+framework, that publish a Quixote application. These servers can make
+it easy to run a small application without having to install and
+configure a full-blown Web server such as Apache.
+
+"""
+
+__revision__ = "$Id: __init__.py 25234 2004-09-30 17:36:19Z nascheme $"
Added: packages/quixote1/branches/upstream/current/server/medusa_http.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/server/medusa_http.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/server/medusa_http.py (added)
+++ packages/quixote1/branches/upstream/current/server/medusa_http.py Mon May 8 19:16:16 2006
@@ -1,0 +1,146 @@
+#!/usr/bin/env python
+
+"""quixote.server.medusa_http
+
+An HTTP handler for Medusa that publishes a Quixote application.
+"""
+
+__revision__ = "$Id: medusa_http.py 25276 2004-10-06 15:46:53Z nascheme $"
+
+# A simple HTTP server, using Medusa, that publishes a Quixote application.
+
+import sys
+import asyncore, rfc822, socket, urllib
+from StringIO import StringIO
+from medusa import http_server, xmlrpc_handler
+from quixote.http_response import Stream
+from quixote.publish import Publisher
+
+
+class StreamProducer:
+ def __init__(self, stream):
+ self.iterator = iter(stream)
+
+ def more(self):
+ try:
+ return self.iterator.next()
+ except StopIteration:
+ return ''
+
+
+class QuixoteHandler:
+ def __init__(self, publisher, server_name, server):
+ """QuixoteHandler(publisher:Publisher, server_name:string,
+ server:medusa.http_server.http_server)
+
+ Publish the specified Quixote publisher. 'server_name' will
+ be passed as the SERVER_NAME environment variable.
+ """
+ self.publisher = publisher
+ self.server_name = server_name
+ self.server = server
+
+ def match(self, request):
+ # Always match, since this is the only handler there is.
+ return 1
+
+ def handle_request(self, request):
+ msg = rfc822.Message(StringIO('\n'.join(request.header)))
+ length = int(msg.get('Content-Length', '0'))
+ if length:
+ request.collector = xmlrpc_handler.collector(self, request)
+ else:
+ self.continue_request('', request)
+
+ def continue_request(self, data, request):
+ msg = rfc822.Message(StringIO('\n'.join(request.header)))
+ remote_addr, remote_port = request.channel.addr
+ if '#' in request.uri:
+ # MSIE is buggy and sometimes includes fragments in URLs
+ [request.uri, fragment] = request.uri.split('#', 1)
+ if '?' in request.uri:
+ [path, query_string] = request.uri.split('?', 1)
+ else:
+ path = request.uri
+ query_string = ''
+
+ path = urllib.unquote(path)
+ server_port = str(self.server.port)
+ http_host = msg.get("Host")
+ if http_host:
+ if ":" in http_host:
+ server_name, server_port = http_host.split(":", 1)
+ else:
+ server_name = http_host
+ else:
+ server_name = (self.server.ip or
+ socket.gethostbyaddr(socket.gethostname())[0])
+
+ environ = {'REQUEST_METHOD': request.command,
+ 'ACCEPT_ENCODING': msg.get('Accept-encoding', ''),
+ 'CONTENT_TYPE': msg.get('Content-type', ''),
+ 'CONTENT_LENGTH': len(data),
+ "GATEWAY_INTERFACE": "CGI/1.1",
+ 'PATH_INFO': path,
+ 'QUERY_STRING': query_string,
+ 'REMOTE_ADDR': remote_addr,
+ 'REMOTE_PORT': str(remote_port),
+ 'REQUEST_URI': request.uri,
+ 'SCRIPT_NAME': '',
+ "SCRIPT_FILENAME": '',
+ 'SERVER_NAME': server_name,
+ 'SERVER_PORT': server_port,
+ 'SERVER_PROTOCOL': 'HTTP/1.1',
+ 'SERVER_SOFTWARE': self.server_name,
+ }
+ for title, header in msg.items():
+ envname = 'HTTP_' + title.replace('-', '_').upper()
+ environ[envname] = header
+
+ stdin = StringIO(data)
+ qreq = self.publisher.create_request(stdin, environ)
+ output = self.publisher.process_request(qreq, environ)
+
+ qresponse = qreq.response
+ if output:
+ qresponse.set_body(output)
+
+ # Copy headers from Quixote's HTTP response
+ for name, value in qresponse.generate_headers():
+ # XXX Medusa's HTTP request is buggy, and only allows unique
+ # headers.
+ request[name] = value
+
+ request.response(qresponse.status_code)
+
+ # XXX should we set a default Last-Modified time?
+ if qresponse.body is not None:
+ if isinstance(qresponse.body, Stream):
+ request.push(StreamProducer(qresponse.body))
+ else:
+ request.push(qresponse.body)
+
+ request.done()
+
+def main():
+ from quixote import enable_ptl
+ enable_ptl()
+
+ if len(sys.argv) == 2:
+ port = int(sys.argv[1])
+ else:
+ port = 8080
+ print 'Now serving the Quixote demo on port %d' % port
+ server = http_server.http_server('', port)
+ publisher = Publisher('quixote.demo')
+
+ # When initializing the Publisher in your own driver script,
+ # you'll want to parse a configuration file.
+ ##publisher.read_config("/full/path/to/demo.conf")
+ publisher.setup_logs()
+ dh = QuixoteHandler(publisher, 'Quixote/demo', server)
+ server.install_handler(dh)
+ asyncore.loop()
+
+if __name__ == '__main__':
+ main()
Added: packages/quixote1/branches/upstream/current/server/twisted_http.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/server/twisted_http.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/server/twisted_http.py (added)
+++ packages/quixote1/branches/upstream/current/server/twisted_http.py Mon May 8 19:16:16 2006
@@ -1,0 +1,275 @@
+#!/usr/bin/env python
+
+"""
+twist -- Demo of an HTTP server built on top of Twisted Python.
+"""
+
+__revision__ = "$Id: medusa_http.py 21221 2003-03-20 16:02:41Z akuchlin $"
+
+# based on qserv, created 2002/03/19, AMK
+# last mod 2003.03.24, Graham Fawcett
+# tested on Win32 / Twisted 0.18.0 / Quixote 0.6b5
+#
+# version 0.2 -- 2003.03.24 11:07 PM
+# adds missing support for session management, and for
+# standard Quixote response headers (expires, date)
+#
+# modified 2004/04/10 jsibre
+# better support for Streams
+# wraps output (whether Stream or not) into twisted type producer.
+# modified to use reactor instead of Application (Appication
+# has been deprecated)
+
+import urllib
+from twisted.protocols import http
+from twisted.web import server
+
+# imports for the TWProducer object
+from twisted.spread import pb
+from twisted.python import threadable
+from twisted.internet import abstract
+
+from quixote.http_response import Stream
+
+class QuixoteTWRequest(server.Request):
+
+ def process(self):
+ self.publisher = self.channel.factory.publisher
+ environ = self.create_environment()
+ # this seek is important, it doesn't work without it (it doesn't
+ # matter for GETs, but POSTs will not work properly without it.)
+ self.content.seek(0, 0)
+ qxrequest = self.publisher.create_request(self.content, environ)
+ self.quixote_publish(qxrequest, environ)
+ resp = qxrequest.response
+ self.setResponseCode(resp.status_code)
+ for hdr, value in resp.generate_headers():
+ self.setHeader(hdr, value)
+ if resp.body is not None:
+ TWProducer(resp.body, self)
+ else:
+ self.finish()
+
+
+ def quixote_publish(self, qxrequest, env):
+ """
+ Warning, this sidesteps the Publisher.publish method,
+ Hope you didn't override it...
+ """
+ pub = self.publisher
+ output = pub.process_request(qxrequest, env)
+
+ # don't write out the output, just set the response body
+ # the calling method will do the rest.
+ if output:
+ qxrequest.response.set_body(output)
+
+ pub._clear_request()
+
+
+ def create_environment(self):
+ """
+ Borrowed heavily from twisted.web.twcgi
+ """
+ # Twisted doesn't decode the path for us,
+ # so let's do it here. This is also
+ # what medusa_http.py does, right or wrong.
+ if '%' in self.path:
+ self.path = urllib.unquote(self.path)
+
+ serverName = self.getRequestHostname().split(':')[0]
+ env = {"SERVER_SOFTWARE": server.version,
+ "SERVER_NAME": serverName,
+ "GATEWAY_INTERFACE": "CGI/1.1",
+ "SERVER_PROTOCOL": self.clientproto,
+ "SERVER_PORT": str(self.getHost()[2]),
+ "REQUEST_METHOD": self.method,
+ "SCRIPT_NAME": '',
+ "SCRIPT_FILENAME": '',
+ "REQUEST_URI": self.uri,
+ "HTTPS": (self.isSecure() and 'on') or 'off',
+ "ACCEPT_ENCODING": self.getHeader('Accept-encoding'),
+ 'CONTENT_TYPE': self.getHeader('Content-type'),
+ 'HTTP_COOKIE': self.getHeader('Cookie'),
+ 'HTTP_REFERER': self.getHeader('Referer'),
+ 'HTTP_USER_AGENT': self.getHeader('User-agent'),
+ 'SERVER_PROTOCOL': 'HTTP/1.1',
+ }
+
+ client = self.getClient()
+ if client is not None:
+ env['REMOTE_HOST'] = client
+ ip = self.getClientIP()
+ if ip is not None:
+ env['REMOTE_ADDR'] = ip
+ xx, xx, remote_port = self.transport.getPeer()
+ env['REMOTE_PORT'] = remote_port
+ env["PATH_INFO"] = self.path
+
+ qindex = self.uri.find('?')
+ if qindex != -1:
+ env['QUERY_STRING'] = self.uri[qindex+1:]
+ else:
+ env['QUERY_STRING'] = ''
+
+ # Propogate HTTP headers
+ for title, header in self.getAllHeaders().items():
+ envname = title.replace('-', '_').upper()
+ if title not in ('content-type', 'content-length'):
+ envname = "HTTP_" + envname
+ env[envname] = header
+
+ return env
+
+
+class TWProducer(pb.Viewable):
+ """
+ A class to represent the transfer of data over the network.
+
+ JES Note: This has more stuff in it than is minimally neccesary.
+ However, since I'm no twisted guru, I built this by modifing
+ twisted.web.static.FileTransfer. FileTransfer has stuff in it
+ that I don't really understand, but know that I probably don't
+ need. I'm leaving it in under the theory that if anyone ever
+ needs that stuff (e.g. because they're running with multiple
+ threads) it'll be MUCH easier for them if I had just left it in
+ than if they have to figure out what needs to be in there.
+ Furthermore, I notice no performance penalty for leaving it in.
+ """
+ request = None
+ def __init__(self, data, request):
+ self.request = request
+ self.data = ""
+ self.size = 0
+ self.stream = None
+ self.streamIter = None
+
+ self.outputBufferSize = abstract.FileDescriptor.bufferSize
+
+ if isinstance(data, Stream): # data could be a Stream
+ self.stream = data
+ self.streamIter = iter(data)
+ self.size = data.length
+ elif data: # data could be a string
+ self.data = data
+ self.size = len(data)
+ else: # data could be None
+ # We'll just leave self.data as ""
+ pass
+
+ request.registerProducer(self, 0)
+
+
+ def resumeProducing(self):
+ """
+ This is twisted's version of a producer's '.more()', or
+ an iterator's '.next()'. That is, this function is
+ responsible for returning some content.
+ """
+ if not self.request:
+ return
+
+ if self.stream:
+ # If we were provided a Stream, let's grab some data
+ # and push it into our data buffer
+
+ buffer = [self.data]
+ bytesInBuffer = len(buffer[-1])
+ while bytesInBuffer < self.outputBufferSize:
+ try:
+ buffer.append(self.streamIter.next())
+ bytesInBuffer += len(buffer[-1])
+ except StopIteration:
+ # We've exhausted the Stream, time to clean up.
+ self.stream = None
+ self.streamIter = None
+ break
+ self.data = "".join(buffer)
+
+ if self.data:
+ chunkSize = min(self.outputBufferSize, len(self.data))
+ data, self.data = self.data[:chunkSize], self.data[chunkSize:]
+ else:
+ data = ""
+
+ if data:
+ self.request.write(data)
+
+ if not self.data:
+ self.request.unregisterProducer()
+ self.request.finish()
+ self.request = None
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ self.data = ""
+ self.request = None
+ self.stream = None
+ self.streamIter = None
+
+ # Remotely relay producer interface.
+
+ def view_resumeProducing(self, issuer):
+ self.resumeProducing()
+
+ def view_pauseProducing(self, issuer):
+ self.pauseProducing()
+
+ def view_stopProducing(self, issuer):
+ self.stopProducing()
+
+ synchronized = ['resumeProducing', 'stopProducing']
+
+threadable.synchronize(TWProducer)
+
+
+
+class QuixoteFactory(http.HTTPFactory):
+
+ def __init__(self, publisher):
+ self.publisher = publisher
+ http.HTTPFactory.__init__(self, None)
+
+ def buildProtocol(self, addr):
+ p = http.HTTPFactory.buildProtocol(self, addr)
+ p.requestFactory = QuixoteTWRequest
+ return p
+
+
+def Server(namespace, http_port):
+ from twisted.internet import reactor
+ from quixote.publish import Publisher
+
+ # If you want SSL, make sure you have OpenSSL,
+ # uncomment the follownig, and uncomment the
+ # listenSSL() call below.
+
+ ##from OpenSSL import SSL
+ ##class ServerContextFactory:
+ ## def getContext(self):
+ ## ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ## ctx.use_certificate_file('/path/to/pem/encoded/ssl_cert_file')
+ ## ctx.use_privatekey_file('/path/to/pem/encoded/ssl_key_file')
+ ## return ctx
+
+ publisher = Publisher(namespace)
+ ##publisher.setup_logs()
+ qf = QuixoteFactory(publisher)
+
+ reactor.listenTCP(http_port, qf)
+ ##reactor.listenSSL(http_port, qf, ServerContextFactory())
+
+ return reactor
+
+
+def run(namespace, port):
+ app = Server(namespace, port)
+ app.run()
+
+
+if __name__ == '__main__':
+ from quixote import enable_ptl
+ enable_ptl()
+ run('quixote.demo', 8080)
Added: packages/quixote1/branches/upstream/current/session.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/session.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/session.py (added)
+++ packages/quixote1/branches/upstream/current/session.py Mon May 8 19:16:16 2006
@@ -1,0 +1,576 @@
+"""quixote.session
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/session.py $
+$Id: session.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Quixote session management. There are two levels to Quixote's
+session management system:
+ - SessionManager
+ - Session
+
+A SessionManager is responsible for creating sessions, setting and reading
+session cookies, maintaining the collection of all sessions, and so forth.
+There is one SessionManager instance per Quixote process.
+
+A Session is the umbrella object for a single session (notionally, a (user,
+host, browser_process) triple). Simple applications can probably get away
+with putting all session data into a Session object (or, better, into an
+application-specific subclass of Session).
+
+The default implementation provided here is not persistent: when the
+Quixote process shuts down, all session data is lost. See
+doc/session-mgmt.txt for information on session persistence.
+"""
+
+from time import time, localtime, strftime
+
+from quixote import get_publisher
+from quixote.errors import SessionError
+from quixote.util import randbytes
+
+class SessionManager:
+ """
+ SessionManager acts as a dictionary of all sessions, mapping session
+ ID strings to individual session objects. Session objects are
+ instances of Session (or a custom subclass for your application).
+ SessionManager is also responsible for creating and destroying
+ sessions, for generating and interpreting session cookies, and for
+ session persistence (if any -- this implementation is not
+ persistent).
+
+ Most applications can just use this class directly; sessions will
+ be kept in memory-based dictionaries, and will be lost when the
+ Quixote process dies. Alternatively an application can subclass
+ SessionManager to implement specific behaviour, such as persistence.
+
+ Instance attributes:
+ session_class : class
+ the class that is instantiated to create new session objects
+ (in new_session())
+ sessions : mapping { session_id:string : Session }
+ the collection of sessions managed by this SessionManager
+ """
+
+ ACCESS_TIME_RESOLUTION = 1 # in seconds
+
+
+ def __init__(self, session_class=None, session_mapping=None):
+ """SessionManager(session_class : class = Session,
+ session_mapping : mapping = {})
+
+ Create a new session manager. There should be one session
+ manager per publisher (really SessionPublisher), ie. one
+ per process. Note that SessionPublisher's constructor will
+ take care of creating a session manager for you if you don't
+ do it yourself.
+
+ session_class is used by the new_session() method -- it returns
+ an instance of session_class.
+ """
+ self.sessions = {}
+ if session_class is None:
+ self.session_class = Session
+ else:
+ self.session_class = session_class
+ if session_mapping is None:
+ self.sessions = {}
+ else:
+ self.sessions = session_mapping
+
+ def __repr__(self):
+ return "<%s at %x>" % (self.__class__.__name__, id(self))
+
+
+ # -- Mapping interface ---------------------------------------------
+ # (subclasses shouldn't need to override any of this, unless
+ # your application passes in a session_mapping object that
+ # doesn't provide all of the mapping methods needed here)
+
+ def keys(self):
+ """keys() -> [string]
+
+ Return the list of session IDs of sessions in this session manager.
+ """
+ return self.sessions.keys()
+
+ def sorted_keys(self):
+ """sorted_keys() -> [string]
+
+ Return the same list as keys(), but sorted.
+ """
+ keys = self.keys()
+ keys.sort()
+ return keys
+
+ def values(self):
+ """values() -> [Session]
+
+ Return the list of sessions in this session manager.
+ """
+ return self.sessions.values()
+
+ def items(self):
+ """items() -> [(string, Session)]
+
+ Return the list of (session_id, session) pairs in this session
+ manager.
+ """
+ return self.sessions.items()
+
+ def get(self, session_id, default=None):
+ """get(session_id : string, default : any = None) -> Session
+
+ Return the session object identified by 'session_id', or None if
+ no such session.
+ """
+ return self.sessions.get(session_id, default)
+
+ def __getitem__(self, session_id):
+ """__getitem__(session_id : string) -> Session
+
+ Return the session object identified by 'session_id'. Raise KeyError
+ if no such session.
+ """
+ return self.sessions[session_id]
+
+ def has_key(self, session_id):
+ """has_key(session_id : string) -> boolean
+
+ Return true if a session identified by 'session_id' exists in
+ the session manager.
+ """
+ return self.sessions.has_key(session_id)
+
+ # has_session() is a synonym for has_key() -- if you override
+ # has_key(), be sure to repeat this alias!
+ has_session = has_key
+
+ def __setitem__(self, session_id, session):
+ """__setitem__(session_id : string, session : Session)
+
+ Store 'session' in the session manager under 'session_id'.
+ """
+ if not isinstance(session, self.session_class):
+ raise TypeError("session not an instance of %r: %r"
+ % (self.session_class, session))
+ assert session.id is not None, "session ID not set"
+ assert session_id == session.id, "session ID mismatch"
+ self.sessions[session_id] = session
+
+ def __delitem__(self, session_id):
+ """__getitem__(session_id : string) -> Session
+
+ Remove the session object identified by 'session_id' from the session
+ manager. Raise KeyError if no such session.
+ """
+ del self.sessions[session_id]
+
+ # -- Transactional interface ---------------------------------------
+ # Useful for applications that provide a transaction-oriented
+ # persistence mechanism. You'll still need to provide a mapping
+ # object that works with your persistence mechanism; these two
+ # methods let you hook into your transaction machinery after a
+ # request is finished processing.
+
+ def abort_changes(self, session):
+ """abort_changes(session : Session)
+
+ Placeholder for subclasses that implement transactional
+ persistence: forget about saving changes to the current
+ session. Called by SessionPublisher when a request fails,
+ ie. when it catches an exception other than PublishError.
+ """
+ pass
+
+ def commit_changes(self, session):
+ """commit_changes(session : Session)
+
+ Placeholder for subclasses that implement transactional
+ persistence: commit changes to the current session. Called by
+ SessionPublisher when a request completes successfully, or is
+ interrupted by a PublishError exception.
+ """
+ pass
+
+
+ # -- Session management --------------------------------------------
+ # these build on the storage mechanism implemented by the
+ # above mapping methods, and are concerned with all the high-
+ # level details of managing web sessions
+
+ def new_session(self, request, id):
+ """new_session(request : HTTPRequest, id : string)
+ -> Session
+
+ Return a new session object, ie. an instance of the session_class
+ class passed to the constructor (defaults to Session).
+ """
+ return self.session_class(request, id)
+
+ def _get_session_id(self, request, config):
+ """_get_session_id(request : HTTPRequest) -> string
+
+ Find the ID of the current session by looking for the session
+ cookie in 'request'. Return None if no such cookie or the
+ cookie has been expired, otherwise return the cookie's value.
+ """
+ id = request.cookies.get(config.session_cookie_name)
+ if id == "" or id == "*del*":
+ return None
+ else:
+ return id
+
+ def _make_session_id(self):
+ # Generate a session ID, which is just the value of the session
+ # cookie we are about to drop on the user. (It's also the key
+ # used with the session manager mapping interface.)
+ id = None
+ while id is None or self.has_session(id):
+ id = randbytes(8) # 64-bit random number
+ return id
+
+ def _create_session(self, request):
+ # Create a new session object, with no ID for now - one will
+ # be assigned later if we save the session.
+ return self.new_session(request, None)
+
+ def get_session(self, request):
+ """get_session(request : HTTPRequest) -> Session
+
+ Fetch or create a session object for the current session, and
+ return it. If a session cookie is found in the HTTP request
+ object 'request', use it to look up and return an existing
+ session object. If no session cookie is found, create a new
+ session. If the session cookie refers to a non-existent
+ session, raise SessionError. If the check_session_addr config
+ variable is true, then a mismatch between the IP address stored
+ in an existing session the IP address of the current request
+ also causes SessionError.
+
+ Note that this method does *not* cause the new session to be
+ stored in the session manager, nor does it drop a session cookie
+ on the user. Those are both the responsibility of
+ maintain_session(), called at the end of a request.
+ """
+ config = get_publisher().config
+ id = self._get_session_id(request, config)
+ if id is not None:
+ session = self.get(id)
+ if session is None:
+ # Note that it's important to revoke the session cookie
+ # so the user doesn't keep getting "Expired session ID"
+ # error pages. However, it has to be done in the
+ # response object for the error document, which doesn't
+ # exist yet. Thus, the code that formats SessionError
+ # exceptions -- SessionError.format() by default -- is
+ # responsible for revoking the session cookie. Yuck.
+ raise SessionError(session_id=id)
+ if (config.check_session_addr and
+ session.get_remote_address() !=
+ request.get_environ("REMOTE_ADDR")):
+ raise SessionError("Remote IP address does not match the "
+ "IP address that created the session",
+ session_id=id)
+
+ if id is None or session is None:
+ # Generate a session ID and create the session.
+ session = self._create_session(request)
+
+ session._set_access_time(self.ACCESS_TIME_RESOLUTION)
+ return session
+
+ # get_session ()
+
+ def maintain_session(self, request, session):
+ """maintain_session(request : HTTPRequest, session : Session)
+
+ Maintain session information. This method is called by
+ SessionPublisher after servicing an HTTP request, just before
+ the response is returned. If a session contains information it
+ is saved and a cookie dropped on the client. If not, the
+ session is discarded and the client will be instructed to delete
+ the session cookie (if any).
+ """
+ if not session.has_info():
+ # Session has no useful info -- forget it. If it previously
+ # had useful information and no longer does, we have to
+ # explicitly forget it.
+ if session.id and self.has_session(session.id):
+ del self[session.id]
+ self.revoke_session_cookie(request)
+ return
+
+ if session.id is None:
+ # This is the first time this session has had useful
+ # info -- store it and set the session cookie.
+ session.id = self._make_session_id()
+ self[session.id] = session
+ self.set_session_cookie(request, session.id)
+
+ elif session.is_dirty():
+ # We have already stored this session, but it's dirty
+ # and needs to be stored again. This will never happen
+ # with the default Session class, but it's there for
+ # applications using a persistence mechanism that requires
+ # repeatedly storing the same object in the same mapping.
+ self[session.id] = session
+
+ def _set_cookie(self, request, value, **attrs):
+ config = get_publisher().config
+ name = config.session_cookie_name
+ if config.session_cookie_path:
+ path = config.session_cookie_path
+ else:
+ path = request.environ['SCRIPT_NAME']
+ if not path.endswith("/"):
+ path += "/"
+ domain = config.session_cookie_domain
+ request.response.set_cookie(name, value, domain=domain,
+ path=path, **attrs)
+ return name
+
+ def set_session_cookie(self, request, session_id):
+ """set_session_cookie(request : HTTPRequest, session_id : string)
+
+ Ensure that a session cookie with value 'session_id' will be
+ returned to the client via 'request.response'.
+ """
+ self._set_cookie(request, session_id)
+
+ def revoke_session_cookie(self, request):
+ """revoke_session_cookie(request : HTTPRequest)
+
+ Remove the session cookie from the remote user's session by
+ resetting the value and maximum age in 'request.response'. Also
+ remove the cookie from 'request' so that further processing of
+ this request does not see the cookie's revoked value.
+ """
+ cookie_name = self._set_cookie(request, "", max_age=0)
+ if request.cookies.has_key(cookie_name):
+ del request.cookies[cookie_name]
+
+ def expire_session(self, request):
+ """expire_session(request : HTTPRequest)
+
+ Expire the current session, ie. revoke the session cookie from
+ the client and remove the session object from the session
+ manager and from 'request'.
+ """
+ self.revoke_session_cookie(request)
+ try:
+ del self[request.session.id]
+ except KeyError:
+ # This can happen if the current session hasn't been saved
+ # yet, eg. if someone tries to leave a session with no
+ # interesting data. That's not a big deal, so ignore it.
+ pass
+ request.session = None
+
+ def has_session_cookie(self, request, must_exist=0):
+ """has_session_cookie(request : HTTPRequest,
+ must_exist : boolean = false)
+ -> boolean
+
+ Return true if 'request' already has a cookie identifying a
+ session object. If 'must_exist' is true, the cookie must
+ correspond to a currently existing session; otherwise (the
+ default), we just check for the existence of the session cookie
+ and don't inspect its content at all.
+ """
+ config = get_publisher().config
+ id = request.cookies.get(config.session_cookie_name)
+ if id is None:
+ return 0
+ if must_exist:
+ return self.has_session(id)
+ else:
+ return 1
+
+# SessionManager
+
+
+class Session:
+ """
+ Holds information about the current session. The only information
+ that is likely to be useful to applications is the 'user' attribute,
+ which applications can use as they please.
+
+ Instance attributes:
+ id : string
+ the session ID (generated by SessionManager and used as the
+ value of the session cookie)
+ user : any
+ an object to identify the human being on the other end of the
+ line. It's up to you whether to store just a string in 'user',
+ or some more complex data structure or object.
+ _remote_address : string
+ IP address of user owning this session (only set when the
+ session is created -- requests for this session from a different
+ IP address will either raise SessionError or be treated
+ normally, depending on the CHECK_SESSION_ADDR config variable)
+ _creation_time : float
+ _access_time : float
+ two ways of keeping track of the "age" of the session.
+ Note that '__access_time' is maintained by the SessionManager that
+ owns this session, using _set_access_time().
+ _form_tokens : [string]
+ outstanding form tokens. This is used as a queue that can grow
+ up to MAX_FORM_TOKENS. Tokens are removed when forms are submitted.
+
+ Feel free to access 'id' and 'user' directly, but do not modify
+ 'id'. The preferred way to set 'user' is with the set_user() method
+ (which you might want to override for type-checking).
+ """
+
+ MAX_FORM_TOKENS = 16 # maximum number of outstanding form tokens
+
+ def __init__(self, request, id):
+ self.id = id
+ self.user = None
+ self._remote_address = request.get_environ("REMOTE_ADDR")
+ self._creation_time = self._access_time = time()
+ self._form_tokens = [] # queue
+
+ def __repr__(self):
+ return "<%s at %x: %s>" % (self.__class__.__name__, id(self), self.id)
+
+ def __str__(self):
+ if self.user:
+ return "session %s (user %s)" % (self.id, self.user)
+ else:
+ return "session %s (no user)" % self.id
+
+ def has_info(self):
+ """has_info() -> boolean
+
+ Return true if this session contains any information that must
+ be saved.
+ """
+ return self.user or self._form_tokens
+
+ def is_dirty(self):
+ """is_dirty() -> boolean
+
+ Return true if this session has changed since it was last saved
+ such that it needs to be saved again.
+
+ Default implementation always returns false since the default
+ storage mechanism is an in-memory dictionary, and you don't have
+ to put the same object into the same slot of a dictionary twice.
+ If sessions are stored to, eg., files in a directory or slots in
+ a hash file, is_dirty() should probably be an alias or wrapper
+ for has_info(). See doc/session-mgmt.txt.
+ """
+ return 0
+
+ def dump(self, file=None, header=1, deep=1):
+ time_fmt = "%Y-%m-%d %H:%M:%S"
+ ctime = strftime(time_fmt, localtime(self._creation_time))
+ atime = strftime(time_fmt, localtime(self._access_time))
+
+ if header:
+ file.write('session %s:' % self.id)
+ file.write(' user %s' % self.user)
+ file.write(' _remote_address: %s' % self._remote_address)
+ file.write(' created %s, last accessed %s' % (ctime, atime))
+ file.write(' _form_tokens: %s\n' % self._form_tokens)
+
+ # dump()
+
+
+ # -- Hooks into the Quixote main loop ------------------------------
+
+ def start_request(self, request):
+ """start_request(request : HTTPRequest)
+
+ Called near the beginning of each request: after the HTTPRequest
+ object has been built and this Session object has been fetched
+ or built, but before we traverse the URL or call the callable
+ object found by URL traversal.
+ """
+ if self.user:
+ request.environ['REMOTE_USER'] = str(self.user)
+
+ def finish_request(self, request):
+ """finish_request(request : HTTPRequest)
+
+ Called near the end of each request: after a callable object has
+ been found and (successfully) called. Not called if there were
+ any errors processing the request.
+ """
+ pass
+
+
+ # -- Simple accessors and modifiers --------------------------------
+
+ def set_user(self, user):
+ self.user = user
+
+ def get_remote_address(self):
+ """Return the IP address (dotted-quad string) that made the
+ initial request in this session.
+ """
+ return self._remote_address
+
+ def get_creation_time(self):
+ """Return the time that this session was created (seconds
+ since epoch).
+ """
+ return self._creation_time
+
+ def get_access_time(self):
+ """Return the time that this session was last accessed (seconds
+ since epoch).
+ """
+ return self._access_time
+
+ def get_creation_age(self, _now=None):
+ """Return the number of seconds since session was created."""
+ # _now arg is not strictly necessary, but there for consistency
+ # with get_access_age()
+ return (_now or time()) - self._creation_time
+
+ def get_access_age(self, _now=None):
+ """Return the number of seconds since session was last accessed."""
+ # _now arg is for SessionManager's use
+ return (_now or time()) - self._access_time
+
+
+ # -- Methods for SessionManager only -------------------------------
+
+ def _set_access_time(self, resolution):
+ now = time()
+ if now - self._access_time > resolution:
+ self._access_time = now
+
+
+ # -- Form token methods --------------------------------------------
+
+ def create_form_token(self):
+ """create_form_token() -> string
+
+ Create a new form token and add it to a queue of outstanding form
+ tokens for this session. A maximum of MAX_FORM_TOKENS are saved.
+ The new token is returned.
+ """
+ token = randbytes(8)
+ self._form_tokens.append(token)
+ extra = len(self._form_tokens) - self.MAX_FORM_TOKENS
+ if extra > 0:
+ del self._form_tokens[:extra]
+ return token
+
+ def has_form_token(self, token):
+ """has_form_token(token : string) -> boolean
+
+ Return true if 'token' is in the queue of outstanding tokens.
+ """
+ return token in self._form_tokens
+
+ def remove_form_token(self, token):
+ """remove_form_token(token : string)
+
+ Remove 'token' from the queue of outstanding tokens.
+ """
+ self._form_tokens.remove(token)
+
+# Session
Added: packages/quixote1/branches/upstream/current/setup.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/setup.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/setup.py (added)
+++ packages/quixote1/branches/upstream/current/setup.py Mon May 8 19:16:16 2006
@@ -1,0 +1,64 @@
+#!/usr/bin/env python
+#$HeadURL: svn+ssh://svn/repos/trunk/quixote/setup.py $
+#$Id: setup.py 25278 2004-10-06 15:49:42Z nascheme $
+
+# Setup script for Quixote
+
+__revision__ = "$Id: setup.py 25278 2004-10-06 15:49:42Z nascheme $"
+
+import sys, os
+from distutils import core
+from distutils.extension import Extension
+from qx_distutils import qx_build_py
+
+# a fast htmltext type
+htmltext = Extension(name="quixote._c_htmltext",
+ sources=["src/_c_htmltext.c"])
+
+# faster import hook for PTL modules
+cimport = Extension(name="quixote.cimport",
+ sources=["src/cimport.c"])
+
+kw = {'name': "Quixote",
+ 'version': "1.2",
+ 'description': "A highly Pythonic Web application framework",
+ 'author': "MEMS Exchange",
+ 'author_email': "quixote at mems-exchange.org",
+ 'url': "http://www.mems-exchange.org/software/quixote/",
+ 'license': "CNRI Open Source License (see LICENSE.txt)",
+
+ 'package_dir': {'quixote':os.curdir},
+ 'packages': ['quixote', 'quixote.demo', 'quixote.form',
+ 'quixote.form2', 'quixote.server'],
+
+ 'ext_modules': [],
+
+ 'cmdclass': {'build_py': qx_build_py},
+ }
+
+
+build_extensions = sys.platform != 'win32'
+
+if build_extensions:
+ # The _c_htmltext module requires Python 2.2 features.
+ if sys.hexversion >= 0x20200a1:
+ kw['ext_modules'].append(htmltext)
+ kw['ext_modules'].append(cimport)
+
+# If we're running Python 2.3, add extra information
+if hasattr(core, 'setup_keywords'):
+ if 'classifiers' in core.setup_keywords:
+ kw['classifiers'] = ['Development Status :: 5 - Production/Stable',
+ 'Environment :: Web Environment',
+ 'License :: OSI Approved :: Python License (CNRI Python License)',
+ 'Intended Audience :: Developers',
+ 'Operating System :: Unix',
+ 'Operating System :: Microsoft :: Windows',
+ 'Operating System :: MacOS :: MacOS X',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ ]
+ if 'download_url' in core.setup_keywords:
+ kw['download_url'] = ('http://www.mems-exchange.org/software/files'
+ '/quixote/Quixote-%s.tar.gz' % kw['version'])
+
+core.setup(**kw)
Added: packages/quixote1/branches/upstream/current/src/Makefile
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/src/Makefile?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/src/Makefile (added)
+++ packages/quixote1/branches/upstream/current/src/Makefile Mon May 8 19:16:16 2006
@@ -1,0 +1,2 @@
+all:
+ python setup.py build
Added: packages/quixote1/branches/upstream/current/src/_c_htmltext.c
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/src/_c_htmltext.c?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/src/_c_htmltext.c (added)
+++ packages/quixote1/branches/upstream/current/src/_c_htmltext.c Mon May 8 19:16:16 2006
@@ -1,0 +1,991 @@
+/* htmltext type and the htmlescape function */
+
+#include "Python.h"
+#include "structmember.h"
+
+typedef struct {
+ PyObject_HEAD
+ PyStringObject *s;
+} htmltextObject;
+
+static PyTypeObject htmltext_Type;
+
+#define htmltextObject_Check(v) ((v)->ob_type == &htmltext_Type)
+
+#define htmltext_STR(v) ((PyObject *)(((htmltextObject *)v)->s))
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *obj;
+} QuoteWrapperObject;
+
+static PyTypeObject QuoteWrapper_Type;
+
+#define QuoteWrapper_Check(v) ((v)->ob_type == &QuoteWrapper_Type)
+
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *obj;
+} DictWrapperObject;
+
+static PyTypeObject DictWrapper_Type;
+
+#define DictWrapper_Check(v) ((v)->ob_type == &DictWrapper_Type)
+
+
+typedef struct {
+ PyObject_HEAD
+ int html;
+ char *buf;
+ size_t size;
+ size_t pos;
+} TemplateIO_Object;
+
+static PyTypeObject TemplateIO_Type;
+
+#define TemplateIO_Check(v) ((v)->ob_type == &TemplateIO_Type)
+
+
+static PyObject *
+type_error(const char *msg)
+{
+ PyErr_SetString(PyExc_TypeError, msg);
+ return NULL;
+}
+
+static PyObject *
+escape_string(PyObject *s)
+{
+ PyObject *new_s;
+ char *ss, *new_ss;
+ size_t i, j, extra_space, size, new_size;
+ if (!PyString_Check(s))
+ return type_error("str object required");
+ ss = PyString_AS_STRING(s);
+ size = PyString_GET_SIZE(s);
+ extra_space = 0;
+ for (i=0; i < size; i++) {
+ switch (ss[i]) {
+ case '&':
+ extra_space += 4;
+ break;
+ case '<':
+ case '>':
+ extra_space += 3;
+ break;
+ case '"':
+ extra_space += 5;
+ break;
+ }
+ }
+ if (extra_space == 0) {
+ Py_INCREF(s);
+ return (PyObject *)s;
+ }
+ new_size = size + extra_space;
+ new_s = PyString_FromStringAndSize(NULL, new_size);
+ if (new_s == NULL)
+ return NULL;
+ new_ss = PyString_AsString(new_s);
+ for (i=0, j=0; i < size; i++) {
+ switch (ss[i]) {
+ case '&':
+ new_ss[j++] = '&';
+ new_ss[j++] = 'a';
+ new_ss[j++] = 'm';
+ new_ss[j++] = 'p';
+ new_ss[j++] = ';';
+ break;
+ case '<':
+ new_ss[j++] = '&';
+ new_ss[j++] = 'l';
+ new_ss[j++] = 't';
+ new_ss[j++] = ';';
+ break;
+ case '>':
+ new_ss[j++] = '&';
+ new_ss[j++] = 'g';
+ new_ss[j++] = 't';
+ new_ss[j++] = ';';
+ break;
+ case '"':
+ new_ss[j++] = '&';
+ new_ss[j++] = 'q';
+ new_ss[j++] = 'u';
+ new_ss[j++] = 'o';
+ new_ss[j++] = 't';
+ new_ss[j++] = ';';
+ break;
+ default:
+ new_ss[j++] = ss[i];
+ break;
+ }
+ }
+ assert (j == new_size);
+ return (PyObject *)new_s;
+}
+
+static PyObject *
+quote_wrapper_new(PyObject *o)
+{
+ QuoteWrapperObject *self;
+ if (htmltextObject_Check(o) ||
+ PyInt_Check(o) ||
+ PyFloat_Check(o) ||
+ PyLong_Check(o)) {
+ /* no need for wrapper */
+ Py_INCREF(o);
+ return o;
+ }
+ self = PyObject_New(QuoteWrapperObject, &QuoteWrapper_Type);
+ if (self == NULL)
+ return NULL;
+ Py_INCREF(o);
+ self->obj = o;
+ return (PyObject *)self;
+}
+
+static void
+quote_wrapper_dealloc(QuoteWrapperObject *self)
+{
+ Py_DECREF(self->obj);
+ PyObject_Del(self);
+}
+
+static PyObject *
+quote_wrapper_repr(QuoteWrapperObject *self)
+{
+ PyObject *qs;
+ PyObject *s = PyObject_Repr(self->obj);
+ if (s == NULL)
+ return NULL;
+ qs = escape_string(s);
+ Py_DECREF(s);
+ return qs;
+}
+
+static PyObject *
+quote_wrapper_str(QuoteWrapperObject *self)
+{
+ PyObject *qs;
+ PyObject *s = PyObject_Str(self->obj);
+ if (s == NULL)
+ return NULL;
+ qs = escape_string(s);
+ Py_DECREF(s);
+ return qs;
+}
+
+static PyObject *
+dict_wrapper_new(PyObject *o)
+{
+ DictWrapperObject *self;
+ self = PyObject_New(DictWrapperObject, &DictWrapper_Type);
+ if (self == NULL)
+ return NULL;
+ Py_INCREF(o);
+ self->obj = o;
+ return (PyObject *)self;
+}
+
+static void
+dict_wrapper_dealloc(DictWrapperObject *self)
+{
+ Py_DECREF(self->obj);
+ PyObject_Del(self);
+}
+
+static PyObject *
+dict_wrapper_subscript(DictWrapperObject *self, PyObject *key)
+{
+ PyObject *v, *w;;
+ v = PyObject_GetItem(self->obj, key);
+ if (v == NULL) {
+ return NULL;
+ }
+ w = quote_wrapper_new(v);
+ Py_DECREF(v);
+ return w;
+}
+
+static PyObject *
+htmltext_from_string(PyObject *s)
+{
+ /* note, this takes a reference */
+ PyObject *self;
+ if (s == NULL)
+ return NULL;
+ assert (PyString_Check(s));
+ self = PyType_GenericAlloc(&htmltext_Type, 0);
+ if (self == NULL) {
+ return NULL;
+ }
+ ((htmltextObject *)self)->s = (PyStringObject *)s;
+ return self;
+}
+
+static PyObject *
+htmltext_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ htmltextObject *self;
+ PyObject *s;
+ static char *kwlist[] = {"s", 0};
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:htmltext", kwlist,
+ &s))
+ return NULL;
+ s = PyObject_Str(s);
+ if (s == NULL)
+ return NULL;
+ self = (htmltextObject *)type->tp_alloc(type, 0);
+ if (self == NULL) {
+ Py_DECREF(s);
+ return NULL;
+ }
+ self->s = (PyStringObject *)s;
+ return (PyObject *)self;
+}
+
+/* htmltext methods */
+
+static void
+htmltext_dealloc(htmltextObject *self)
+{
+ Py_DECREF(self->s);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+static long
+htmltext_hash(PyObject *self)
+{
+ return PyObject_Hash(htmltext_STR(self));
+}
+
+static PyObject *
+htmltext_str(htmltextObject *self)
+{
+ Py_INCREF(self->s);
+ return (PyObject *)self->s;
+}
+
+static PyObject *
+htmltext_repr(htmltextObject *self)
+{
+ PyObject *sr, *rv;
+ sr = PyObject_Repr((PyObject *)self->s);
+ if (sr == NULL)
+ return NULL;
+ rv = PyString_FromFormat("<htmltext %s>", PyString_AsString(sr));
+ Py_DECREF(sr);
+ return rv;
+}
+
+static PyObject *
+htmltext_richcompare(PyObject *a, PyObject *b, int op)
+{
+ PyObject *sa, *sb;
+ if (PyString_Check(a)) {
+ sa = a;
+ } else if (htmltextObject_Check(a)) {
+ sa = htmltext_STR(a);
+ } else {
+ goto fail;
+ }
+ if (PyString_Check(b)) {
+ sb = b;
+ } else if (htmltextObject_Check(b)) {
+ sb = htmltext_STR(b);
+ } else {
+ goto fail;
+ }
+ return sa->ob_type->tp_richcompare(sa, sb, op);
+
+fail:
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+}
+
+static long
+htmltext_length(htmltextObject *self)
+{
+ return ((PyStringObject *)self->s)->ob_size;
+}
+
+
+static PyObject *
+wrap_arg(PyObject *arg)
+{
+ PyObject *warg;
+ if (htmltextObject_Check(arg)) {
+ /* don't bother with wrapper object */
+ warg = arg;
+ Py_INCREF(arg);
+ } else {
+ warg = quote_wrapper_new(arg);
+ }
+ return warg;
+}
+
+static PyObject *
+htmltext_format(htmltextObject *self, PyObject *args)
+{
+ /* wrap the format arguments with QuoteWrapperObject */
+ int do_dict = 0;
+ PyObject *rv, *wargs;
+ if (args->ob_type->tp_as_mapping && !PyTuple_Check(args) &&
+ !PyString_Check(args)) {
+ char *fmt = PyString_AS_STRING(htmltext_STR(self));
+ size_t i, n = PyString_GET_SIZE(htmltext_STR(self));
+ char last = 0;
+ /* second check necessary since '%s' % {} => '{}' */
+ for (i=0; i < n; i++) {
+ if (last == '%' && fmt[i] == '(') {
+ do_dict = 1;
+ break;
+ }
+ last = fmt[i];
+ }
+ }
+ if (do_dict) {
+ wargs = dict_wrapper_new(args);
+ if (wargs == NULL)
+ return NULL;
+ }
+ else if (PyTuple_Check(args)) {
+ long i, n = PyTuple_GET_SIZE(args);
+ wargs = PyTuple_New(n);
+ for (i=0; i < n; i++) {
+ PyObject *wvalue = wrap_arg(PyTuple_GET_ITEM(args, i));
+ if (wvalue == NULL) {
+ Py_DECREF(wargs);
+ return NULL;
+ }
+ PyTuple_SetItem(wargs, i, wvalue);
+ }
+ }
+ else {
+ wargs = wrap_arg(args);
+ if (wargs == NULL) {
+ return NULL;
+ }
+ }
+ rv = PyString_Format((PyObject *)self->s, wargs);
+ Py_DECREF(wargs);
+ return htmltext_from_string(rv);
+}
+
+static PyObject *
+htmltext_add(PyObject *v, PyObject *w)
+{
+ PyObject *qv, *qw;
+ if (htmltextObject_Check(v) && htmltextObject_Check(w)) {
+ qv = htmltext_STR(v);
+ qw = htmltext_STR(w);
+ Py_INCREF(qv);
+ Py_INCREF(qw);
+ }
+ else if (PyString_Check(w)) {
+ assert (htmltextObject_Check(v));
+ qv = htmltext_STR(v);
+ qw = escape_string(w);
+ if (qw == NULL)
+ return NULL;
+ Py_INCREF(qv);
+ }
+ else if (PyString_Check(v)) {
+ assert (htmltextObject_Check(w));
+ qv = escape_string(v);
+ if (qv == NULL)
+ return NULL;
+ qw = htmltext_STR(w);
+ Py_INCREF(qw);
+ }
+ else {
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+ }
+ PyString_ConcatAndDel(&qv, qw);
+ return htmltext_from_string(qv);
+}
+
+static PyObject *
+htmltext_repeat(htmltextObject *self, int n)
+{
+ PyObject *s = PySequence_Repeat(htmltext_STR(self), n);
+ if (s == NULL)
+ return NULL;
+ return htmltext_from_string(s);
+}
+
+static PyObject *
+htmltext_join(PyObject *self, PyObject *args)
+{
+ long i;
+ PyObject *qargs, *rv;
+ if (!PySequence_Check(args)) {
+ return type_error("argument must be a sequence");
+ }
+ qargs = PyList_New(PySequence_Size(args));
+ if (qargs == NULL)
+ return NULL;
+ for (i=0; i < PySequence_Size(args); i++) {
+ PyObject *value, *qvalue;
+ value = PySequence_GetItem(args, i);
+ if (value == NULL) {
+ goto error;
+ }
+ if (htmltextObject_Check(value)) {
+ qvalue = htmltext_STR(value);
+ Py_INCREF(qvalue);
+ Py_DECREF(value);
+ }
+ else if (PyString_Check(value)) {
+ qvalue = escape_string(value);
+ Py_DECREF(value);
+ }
+ else {
+ Py_DECREF(value);
+ type_error("join requires a list of strings");
+ goto error;
+ }
+ if (PyList_SetItem(qargs, i, qvalue) < 0) {
+ goto error;
+ }
+ }
+ rv = _PyString_Join(htmltext_STR(self), qargs);
+ Py_DECREF(qargs);
+ return htmltext_from_string(rv);
+
+error:
+ Py_DECREF(qargs);
+ return NULL;
+}
+
+static PyObject *
+quote_arg(PyObject *s)
+{
+ PyObject *ss;
+ if (PyString_Check(s)) {
+ ss = escape_string(s);
+ if (ss == NULL)
+ return NULL;
+ }
+ else if (htmltextObject_Check(s)) {
+ ss = htmltext_STR(s);
+ Py_INCREF(ss);
+ }
+ else {
+ return type_error("string object required");
+ }
+ return ss;
+}
+
+static PyObject *
+htmltext_call_method1(PyObject *self, PyObject *s, char *method)
+{
+ PyObject *ss, *rv;
+ ss = quote_arg(s);
+ if (ss == NULL)
+ return NULL;
+ rv = PyObject_CallMethod(htmltext_STR(self), method, "O", ss);
+ Py_DECREF(ss);
+ return rv;
+}
+
+static PyObject *
+htmltext_startswith(PyObject *self, PyObject *s)
+{
+ return htmltext_call_method1(self, s, "startswith");
+}
+
+static PyObject *
+htmltext_endswith(PyObject *self, PyObject *s)
+{
+ return htmltext_call_method1(self, s, "endswith");
+}
+
+static PyObject *
+htmltext_replace(PyObject *self, PyObject *args)
+{
+ PyObject *old, *new, *q_old, *q_new, *rv;
+ int maxsplit = -1;
+ if (!PyArg_ParseTuple(args,"OO|i:replace", &old, &new, &maxsplit))
+ return NULL;
+ q_old = quote_arg(old);
+ if (q_old == NULL)
+ return NULL;
+ q_new = quote_arg(new);
+ if (q_new == NULL) {
+ Py_DECREF(q_old);
+ return NULL;
+ }
+ rv = PyObject_CallMethod(htmltext_STR(self), "replace", "OOi",
+ q_old, q_new, maxsplit);
+ Py_DECREF(q_old);
+ Py_DECREF(q_new);
+ return htmltext_from_string(rv);
+}
+
+
+static PyObject *
+htmltext_lower(PyObject *self)
+{
+ return htmltext_from_string(PyObject_CallMethod(htmltext_STR(self),
+ "lower", ""));
+}
+
+static PyObject *
+htmltext_upper(PyObject *self)
+{
+ return htmltext_from_string(PyObject_CallMethod(htmltext_STR(self),
+ "upper", ""));
+}
+
+static PyObject *
+htmltext_capitalize(PyObject *self)
+{
+ return htmltext_from_string(PyObject_CallMethod(htmltext_STR(self),
+ "capitalize", ""));
+}
+
+static PyObject *
+template_io_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ TemplateIO_Object *self;
+ int html = 0;
+ static char *kwlist[] = {"html", 0};
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:TemplateIO",
+ kwlist, &html))
+ return NULL;
+ self = (TemplateIO_Object *)type->tp_alloc(type, 0);
+ if (self == NULL) {
+ return NULL;
+ }
+ self->html = html != 0;
+ self->buf = NULL;
+ self->size = 0;
+ self->pos = 0;
+ return (PyObject *)self;
+}
+
+static void
+template_io_dealloc(TemplateIO_Object *self)
+{
+ if (self->size > 0)
+ PyMem_Free(self->buf);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *
+template_io_str(TemplateIO_Object *self)
+{
+ return PyString_FromStringAndSize(self->buf, self->pos);
+}
+
+static PyObject *
+template_io_getvalue(TemplateIO_Object *self)
+{
+ if (self->html) {
+ return htmltext_from_string(template_io_str(self));
+ }
+ else {
+ return template_io_str(self);
+ }
+}
+
+static PyObject *
+template_io_repr(TemplateIO_Object *self)
+{
+ PyObject *s, *sr, *rv;
+ s = template_io_str(self);
+ if (s == NULL)
+ return NULL;
+ sr = PyObject_Repr(s);
+ Py_DECREF(s);
+ if (sr == NULL)
+ return NULL;
+ rv = PyString_FromFormat("<TemplateIO %s>", PyString_AsString(sr));
+ Py_DECREF(sr);
+ return rv;
+}
+
+
+static PyObject *
+template_io_do_concat(TemplateIO_Object *self, char *s, size_t size)
+{
+ /* note this adds a reference to self */
+ if (self->pos + size > self->size) {
+ size_t new_size;
+ char *new_buf;
+ if (self->size > size)
+ new_size = self->size * 2;
+ else
+ new_size = size * 2;
+ new_buf = PyMem_Realloc(self->buf, new_size);
+ if (new_buf == NULL)
+ return NULL;
+ self->buf = new_buf;
+ self->size = new_size;
+ }
+ assert (self->pos + size <= self->size);
+ memcpy(self->buf + self->pos, s, size);
+ self->pos += size;
+ Py_INCREF(self);
+ return (PyObject *)self;
+}
+
+
+static PyObject *
+template_io_iadd(TemplateIO_Object *self, PyObject *other)
+{
+ PyObject *rv;
+ PyObject *s = NULL;
+ if (!TemplateIO_Check(self))
+ return type_error("TemplateIO object required");
+ if (other == Py_None) {
+ Py_INCREF(self);
+ return (PyObject *)self;
+ }
+ else if (TemplateIO_Check(other)) {
+ TemplateIO_Object *o = (TemplateIO_Object *)other;
+ if (self->html && !o->html) {
+ PyObject *ss = PyString_FromStringAndSize(o->buf,
+ o->pos);
+ if (ss == NULL)
+ return NULL;
+ s = escape_string(ss);
+ Py_DECREF(ss);
+ goto concat_str;
+ }
+ rv = template_io_do_concat(self, o->buf, o->pos);
+ }
+ else if (htmltextObject_Check(other)) {
+ PyStringObject *s = ((htmltextObject *)other)->s;
+ rv = template_io_do_concat(self,
+ PyString_AS_STRING(s),
+ PyString_GET_SIZE(s));
+ }
+ else {
+ if (self->html) {
+ PyObject *ss = PyObject_Str(other);
+ if (ss == NULL)
+ return NULL;
+ s = escape_string(ss);
+ Py_DECREF(ss);
+ } else {
+ s = PyObject_Str(other);
+ }
+concat_str:
+ if (s == NULL)
+ return NULL;
+ rv = template_io_do_concat(self, PyString_AS_STRING(s),
+ PyString_GET_SIZE(s));
+ Py_XDECREF(s);
+ }
+ return rv;
+}
+
+static PyMethodDef htmltext_methods[] = {
+ {"join", (PyCFunction)htmltext_join, METH_O, ""},
+ {"startswith", (PyCFunction)htmltext_startswith, METH_O, ""},
+ {"endswith", (PyCFunction)htmltext_endswith, METH_O, ""},
+ {"replace", (PyCFunction)htmltext_replace, METH_VARARGS, ""},
+ {"lower", (PyCFunction)htmltext_lower, METH_NOARGS, ""},
+ {"upper", (PyCFunction)htmltext_upper, METH_NOARGS, ""},
+ {"capitalize", (PyCFunction)htmltext_capitalize, METH_NOARGS, ""},
+ {NULL, NULL}
+};
+
+static PyMemberDef htmltext_members[] = {
+ {"s", T_OBJECT, offsetof(htmltextObject, s), READONLY, "the string"},
+ {NULL},
+};
+
+static PySequenceMethods htmltext_as_sequence = {
+ (inquiry)htmltext_length, /*sq_length*/
+ 0, /*sq_concat*/
+ (intargfunc)htmltext_repeat, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+};
+
+static PyNumberMethods htmltext_as_number = {
+ (binaryfunc)htmltext_add, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ (binaryfunc)htmltext_format, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+};
+
+static PyTypeObject htmltext_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size*/
+ "htmltext", /*tp_name*/
+ sizeof(htmltextObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)htmltext_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ (unaryfunc)htmltext_repr,/*tp_repr*/
+ &htmltext_as_number, /*tp_as_number*/
+ &htmltext_as_sequence, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ htmltext_hash, /*tp_hash*/
+ 0, /*tp_call*/
+ (unaryfunc)htmltext_str,/*tp_str*/
+ PyObject_GenericGetAttr,/*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE \
+ | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ htmltext_richcompare, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ htmltext_methods, /*tp_methods*/
+ htmltext_members, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ 0, /*tp_init*/
+ PyType_GenericAlloc, /*tp_alloc*/
+ htmltext_new, /*tp_new*/
+ _PyObject_Del, /*tp_free*/
+ 0, /*tp_is_gc*/
+};
+
+static PyNumberMethods quote_wrapper_as_number = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+};
+
+static PyTypeObject QuoteWrapper_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size*/
+ "QuoteWrapper", /*tp_name*/
+ sizeof(QuoteWrapperObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)quote_wrapper_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ (unaryfunc)quote_wrapper_repr,/*tp_repr*/
+ "e_wrapper_as_number,/*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ (unaryfunc)quote_wrapper_str, /*tp_str*/
+};
+
+static PyMappingMethods dict_wrapper_as_mapping = {
+ 0, /*mp_length*/
+ (binaryfunc)dict_wrapper_subscript, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyTypeObject DictWrapper_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size*/
+ "DictWrapper", /*tp_name*/
+ sizeof(DictWrapperObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)dict_wrapper_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ &dict_wrapper_as_mapping,/*tp_as_mapping*/
+};
+
+static PyNumberMethods template_io_as_number = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ (binaryfunc)template_io_iadd, /*nb_inplace_add*/
+};
+
+static PyMethodDef template_io_methods[] = {
+ {"getvalue", (PyCFunction)template_io_getvalue, METH_NOARGS, ""},
+ {NULL, NULL}
+};
+
+static PyTypeObject TemplateIO_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size*/
+ "TemplateIO", /*tp_name*/
+ sizeof(TemplateIO_Object),/*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)template_io_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ (unaryfunc)template_io_repr,/*tp_repr*/
+ &template_io_as_number, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ (unaryfunc)template_io_str,/*tp_str*/
+ PyObject_GenericGetAttr,/*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ 0, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ template_io_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ 0, /*tp_init*/
+ PyType_GenericAlloc, /*tp_alloc*/
+ template_io_new, /*tp_new*/
+ _PyObject_Del, /*tp_free*/
+ 0, /*tp_is_gc*/
+};
+
+/* --------------------------------------------------------------------- */
+
+static PyObject *
+html_escape(PyObject *self, PyObject *o)
+{
+ if (htmltextObject_Check(o)) {
+ Py_INCREF(o);
+ return o;
+ }
+ else {
+ PyObject *rv;
+ PyObject *s = PyObject_Str(o);
+ if (s == NULL)
+ return NULL;
+ rv = escape_string(s);
+ Py_DECREF(s);
+ return htmltext_from_string(rv);
+ }
+}
+
+static PyObject *
+py_escape_string(PyObject *self, PyObject *o)
+{
+ PyObject *rv;
+ if (!PyString_Check(o))
+ return type_error("string required");
+ rv = escape_string(o);
+ return rv;
+}
+
+/* List of functions defined in the module */
+
+static PyMethodDef htmltext_module_methods[] = {
+ {"htmlescape", (PyCFunction)html_escape, METH_O},
+ {"_escape_string", (PyCFunction)py_escape_string, METH_O},
+ {NULL, NULL}
+};
+
+static char module_doc[] = "htmltext string type";
+
+void
+init_c_htmltext(void)
+{
+ PyObject *m;
+
+ /* Initialize the type of the new type object here; doing it here
+ * is required for portability to Windows without requiring C++. */
+ htmltext_Type.ob_type = &PyType_Type;
+ QuoteWrapper_Type.ob_type = &PyType_Type;
+ TemplateIO_Type.ob_type = &PyType_Type;
+
+ /* Create the module and add the functions */
+ m = Py_InitModule4("_c_htmltext", htmltext_module_methods, module_doc,
+ NULL, PYTHON_API_VERSION);
+
+ Py_INCREF((PyObject *)&htmltext_Type);
+ Py_INCREF((PyObject *)&QuoteWrapper_Type);
+ Py_INCREF((PyObject *)&TemplateIO_Type);
+ PyModule_AddObject(m, "htmltext", (PyObject *)&htmltext_Type);
+ PyModule_AddObject(m, "TemplateIO", (PyObject *)&TemplateIO_Type);
+}
Added: packages/quixote1/branches/upstream/current/src/cimport.c
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/src/cimport.c?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/src/cimport.c (added)
+++ packages/quixote1/branches/upstream/current/src/cimport.c Mon May 8 19:16:16 2006
@@ -1,0 +1,483 @@
+/* Mostly stolen from Python/import.c. PSF license applies. */
+
+
+#include "Python.h"
+#include "osdefs.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/* Python function to find and load a module. */
+static PyObject *loader_hook;
+
+
+PyObject *
+call_find_load(char *fullname, char *subname, PyObject *path)
+{
+ PyObject *args, *m;
+
+ if (!(args = Py_BuildValue("(ssO)", fullname, subname,
+ path != NULL ? path : Py_None)))
+ return NULL;
+
+ m = PyEval_CallObject(loader_hook, args);
+
+ Py_DECREF(args);
+ return m;
+}
+
+
+/* Forward declarations for helper routines */
+static PyObject *get_parent(PyObject *globals, char *buf, int *p_buflen);
+static PyObject *load_next(PyObject *mod, PyObject *altmod,
+ char **p_name, char *buf, int *p_buflen);
+static int mark_miss(char *name);
+static int ensure_fromlist(PyObject *mod, PyObject *fromlist,
+ char *buf, int buflen, int recursive);
+static PyObject * import_submodule(PyObject *mod, char *name, char *fullname);
+
+
+static PyObject *
+import_module(char *name, PyObject *globals, PyObject *locals,
+ PyObject *fromlist)
+{
+ char buf[MAXPATHLEN+1];
+ int buflen = 0;
+ PyObject *parent, *head, *next, *tail;
+
+ parent = get_parent(globals, buf, &buflen);
+ if (parent == NULL)
+ return NULL;
+
+ head = load_next(parent, Py_None, &name, buf, &buflen);
+ if (head == NULL)
+ return NULL;
+
+ tail = head;
+ Py_INCREF(tail);
+ while (name) {
+ next = load_next(tail, tail, &name, buf, &buflen);
+ Py_DECREF(tail);
+ if (next == NULL) {
+ Py_DECREF(head);
+ return NULL;
+ }
+ tail = next;
+ }
+
+ if (fromlist != NULL) {
+ if (fromlist == Py_None || !PyObject_IsTrue(fromlist))
+ fromlist = NULL;
+ }
+
+ if (fromlist == NULL) {
+ Py_DECREF(tail);
+ return head;
+ }
+
+ Py_DECREF(head);
+ if (!ensure_fromlist(tail, fromlist, buf, buflen, 0)) {
+ Py_DECREF(tail);
+ return NULL;
+ }
+
+ return tail;
+}
+
+static PyObject *
+get_parent(PyObject *globals, char *buf, int *p_buflen)
+{
+ static PyObject *namestr = NULL;
+ static PyObject *pathstr = NULL;
+ PyObject *modname, *modpath, *modules, *parent;
+
+ if (globals == NULL || !PyDict_Check(globals))
+ return Py_None;
+
+ if (namestr == NULL) {
+ namestr = PyString_InternFromString("__name__");
+ if (namestr == NULL)
+ return NULL;
+ }
+ if (pathstr == NULL) {
+ pathstr = PyString_InternFromString("__path__");
+ if (pathstr == NULL)
+ return NULL;
+ }
+
+ *buf = '\0';
+ *p_buflen = 0;
+ modname = PyDict_GetItem(globals, namestr);
+ if (modname == NULL || !PyString_Check(modname))
+ return Py_None;
+
+ modpath = PyDict_GetItem(globals, pathstr);
+ if (modpath != NULL) {
+ int len = PyString_GET_SIZE(modname);
+ if (len > MAXPATHLEN) {
+ PyErr_SetString(PyExc_ValueError,
+ "Module name too long");
+ return NULL;
+ }
+ strcpy(buf, PyString_AS_STRING(modname));
+ *p_buflen = len;
+ }
+ else {
+ char *start = PyString_AS_STRING(modname);
+ char *lastdot = strrchr(start, '.');
+ size_t len;
+ if (lastdot == NULL)
+ return Py_None;
+ len = lastdot - start;
+ if (len >= MAXPATHLEN) {
+ PyErr_SetString(PyExc_ValueError,
+ "Module name too long");
+ return NULL;
+ }
+ strncpy(buf, start, len);
+ buf[len] = '\0';
+ *p_buflen = len;
+ }
+
+ modules = PyImport_GetModuleDict();
+ parent = PyDict_GetItemString(modules, buf);
+ if (parent == NULL)
+ parent = Py_None;
+ return parent;
+ /* We expect, but can't guarantee, if parent != None, that:
+ - parent.__name__ == buf
+ - parent.__dict__ is globals
+ If this is violated... Who cares? */
+}
+
+/* altmod is either None or same as mod */
+static PyObject *
+load_next(PyObject *mod, PyObject *altmod, char **p_name, char *buf,
+ int *p_buflen)
+{
+ char *name = *p_name;
+ char *dot = strchr(name, '.');
+ size_t len;
+ char *p;
+ PyObject *result;
+
+ if (dot == NULL) {
+ *p_name = NULL;
+ len = strlen(name);
+ }
+ else {
+ *p_name = dot+1;
+ len = dot-name;
+ }
+ if (len == 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "Empty module name");
+ return NULL;
+ }
+
+ p = buf + *p_buflen;
+ if (p != buf)
+ *p++ = '.';
+ if (p+len-buf >= MAXPATHLEN) {
+ PyErr_SetString(PyExc_ValueError,
+ "Module name too long");
+ return NULL;
+ }
+ strncpy(p, name, len);
+ p[len] = '\0';
+ *p_buflen = p+len-buf;
+
+ result = import_submodule(mod, p, buf);
+ if (result == Py_None && altmod != mod) {
+ Py_DECREF(result);
+ /* Here, altmod must be None and mod must not be None */
+ result = import_submodule(altmod, p, p);
+ if (result != NULL && result != Py_None) {
+ if (mark_miss(buf) != 0) {
+ Py_DECREF(result);
+ return NULL;
+ }
+ strncpy(buf, name, len);
+ buf[len] = '\0';
+ *p_buflen = len;
+ }
+ }
+ if (result == NULL)
+ return NULL;
+
+ if (result == Py_None) {
+ Py_DECREF(result);
+ PyErr_Format(PyExc_ImportError,
+ "No module named %.200s", name);
+ return NULL;
+ }
+
+ return result;
+}
+
+static int
+mark_miss(char *name)
+{
+ PyObject *modules = PyImport_GetModuleDict();
+ return PyDict_SetItemString(modules, name, Py_None);
+}
+
+static int
+ensure_fromlist(PyObject *mod, PyObject *fromlist, char *buf, int buflen,
+ int recursive)
+{
+ int i;
+
+ if (!PyObject_HasAttrString(mod, "__path__"))
+ return 1;
+
+ for (i = 0; ; i++) {
+ PyObject *item = PySequence_GetItem(fromlist, i);
+ int hasit;
+ if (item == NULL) {
+ if (PyErr_ExceptionMatches(PyExc_IndexError)) {
+ PyErr_Clear();
+ return 1;
+ }
+ return 0;
+ }
+ if (!PyString_Check(item)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Item in ``from list'' not a string");
+ Py_DECREF(item);
+ return 0;
+ }
+ if (PyString_AS_STRING(item)[0] == '*') {
+ PyObject *all;
+ Py_DECREF(item);
+ /* See if the package defines __all__ */
+ if (recursive)
+ continue; /* Avoid endless recursion */
+ all = PyObject_GetAttrString(mod, "__all__");
+ if (all == NULL)
+ PyErr_Clear();
+ else {
+ if (!ensure_fromlist(mod, all, buf, buflen, 1))
+ return 0;
+ Py_DECREF(all);
+ }
+ continue;
+ }
+ hasit = PyObject_HasAttr(mod, item);
+ if (!hasit) {
+ char *subname = PyString_AS_STRING(item);
+ PyObject *submod;
+ char *p;
+ if (buflen + strlen(subname) >= MAXPATHLEN) {
+ PyErr_SetString(PyExc_ValueError,
+ "Module name too long");
+ Py_DECREF(item);
+ return 0;
+ }
+ p = buf + buflen;
+ *p++ = '.';
+ strcpy(p, subname);
+ submod = import_submodule(mod, subname, buf);
+ Py_XDECREF(submod);
+ if (submod == NULL) {
+ Py_DECREF(item);
+ return 0;
+ }
+ }
+ Py_DECREF(item);
+ }
+
+ /* NOTREACHED */
+}
+
+static PyObject *
+import_submodule(PyObject *mod, char *subname, char *fullname)
+{
+ PyObject *modules = PyImport_GetModuleDict();
+ PyObject *m;
+
+ /* Require:
+ if mod == None: subname == fullname
+ else: mod.__name__ + "." + subname == fullname
+ */
+
+ if ((m = PyDict_GetItemString(modules, fullname)) != NULL) {
+ Py_INCREF(m);
+ }
+ else {
+ PyObject *path;
+
+ if (mod == Py_None)
+ path = NULL;
+ else {
+ path = PyObject_GetAttrString(mod, "__path__");
+ if (path == NULL) {
+ PyErr_Clear();
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ }
+
+ m = call_find_load(fullname, subname, path);
+
+ if (m != NULL && mod != Py_None) {
+ if (PyObject_SetAttrString(mod, subname, m) < 0) {
+ Py_DECREF(m);
+ m = NULL;
+ }
+ }
+ }
+
+ return m;
+}
+
+
+PyObject *
+reload_module(PyObject *m)
+{
+ PyObject *modules = PyImport_GetModuleDict();
+ PyObject *path = NULL;
+ char *name, *subname;
+
+ if (m == NULL || !PyModule_Check(m)) {
+ PyErr_SetString(PyExc_TypeError,
+ "reload_module() argument must be module");
+ return NULL;
+ }
+ name = PyModule_GetName(m);
+ if (name == NULL)
+ return NULL;
+ if (m != PyDict_GetItemString(modules, name)) {
+ PyErr_Format(PyExc_ImportError,
+ "reload(): module %.200s not in sys.modules",
+ name);
+ return NULL;
+ }
+ subname = strrchr(name, '.');
+ if (subname == NULL)
+ subname = name;
+ else {
+ PyObject *parentname, *parent;
+ parentname = PyString_FromStringAndSize(name, (subname-name));
+ if (parentname == NULL)
+ return NULL;
+ parent = PyDict_GetItem(modules, parentname);
+ Py_DECREF(parentname);
+ if (parent == NULL) {
+ PyErr_Format(PyExc_ImportError,
+ "reload(): parent %.200s not in sys.modules",
+ name);
+ return NULL;
+ }
+ subname++;
+ path = PyObject_GetAttrString(parent, "__path__");
+ if (path == NULL)
+ PyErr_Clear();
+ }
+ m = call_find_load(name, subname, path);
+ Py_XDECREF(path);
+ return m;
+}
+
+
+static PyObject *
+cimport_import_module(PyObject *self, PyObject *args)
+{
+ char *name;
+ PyObject *globals = NULL;
+ PyObject *locals = NULL;
+ PyObject *fromlist = NULL;
+
+ if (!PyArg_ParseTuple(args, "s|OOO:import_module", &name, &globals,
+ &locals, &fromlist))
+ return NULL;
+ return import_module(name, globals, locals, fromlist);
+}
+
+static PyObject *
+cimport_reload_module(PyObject *self, PyObject *args)
+{
+ PyObject *m;
+ if (!PyArg_ParseTuple(args, "O:reload_module", &m))
+ return NULL;
+ return reload_module(m);
+}
+
+static char doc_reload_module[] =
+"reload(module) -> module\n\
+\n\
+Reload the module. The module must have been successfully imported before.";
+
+static PyObject *
+cimport_set_loader(PyObject *self, PyObject *args)
+{
+ PyObject *l = NULL;
+ if (!PyArg_ParseTuple(args, "O:set_loader", &l))
+ return NULL;
+ if (!PyCallable_Check(l)) {
+ PyErr_SetString(PyExc_TypeError, "callable object needed");
+ return NULL;
+ }
+ Py_XDECREF(loader_hook);
+ loader_hook = l;
+ Py_INCREF(loader_hook);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+static char doc_set_loader[] = "\
+Set the function that will be used to import modules.\n\
+\n\
+The function should should have the signature:\n\
+\n\
+ loader(fullname : str, subname : str, path : [str] | None) -> module | None\n\
+\n\
+It should return the initialized module or None if it is not found.\n\
+";
+
+
+static PyObject *
+cimport_get_loader(PyObject *self, PyObject *args)
+{
+ if (!PyArg_ParseTuple(args, ":get_loader"))
+ return NULL;
+ Py_INCREF(loader_hook);
+ return loader_hook;
+}
+
+static char doc_get_loader[] = "\
+Get the function that will be used to import modules.\n\
+";
+
+static char doc_import_module[] = "\
+import_module(name, globals, locals, fromlist) -> module\n\
+\n\
+Import a module. The globals are only used to determine the context;\n\
+they are not modified. The locals are currently unused. The fromlist\n\
+should be a list of names to emulate ``from name import ...'', or an\n\
+empty list to emulate ``import name''.\n\
+\n\
+When importing a module from a package, note that import_module('A.B', ...)\n\
+returns package A when fromlist is empty, but its submodule B when\n\
+fromlist is not empty.\n\
+";
+
+
+static PyMethodDef cimport_methods[] = {
+ {"import_module", cimport_import_module, 1, doc_import_module},
+ {"reload_module", cimport_reload_module, 1, doc_reload_module},
+ {"get_loader", cimport_get_loader, 1, doc_get_loader},
+ {"set_loader", cimport_set_loader, 1, doc_set_loader},
+ {NULL, NULL} /* sentinel */
+};
+
+void
+initcimport(void)
+{
+ PyObject *m, *d;
+
+ m = Py_InitModule4("cimport", cimport_methods, "",
+ NULL, PYTHON_API_VERSION);
+ d = PyModule_GetDict(m);
+
+}
Added: packages/quixote1/branches/upstream/current/src/setup.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/src/setup.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/src/setup.py (added)
+++ packages/quixote1/branches/upstream/current/src/setup.py Mon May 8 19:16:16 2006
@@ -1,0 +1,14 @@
+
+from distutils.core import setup
+from distutils.extension import Extension
+
+Import = Extension(name="cimport",
+ sources=["cimport.c"])
+
+setup(name = "cimport",
+ version = "0.1",
+ description = "Import tools for Python",
+ author = "Neil Schemenauer",
+ author_email = "nas at mems-exchange.org",
+ ext_modules = [Import]
+ )
Added: packages/quixote1/branches/upstream/current/test/__init__.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/test/__init__.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/test/__init__.py (added)
+++ packages/quixote1/branches/upstream/current/test/__init__.py Mon May 8 19:16:16 2006
@@ -1,0 +1,2 @@
+
+# Empty file to make this directory a package
Added: packages/quixote1/branches/upstream/current/test/ua_test.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/test/ua_test.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/test/ua_test.py (added)
+++ packages/quixote1/branches/upstream/current/test/ua_test.py Mon May 8 19:16:16 2006
@@ -1,0 +1,28 @@
+#!/usr/bin/env python
+
+# Test Quixote's ability to parse the "User-Agent" header, ie.
+# the 'guess_browser_version()' method of HTTPRequest.
+#
+# Reads User-Agent strings on stdin, and writes Quixote's interpretation
+# of each on stdout. This is *not* an automated test!
+
+import sys, os
+from copy import copy
+from quixote.http_request import HTTPRequest
+
+env = copy(os.environ)
+file = sys.stdin
+while 1:
+ line = file.readline()
+ if not line:
+ break
+ if line[-1] == "\n":
+ line = line[:-1]
+
+ env["HTTP_USER_AGENT"] = line
+ req = HTTPRequest(None, env)
+ (name, version) = req.guess_browser_version()
+ if name is None:
+ print "%s -> ???" % line
+ else:
+ print "%s -> (%s, %s)" % (line, name, version)
Added: packages/quixote1/branches/upstream/current/test/utest_html.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/test/utest_html.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/test/utest_html.py (added)
+++ packages/quixote1/branches/upstream/current/test/utest_html.py Mon May 8 19:16:16 2006
@@ -1,0 +1,234 @@
+#!/www/python/bin/python
+"""
+$URL: svn+ssh://svn/repos/trunk/quixote/test/utest_html.py $
+$Id: utest_html.py 25234 2004-09-30 17:36:19Z nascheme $
+"""
+from sancho.utest import UTest
+from quixote import _py_htmltext
+
+escape = htmlescape = None # so that checker does not complain
+
+class Wrapper:
+ def __init__(self, s):
+ self.s = s
+
+ def __repr__(self):
+ return self.s
+
+ def __str__(self):
+ return self.s
+
+class Broken:
+ def __str__(self):
+ raise RuntimeError, 'eieee'
+
+ def __repr__(self):
+ raise RuntimeError, 'eieee'
+
+markupchars = '<>&"'
+quotedchars = '<>&"'
+
+class HTMLTest (UTest):
+
+ def _pre(self):
+ global htmltext, escape, htmlescape
+ htmltext = _py_htmltext.htmltext
+ escape = _py_htmltext._escape_string
+ htmlescape = _py_htmltext.htmlescape
+
+ def _post(self):
+ pass
+
+
+ def check_init(self):
+ assert str(htmltext('foo')) == 'foo'
+ assert str(htmltext(markupchars)) == markupchars
+ assert str(htmltext(None)) == 'None'
+ assert str(htmltext(1)) == '1'
+ try:
+ htmltext(Broken())
+ assert 0
+ except RuntimeError: pass
+
+ def check_escape(self):
+ assert htmlescape(markupchars) == quotedchars
+ assert isinstance(htmlescape(markupchars), htmltext)
+ assert escape(markupchars) == quotedchars
+ assert isinstance(escape(markupchars), str)
+ assert htmlescape(htmlescape(markupchars)) == quotedchars
+ try:
+ escape(1)
+ assert 0
+ except TypeError: pass
+
+ def check_cmp(self):
+ s = htmltext("foo")
+ assert s == 'foo'
+ assert s != 'bar'
+ assert s == htmltext('foo')
+ assert s != htmltext('bar')
+ assert htmltext('1') != 1
+ assert 1 != s
+
+ def check_len(self):
+ assert len(htmltext('foo')) == 3
+ assert len(htmltext(markupchars)) == len(markupchars)
+ assert len(htmlescape(markupchars)) == len(quotedchars)
+
+ def check_hash(self):
+ assert hash(htmltext('foo')) == hash('foo')
+ assert hash(htmltext(markupchars)) == hash(markupchars)
+ assert hash(htmlescape(markupchars)) == hash(quotedchars)
+
+ def check_concat(self):
+ s = htmltext("foo")
+ assert s + 'bar' == "foobar"
+ assert 'bar' + s == "barfoo"
+ assert s + htmltext('bar') == "foobar"
+ assert s + markupchars == "foo" + quotedchars
+ assert isinstance(s + markupchars, htmltext)
+ assert markupchars + s == quotedchars + "foo"
+ assert isinstance(markupchars + s, htmltext)
+ try:
+ s + 1
+ assert 0
+ except TypeError: pass
+ try:
+ 1 + s
+ assert 0
+ except TypeError: pass
+
+ def check_repeat(self):
+ s = htmltext('a')
+ assert s * 3 == "aaa"
+ assert isinstance(s * 3, htmltext)
+ assert htmlescape(markupchars) * 3 == quotedchars * 3
+ try:
+ s * 'a'
+ assert 0
+ except TypeError: pass
+ try:
+ 'a' * s
+ assert 0
+ except TypeError: pass
+ try:
+ s * s
+ assert 0
+ except TypeError: pass
+
+ def check_format(self):
+ s_fmt = htmltext('%s')
+ assert s_fmt % 'foo' == "foo"
+ assert isinstance(s_fmt % 'foo', htmltext)
+ assert s_fmt % markupchars == quotedchars
+ assert s_fmt % None == "None"
+ assert htmltext('%r') % Wrapper(markupchars) == quotedchars
+ assert htmltext('%s%s') % ('foo', htmltext(markupchars)) == (
+ "foo" + markupchars)
+ assert htmltext('%d') % 10 == "10"
+ assert htmltext('%.1f') % 10 == "10.0"
+ try:
+ s_fmt % Broken()
+ assert 0
+ except RuntimeError: pass
+ try:
+ htmltext('%r') % Broken()
+ assert 0
+ except RuntimeError: pass
+ try:
+ s_fmt % (1, 2)
+ assert 0
+ except TypeError: pass
+ assert htmltext('%d') % 12300000000000000000L == "12300000000000000000"
+
+ def check_dict_format(self):
+ assert htmltext('%(a)s %(a)r %(b)s') % (
+ {'a': 'foo&', 'b': htmltext('bar&')}) == "foo& 'foo&' bar&"
+ assert htmltext('%(a)s') % {'a': 'foo&'} == "foo&"
+ assert isinstance(htmltext('%(a)s') % {'a': 'a'}, htmltext)
+ assert htmltext('%s') % {'a': 'foo&'} == "{'a': 'foo&'}"
+ try:
+ htmltext('%(a)s') % 1
+ assert 0
+ except TypeError: pass
+ try:
+ htmltext('%(a)s') % {}
+ assert 0
+ except KeyError: pass
+
+ def check_join(self):
+ assert htmltext(' ').join(['foo', 'bar']) == "foo bar"
+ assert htmltext(' ').join(['foo', markupchars]) == (
+ "foo " + quotedchars)
+ assert htmlescape(markupchars).join(['foo', 'bar']) == (
+ "foo" + quotedchars + "bar")
+ assert htmltext(' ').join([htmltext(markupchars), 'bar']) == (
+ markupchars + " bar")
+ assert isinstance(htmltext('').join([]), htmltext)
+ try:
+ htmltext('').join(1)
+ assert 0
+ except TypeError: pass
+ try:
+ htmltext('').join([1])
+ assert 0
+ except TypeError: pass
+
+ def check_startswith(self):
+ assert htmltext('foo').startswith('fo')
+ assert htmlescape(markupchars).startswith(markupchars[:3])
+ assert htmltext(markupchars).startswith(htmltext(markupchars[:3]))
+ try:
+ htmltext('').startswith(1)
+ assert 0
+ except TypeError: pass
+
+ def check_endswith(self):
+ assert htmltext('foo').endswith('oo')
+ assert htmlescape(markupchars).endswith(markupchars[-3:])
+ assert htmltext(markupchars).endswith(htmltext(markupchars[-3:]))
+ try:
+ htmltext('').endswith(1)
+ assert 0
+ except TypeError: pass
+
+ def check_replace(self):
+ assert htmlescape('&').replace('&', 'foo') == "foo"
+ assert htmltext('&').replace(htmltext('&'), 'foo') == "foo"
+ assert htmltext('foo').replace('foo', htmltext('&')) == "&"
+ assert isinstance(htmltext('a').replace('a', 'b'), htmltext)
+ try:
+ htmltext('').replace(1, 'a')
+ assert 0
+ except TypeError: pass
+
+ def check_lower(self):
+ assert htmltext('aB').lower() == "ab"
+ assert isinstance(htmltext('a').lower(), htmltext)
+
+ def check_upper(self):
+ assert htmltext('aB').upper() == "AB"
+ assert isinstance(htmltext('a').upper(), htmltext)
+
+ def check_capitalize(self):
+ assert htmltext('aB').capitalize() == "Ab"
+ assert isinstance(htmltext('a').capitalize(), htmltext)
+
+
+try:
+ from quixote import _c_htmltext
+except ImportError:
+ _c_htmltext = None
+
+if _c_htmltext:
+ class CHTMLTest(HTMLTest):
+ def _pre(self):
+ # using globals like this is a bit of a hack since it assumes
+ # Sancho tests each class individually, oh well
+ global htmltext, escape, htmlescape
+ htmltext = _c_htmltext.htmltext
+ escape = _c_htmltext._escape_string
+ htmlescape = _c_htmltext.htmlescape
+
+if __name__ == "__main__":
+ HTMLTest()
Propchange: packages/quixote1/branches/upstream/current/test/utest_html.py
------------------------------------------------------------------------------
svn:executable =
Added: packages/quixote1/branches/upstream/current/upload.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/upload.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/upload.py (added)
+++ packages/quixote1/branches/upstream/current/upload.py Mon May 8 19:16:16 2006
@@ -1,0 +1,377 @@
+"""quixote.upload
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/upload.py $
+$Id: upload.py 25234 2004-09-30 17:36:19Z nascheme $
+
+Code for handling HTTP upload requests. Provides HTTPUploadRequest, a
+subclass of HTTPRequest that is created when handling an HTTP request
+whose Content-Type is "multipart/form-data". Also provides the Upload
+class, which is used as the form value for "file upload" variables.
+"""
+
+__revision__ = "$Id: upload.py 25234 2004-09-30 17:36:19Z nascheme $"
+
+import os, string
+import errno
+from cgi import parse_header
+from rfc822 import Message
+from time import time, strftime, localtime
+
+from quixote.http_request import HTTPRequest
+from quixote.errors import RequestError
+from quixote.config import ConfigError
+
+CRLF = "\r\n"
+LF = "\n"
+
+
+def read_mime_part(file, boundary, lines=None, ofile=None):
+ """
+ Read lines from 'file' up to and including a MIME message boundary
+ derived from 'boundary'. Return true if there is no more data to be
+ read from 'file', ie. we hit either a MIME outer boundary or EOF.
+
+ If 'lines' is supplied, each line is stripped of line-endings and
+ appended to 'lines'. If 'ofile' is supplied, each line is written
+ to 'ofile' as-is (ie. with line-endings intact). Neither the
+ boundary line nor a blank line preceding it (if any) will be
+ saved/written. If neither 'lines' nor 'ofile' is supplied, the data
+ read is discarded.
+ """
+ # Algorithm based on read_lines_to_outerboundary() in cgi.py
+
+ next = "--" + boundary
+ last = "--" + boundary + "--"
+
+ # XXX reading arbitrary binary data (which is possible in a file
+ # upload) a line-at-a-time might be problematic. Eg. I have
+ # observed one .GDS file in the wild where the longest "line" was
+ # around 1 MB. Most binary files that I looked at have reasonable
+ # "line" lengths though -- maximum 5-10k. However, reading in
+ # fixed-size chunks would make spotting the MIME boundary tricky.
+ # One more reason why HTTP upload is stupid.
+
+ prev_delim = ""
+ while 1:
+ line = file.readline()
+
+ # Hit EOF -- nothing more to read. (This should *not* happen
+ # in a well-formed MIME message, but let's assume the worst.)
+ if not line:
+ return 1
+
+ # Strip (but remember) line ending.
+ if line[-2:] == CRLF:
+ line = line[:-2]
+ delim = CRLF
+ elif line[-1:] == LF:
+ line = line[:-1]
+ delim = LF
+ else:
+ delim = ""
+
+ # If we hit the boundary line, return now. Forget the current
+ # line *and* the delimiter of the previous line -- in
+ # particular, we do not want to preserve the blank line that
+ # comes after an uploaded file's contents and the following
+ # boundary line.
+ if line == next: # hit boundary, but more to come
+ return 0
+ elif line == last: # final boundary -- no more to read
+ return 1
+
+ if lines is not None:
+ lines.append(line)
+ if ofile is not None:
+ ofile.write(prev_delim + line)
+ prev_delim = delim
+
+
+SAFE_CHARS = string.letters + string.digits + "-@&+=_., "
+_safe_trans = None
+
+def make_safe(s):
+ global _safe_trans
+ if _safe_trans is None:
+ _safe_trans = ["_"] * 256
+ for c in SAFE_CHARS:
+ _safe_trans[ord(c)] = c
+ _safe_trans = "".join(_safe_trans)
+
+ return s.translate(_safe_trans)
+
+
+class Upload:
+ """
+ Represents a single uploaded file. Uploaded files live in the
+ filesystem, *not* in memory -- this is not a file-like object! It's
+ just a place to store a couple of filenames. Specifically, feel
+ free to access the following instance attributes:
+
+ orig_filename
+ the complete filename supplied by the user-agent in the
+ request that uploaded this file. Depending on the browser,
+ this might have the complete path of the original file
+ on the client system, in the client system's syntax -- eg.
+ "C:\foo\bar\upload_this" or "/foo/bar/upload_this" or
+ "foo:bar:upload_this".
+ base_filename
+ the base component of orig_filename, shorn of MS-DOS,
+ Mac OS, and Unix path components and with "unsafe"
+ characters neutralized (see make_safe())
+ tmp_filename
+ where you'll actually find the file on the current system
+ content_type
+ the content type provided by the user-agent in the request
+ that uploaded this file.
+ """
+
+ def __init__(self, orig_filename, content_type=None):
+ if orig_filename:
+ self.orig_filename = orig_filename
+ bspos = orig_filename.rfind("\\")
+ cpos = orig_filename.rfind(":")
+ spos = orig_filename.rfind("/")
+ if bspos != -1: # eg. "\foo\bar" or "D:\ding\dong"
+ filename = orig_filename[bspos+1:]
+ elif cpos != -1: # eg. "C:foo" or ":ding:dong:foo"
+ filename = orig_filename[cpos+1:]
+ elif spos != -1: # eg. "foo/bar/baz" or "/tmp/blah"
+ filename = orig_filename[spos+1:]
+ else:
+ filename = orig_filename
+
+ self.base_filename = make_safe(filename)
+ else:
+ self.orig_filename = None
+ self.base_filename = None
+
+ self.content_type = content_type
+ self.tmp_filename = None
+
+ def __str__(self):
+ return str(self.orig_filename)
+
+ def __repr__(self):
+ return "<%s at %x: %s>" % (self.__class__.__name__, id(self), self)
+
+ def _open(self, dir):
+ """
+ Generate a unique filename in 'dir'. Open and return a
+ writeable file object from it.
+ """
+ flags = os.O_WRONLY|os.O_CREAT|os.O_EXCL
+ try:
+ flags |= os.O_BINARY # for Windows
+ except AttributeError:
+ pass
+ tstamp = strftime("%Y%m%d.%H%M%S", localtime(time()))
+ counter = 0
+ while 1:
+ filename = "upload.%s.%s" % (tstamp, counter)
+ filename = os.path.join(dir, filename)
+ try:
+ fd = os.open(filename, flags)
+ except OSError, err:
+ if err.errno == errno.EEXIST:
+ # Filename collision -- try again
+ counter += 1
+ else:
+ # Bomb on any other error.
+ raise
+ else:
+ # Opened the file just fine; it now exists so no other
+ # process or thread will be able to grab that filename.
+ break
+
+ # Wrap a file object around the file descriptor.
+ return (os.fdopen(fd, "wb"), filename)
+
+ def receive(self, file, boundary, dir):
+ (ofile, filename) = self._open(dir)
+ done = read_mime_part(file, boundary, ofile=ofile)
+ ofile.close()
+ self.tmp_filename = filename
+ return done
+
+ def get_size(self):
+ """get_size() : int
+ Return the size of the file, measured in bytes, or None if
+ the file doesn't exist.
+ """
+ stats = os.stat(self.tmp_filename)
+ return stats.st_size
+
+class CountingFile:
+ """A file-like object that records the number of bytes read
+ from the underlying file. Ignores seek(), because it's only
+ used by HTTPUploadRequest on an unseekable file (stdin).
+ """
+
+ def __init__(self, file):
+ self.__file = file
+ self.__bytesread = 0
+
+ def read(self, nbytes):
+ data = self.__file.read(nbytes)
+ self.__bytesread += len(data)
+ return data
+
+ def readline(self):
+ line = self.__file.readline()
+ self.__bytesread += len(line)
+ return line
+
+ def get_bytesread(self):
+ return self.__bytesread
+
+
+class HTTPUploadRequest(HTTPRequest):
+ """
+ Represents a single HTTP request with Content-Type
+ "multipart/form-data", which is used for HTTP uploads. (It's
+ actually possible for any HTML form to specify an encoding type of
+ "multipart/form-data", even if there are no file uploads in that
+ form. In that case, you'll still get an HTTPUploadRequest object --
+ but since this is a subclass of HTTPRequest, that shouldn't cause
+ you any problems.)
+
+ When processing the upload request, any uploaded files are stored
+ under a temporary filename in the directory specified by the
+ 'upload_dir' instance attribute (which is normally set, by
+ Publisher, from the UPLOAD_DIR configuration variable).
+ HTTPUploadRequest then creates an Upload object which contains the
+ various filenames for this upload.
+
+ Other form variables are stored as usual in the 'form' dictionary,
+ to be fetched later with get_form_var(). Uploaded files can also be
+ accessed via get_form_var(), which returns the Upload object created
+ at upload-time, rather than a string.
+
+ Eg. if your upload form contains this:
+ <input type="file" name="upload">
+
+ then, when processing the form, you might do this:
+ upload = request.get_form_var("upload")
+
+ after which you could open the uploaded file immediately:
+ file = open(upload.tmp_filename)
+
+ or move it to a more permanent home before doing anything with it:
+ permanent_name = os.path.join(permanent_upload_dir,
+ upload.base_filename)
+ os.rename(upload.tmp_filename, permanent_name)
+ """
+
+ def __init__(self, stdin, environ, content_type=None):
+ HTTPRequest.__init__(self, stdin, environ, content_type)
+
+ self.upload_dir = None
+ self.upload_dir_mode = 0775
+
+ def set_upload_dir(self, dir, mode=None):
+ self.upload_dir = dir
+ if mode is not None:
+ self.upload_dir_mode = mode
+
+ def parse_content_type(self):
+ full_ctype = self.get_header('Content-Type')
+ if full_ctype is None:
+ raise RequestError("no Content-Type header")
+
+ (ctype, ctype_params) = parse_header(full_ctype)
+ boundary = ctype_params.get('boundary')
+
+ if not (ctype == "multipart/form-data" and boundary):
+ raise RequestError("expected Content-Type: multipart/form-data "
+ "with a 'boundary' parameter: got %r"
+ % full_ctype)
+
+ return (ctype, boundary)
+
+ def parse_content_disposition(self, full_cdisp):
+ (cdisp, cdisp_params) = parse_header(full_cdisp)
+ name = cdisp_params.get("name")
+
+ if not (cdisp == "form-data" and name):
+ raise RequestError("expected Content-Disposition: form-data "
+ "with a 'name' parameter: got %r" % full_cdisp)
+
+ return (name, cdisp_params.get("filename"))
+
+ def check_upload_dir(self):
+ if not os.path.isdir(self.upload_dir):
+ print "creating %s with mode %o" % (self.upload_dir,
+ self.upload_dir_mode)
+ os.mkdir(self.upload_dir, self.upload_dir_mode)
+
+ def handle_upload(self, name, filename, file, boundary, content_type):
+ if self.upload_dir is None:
+ raise ConfigError("upload_dir not set")
+ upload = Upload(filename, content_type)
+ self.check_upload_dir()
+ done = upload.receive(file, boundary, self.upload_dir)
+ self.add_form_value(name, upload)
+ return done
+
+ def handle_regular_var(self, name, file, boundary):
+ lines = []
+ done = read_mime_part(file, boundary, lines=lines)
+ if len(lines) == 1:
+ value = lines[0]
+ else:
+ value = "\n".join(lines)
+ self.add_form_value(name, value)
+ #form_vars.append((name, value))
+ return done
+
+ def parse_body(self, file, boundary):
+ total_bytes = 0 # total bytes read from 'file'
+ done = 0
+ while not done:
+ headers = Message(file)
+ cdisp = headers.get('content-disposition')
+ if not cdisp:
+ raise RequestError("expected Content-Disposition header "
+ "in body sub-part")
+ (name, filename) = self.parse_content_disposition(cdisp)
+ if filename:
+ content_type = headers.get('content-type')
+ done = self.handle_upload(name, filename, file,
+ boundary, content_type)
+ else:
+ done = self.handle_regular_var(name, file, boundary)
+
+ def check_length_read(self, file):
+ # Parse Content-Length header.
+ # XXX if we want to worry about disk free space, this should
+ # be done *before* parsing the body!
+ clen = self.get_header("Content-Length")
+ if clen is not None:
+ clen = int(clen)
+
+ total_bytes = file.get_bytesread()
+ if total_bytes != clen:
+ raise RequestError(
+ "upload request length mismatch: expected %d bytes, got %d"
+ % (clen, total_bytes))
+
+ def process_inputs(self):
+ self.start_time = time()
+
+ # Parse Content-Type header -- mainly to get the 'boundary'
+ # parameter. Barf if not there or unexpected type.
+ (ctype, boundary) = self.parse_content_type()
+
+ # The meat of the body starts after the first occurrence of
+ # the boundary, so read up to that point.
+ file = CountingFile(self.stdin)
+ read_mime_part(file, boundary)
+
+ # Parse the parts of the message, ie. the form variables. Some of
+ # these will presumably be "file upload" variables, so need to be
+ # treated specially.
+ self.parse_body(file, boundary)
+
+ # Ensure that we read exactly as many bytes as were promised
+ # by the Content-Length header.
+ self.check_length_read(file)
Added: packages/quixote1/branches/upstream/current/util.py
URL: http://svn.debian.org/wsvn/python-modules/packages/quixote1/branches/upstream/current/util.py?rev=532&op=file
==============================================================================
--- packages/quixote1/branches/upstream/current/util.py (added)
+++ packages/quixote1/branches/upstream/current/util.py Mon May 8 19:16:16 2006
@@ -1,0 +1,326 @@
+"""quixote.util
+$HeadURL: svn+ssh://svn/repos/trunk/quixote/util.py $
+$Id: util.py 25257 2004-10-05 16:28:23Z nascheme $
+
+Contains various useful functions and classes:
+
+ xmlrpc(request, func) : Processes the body of an XML-RPC request, and calls
+ 'func' with the method name and parameters.
+ StaticFile : Wraps a file from a filesystem as a
+ Quixote resource.
+ StaticDirectory : Wraps a directory containing static files as
+ a Quixote namespace.
+
+StaticFile and StaticDirectory were contributed by Hamish Lawson.
+See doc/static-files.txt for examples of their use.
+"""
+
+import sys
+import os
+import time
+import binascii
+import mimetypes
+import urllib
+import xmlrpclib
+from cStringIO import StringIO
+from rfc822 import formatdate
+from quixote import errors, html
+from quixote.http_response import Stream
+
+if hasattr(os, 'urandom'):
+ # available in Python 2.4 and also works on win32
+ def randbytes(bytes):
+ """Return bits of random data as a hex string."""
+ return binascii.hexlify(os.urandom(bytes))
+
+elif os.path.exists('/dev/urandom'):
+ # /dev/urandom is just as good as /dev/random for cookies (assuming
+ # SHA-1 is secure) and it never blocks.
+ def randbytes(bytes):
+ """Return bits of random data as a hex string."""
+ return binascii.hexlify(open("/dev/urandom").read(bytes))
+
+else:
+ # this is much less secure than the above function
+ import sha
+ class _PRNG:
+ def __init__(self):
+ self.state = sha.new(str(time.time() + time.clock()))
+ self.count = 0
+
+ def _get_bytes(self):
+ self.state.update('%s %d' % (time.time() + time.clock(),
+ self.count))
+ self.count += 1
+ return self.state.hexdigest()
+
+ def randbytes(self, bytes):
+ """Return bits of random data as a hex string."""
+ s = ""
+ chars = 2*bytes
+ while len(s) < chars:
+ s += self._get_bytes()
+ return s[:chars]
+
+ randbytes = _PRNG().randbytes
+
+
+def xmlrpc(request, func):
+ """xmlrpc(request:Request, func:callable) : string
+
+ Processes the body of an XML-RPC request, and calls 'func' with
+ two arguments, a string containing the method name and a tuple of
+ parameters.
+ """
+
+ # Get contents of POST body
+ if request.get_method() != 'POST':
+ request.response.set_status(405, "Only the POST method is accepted")
+ return "XML-RPC handlers only accept the POST method."
+
+ length = int(request.environ['CONTENT_LENGTH'])
+ data = request.stdin.read(length)
+
+ # Parse arguments
+ params, method = xmlrpclib.loads(data)
+
+ try:
+ result = func(method, params)
+ except xmlrpclib.Fault, exc:
+ result = exc
+ except:
+ # report exception back to client
+ result = xmlrpclib.dumps(
+ xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
+ )
+ else:
+ result = (result,)
+ result = xmlrpclib.dumps(result, methodresponse=1)
+
+ request.response.set_content_type('text/xml')
+ return result
+
+
+class FileStream(Stream):
+
+ CHUNK_SIZE = 20000
+
+ def __init__(self, fp, size=None):
+ self.fp = fp
+ self.length = size
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ chunk = self.fp.read(self.CHUNK_SIZE)
+ if not chunk:
+ raise StopIteration
+ return chunk
+
+
+class StaticFile:
+
+ """
+ Wrapper for a static file on the filesystem.
+ """
+
+ def __init__(self, path, follow_symlinks=0,
+ mime_type=None, encoding=None, cache_time=None):
+ """StaticFile(path:string, follow_symlinks:bool)
+
+ Initialize instance with the absolute path to the file. If
+ 'follow_symlinks' is true, symbolic links will be followed.
+ 'mime_type' specifies the MIME type, and 'encoding' the
+ encoding; if omitted, the MIME type will be guessed,
+ defaulting to text/plain.
+
+ Optional cache_time parameter indicates the number of
+ seconds a response is considered to be valid, and will
+ be used to set the Expires header in the response when
+ quixote gets to that part. If the value is None then
+ the Expires header will not be set.
+ """
+
+ # Check that the supplied path is absolute and (if a symbolic link) may
+ # be followed
+ self.path = path
+ if not os.path.isabs(path):
+ raise ValueError, "Path %r is not absolute" % path
+ if os.path.islink(path) and not follow_symlinks:
+ raise errors.TraversalError(private_msg="Path %r is a symlink"
+ % path)
+
+ # Decide the Content-Type of the file
+ guess_mime, guess_enc = mimetypes.guess_type(os.path.basename(path),
+ strict=0)
+ self.mime_type = mime_type or guess_mime or 'text/plain'
+ self.encoding = encoding or guess_enc or None
+ self.cache_time = cache_time
+
+ def __call__(self, request):
+ stat = os.stat(self.path)
+ last_modified = formatdate(stat.st_mtime)
+ if last_modified == request.get_header('If-Modified-Since'):
+ # handle exact match of If-Modified-Since header
+ request.response.set_status(304)
+ return ''
+
+ # Set the Content-Type for the response and return the file's contents.
+ request.response.set_content_type(self.mime_type)
+ if self.encoding:
+ request.response.set_header("Content-Encoding", self.encoding)
+
+ request.response.set_header('Last-Modified', last_modified)
+
+ if self.cache_time is None:
+ request.response.cache = None # don't set the Expires header
+ else:
+ # explicitly allow client to cache page by setting the Expires
+ # header, this is even more efficient than the using
+ # Last-Modified/If-Modified-Since since the browser does not need
+ # to contact the server
+ request.response.cache = self.cache_time
+
+ return FileStream(open(self.path, 'rb'), stat.st_size)
+
+
+class StaticDirectory:
+
+ """
+ Wrap a filesystem directory containing static files as a Quixote namespace.
+ """
+
+ _q_exports = []
+
+ FILE_CLASS = StaticFile
+
+ def __init__(self, path, use_cache=0, list_directory=0, follow_symlinks=0,
+ cache_time=None, file_class=None, index_filenames=None):
+ """StaticDirectory(path:string, use_cache:bool, list_directory:bool,
+ follow_symlinks:bool, cache_time:int,
+ file_class=None, index_filenames:[string])
+
+ Initialize instance with the absolute path to the file.
+ If 'use_cache' is true, StaticFile instances will be cached in memory.
+ If 'list_directory' is true, users can request a directory listing.
+ If 'follow_symlinks' is true, symbolic links will be followed.
+
+ Optional parameter cache_time allows setting of Expires header in
+ response object (see note for StaticFile for more detail).
+
+ Optional parameter 'index_filenames' specifies a list of
+ filenames to be used as index files in the directory. First
+ file found searching left to right is returned.
+ """
+
+ # Check that the supplied path is absolute
+ self.path = path
+ if not os.path.isabs(path):
+ raise ValueError, "Path %r is not absolute" % path
+
+ self.use_cache = use_cache
+ self.cache = {}
+ self.list_directory = list_directory
+ self.follow_symlinks = follow_symlinks
+ self.cache_time = cache_time
+ if file_class is not None:
+ self.file_class = file_class
+ else:
+ self.file_class = self.FILE_CLASS
+ self.index_filenames = index_filenames
+
+ def _q_index(self, request):
+ """
+ If directory listings are allowed, generate a simple HTML
+ listing of the directory's contents with each item hyperlinked;
+ if the item is a subdirectory, place a '/' after it. If not allowed,
+ return a page to that effect.
+ """
+ if self.index_filenames:
+ for name in self.index_filenames:
+ try:
+ obj = self._q_lookup(request, name)
+ except errors.TraversalError:
+ continue
+ if not isinstance(obj, StaticDirectory) and callable(obj):
+ return obj(request)
+ # FIXME: this is not a valid HTML document!
+ out = StringIO()
+ if self.list_directory:
+ template = html.htmltext('<a href="%s">%s</a>%s')
+ print >>out, (html.htmltext("<h1>%s</h1>")
+ % request.environ['REQUEST_URI'])
+ print >>out, "<pre>"
+ print >>out, template % ('..', '..', '')
+ files = os.listdir(self.path)
+ files.sort()
+ for filename in files:
+ filepath = os.path.join(self.path, filename)
+ marker = os.path.isdir(filepath) and "/" or ""
+ print >>out, \
+ template % (urllib.quote(filename), filename, marker)
+ print >>out, "</pre>"
+ else:
+ print >>out, "<h1>Directory listing denied</h1>"
+ print >>out, \
+ "<p>This directory does not allow its contents to be listed.</p>"
+ return out.getvalue()
+
+ def _q_lookup(self, request, name):
+ """
+ Get a file from the filesystem directory and return the StaticFile
+ or StaticDirectory wrapper of it; use caching if that is in use.
+ """
+ if name in ('.', '..'):
+ raise errors.TraversalError(private_msg="Attempt to use '.', '..'")
+ if self.cache.has_key(name):
+ # Get item from cache
+ item = self.cache[name]
+ else:
+ # Get item from filesystem; cache it if caching is in use.
+ item_filepath = os.path.join(self.path, name)
+ while os.path.islink(item_filepath):
+ if not self.follow_symlinks:
+ raise errors.TraversalError
+ else:
+ dest = os.readlink(item_filepath)
+ item_filepath = os.path.join(self.path, dest)
+
+ if os.path.isdir(item_filepath):
+ # avoid passing post 1.0 keyword arguments to subclasses that
+ # may not support them
+ kwargs = {}
+ if self.index_filenames is not None:
+ kwargs['index_filenames'] = self.index_filenames
+ item = self.__class__(item_filepath, self.use_cache,
+ self.list_directory,
+ self.follow_symlinks, self.cache_time,
+ self.file_class, **kwargs)
+ elif os.path.isfile(item_filepath):
+ item = self.file_class(item_filepath, self.follow_symlinks,
+ cache_time=self.cache_time)
+ else:
+ raise errors.TraversalError
+ if self.use_cache:
+ self.cache[name] = item
+ return item
+
+
+class Redirector:
+ """
+ A simple class that can be used from inside _q_lookup() to redirect
+ requests.
+ """
+
+ _q_exports = []
+
+ def __init__(self, location, permanent=0):
+ self.location = location
+ self.permanent = permanent
+
+ def _q_lookup(self, request, component):
+ return self
+
+ def __call__(self, request):
+ return request.redirect(self.location, self.permanent)
More information about the Python-modules-commits
mailing list