The blogging system will feature some basic functionalities such as :
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 :
blog/blog.py
blog/LICENSE
blog/README
blog/media/
blog/media/conf/
blog/media/css/
blog/media/db/
blog/media/feed/
blog/media/templates/
blog/media/utils/
blog/src/
Again this structure is not required by CherryPy itself. It is a choice made for this tutorial.
Mainly one can look at four the following directions when deciding to store and persist content :
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.
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.
The first file we need to create is the blog.py file which will be
the entry point of the blog.
First we need to import the modules we will be using from this entry point.
Example 2.4. Blogging system - import modules
import sys
import time
sys.path.append('src')
import cherrypy
from cherrytemplate import cherrytemplate, renderTemplate
from entryManager import BlogEntryManager
from commentManager import BlogCommentManager
from comment import BlogComment
from admin import BlogAdmin
from cal import getCurrentCalendar
Example 2.5. Blogging system - the root class
class Blog: def __init__(self):
self.em = BlogEntryManager() self.cm = BlogCommentManager() cherrytemplate.defaultOutputEncoding = 'latin-1' cherrytemplate.defaultInputEncoding = 'latin-1' cherrytemplate.defaultTemplateDir = 'media/templates/'
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.
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
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
We will define two more exposed methods to our Blog class.
Example 2.7.
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
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
The core functionnailities will be held in some scripts that we are going to study in the following sections.
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
_dbLocker = threading.Lock()
class BlogBackendManager:
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