Quixote-2.7b2/ 0000775 0001750 0001750 00000000000 11326377244 011403 5 ustar nas nas Quixote-2.7b2/doc/ 0000775 0001750 0001750 00000000000 11326377244 012150 5 ustar nas nas Quixote-2.7b2/doc/CHANGES_24.txt 0000664 0001750 0001750 00000123654 11103004231 014251 0 ustar nas nas 2.4 (2005-12-14) r27719: * Fix cimport bug (caused None to be bound to an imported name that did not exist). * Add Publisher.process(), which is like process_request() except that the caller provides an input stream and an environment instead of an HTTPRequest instance. Modify the included drivers to use process() instead of process_request(). * Update widgets.txt doc file (from Titus Brown). 2.3 (2005-10-18) r27556: * Remove some typechecking asserts from sendmail module to allow for unicode. * Make the generated http response header for content length be a str instead of an int. 2.2 (2005-9-27) r27459: * Remove check_session_addr from Config.config_var_list. * Remove Config.dump(). * When the PATH_INFO does not start with a '/' (or is empty), return a redirect instead of raising AssertionError. * Change htmltextObject_Check() to allow subtypes. * Improve unicode/charset handling. Add quixote.DEFAULT_CHARSET and use this as needed to encode log output, email bodies, and response bodies. When the response body is a str, let it pass through without re-encoding. When the response charset is None, omit the charset parameter from the content-type header. Use the default charset for decoding multipart requests. * Replace relative import in ptl_import.py. * Introduce a unicode-wrapper helper class to be used by htmltext formatting. It's purpose is to work around a PyString_Format bug that exists in all current versions of Python. Thanks go to Alexander J. Kozlovsky for the idea and for various other minor fixes. 2.1 (2005-8-9) r27151: * Make htmltag work better with unicode attribute values. * Modify publisher's exception handler to let SystemExit through. * Modify simple server so that SystemExit exceptions that make it to the server's error handler really are terminal. * Make encoding declarations work in ptl files. * Add HEAD request support to the simple server. * Modify simple server to avoid duplicate Date headers. * Add include_body keyword to response write(). 2.0 (2005-4-12) r26537: * Remove SessionError: just make a new session when the cookie does not identify an existing one. Remove CHECK_SESSION_ADDR configuration variable. * Gather ptl-related code into a sub-package: quixote.ptl. Add quixote.ptl.install: importing this module installs the ptl import hooks. Add quixote.ptl.ptlrun.py, a script for running ptl files from the command line. Gather the quixote.html and support files for htmltext into a sub-package. These changes should be compatible with current code, except that quixote.ptl_import is now in quixote.ptl.ptl_import, quixote.qx_distutils is now in quixote.ptl.qx_distutils, quixote._py_htmltext is now quixote.html._py_htmltext, and quixote._c_htmltext is now quixote.html._c_htmltext. * Insert REMOTE_USER environment value. * Change name: html_url() -> url_with_query() * Have StaticFile set the Expires header for 304 responses. * Add --https option to simple server to support https through stunnel. * Allow the label for the 'Add row' button to be specified for WidgetDict. * Add Session.get_user(). * Add Daniele Varrazzo's patch to _c_htmltext.c that makes it work on win32 with the mingw compiler. * Use private_msg when Directory raises TraversalError. * Allow setting of request's DEFAULT_CHARSET. 2.0a5 (2005-3-7) r26297: * SessionPublisher merged into Publisher. * Publisher 'session_mgr' keyword changed to 'session_manager'. * Form 'action_url' keyword and attribute changed to 'action.' * Make sure that HTTPResponse charset attribute is a str. * Don't require the traversed-into-object to be a Directory. * Make redirect() call str() on the location, so that calls from ptl templates don't need to. * Add an html_url() function that assembles a quoted url with a query. * Don't remove double-slashes from the path. * Make it easier to customize form error messages. * Make Form and Widget new-style classes so that properties can be used on them. * Call maintain_session on "interrupted" requests, too. * Add class variables with default values for content_type and charset. * Add HTTPRequest.get_fields(). * Add HTTPRequest.get_cookies(). * Remove name keyword from Form constructor. 2.0a4 (2005-1-19) r25893: * Remove support for $-substition. If you changed existing templates to work with 2.0a2 or 2.0a3 by escaping $'s, then you should change them back. * Change simple_server so that the PATH_INFO value is unquoted. * Add HTTPResponse.get_content_type(). * Update mod_python_handler.py to Quixote 2. 2.0a3 (2004-12-09) r25738: * Update most of the doc files. Add q1 -> q2 upgrade notes. Put notes at the start of the remaining doc files so that the reader knows that they have not been updated. * Make the simple_server provide remaining HTTP_* headers. * Generate a valid HTML document when listing static directories. Also, use get_path() instead of REQUEST_URI. * Work file upload into the basic forms demo. * Remove old session demo. The altdemo.py serves this purpose. * Add mini_demo. * Use the Twisted addCookie() method. It allows multiple Set-Cookie headers to end up in the response. * Fix a string formatting but that affected the htmltext type. 2.0a2 (2004-11-23) r25669: * Refactor the Publisher object. The new design gives "namespaces" more control over traversal. Packages are no longer imported automatically. By default, namespaces must be 'Directory' instances. The _q_access hook is gone. The 'namespace_stack' attribute is also gone. * Provide a Publisher object that works more like Quixote 1 (available in the 'publish1' module). Also, restore the original form framework with the name 'form1'. * Stop passing HTTPRequest to exported functions and _q_lookup(). You will need to use get_request() to access it. Also, require that _q_index be explicitly exported. * Remove the HTTPRequest.redirect() method. * Split error log and access log functionality into a separate class. Try to simplify site configuration. Configuration options can now be passed as keyword args to the Publisher class. The setup_logs() and shutdown_logs() methods are gone. Logs are now opened when the publisher is initialized. * Support $-substitution in templates, as in Python 2.4's string.Template class. $-substitution is applied to every '$'-containing literal in a template. Templates written before this change, if they contain literals containing '$', *must* be converted by replacing each '$' with '$$'. * Remove support for old style templates and support for versions of Python older than 2.3. * Use a common pattern for interfacing with HTTP servers. Simplify and cleanup the Medusa and Twisted code. Add a SCGI server module (it makes more sense to be part of Quixote rather than in the "scgi" package). * Rename HTTPRequest.get_form_var() to get_field(). That matches the terminology used by RFCs. * Add HTTPResponse.set_expires() method. * Add HTTPRequest.get_query() method. Use it instead of the QUERY_STRING environment variable. Add HTTPRequest.get_status_code(). * Compile .ptl files to .pyc files. Also, remove 'htmltext' and 'TemplateIO' from the globals of .ptl modules. * Move page compression functionality to http_response module. * Add a 'content_type' attribute to HTTPResponse. Refactor response to make it harder to screw up the character set of the response. * Add quixote.util.get_directory_path() for obtaining the list of _q_traversed() Directories (essentially a replacement for 'namespace_stack') * Move missing trailing slash detection and action to Directory.__call__() and remove FIX_TRAILING_SLASH configuration option. * Remove RUN_ONCE configuration variable and 'exit_now' attribute of publisher. * Stop generating 'Status' header for servers that don't need it. Add 'include_status' keyword to HTTPResponse.write(). * Overhaul the demo. 2.0a1 (2004-10-14) r25357: * Remove form1 library and rename form2 to form. * Remove HTTPRequest.dump_html() method and add dump_request to 'util' module. Remove obsolete functions from 'html' module (html_quote(), value_quote(), link() and render_tag()). * Use bool objects where appropriate. Prefer using builtin types over using the 'types' module. * Add support for unicode. The HTTPResponse object now has a set_charset() method. Also, the htmltext and TemplateIO types can contain unicode strings. * Add support to HTTPRequest for multipart/form-data type requests. Enhance MIME parsing so that it doesn't read huge lines into memory. Also implement parsing of application/x-www-form-urlencoded data rather than using the standard cgi.py module. Remove UPLOAD_DIR and UPLOAD_DIR_MODE config options. Uploaded files are now created using the tempfile module. * Remove the DEBUG_LOG configuration option. Debugging output now always goes to the error log. * Rewrite http_request.parse_cookie() and rename it to parse_cookies(). When generating cookies, escape quote characters that appear in cookie values. 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): "
Quixote requires Python 2.3 or later.
If you have a previously installed quixote, we strongly recommend that you remove it 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.)
Now install the new version by running (in the distribution directory),
python setup.py install
and you're done.
In a terminal window, run server/simple_server.py. In a browser, open http://localhost:8080
See upgrading.txt for details.
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.
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
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.
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 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 .pyc. 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. A similar problem has been reported for BioPython and win32com.client imports.)
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.
Quixote comes with some demonstration applications in the demo directory. After quixote is installed (see INSTALL.txt for instructions), you can run the demos using the scripts located in the server directory.
Each server script is written for a specific method of connecting a quixote publisher to a web server, and you will ultimately want to choose the one that matches your needs. More information about the different server scripts may be found in the scripts themselves and in web-server.txt. To start, though, the easiest way to view the demos is as follows: in a terminal window, run server/simple_server.py, and in a browser, open http://localhost:8080.
The simple_server.py script prints a usage message if you run it with a '--help' command line argument. You can run different demos by using the '--factory' option to identify a callable that creates the publisher you want to use. In particular, you might try these demos:
simple_server.py --factory quixote.demo.mini_demo.create_publisher
or
simple_server.py --factory quixote.demo.altdemo.create_publisher
In a browser, load http://localhost:8080. In your browser, you should see "Welcome ..." page. In your terminal window, you will see a "localhost - - ..." line for each request. These are access log messages from the web server.
Look at the source code in demo/mini_demo.py. Near the bottom you will find the create_publisher() function. The create_publisher() function creates a Publisher instance whose root directory is an instance of the RootDirectory class defined just above. When a request arrives, the Publisher calls the _q_traverse() method on the root directory. In this case, the RootDirectory is using the standard _q_traverse() implementation, inherited from Directory.
Look, preferably in another window, at the source code for _q_traverse() in directory.py. The path argument provided to _q_traverse() is a list of string components of the path part of the URL, obtained by splitting the request location at each '/' and dropping the first element (which is always '') For example, if the path part of the URL is '/', the path argument to _q_traverse() is ['']. If the path part of the URL is '/a', the path argument to _q_traverse() is ['a']. If the path part of the URL is '/a/', the path argument to _q_traverse() is ['a', ''].
Looking at the code of _q_traverse(), observe that it starts by splitting off the first component of the path and calling _q_translate() to see if there is a designated attribute name corresponding to this component. For the '/' page, the component is '', and _q_translate() returns the attribute name '_q_index'. The _q_traverse() function goes on to lookup the _q_index method and return the result of calling it.
Looking back at mini_demo.py, you can see that the RootDirectory class includes a _q_index() method, and this method does return the HTML for http://localhost:8080/
As mentioned above, the _q_translate() identifies a "designated" attribute name for a given component. The default implementation uses self._q_exports to define this designation. In particular, if the component is in self._q_exports, then it is returned as the attribute name, except in the special case of '', which is translated to the special attribute name '_q_index'.
When you click on the link on the top page, you get http://localhost:8080/hello. In this case, the path argument to the _q_traverse() call is ['hello'], and the return value is the result of calling the hello() method.
Feeling bold? (Just kidding, this won't hurt at all.) Try opening http://localhost:8080/bogus. This is what happens when _q_traverse() raises a TraversalError. A TraversalError is no big deal, but how does quixote handle more exceptional exceptions? To see, you can introduce one by editing mini_demo.py. Try inserting the line "raise 'ouch'" into the hello() method. Kill the demo server (Control-c) and start a new one with the same command as before. Now load the http://localhost:8080/hello page. You should see a plain text python traceback followed by some information extracted from the HTTP request. This information is always printed to the error log on an exception. Here, it is also displayed in the browser because the create_publisher() function made a publisher using the 'plain' value for the display_exceptions keyword argument. If you omit that keyword argument from the Publisher constructor, the browser will get an "Internal Server Error" message instead of the full traceback. If you provide the value 'html', the browser displays a prettier version of the traceback.
One more thing to try here. Replace your 'raise "ouch"' line in the hello() method with 'print "ouch"'. If you restart the server and load the /hello page, you will see that print statements go the the error log (in this case, your terminal window). This can be useful.
In a browser, open http://localhost:8080 as before. Click around at will.
This is the default demo, but it is more complicated than the mini_demo described above. The create_publisher() function in quixote.demo.__init__.py creates a publisher whose root directory is an instance of quixote.demo.root.RootDirectory. Note that the source code is a file named "root.ptl". The suffix of "ptl" indicates that it is a PTL file, and the import must follow a call to quixote.enable_ptl() or else the source file will not be found or compiled. The quixote.demo.__init__.py file takes care of that.
Take a look at the source code in root.ptl. You will see code that looks like regular python, except that some function definitions have "[html]" between the function name and the parameter list. These functions are ptl templates. For details about PTL, see the PTL.txt file.
This RootDirectory class is similar to the one in mini_demo.py, in that it has a _q_index() method and '' appears in the _q_exports list. One new feature here is the presence of a tuple in the _q_exports list. Most of the time, the elements of the _q_exports lists are just strings that name attributes that should be available as URL components. This pattern does not work, however, when the particular URL component you want to use includes characters (like '.') that can't appear in Python attribute names. To work around these cases, the _q_exports list may contain tuples such as ("favicon.ico", "favicon_ico") to designate "favicon_ico" as the attribute name corresponding the the "favicon.ico" URL component.
Looking at the RootDirectoryMethods, including plain(), css() and favon_ico(), you will see examples where, in addition to returning a string containing the body of the HTTP response, the function also makes side-effect modifications to the response object itself, to set the content type and the expiration time for the response. Most of the time, these direct modifications to the response are not needed. When they are, though, the get_response() function gives you direct access to the response instance.
The RootDirectory here also sets an 'extras' attribute to be an instance of ExtraDirectory, imported from the quixote.demo.extras module. Note that 'extras' also appears in the _q_exports list. This is the ordinary way to extend your URL space through another '/'. For example, the URL path '/extras/' will result in a call to the ExtraDirectory instance's _q_index() method.
Now take a look at the ExtraDirectory class in extras.ptl. This class exhibits some more advanced publishing features. If you look back at the default _q_traverse() implementation (in directory.py), you will see that the _q_traverse does not give up if _q_translate() returns None, indicating that the path component has no designated corresponding attribute name. In this case, _q_traverse() tries calling self._q_lookup() to see if the object of interest can be found in a different way. Note that _q_lookup() takes the component as an argument and must return either (if there is more path to traverse) a Directory instance, or else (if the component is the last in the path) a callable or a string.
In this particular case, the ExtrasDirectory._q_lookup() call returns an instance of IntegerUI (a subclass of Directory). The interest here, unlike the ExtrasDirectory() instance itself, is created on-the-fly during the traversal, especially for this particular component. Try loading http://localhost:8080/extras/12/ to see how this behaves.
Note that the correct URL to get to the IntegerUI(12)._q_index() call ends with a '/'. This can sometimes be confusing to people who expect http://localhost:8080/extras/12 to yield the same page as http://localhost:8080/extras/12/. If given the path ['extras', '12'], the default _q_traverse() ends up calling the instance of IntegerUI. The Directory.__call__() (see directory.py) determines the result: if no form values were submitted and adding a slash would produce a page, the call returns the result of calling quixote.redirect(). The redirect() call here causes the server to issue a permanent redirect response to the path with the slash added. When this automatic redirect is used, a message is printed to the error log. If the conditions for a redirect are not met, the call falls back to raising a TraversalError. [Note, if you don't like this redirect behavior, override, replace, or delete Directory.__call__]
The _q_lookup() pattern is useful when you want to allow URL components that you either don't know or don't want to list in _q_exports ahead of time.
Note that the ExtraDirectory class inherits from Resolving (in addition to Directory). The Resolving mixin modifies the _q_traverse() so that, when a component has an attribute name designated by _q_translate(), but the Directory instance does not actually have that attribute, the _q_resolve() method is called to "resolve" the trouble. Typically, the _q_resolve() imports or constructs what should be the value of the designated attribute. The modified _q_translate() sets the attribute value so that the _q_resolve() won't be called again for the same attribute. The _q_resolve() pattern is useful when you want to delay the work of constructing the values for exported attributes.
You can't get very far writing web applications without writing forms. The root demo includes, at http://localhost:8080/extras/form, a page that demonstrates basic usage of the Form class and widgets defined in the quixote.form package.
The packages names have changed in Quixote 2.
Quixote form1 forms are now in the package quixote.form1. (In Quixote 1, they were in quixote.form.)
Quixote form2 forms are now in the package quixote.form. (In Quixote 1, they were in quixote.form2.)
These are some notes and examples for converting Quixote form1 forms, that is forms derived from quixote.form1.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.
Here's the short list of things to do to convert form1 forms to form2 using compatibility.
Import the Form base class from quixote.form.compatibility rather than from quixote.form1.
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...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.
- 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.form.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.form.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.
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.form.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.
- It determines if it's to create a new object or edit an existing one
- It adds submit buttons and widgets
- It has a function that knows how to render the form
- It has a function that knows how to do error processing on the form
- 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.
- Get the value of any submit buttons using form.get_submit
- If the form has not been submitted yet, return render().
- See if the cancel button was pressed, if so return a redirect.
- 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.
- 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.
- If the form has not been submitted or if the form has errors, you simply want to render the form.
- 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.
- 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)
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.
This document explains how a Quixote application is structured. The demo.txt file should probably be read before you read this file. There are three components to a Quixote application:
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.
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; the default value is false, to favor security over convenience.
Finally, the bulk of the code will be called through a call (by the Publisher) to the _q_traverse() method of an instance designated as the root_directory. Normally, the root_directory will be an instance of the Directory class.
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:
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 directory of your application.
The quixote.servers package includes driver modules for cgi, fastcgi, scgi, medusa, twisted, and the simple_server. Each of these modules includes a run() function that you can use in a driver script that provides a function to create the publisher that you want. For an example of this pattern, see the __main__ part of demo/mini_demo.py. You could run the mini_demo.py with scgi by using the run() function imported from quixote.server.scgi_server instead of the one from quixote.server.simple_server. (You would also need your http server set up to use the scgi server.)
That's almost the simplest possible case -- there's no application-specific configuration info apart from the root directory.
Getting the driver script to actually run is between you and your web server. See the web-server.txt document for help.
By default, the Publisher uses the configuration information from quixote/config.py. 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. If you want to customize any of the configuration variables, your driver script should provide your customized Config instance as an argument to the Publisher constructor.
The publisher also accepts an optional logger keyword argument, that should, if provided, support the same methods as the default value, an instance of DefaultLogger. Even if you use the default logger, you can still customize the behavior by setting configuration values for access_log, error_log, and/or error_email. These configuration variables are described more fully in config.py.
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:
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 three purposes:
If no error log is configured (with ERROR_LOG), then all output is redirected 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.
Having stdout redirected to the error log is useful for debugging. You can just sprinkle print statements into your application and the output will wind up in the error log.
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. You may, optionally, wish to use PTL, which is simply Python with a novel way of generating function return values -- see PTL.txt for details.
Quixote's Publisher constructs a request, splits the path into a list of components, and calls the root directory's _q_traverse() method, giving the component list as an argument. The _q_traverse() will either return a value that will become the content of the HTTPResponse, or else it may raise an Exception. Exceptions are caught by the Publisher and handled as needed, depending on configuration variables and whether or not the Exception is an instance of PublisherError.
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, the session management mechanism that comes with Quixote is cookie-based. (The most common alternative is to embed the session identifier in the URL. Since Quixote views the URL as a fundamental part of the web user interface, a URL-based session management scheme is considered 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.
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. The get_session() function provides uniform access to the current 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 made available, through the get_session() function, as the application code processes the request.
There are two caveats to keep in mind before proceeding, one major and one minor:
There's a simple demo of Quixote's session management in demo/altdemo.py. If the durus (http://www.mems-exchange.org/software/durus/) package is installed, the demo uses a durus database to store sessions, so sessions will be preserved, even if your are running it with plain cgi.
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 altdemo.py:
def __init__ (self, id): Session.__init__(self, id) self.num_requests = 0 def start_request (self): Session.start_request(self) 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. If you want the value of the user attribute of the current session, just call get_user(). If you want some other attribute or method Use get_session() to get the current Session if you need access to other attributes (such as num_requests in the demo) or methods of the current Session instance.
Note that the Session class initializes the user attribute to None, so get_user() will return None if no user has been identified for this session. Application code can use this to change behavior, as in the following:
if not get_user(): content += htmltext('<p>%s</p>' % href('login', 'login')) else: content += htmltext( '<p>Hello, %s.</p>') % get_user() content += htmltext('<p>%s</p>' % href('logout', 'logout'))
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/altdemo.py)
if get_field("name"): session = get_session() session.set_user(get_field("name")) # This is the important part.
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.
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.
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:
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 defense 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).
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.
The second hook (two hooks, really) apply if you use a transactional persistence mechanism to provide your SessionManager's mapping. The altdemo.py script does this with Durus, if the durus package is installed, but you could also use ZODB or a relational database for this purpose. The hooks make sure that session (and other) changes get committed or aborted at the appropriate times. SessionManager provides two methods for you to override: forget_changes() and commit_changes(). forget_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. You'll have to use your own SessionManager subclass if you need to take advantage of these hooks for transactional session persistence.
The third available hook is the Session's is_dirty() method. This is used when your mapping class uses a more primitive storage mechanism, as, for example, 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_manager = SessionManager(session_mapping=sessions)
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, 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.
%s
' % href('login', 'login')) else: content += htmltext( 'Hello, %s.
') % get_user() content += htmltext('%s
' % href('logout', 'logout')) 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/altdemo.py``) :: if get_field("name"): session = get_session() session.set_user(get_field("name")) # This is the important part. 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 defense 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. The second hook (two hooks, really) apply if you use a transactional persistence mechanism to provide your SessionManager's mapping. The ``altdemo.py`` script does this with Durus, if the durus package is installed, but you could also use ZODB or a relational database for this purpose. The hooks make sure that session (and other) changes get committed or aborted at the appropriate times. SessionManager provides two methods for you to override: ``forget_changes()`` and ``commit_changes()``. ``forget_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. You'll have to use your own SessionManager subclass if you need to take advantage of these hooks for transactional session persistence. The third available hook is the Session's is_dirty() method. This is used when your mapping class uses a more primitive storage mechanism, as, for example, 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_manager = SessionManager(session_mapping=sessions) 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, 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. Quixote-2.7b2/doc/static-files.html 0000664 0001750 0001750 00000006003 11103004231 015374 0 ustar nas nasThe quixote.util module includes classes for making files and directories available as Quixote resources. Here are some examples.
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 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")
This document lists backward-incompatible changes in Quixote, and explains how to update application code to work with the newer version.
Change any imports you have from quixote.form to be from quixote.form1.
Change any imports you have from quixote.form2 to be from quixote.form.
Replace calls to HTTPRequest.get_form_var() with calls to get_field().
Define a create_publisher() function to get the publisher you need and figure out how you want to connect it to web server. See files in demo and server for examples. Note that publish1.py contains a publisher that works more like the Quixote1 Publisher, and does not require the changes listed below.
Make every namespace be an instance of quixote.directory.Directory. Update namespaces that are modules (or in the init.py of a package) by defining a new class in the module that inherits from Directory and moving your _q_exports and _q_* functions onto the class. Replace "request" parameters with "self" parameters on the new methods. If you have a _q_resolve method, include Resolving in the bases of your new class.
Remove request from calls to _q_ functions. If request, session, user, path, or redirect is used in these new methods, replace as needed with calls to get_request(), get_session(), get_user(), get_path(), and/or redirect(), imported from quixote.
In every namespace that formerly traversed into a module, import the new Directory class from the module and create an instance of the Directory in a variable whose name is the name of the module.
In every namespace with a _q_exports and a _q_index, either add "" to _q_exports or make sure that _q_lookup handles "" by returning the result of a call to _q_index.
If your code depends on the Publisher's namespace_stack attribute, try using quixote.util.get_directory_path() instead. If you need the namespace stack after the traversal, override Directory._q_traverse() to call get_directory_path() when the end of the path is reached, and record the result somewhere for later reference.
If your code depends on _q_exception_handler, override the _q_traverse on your root namespace or on your own Directory class to catch exceptions and handle them the way you want. If you just want a general customization for exception responses, you can change or override Publisher.format_publish_error().
If your code depended on _q_access, include the AccessControlled with the bases of your Directory classes as needed.
Provide imports as needed to htmltext, TemplateIO, get_field, get_request, get_session, get_user, get_path, redirect, ?. You may find dulcinea/bin/unknown.py useful for identifying missing imports.
Quixote 1's secure_errors configuration variable is not present in Quixote 2.
Form.__init__ no longer has name or attrs keywords. If your existing code calls Form.__init__ with 'attrs=foo', you'll need to change it to '**foo'. Form instances no longer have a name attribute. If your code looks for form.name, you can find it with form.attrs.get('name'). The Form.__init__ keyword parameter (and attribute) 'action_url' is now named 'action'.
The SessionPublisher class is gone. Use the Publisher class instead. Also, the 'session_mgr' keyword has been renamed to 'session_manager'.
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.
_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.
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.
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.
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:
Consult upload.txt for more information about handling file uploads.
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().
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.
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.
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.