3. A real case application : A blog.

3.1. Features of the blog

Abstract

The following sections will describe the step to build a simple blogging system. It does not aim to create a comprehensive one but to give you an overview of how to build a CherryPy application.

The blogging system will feature some basic functionalities such as :

  • Add new entries
  • Delete existing entries
  • Add a new comment
  • Display a calendar
  • Use positional parameters
  • Present a RSS feed

3.2. Setting up the project

Our first step will be to define the directory structure of the projet. This structure is only one amongst others of course.

Example 2.3. Directory structure of the blog project

Imagine our root directory is called blog, then the structure will be :

      1 blog/blog.py
      2 blog/LICENSE
      3 blog/README
      4 blog/media/
      5 blog/media/conf/
      6 blog/media/css/
      7 blog/media/db/
      8 blog/media/feed/
      9 blog/media/templates/
      10 blog/media/utils/
      11 blog/src/
      
1

This is the main script of our application. It contains the code to start up the web server and the application.

2

Your application should always contain a license.

3

Your application should also come with a readme file.

4

We will divide the directory structure into two main sub directories. One will deal with the source code itself, and the other one will deal with all the data you might work with.

5

Contains the configuration file we will define for the application.

6

Contains the CSS stylesheet we will be using.

7

Our blogging system will use a very simple interface to store content as you will see later. This directory will hold the data.

8

We will only support RSS here but as you may extend yourself the applictaion, this directory will let you hold all different kind of feed format files.

9

Contains all templating files we will be using.

10

Where to put some helper files we will need.

11

Contains the source code itself.

Again this structure is not required by CherryPy itself. It is a choice made for this tutorial.

3.3. Design consideration

3.4. Tools

Abstract

3.4.1. The backend: persistence

Mainly one can look at four the following directions when deciding to store and persist content :

  • Native SQL driven database
  • Native XML driven database
  • Native Objects driven database

By far the first choice is the most used at any rate. It is efficient, reliable, well-known and powerful. XML and native object database are gaining some success slowly but are still quite young.

The solution we have chosen is non of the above for keeping this tutorial simple. We do want to avoid having to import too many external modules. Besides since this blogging system is so simple, we can use another way to store and persist our content : pickling pure python objects thanks to the standard shelvemodule.

A ``shelf'' is a persistent, dictionary-like object. The difference with ``dbm'' databases is that the values (not the keys!) in a shelf can be essentially arbitrary Python objects -- anything that the pickle module can handle. This includes most class instances, recursive data types, and objects containing lots of shared sub-objects. The keys are ordinary strings.

This solution allows us to focus on CherryPy and not too much on the backend itself.

3.4.2. Templating : presentation

A templating language is meant to offer the developer and easy way to transform an input into an output of his/her choice. In the web application field, usually it refers to the presentation system to transform the content into nice XHTML output sent to the browser.

The CherryPy developers have made the clear choice of not providing any default templating system. The main reason is that CherryPy must to stay at low level and developers should not have the feeling like they must use a particular templating system. CherryPy allows actually the developer to choose his/her favourite templating system and work with it.

We could have written this blogging system by generating the HTML directly from the source code. Although this is totally possible we have felt it would be much cleaner to use a dedicated templating language. Our choise was CherryTemplate written and maintained by Remi Delon. It is a small package to install and which provides the basic functionnalities we will need : loop, condition, etc. Its learning curve is really low.

3.5. Building the application

3.5.1. Main entry point

The first file we need to create is the blog.py file which will be the entry point of the blog.

3.5.1.1. Importing required modules

First we need to import the modules we will be using from this entry point.

Example 2.4. Blogging system - import modules

    1 import sys
    import time
            
    2  sys.path.append('src')
            
    3  import cherrypy
    4  from cherrytemplate import cherrytemplate, renderTemplate

    5
    from entryManager import BlogEntryManager
    from commentManager import BlogCommentManager
    from comment import BlogComment
    from admin import BlogAdmin
    from cal import getCurrentCalendar
          
1

We use the sys module to be able to add a directory to the search path where Python looks for modules.

2

Here we add the sub-directory src to the Python search path.

3

CherryPy requires only one import. In version 2.0 of CherryPy, it used to be from cherrypy import cpg but not any longer.

4

Import two modules from CherryTemplate. The first one is used to modify some global variable of CherryTemplate as we will see later on. The second one is the actual function to parse a template file and fetch the output.

5

The last modules imported are those we will write later on in this tutorial.

3.5.1.2. The main class

Example 2.5. Blogging system - the root class

1class Blog:
  def __init__(self):
    2
    self.em = BlogEntryManager()
    self.cm = BlogCommentManager()
    cherrytemplate.defaultOutputEncoding = 'latin-1'
    cherrytemplate.defaultInputEncoding = 'latin-1'
    cherrytemplate.defaultTemplateDir = 'media/templates/'
          
1

CherryPy needs at least one python callable at its root (eg: a class or a function). Here we will eb using a class called Blog.

2

Since a CherryPy application runs as a long process on your system, we can define variables that will be shared during the full run of the process. In this case we need a manager of entries and comments, classes tha we will define later on. Wel also define some global variable used by CherryTemplate.

3.5.1.3. Defining an index and a default method

