SVN: r25684 - trunk/quixote/doc
David Binger <dbinger-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Mon, 6 Dec 2004 19:01:00 -0500
| Newsgroups | gmane.comp.web.quixote.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: dbinger
Date: 2004-11-30 14:51:25 -0500 (Tue, 30 Nov 2004)
New Revision: 25684
Modified:
trunk/quixote/doc/demo.txt
Log:
Revise demo.txt.
Modified: trunk/quixote/doc/demo.txt
===================================================================
--- trunk/quixote/doc/demo.txt 2004-11-30 19:48:13 UTC (rev 25683)
+++ trunk/quixote/doc/demo.txt 2004-11-30 19:51:25 UTC (rev 25684)
@@ -1,640 +1,221 @@
-Running the Quixote Demo
-========================
+Running the Quixote Demos
+=========================
-Quixote comes with a tiny demonstration application that you can install
-and run on your web server. In a few dozen lines of Python and PTL
-code, it demonstrates most of Quixote's basic capabilities. It's also
-an easy way to make sure that your Python installation and web server
-configuration are cooperating so that Quixote applications can work.
+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.
-Installation
-------------
+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:
-The demo is included in the quixote.demo package, which is installed
-along with the rest of Quixote when you run ``python setup.py install``.
-The driver script (demo.cgi) and associated configuration file
-(demo.conf) are *not* installed automatically -- you'll have to copy
-them from the demo/ subdirectory to your web server's CGI directory.
-Eg., if you happen to use the same web server tree as we do::
+ simple_server.py --factory quixote.demo.mini_demo.create_publisher
- cp -p demo/demo.cgi demo/demo.conf /www/cgi-bin
+or
-You'll almost certainly need to edit the ``#!`` line of demo.cgi to ensure
-that it points to the correct Python interpreter -- it should be the
-same interpreter that you used to run ``setup.py install``.
+ simple_server.py --factory quixote.demo.altdemo.create_publisher
-Verifying the installation
---------------------------
-Before we try to access the demo via your web server, let's make sure
-that the quixote and quixote.demo packages are installed on your system::
+Understanding the mini_demo
+---------------------------
- $ python
- Python 2.1.1 (#2, Jul 30 2001, 12:04:51)
- [GCC 2.95.2 20000220 (Debian GNU/Linux)] on linux2
- Type "copyright", "credits" or "license" for more information.
- >>> import quixote
- >>> quixote.enable_ptl()
- >>> import quixote.demo
+Start the mini demo by running the command:
+ simple_server.py --factory quixote.demo.mini_demo.create_publisher
-(Quixote requires Python 2.0 or greater; you might have to name an
-explicit Python interpreter, eg. ``/usr/local/bin/python2.1``. Make
-sure that the Python interpreter you use here is the same as you put in
-the ``#!`` line of demo.cgi, and the same that you used to install
-Quixote.)
+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.
-If this runs without errors, then Quixote (and its demo) are installed
-such that you can import them. It remains to be seen if the user that
-will run the driver script -- usually ``nobody`` -- can import them.
+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', ''].
-Running the demo directly
--------------------------
+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.
-Assuming that
+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/
-* your web server is running on the current host
-* your web server is configured to handle requests to
- /cgi-bin/demo.cgi by running the demo.cgi script that you just
- installed (eg. to /www/cgi-bin/demo.cgi)
+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'.
-then you should now be able to run the Quixote demo by directly
-referring to the demo.cgi script.
+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.
-Another option, if you have the Medusa package installed, is to run::
+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.
- python server/medusa_http.py
+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.
-This will start a small pure-Python web server running on port 8080.
-(Medusa is available from http://www.amk.ca/python/code/medusa.html .)
-Start a web browser and load ::
-
- http://localhost/cgi-bin/demo.cgi/
-
-or ::
-
- http://localhost:8080/
-
-if you're using Medusa.
-
-You should see a page titled "Quixote Demo" with the headline "Hello,
-world!". Feel free to poke around; you can't break anything through the
-demo. (That's not to say you can't break things with Quixote in
-general; since Quixote gives you the full power of Python for your web
-applications, you have the power to create stupid security holes.)
-
-If you don't get the "Quixote Demo" page, go look in your web server's
-error log. Some things that might go wrong:
-
-* your web server is not configured to run CGI scripts, or it
- might use a different base URL for them. If you're running
- Apache, look for something like ::
-
- ScriptAlias /cgi-bin/ /www/cgi-bin/
-
- in your httpd.conf (for some value of "/www/cgi-bin").
-
- (This is not a problem with Quixote or the Quixote demo; this is a
- problem with your web server's configuration.)
-
-* your web server was unable to execute the script. Make sure
- its permissions are correct::
-
- chmod 755 /www/cgi-bin/demo.cgi
-
- (This shouldn't happen if you installed demo.cgi with ``cp -p`` as
- illustrated above.)
-
-* demo.cgi started, but was unable to import the Quixote modules.
- In this case, there should be a short Python traceback in your web
- server's error log ending with a message like ::
-
- ImportError: No module named quixote
-
- Remember, just because you can "import quixote" in a Python
- interpreter doesn't mean the user that runs CGI scripts (usually
- "nobody") can. You might have installed Quixote in a non-standard
- location, in which case you should either install it in the standard
- location (your Python interpreter's "site-packages" directory) or
- instruct your web server to set the PYTHONPATH environment variable.
- Or you might be using the wrong Python interpreter -- check the ``#!``
- line of demo.cgi.
-
-* demo.cgi started and imported Quixote, but was unable to read its
- config file. There should be a short Python traceback in your web
- server's error log ending with a message like ::
-
- IOError: [Errno 2] No such file or directory: 'demo.conf'
-
- in this case.
-
- Make sure you copied demo.conf to the same directory as demo.cgi,
- and make sure it is readable::
-
- chmod 644 /www/cgi-bin/demo.conf
-
- (This shouldn't happen if you install demo.conf with ``cp -p`` as
- illustrated above.)
-
-
-Running the demo indirectly
+Understanding the root demo.
---------------------------
-One of the main tenets of Quixote's design is that, in a web
-application, the URL is part of the user interface. We consider it
-undesirable to expose implementation details -- such as
-"/cgi-bin/demo.cgi" -- to users. That sort of thing should be tucked
-away out of sight. Depending on your web server, this should be easy to
-do with a simple tweak to its configuration.
+Start the root demo by running the command:
+ simple_server.py --factory quixote.demo.create_publisher
-For example, say you want the "/qdemo" URL to be the location of the
-Quixote demo. If you're using Apache with the rewrite engine loaded and
-enabled, all you need to do is add this to your httpd.conf::
+In a browser, open http://localhost:8080 as before.
+Click around at will.
- RewriteRule ^/qdemo(/.*) /www/cgi-bin/demo.cgi$1 [last]
+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.
-With this rule in effect (don't forget to restart your server!),
-accesses to "/qdemo/" are the same as accesses to "/cgi-bin/demo.cgi/" --
-except they're a lot easier for the user to understand and don't expose
-implementation details of your application.
+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.
-Try it out. In your web browser, visit ``http://localhost/qdemo/``.
+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.
-You should get exactly the same page as you got visiting
-"/cgi-bin/demo.cgi/" earlier, and all the links should work exactly the
-same.
+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.
-You can use any URL prefix you like -- there's nothing special about
-"/qdemo".
+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.
-One small but important detail here is "/qdemo" versus "/qdemo/". In
-the above configuration, requests for "/qdemo" will fail, and requests
-for "/qdemo/" will succeed. See the "URL rewriting" section of
-web-server.txt for details and how to fix this.
-
-
-Understanding the demo
+The _q_lookup() method
----------------------
-Now that you've gotten the demo to run successfully, let's look under
-the hood and see how it works. Before we start following links in the
-demo (don't worry if you already have, you can't hurt anything), make
-sure you're watching all the relevant log files. As with any web
-application, log files are essential for debugging Quixote applications.
+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.
-Assuming that your web server's error log is in /www/log/error_log, and
-that you haven't changed the ERROR_LOG setting in demo.conf::
+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.
- $ tail -f /www/log/error_log & \
- tail -f /tmp/quixote-demo-error.log
+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__]
-(Note that recent versions of GNU tail let you tail multiple files with
-the same command. Cool!)
+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.
-Lesson 1: the top page
-----------------------
+The _q_resolve() method
+-----------------------
-Reload the top of the demo, presumably ``http://localhost/qdemo/``. You
-should see "debug message from the index page" in the quixote error log.
+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.
-Where is this message coming from? To find out, we need to delve into
-the source code for the demo. Load up demo/__init__.py and let's take a
-look. In the process, we'll learn how to explore a Quixote application
-and find the source code that corresponds to a given URL.
+Forms
+-----
-First, why are we loading demo/__init__.py? Because that's where some
-of the names in the "quixote.demo" namespace are defined, and it's where
-the list of names that may be "exported" by Quixote from this namespace
-to the web is given. Recall that under Quixote, every URL boils down to
-a callable Python object -- usually a function or method. The root of
-this application is a Python package ("quixote.demo"), which is just a
-special kind of module. But modules aren't callable -- so what does the
-"/qdemo/" URL boil down to? That's what ``_q_index()`` is for -- you
-can define a special function that is called by default when Quixote
-resolves a URL to a namespace rather than a callable. That is,
-"/qdemo/" resolves to the "quixote.demo" package; a package is a
-namespace, so it can't be called; therefore Quixote looks for a function
-called ``_q_index()`` in that namespace and calls it.
+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.
-In this case, ``_q_index()`` is not defined in demo/__init__.py -- but
-it is imported there from the quixote.demo.pages module. This is
-actually a PTL module -- demo/pages.py does not exist, but
-demo/pages.ptl does. So load it up and take a look::
-
- def _q_index [plain] (request):
- print "debug message from the index page"
- """
- <html>
- <head><title>Quixote Demo</title></head>
- <body>
- <h1>Hello, world!</h1>
- [...]
- </body>
- </html>
- """
-
-A-ha! There's the PTL code that generates the "Quixote Demo" page.
-This ``_q_index()`` template is quite simple PTL -- it's mostly an HTML
-document with a single debug print thrown in to demonstrate Quixote's
-debug logging facility.
-
-Outcome of lesson 1:
-
-* a URL maps to either a namespace (package, module, class instance)
- or a callable (function, method, PTL template)
-
-* if a URL maps to a namespace, Quixote looks for a callable
- ``_q_index()`` in that namespace and calls it
-
-* ``_q_index()`` doesn't have to be explicitly exported by your
- namespace; if it exists, it will be used
-
-* anything your application prints to standard output goes to
- Quixote's error log.
-
-
-Lesson 2: a link to a simple document
--------------------------------------
-
-The first two links in the "Quixote Demo" page are quite simple. Each
-one is handled by a Python function defined in the "quixote.demo"
-namespace, i.e. in demo/__init__.py. For example, following the
-"simple" link is equivalent to calling the ``simple()`` function in
-"quixote.demo". Let's take a look at that function::
-
- def simple (request):
- request.response.set_content_type("text/plain")
- return "This is the Python function 'quixote.demo.simple'.\n"
-
-Note that this could equivalently be coded in PTL::
-
- def simple [plain] (request):
- request.response.set_content_type("text/plain")
- "This is the Python function 'quixote.demo.simple'.\n"
-
-...but for such a simple document, why bother?
-
-Since this function doesn't generate an HTML document, it would be
-misleading for the HTTP response that Quixote generates to claim a
-"Content-type" of "text/html". That is the default for Quixote's HTTP
-responses, however, since most HTTP responses are indeed HTML documents.
-Therefore, if the content you're returning is anything other than an
-HTML document, you should set the "Content-type" header on the HTTP
-response.
-
-This brings up a larger issue: request and response objects. Quixote
-includes two classes, HTTPRequest and HTTPResponse, to encapsulate every
-HTTP request and its accompanying response. Whenever Quixote resolves a
-URL to a callable and calls it, it passes precisely one argument: an
-HTTPRequest object.
-
-The HTTPRequest object includes (almost) everything you might want to
-know about the HTTP request that caused Quixote to be invoked and to
-call a particular function, method, or PTL template. You have access to
-CGI environment variables, HTML form variables (parsed and
-ready-to-use), and HTTP cookies. Finally, the HTTPRequest object also
-includes an HTTPResponse object -- after all, every request implies a
-response. You can set the response status, set response headers, set
-cookies, or force a redirect using the HTTPResponse object.
-
-Note that it's not enough that the ``simple()`` function merely exists.
-If that were the case, then overly-curious users or attackers could
-craft URLs that point to any Python function in any module under your
-application's root namespace, potentially causing all sorts of havoc.
-You need to explicitly declare which names are exported from your
-application to the web, using the ``_q_exports`` variable. For example,
-demo/__init__.py has this export list::
-
- _q_exports = ["simple", "error"]
-
-This means that only these two names are explicitly exported by the
-Quixote demo. (The empty string is implicitly exported from a namespace
-if a ``_q_index()`` callable exists there -- thus "/qdemo/" is handled
-by ``_q_index()`` in the "quixote.demo" namespace. Arbitrary names may
-be implicitly exported using a ``_q_lookup()`` function; see Lesson 4
-below.)
-
-
-Lesson 3: error-handling
-------------------------
-
-The next link in the "Quixote Demo" page is to the "error" document,
-which is handled by the ``error()`` function in demo/__init__.py. All
-this function does is raise an exception::
-
- def error (request):
- raise ValueError, "this is a Python exception"
-
-Follow the link, and you should see a Python traceback followed by a
-dump of the CGI environment for this request (along with other request
-data, such as a list of cookies).
-
-This is extremely useful when developing, testing, and debugging. In a
-production environment, though, it reveals way too much about your
-implementation to hapless users who should happen to hit an error, and
-it also reveals internal details to attackers who might use it to crack
-your site. (It's just as easy to write an insecure web application with
-Quixote as with any other tool.)
-
-Thus, Quixote offers the ``DISPLAY_EXCEPTIONS`` config variable. This
-is false by default, but the demo.conf file enables it. To see what
-happens with ``DISPLAY_EXCEPTIONS`` off, edit demo.conf and reload the
-"error" page. You should see a bland, generic error message that
-reveals very little about your implementation. (This error page is
-deliberately very similar, but not identical, to Apache's "Internal
-Server Error" page.)
-
-Unhandled exceptions raised by application code (aka "application bugs")
-are only one kind of error you're likely to encounter when developing a
-Quixote application. The other ones are:
-
-* driver script crashes or doesn't run (eg. can't import quixote
- modules, can't load config file). This is covered under
- "Running the demo directly" above
-
-* publishing errors, such as a request for "/simpel" that should
- have been "/simple", or a request for a resource that exists but is
- denied to the current user. Quixote has a family of exception
- classes for dealing with these; such exceptions may be raised by
- Quixote itself or by your application. They are usually handled by
- Quixote and turned into HTTP error responses (4xx status code),
- but it's possible for your application to define a special handler
- for such exceptions.
-
-* bugs in Quixote itself; hopefully this won't happen, but you
- never know. These usually look a lot like problems in the driver
- script: the script crashes and prints a traceback to stderr, which
- most likely winds up in your web server's error log. The length of
- the traceback is generally a clue as to whether there's a problem
- with your driver script or a bug in Quixote.
-
-Publishing errors result in a 4xx HTTP response code, and can be
-entirely handled by Quixote -- that is, your web server just returns
-the HTTP response that Quixote prepares. It's also possible to write
-a ``_q_exception_handler()`` method that will be called on triggering
-a publishing error. This method can then provide a friendlier
-response; for example, the page might provide a link to the site map,
-or the method might look at the problematic path and try to correct
-misspellings. The demo defines a ``_q_exception_handler()`` in
-demo/pages.ptl.
-
-Application bugs result in a 5xx HTTP response code, and are
-entirely handled by Quixote. Don't get confused by the fact that
-Quixote's and Apache's "Internal Server Error" pages are quite similar!
-
-Driver script crashes and Quixote bugs (which are essentially the same
-thing; the main difference is who to blame) are handled by your web
-server. (In the first case, Quixote doesn't even enter into it; in the
-second case, Quixote dies horribly and is no longer in control.) Under
-Apache, the Python traceback resulting from the crash is written to
-Apache's error log, and a 5xx response is returned to the client with
-Apache's "Internal Server Error" error page.
-
-
-Lesson 4: object publishing
----------------------------
-
-Publishing Python callables on the web -- i.e., translating URLs to
-Python functions/methods/PTL templates and calling them to determine the
-HTTP response -- is a very powerful way of writing web applications.
-However, Quixote has one more trick up its sleeve: object publishing.
-You can translate arbitrary names to arbitrary objects which are then
-published on the web, and you can create URLs that call methods on those
-objects.
-
-This is all accomplished with the ``_q_lookup()`` function. Every
-namespace that Quixote encounters may have a ``_q_lookup()``, just like
-it may have a ``_q_index()``. ``_q_index()`` is used to handle requests for
-the empty name -- as we saw in Lesson 1, a request for "/qdemo/", maps to
-the "quixote.demo" namespace; the empty string after the last slash
-means that Quixote will call ``_q_index()`` in this namespace to handle
-the request.
-
-``_q_lookup()`` is for requests that aren't handled by a Python callable
-in the namespace. As seen in Lessons 2 and 3, requests for
-"/qdemo/simple" and "/qdemo/error" are handled by the ``simple()`` and
-``error()`` functions in the "quixote.demo" namespace. What if someone
-requests "/qdemo/foo"? There's no function ``foo()`` in the
-"quixote.demo" namespace, so normally this would be an error.
-(Specifically, it would be a publishing error: Quixote would raise
-TraversalError, which is the error used for non-existent or non-exported
-names. Another part of Quixote then turns this into an HTTP 404
-response.)
-
-However, this particular namespace also defines a ``_q_lookup()``
-function. That means that the application wants a chance to handle
-unknown names before Quixote gives up entirely. Let's take a look at
-the implementation of ``_q_lookup()``::
-
- from quixote.demo.integer_ui import IntegerUI
- [...]
- def _q_lookup(request, component):
- return IntegerUI(request, component)
-
-Pretty simple: we just construct an IntegerUI object and return it. So
-what is IntegerUI? Take a look in the demo/integer_ui.py file to see;
-it's just a web interface to integers. (Normally, you would write a
-wrapper class that provides a web interface to something more
-interesting than integers. This just demonstrates how simple an object
-published by Quixote can be.)
-
-So, what is an IntegerUI object? From Quixote's point of view, it's
-just another namespace to publish: like modules and packages, class
-instances have attributes, some of which (methods) are callable. In the
-case of IntegerUI, two of those attributes are ``_q_exports`` and
-``_q_index`` -- every namespace published by Quixote must have an export
-list, and an index function is almost always advisable.
-
-What this means is that any name that the IntegerUI constructor accepts
-is a valid name to tack onto the "/qdemo/" URL. Take a look at the
-IntegerUI constructor; you'll see that it works fine when passed
-something that can be converted to an integer (eg. "12" or 1.0), and
-raises Quixote's TraversalError if not. As it happens, Quixote always
-passes in a string -- URLs are just strings, after all -- so we only
-have to worry about things like "12" or "foo".
-
-The error case is actually easier to understand, so try to access
-``http://localhost/qdemo/foo/``. You should get an error page that
-complains about an "invalid literal for int()".
-
-Now let's build a real IntegerUI object and see the results. Follow the
-third link in the "Quixote Demo" page, or just go to
-``http://localhost/qdemo/12/``. You should see a web page titled "The
-Number 12".
-
-This web page is generated by the ``_q_index()`` method of IntegerUI:
-after all, you've selected a namespace (the IntegerUI object
-corresponding to the number 12) with no explicit callable, so Quixote
-falls back on the ``_q_index()`` attribute of that namespace.
-
-IntegerUI only exports one interesting method, ``factorial()``. You can
-call this method by following the "factorial" link, or just by accessing
-``http://localhost/qdemo/12/factorial``.
-
-Remember how I said the URL is part of the user interface? Here's a
-great example: edit the current URL to point to a different integer. A
-fun one to try is 2147483646. If you follow the "next" link, you'll get
-an OverflowError traceback (unless you're using a 64-bit Python!),
-because the web page for 2147483647 attempts to generate its own "next"
-link to the web page for 2147483648 -- but that fails because current
-versions of Python on 32-bit platforms can't handle regular integers
-larger than 2147483647.
-
-Now go back to the page for 2147483646 and hit the "factorial" link.
-Run "top" on the web server. Get yourself a coffee. Await the heat
-death of the universe. (Actually, your browser will probably timeout
-first.) This doesn't overflow, because the factorial() function uses
-Python long integers, which can handle any integer -- they just take a
-while to get there. However, it illustrates another interesting
-vulnerability: an attacker could use this to launch a denial-of-service
-attack on the server running the Quixote demo. (Hey, it's just a demo!)
-
-Rather than fix the DoS vulnerability, I decided to use it to illustrate
-another Quixote feature: if you write to stderr, the message winds up in
-the Quixote error log for this application (/tmp/quixote-demo-error.log
-by default). The IntegerUI.factorial() method uses this to log a
-warning of an apparent denial-of-service attack::
-
- def factorial (self, request):
- if self.n > 10000:
- sys.stderr.write("warning: possible denial-of-service attack "
- "(request for factorial(%d))\n" % self.n)
- return "%d! = %d" % (self.n, fact(self.n))
-
-Since the Quixote error log is where application tracebacks are
-recorded, you should be watching this log file regularly, so you would
-presumably notice these messages.
-
-In real life, you'd probably just deny such a ludicrous request. You
-could do this by raising a Quixote publishing error. For example::
-
- def factorial (self, request):
- from quixote.errors import AccessError
- if self.n > 10000:
- raise AccessError("ridiculous request denied")
- return "%d! = %d" % (self.n, fact(self.n))
-
-
-Lesson 5: widgets
------------------
-
-You can't get very far writing web applications without writing forms,
-and the building blocks of web forms are generally called "form
-elements": string input, checkboxes, radiobuttons, select lists, and so
-forth. Quixote provides an abstraction for all of these form elements:
-the Widget class hierarchy. The widget classes are explained in detail
-in widget.txt; I'm going to give a brief description of the "Quixote
-Widget Demo" page and the code behind it here.
-
-If you follow the "widgets" link from the main page, you'll see a fairly
-ordinary-looking web form -- the sort of thing you might have to fill
-out to order a pizza on-line, with the oddity that this pizza shop is
-asking for your eye colour. (Hey, I had to demonstrate radiobuttons
-somehow!) This form demonstrates most of HTML's basic form
-capabilities: a simple string, a password, a checkbox, a set of
-radiobuttons, a single-select list, and a multiple-select list.
-
-Whenever you implement a web form, there are two things you have to
-worry about: generating the form elements and processing the
-client-submitted form data. There are as many ways of dividing up this
-work as there are web programmers. (Possibly more: every time I tackle
-this problem, I seem to come up with a different way of solving it.)
-The form in the Quixote widget demo is implemented in three parts, all
-of them in demo/widgets.ptl:
-
-* ``widgets()`` is the callable that handles the "/qdemo/widgets" URL.
- This template creates all the widget objects needed for the form and
- then calls either ``render_widgets()`` or ``process_widgets()`` as
- appropriate.
-
-* ``render_widgets()`` is called by ``widgets()`` when there is no form
- data to process, eg. on the first access to the "/qdemo/widgets" URL.
- It generates the form elements and returns an HTML document consisting
- of a table that lays them out in an attractive form.
-
-* ``process_widgets()`` is called by ``widgets()`` when there is form data
- to process, ie. when the form generated by ``render_widgets()`` is
- submitted by the user. It processes the form data, ie. it looks up
- the user-submitted form values and returns an HTML document listing
- those values.
-
-This division of labour works well with Quixote's widget classes, since
-you need a collection of Widget objects whether you are generating the
-form elements or processing form data. For generating the form, we (1)
-create all the widget objects, and (2) generate an HTML document that
-includes the output of each widget object's ``render()`` method. (Laying
-out the form is the responsibility of render_widgets(), which is why
-it's littered with table tags.) For processing the form, we (1) create
-all the widget objects, and (2) generate an HTML document incorporating
-the user-submitted form values. In both cases, step (1) is handled by
-the widgets() template, which then calls either render_widgets() or
-process_widgets() for step (2).
-
-Thus, there are three things you have to understand about widget
-objects: how to create them, how to render them, and how to use them to
-parse form values. Widget creation is the only step that's very
-interesting, since each widget class has different constructor
-arguments. For example, here's how we create the "name" widget in the
-pizza shop form::
-
- widgets['name'] = widget.StringWidget('name', size=20)
-
-When rendered, this widget will produce the following HTML::
-
- <input size="20" name="name" type="text">
-
-A more complex example is the "pizza size" widget::
-
- widgets['size'] = widget.SingleSelectWidget(
- 'size', value='medium',
- allowed_values=['tiny', 'small', 'medium', 'large', 'enormous'],
- descriptions=['Tiny (4")', 'Small (6")', 'Medium (10")',
- 'Large (14")', 'Enormous (18")'],
- size=5)
-
-which will generate the following HTML when rendered::
-
- <select size="5" name="size">
- <option value="0">Tiny (4")
- <option value="1">Small (6")
- <option selected value="2">Medium (10")
- <option value="3">Large (14")
- <option value="4">Enormous (18")
- </select>
-
-Some things you might need to know about widget creation:
-
-* the standard widget classes are in the quixote.form.widget module;
- see widget.txt and/or the source code for the complete list
-
-* every widget class constructor has exactly one required argument:
- the widget name. This is used as the form element name
- in the generated HTML. (Things are a bit different for compound
- widgets, but I'm not covering them in this document.)
-
-* every widget class supports a number of keyword arguments that
- generally correspond to attributes of some HTML tag. The one
- argument common to all widget classes is ``value``, the current
- value for this widget.
-
-Rendering widgets is easy: just call the render() method, passing in the
-current HTTPRequest object. (It's currently not used by the standard
-widget classes, but could be used by derived or compound widget classes
-for context-sensitive widget rendering.)
-
-Parsing form values is just as easy: call the parse() method, again
-passing in the current HTTPRequest object. The return value depends on
-the nature of the widget, eg.:
-
-* StringWidget and PasswordWidget return a string
-* CheckboxWidget returns a boolean
-* RadiobuttonsWidget, SingleSelectWidget, and MultipleSelectWidget
- return one of the values supplied in ``allowed_values`` -- in the
- demo these are all strings, but they can be any Python value.
- (If the client submits bogus data, the widget will return None.)
-
-
$Id$