Running the Quixote Demos

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:

python3 -m quixote run

and in a browser, open http://localhost:8080. If you wish to run the demo on a remote computer, you will need to ask the server to listen on a public network interface. Usually:

python3 -m quixote run --host=0

In your browser, replace localhost with the name of the server.

The 'quixote run' command prints a usage message if you run it with a --help command line argument. You can run different Quixote applications by using the --app option to identify a package that contains your Quixote application. The top-level module in the package should contain a factory function create_publisher that will return a Quixote Publisher instance. You might wish to try these demos:

python3 -m quixote run --app quixote.demo.mini_demo

or:

python3 -m quixote run --app quixote.demo.altdemo

Understanding the mini_demo

Start the mini demo by running the command:

python3 -m quixote run --app quixote.demo.mini_demo

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. Each Quixote application has one Publisher instance. When a request arrives, the Publisher traverses the path of the URL, using the RootDirectory object.

For the URL http://localhost:8080, Quixote traverses the path list:

['']

This path results in the function exported using the empty string as its name being called. The return value is send back to the browser, after adding some basic HTTP headers.

To prevent potentially sensitive internal parts of your application are not exposed as callable URLs, Quixote requires that each Directory component must be explictly exported. Internally this is done using the _q_exports attribute of Directory objects. For convenience, a decorator function called 'export' is made available which takes care of updating the _q_exports list.

The 'export' decorator takes a single, optional keyword argument 'name'. If not provided, the exported name defaults to the name of the function.

The mini demo contains only a single page besides the root page. The URL is http://localhost:8080/hello. Quixote traverses the path list:

['hello']

and finds the hello method of the RootDirectory object. It calls the method and returns the result as page.

Understanding the root demo

Start the root demo by running the command:

python3 -m quixote run

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 index() method and it is exported with the name '' (i.e. the empty string).

Looking at the RootDirectoryMethods, including plain(), css() and favicon_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' explicitly 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 index() method.

The _q_lookup() 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).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.

The _q_resolve() method

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.

Forms

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.