CherryPy does not force you to define any kind of methods but index and default are the ones CherryPy will look for by default. The index() method is the equivalent to the index.html page used by default by Apache. The default() method is called by CherryPy when all other lookup fail. If CherryPy doesn't find any matching callable object, it will issue an exception. index() is by far the most common one to be defined.

Before a method can be used to serve content it needs to be published and exposed. Publishing is the process of attaching an instance of a given callable to the main cherrypy object tree as we will see later. Exposing is done by setting the exposed attribute to the method that we want to be able to call from the client side.

Now we can define both methods as follow.

Example 2.6. 

 1
    def index(self):
         entries = self.em.fetchLastEntries()
         cal = getCurrentCalendar()
         rssPath = cherrypy.request.base + '/media/feed/rss.xml'
         return renderTemplate(file = "Blog_show_entry.tmpl")
    index.exposed = True
           
 2
    def default(self, *args):
         cal = getCurrentCalendar()
         rssPath = cherrypy.request.base + '/media/feed/rss.xml'
         if len(args) == 3:
             date = "%s-%s-%s" % (args[0], args[1], args[2])
             entries = self.em.fetchEntriesByDate(date)
         elif len(args) == 4:
             entry = self.em.fetchEntry(args[0], args[1], args[2], args[3])
         if not entry:
             return renderTemplate(file = "error404.tmpl")
             entries = [entry]
         else:
             return self.index()
         return renderTemplate(file = "Blog_show_entry.tmpl")
    default.exposed = True
          
1

When the index method is called we fetch the last entries added to the blog as well as the calendar of the current month (we will see later how to build it). Then we define the RSS feed path.

Those three variables will be transparently passed to the CherryTemplate which is rendered through the renderTemplate() function. Since we have defined the templates directory as a global variable of CherryTemplate, we simply need to provide the name of the template itself to the method.

We will then serve to the client the string resulting from the rendering, although CherryPy allows you to return a string, a list, a generator to serve the content

index.exposed = True tells CherryPy that this method is exposed and available to serve content.

2

The default method is called when CherryPy failed to map the requested URL to an exposed object in the published objects tree, but you can also use it to slightly change CherryPy's behavior.

In this particular context we will use default() allow URL with positional parameters such as : http://myhost/2005/06/18/78, which would be the equivalent to : http://myhost/?year=2005&month=06&day=18&entry=78. The former URL looks nicer and is more search engine friendly.

This behavior is not automatically handled by CherryPy and using the default method is the most common workaround.

First we get the current month calendar then we set the RSS feed path. Then we check what to do next by testing the number of passed arguments to the request. If this equals to three then the user asked to view a full day and all its entries. If it equals to four he specified the entry id as well. Otherwise we simply fallback to callback index().

3.5.1.4. Serving more content

We will define two more exposed methods to our Blog class.

Example 2.7. 

 1
def comment(self, id):
    entryId = id
    entry = self.em.fetchEntryById(id)
    comments = self.cm.fetchCommentsByEntry(id)
    cal = getCurrentCalendar()
    rssPath = cherrypy.request.base + '/media/feed/rss.xml'
    return renderTemplate(file = "Blog_comment_add.tmpl")
comment.exposed = True
 
 2
def addComment(self, entryId, title, author, message):
    rssPath = cherrypy.request.base + '/media/feed/rss.xml'
    self.cm.addComment(BlogComment(title, author, message, entryId))
    return self.comment(entryId)
addComment.exposed = True
          
1

Called when an user wants to read the comments added to an entry.

2

Called when an user submitted a new comment through the HTML form.

3.5.1.5. Setting up the tree and the server

Finally we need to set up the CherryPy tree of published objects, specify what settings we need and finally start the server.

Example 2.8. 

1
    cherrypy.root = Blog()
    cherrypy.root.admin = BlogAdmin()

2
    cherrypy.config.update(file='media/conf/Blog.conf')
3
    cherrypy.server.start()
          
1

Define the tree structure. Our blogging system only publish two objects.

2

Our settings will be defined in a seperate file. We could have defined them inline as a Python dictionnary.

3

Let's start the server.

3.5.2. Building the core

The core functionnailities will be held in some scripts that we are going to study in the following sections.

3.5.2.1. The backend

We will define a file called backendManager.py located under the src directory. This file will define a class and its set of methods to facilitate access and queries to our backend. Basically we will follow the CRUD interface : Create, Retrieve, Update and Delete. Following is a snippet code from that module:

Example 2.9. 

          import shelve
          import threading
          import time
          1
          _dbLocker = threading.Lock()
          
          class BlogBackendManager:
              2
              def fetchAll(self):
                  data = []
                  try:
                    _dbLocker.acquire()
                    db = shelve.open('media/db/blob.db', 'r')
                    data = db.keys()
                    if db: db.close()
                  finally:
                    _dbLocker.release()
                  return data
        
1 We will hold a global lock to ensure the database access will be protected. This is needed since CherryPy is a multithreaded application, each thread handling one user agent request. As you can notice this lock is global to the entire application, therefore no more than one thread will be able to access the database at one given time. This is a very simplistic principle and not a usable one on a larger application, but as we already said, our purpose is to show how to build an application without too much complexity.
2 This method will simply connect to the database and fetch all entries. We make sure the lock is always released by calliong the release method within the finallystatement, thus whatever happens after the lock has been acquired it will be released.

3.6. Running the application

Abstract