Quixote: a Python-Centric Web Application Framework

If you need to create dynamic web sites and don't want to learn the syntax and arbitrary limitations of yet another templating language, you should give Quixote a serious look.
Beyond _q_index()

As the table above implies, Quixote can publish the results of any Python function or PTL template, as long as you list them in _q_exports. For instance, I might want to borrow a convention from GUI programming and add an “About” page to SPLAT!. The obvious place to put this in URL-space is /bugs/about, so I need to add a callable object about() to the splat.web package. One way to do this (although not necessarily a recommended practice) is to define a Python function in splat/web/__init__.py:

import splat  # for __version__
from splat.web.util import header, footer
[...]
def about (request):
    text = '''\
        <p>This bug database is brought to you by:</p>
        <p align="center"><font size="+3">SPLAT! %s</font></p>
        <p>For more information, please visit the
        <a href="http://www.mems-exchange.org/software/splat/">
        SPLAT! web page</a>.</p>
        ''' % splat.__version__
    return header("About") + text + footer()

This demonstrates how cleanly Python and PTL code mesh. I can import the header() and footer() templates shown above (which incidentally, actually live in splat.web.util) and call them just like a Python function.

The about() function doesn't actually work yet, though. It would be dangerous for Quixote to trust that any random Python function that happens to be named by a URL should be published on the Web. Thus, you must explicitly declare that about() is meant to be exported to the world by listing it in the _q_exports list for this namespace, which also lives in splat/web/__init__.py:

_q_exports = ['about']

You get pretty far with Quixote just writing Python functions and PTL templates, and having Quixote publish their results via the Web. However, making the URL part of the user interface means that the obvious way for SPLAT! to publish individual bugs is via URLs like /bugs/0001, /bugs/0134, etc. Quixote has a nice hook that lets you handle arbitrary URLs like this.

Object Publishing with _q_getname()

Object publishing is just a fancy term (shamelessly stolen from Zope) to mean that you can use Quixote to wrap a web interface around arbitrary objects. As always with Quixote, the trick is to map a URL onto Python code. But now, instead of providing a Python function that is named to match a URL component, you provide a Python function, _q_getname(), that Quixote uses as a fallback. For example, if Quixote is processing a request for the URL /bugs/0124, it's not going to find a function called 0124 in the splat.web package. Before giving up and raising an exception (which turns into an HTTP 404 error), Quixote looks for a function _q_getname() in that package. If it finds one, Quixote calls your _q_getname(), passing it the string “0124”--the URL component currently being examined.

Don't think of _q_getname() as being like _q_index() or about(). Quixote only calls functions like these at the end of URL traversal: e.g., when processing the URL /bugs/about, the bugs component corresponds to a Python namespace, splat.web, so Quixote doesn't have to call anything. Only when it's looking at the terminal component, about, does it call a Python function—the splat.web.about() function defined above. Likewise, in processing the URL /bugs/, Quixote only calls _q_index() because the terminal component of the URL (the part after the last slash) is the empty string.

However, _q_getname() can be called anywhere along a URL. For instance, SPLAT! actually implements per-bug URLs as namespaces (e.g., /bugs/0134/ calls a _q_index() method, /bugs/0134/edit calls an edit() method, etc.). In this case, the bug ID is not the terminal component of the URL, but Quixote handles it the same way, via _q_getname(). For this article, though, the bug ID will be the terminal URL component, and we're only going to deal with URLs like /bugs/0134. The easiest way to do this is to write a _q_getname() function. Again, assume this code lives in splat/web/__init__.py, which returns the HTML page for the requested bug:

from quixote.errors import TraversalError
from splat.web.util import get_bug_database
[...]
def _q_getname (request, name):
    try:
        bug_id = int(name)
    except ValueError:
        raise TraversalError("invalid bug ID: %r (not an integer)" % name)
    bug_db = get_bug_database()
    bug = bug_db.get_bug(bug_id)
    if bug is None:
        raise TraversalError("no such bug: %r" % bug_id)
    return header("Bug %s" % bug) + format_bug_page(bug) + footer()

(I'm omitting the implementation of format_bug_page().) Most of this function is concerned with taking arbitrary user input (in the form of a URL component) and either fetching a bug object from the bug database or raising the appropriate exception. (Quixote exceptions generally correspond to HTTP error codes; TraversalException becomes a 404 “not found” error. The only time applications need to raise TraversalException is inside _q_getname() functions, because all other URL interpretation is handled by Quixote internally.)

Using _q_getname() to publish a namespace for an object rather than a single page is even more fun, but beyond the scope of this article. Now that we've got a good feel for programming with Quixote, let's take a look at the bureaucracy necessary to get from your web server to Quixote application code.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

quixote wiki link

charter97's picture

welcome to quixote.ca