Enter the following code and run it. If you point your web browser to the familiar http://localhost:8080 address, you will see something similar to the picture below with the time changing every five seconds or so. (The code is also available as timer.py)
Our small CherryPy application offers just two methods (both highlighted in the code). The index()
method returns a minimalistic HTML page with some static text and a small piece of JavaScript that takes care of retrieving the current time from the server. It also features a time()
method that simply returns the current time as plain text.
Chapter4/timer.py
import cherrypy import os.path from time import asctime current_dir = os.path.dirname(os.path.abspath(__file__)) class Root(object): @cherrypy.expose def index(self): return '''<html> <head><script type="text/javascript" src="/jquery.js" ></script></ head> <body><h1>The current time is ...</h1><div id="time"></div> <script type="text/javascript"> window.setInterval(function(){$.ajax({url:"time",cache:false,success: function(data,status,request){ $("#time").html(data); }});},5000); </script> </body> </html>''' @cherrypy.expose def time(self,_=None): return asctime() cherrypy.quickstart(Root(),config={ '/jquery.js': { 'tools.staticfile.on':True, 'tools.staticfile.filename':os.path.join(current_ dir,"static","jquery","jquery-1.4.2.js") } })
The magic is in that small piece of JavaScript (highlighted). This script is executed once the static page is loaded and it calls the setInterval()
method of the window
object. The arguments to the setInterval()
method are an anonymous function and a time interval in milliseconds. We set the time interval to five seconds. The function passed to setInterval()
is called at the end of each interval.
In this example, we pass an anonymous function to setInterval()
that relies on jQuery's ajax()
function to retrieve the time. The ajax()
function's only argument is an object that may contain numerous options. The url
option tells which URL to use to retrieve the data from, in this case, the relative URL time
(relative to the page that serves the content the script is embedded in, /
, so it actually refers to http://localhost:8080/time)
.
The cache
option is set to false
to prevent the browser from using a cached result when instructed to get the time URL it has seen already. This is ensured by the underlying JavaScript library by appending an extra _
parameter (that is the name of this parameter which consists of a single underscore) to the URL. This extra parameter contains a random number, so the browser will regard each call as a call to a new URL. The time()
method is defined to accept this parameter because otherwise CherryPy would raise an exception, but the contents of the parameter are ignored.
The success
option is set to a function that will be called when the data is successfully received. This function will receive three arguments when called: the data that was retrieved by the ajax()
function, the status, and the original request object. We will only use the data here.
We select the<div>
element with the time
ID and replace its contents by passing the data to its html()
method. Note that even though the time()
method just produces text, it could just as easily have returned text containing some markup this way.
We explicitly instructed the ajax()
function not to cache the result of the query, but instead we could also decorate our time()
method with CherryPy's expires
tool. This would instruct the time()
method to insert the correct http headers in response to instruct the browser not to cache the results. This is illustrated in the following code (available in timer2.py):
@cherrypy.tools.expires(secs=0,force=True) @cherrypy.expose def time(self,_=None): return asctime()
Using the @cherrypy.tools.expires
decorator means we do not have to instruct the ajax()
method not to cache the result, which gives us the option to use a shortcut method. The JavaScript code may then be rewritten to use jQuery's load()
method, shown as follows:
<script type="text/javascript"> window.setInterval(function(){$("#time").load("time");},5000); </script>
The load()
method is passed the URL where it will retrieve the data and, upon success, will replace the contents of the selected DOMelement
with the data it received.
The tasklist application will consist of two parts: an authentication part for which we will reuse the LogonDB
class and new TaskApp
class. The TaskApp
class will implement the methods necessary to deliver the page with an overview of all tasks for the authenticated user plus additional methods to respond to AJAX requests.
Instead of a filesystem, SQLite will be used to store the tasks for all users. Note that this is a separate database from the one used to store usernames and passwords. Such a setup allows us to keep the authentication functionality separate from other concerns, allowing for easier reuse. Once the user is authenticated, we do, of course, use his/her username to identify the tasks belonging to him/her.
Access to the task database will be encapsulated in a tasklistdb
module. It provides classes and methods to retrieve, add, and modify tasks for a given user. It is not concerned with checking access permission, as this is the responsibility of the TaskApp
class. You can picture this separation as a two layer model, the top layer checking user credentials and serving content, and the bottom layer actually interfacing with a database.
The design of our task database (the database schema) is very straightforward. It consists of a single table, which contains columns to define a task.
Task | ||||
---|---|---|---|---|
task_id |
description |
duedate |
completed |
user_id |
integer primary key autoincrement |
Most columns do not have a specific type defined, as SQLite will let us store anything in a column. Furthermore, most columns do not have special constraints except for the task_id
column that we designate to be the primary key. We do explicitly type the task_id
column as an integer and designate it as autoincrement
. This way, we do not have to set the value of this column explicitly, but a new unique integer will be inserted for us every time we add a new task to the table.
3.129.210.91