Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cherrypy-2.1/docs/book/xml/buildingblog.xml

Revision 749 (checked in by lawouach, 3 years ago)

Set the building blog section as being rewritten from scratch soon

Line 
1 <?xml version="1.0" encoding="utf-8"?>
2 <section xmlns:db="http://docbook.org/docbook-ng" xmlns:xi="http://www.w3.org/2001/XInclude"
3   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:id="buildingblog">
4   <title>A real case application : A blog.</title>
5   <section>
6     <sectioninfo>
7       <abstract>
8       <para><emphasis>This section is about to be rewritten completely in a better way. So feel free to have al ook at it but don't spend too much time on it.</emphasis></para>
9       <para>The following sections will describe the step to build a simple blogging system. It
10           does not aim to create a comprehensive one but to give you an overview of how to build a
11           CherryPy application.</para>
12       </abstract>
13     </sectioninfo>
14     <title>Features of the blog</title>
15     <para>The blogging system will feature some basic functionalities such as :</para>
16     <itemizedlist>
17       <listitem>Add new entries</listitem>
18       <listitem>Delete existing entries</listitem>
19       <listitem>Add a new comment</listitem>
20       <listitem>Display a calendar</listitem>
21       <listitem>Use positional parameters</listitem>
22       <listitem>Present a RSS feed</listitem>
23     </itemizedlist>
24   </section>
25   <section>
26     <title>Setting up the project</title>
27     <para>Our first step will be to define the directory structure of the projet. This structure is
28       only one amongst others of course.</para>
29     <example>
30       <title>Directory structure of the blog project</title>
31       <para>Imagine our root directory is called <code>blog</code>, then the structure will be :</para>
32       <screen>
33       <co id="blogpy"/> blog/blog.py
34       <co id="license"/> blog/LICENSE
35       <co id="readme"/> blog/README
36       <co id="media"/> blog/media/
37       <co id="mediaconf"/> blog/media/conf/
38       <co id="mediacss"/> blog/media/css/
39       <co id="mediadb"/> blog/media/db/
40       <co id="mediafeed"/> blog/media/feed/
41       <co id="mediatemplates"/> blog/media/templates/
42       <co id="mediautils"/> blog/media/utils/
43       <co id="src"/> blog/src/
44       </screen>
45       <calloutlist>
46         <callout arearefs="blogpy">
47           <para>This is the main script of our application. It contains the code to start up the web
48             server and the application. </para>
49         </callout>
50         <callout arearefs="license">
51           <para>Your application should always contain a license. </para>
52         </callout>
53         <callout arearefs="readme">
54           <para>Your application should also come with a readme file. </para>
55         </callout>
56         <callout arearefs="media">
57           <para>We will divide the directory structure into two main sub directories. One will deal
58             with the source code itself, and the other one will deal with all the data you might
59             work with. </para>
60         </callout>
61         <callout arearefs="mediaconf">
62           <para>Contains the configuration file we will define for the application. </para>
63         </callout>
64         <callout arearefs="mediacss">
65           <para>Contains the CSS stylesheet we will be using. </para>
66         </callout>
67         <callout arearefs="mediadb">
68           <para>Our blogging system will use a very simple interface to store content as you will
69             see later. This directory will hold the data. </para>
70         </callout>
71         <callout arearefs="mediafeed">
72           <para>We will only support RSS here but as you may extend yourself the applictaion, this
73             directory will let you hold all different kind of feed format files. </para>
74         </callout>
75         <callout arearefs="mediatemplates">
76           <para>Contains all templating files we will be using. </para>
77         </callout>
78         <callout arearefs="mediautils">
79           <para>Where to put some helper files we will need. </para>
80         </callout>
81         <callout arearefs="src">
82           <para>Contains the source code itself. </para>
83         </callout>
84       </calloutlist>
85     </example>
86     <para>Again this structure is not required by CherryPy itself. It is a choice made for this
87       tutorial.</para>
88   </section>
89   <section>
90     <title>Design consideration</title>
91     <para/>
92   </section>
93   <section>
94     <sectioninfo>
95       <abstract>
96         <para/>
97       </abstract>
98     </sectioninfo>
99     <title>Tools</title>
100     <section>
101       <title>The backend: persistence</title>
102       <para>Mainly one can look at four the following directions when deciding to store and persist
103         content :</para>
104       <itemizedlist>
105         <listitem>Native SQL driven database</listitem>
106         <listitem>Native XML driven database</listitem>
107         <listitem>Native Objects driven database</listitem>
108       </itemizedlist>
109       <para>By far the first choice is the most used at any rate. It is efficient, reliable,
110         well-known and powerful. XML and native object database are gaining some success slowly but
111         are still quite young.</para>
112       <para>The solution we have chosen is non of the above for keeping this tutorial simple. We do
113         want to avoid having to import too many external modules. Besides since this blogging system
114         is so simple, we can use another way to store and persist our content : pickling pure python
115         objects thanks to the standard <ulink url="http://docs.python.org/lib/module-shelve.html"
116           >shelve</ulink>module.</para>
117       <para>
118         <blockquote> A ``shelf&apos;&apos; is a persistent, dictionary-like object. The
119           difference with ``dbm&apos;&apos; databases is that the values (not the keys!) in
120           a shelf can be essentially arbitrary Python objects -- anything that the pickle module can
121           handle. This includes most class instances, recursive data types, and objects containing
122           lots of shared sub-objects. The keys are ordinary strings. </blockquote>
123       </para>
124       <para>This solution allows us to focus on CherryPy and not too much on the backend
125       itself.</para>
126     </section>
127     <section>
128       <title>Templating : presentation</title>
129       <para>A templating language is meant to offer the developer and easy way to transform an input
130         into an output of his/her choice. In the web application field, usually it refers to the
131         presentation system to transform the content into nice XHTML output sent to the browser.</para>
132       <para>The CherryPy developers have made the clear choice of not providing any default
133         templating system. The main reason is that CherryPy must to stay at low level and developers
134         should not have the feeling like they must use a particular templating system. CherryPy
135         allows actually the developer to choose his/her favourite templating system and work with
136         it. </para>
137       <para>We could have written this blogging system by generating the HTML directly from the
138         source code. Although this is totally possible we have felt it would be much cleaner to use
139         a dedicated templating language. Our choise was <ulink
140           url="http://cherrytemplate.python-hosting.com/">CherryTemplate</ulink> written and
141         maintained by Remi Delon. It is a small package to install and which provides the basic
142         functionnalities we will need : loop, condition, etc. Its learning curve is really
143       low.</para>
144     </section>
145   </section>
146   <section>
147     <title>Building the application</title>
148     <section>
149       <title>Main entry point</title>
150       <para>The first file we need to create is the <filename>blog.py</filename> file which will be
151         the entry point of the blog.</para>
152       <section>
153         <title>Importing required modules</title>
154         <para>First we need to import the modules we will be using from this entry point.</para>
155         <example>
156           <title>Blogging system - import modules</title>
157           <programlisting linenumbering="numbered">
158     <co id="modsys"/> import sys
159     import time
160            
161     <co id="sysapp"/>  sys.path.append(&apos;src&apos;)
162            
163     <co id="modcp"/>  import cherrypy
164     <co id="modct"/>  from cherrytemplate import cherrytemplate, renderTemplate
165
166     <co id="modother"/>
167     from entryManager import BlogEntryManager
168     from commentManager import BlogCommentManager
169     from comment import BlogComment
170     from admin import BlogAdmin
171     from cal import getCurrentCalendar
172           </programlisting>
173           <calloutlist>
174             <callout arearefs="modsys">
175               <para>We use the sys module to be able to add a directory to the search path where
176                 Python looks for modules.</para>
177             </callout>
178             <callout arearefs="sysapp">
179               <para>Here we add the sub-directory <code>src</code> to the Python search path.</para>
180             </callout>
181             <callout arearefs="modcp">
182               <para>CherryPy requires only one import. In version 2.0 of CherryPy, it used to be
183                   <code>from cherrypy import cpg</code> but not any longer.</para>
184             </callout>
185             <callout arearefs="modct">
186               <para>Import two modules from CherryTemplate. The first one is used to modify some
187                 global variable of CherryTemplate as we will see later on. The second one is the
188                 actual function to parse a template file and fetch the output.</para>
189             </callout>
190             <callout arearefs="modother">
191               <para>The last modules imported are those we will write later on in this
192               tutorial.</para>
193             </callout>
194           </calloutlist>
195         </example>
196       </section>
197       <section>
198         <title>The main class</title>
199         <example>
200           <title>Blogging system - the root class</title>
201           <programlisting linenumbering="numbered">
202 <co id="classname"/>class Blog:
203   def __init__(self):
204     <co id="classinit"/>
205     self.em = BlogEntryManager()
206     self.cm = BlogCommentManager()
207     cherrytemplate.defaultOutputEncoding = &apos;latin-1&apos;
208     cherrytemplate.defaultInputEncoding = &apos;latin-1&apos;
209     cherrytemplate.defaultTemplateDir = &apos;media/templates/&apos;
210           </programlisting>
211           <calloutlist>
212             <callout arearefs="classname">
213               <para>CherryPy needs at least one python callable at its root (eg: a class or a
214                 function). Here we will eb using a class called Blog. </para>
215             </callout>
216             <callout arearefs="classinit">
217               <para>Since a CherryPy application runs as a long process on your system, we can
218                 define variables that will be shared during the full run of the process. In this
219                 case we need a manager of entries and comments, classes tha we will define later on.
220                 Wel also define some global variable used by CherryTemplate.</para>
221             </callout>
222           </calloutlist>
223         </example>
224       </section>
225       <section>
226         <title>Defining an index and a default method</title>
227         <para>CherryPy does not force you to define any kind of methods but <code>index</code> and
228             <code>default</code> are the ones CherryPy will look for by default. The
229           <code>index()</code> method is the equivalent to the index.html page used by default by
230           Apache. The <code>default()</code> method is called by CherryPy when all other lookup
231           fail. If CherryPy doesn&apos;t find any matching callable object, it will issue an
232           exception. <code>index()</code> is by far the most common one to be defined.</para>
233         <para>Before a method can be used to serve content it needs to be <emphasis role="bold"
234             >published</emphasis> and <emphasis role="bold">exposed</emphasis>. Publishing is the
235           process of attaching an instance of a given callable to the main cherrypy object tree as
236           we will see later. Exposing is done by setting the exposed attribute to the method that we
237           want to be able to call from the client side.</para>
238         <para>Now we can define both methods as follow.</para>
239         <example>
240           <programlisting>
241  <co id="index"/>
242     def index(self):
243          entries = self.em.fetchLastEntries()
244          cal = getCurrentCalendar()
245          rssPath = cherrypy.request.base + &apos;/media/feed/rss.xml&apos;
246          return renderTemplate(file = "Blog_show_entry.tmpl")
247     index.exposed = True
248            
249  <co id="default"/>
250     def default(self, *args):
251          cal = getCurrentCalendar()
252          rssPath = cherrypy.request.base + &apos;/media/feed/rss.xml&apos;
253          if len(args) == 3:
254              date = "%s-%s-%s" % (args[0], args[1], args[2])
255              entries = self.em.fetchEntriesByDate(date)
256          elif len(args) == 4:
257              entry = self.em.fetchEntry(args[0], args[1], args[2], args[3])
258          if not entry:
259              return renderTemplate(file = "error404.tmpl")
260              entries = [entry]
261          else:
262              return self.index()
263          return renderTemplate(file = "Blog_show_entry.tmpl")
264     default.exposed = True
265           </programlisting>
266           <calloutlist>
267             <callout arearefs="index">
268               <para>When the index method is called we fetch the last entries added to the blog as
269                 well as the calendar of the current month (we will see later how to build it). Then
270                 we define the RSS feed path.</para>
271               <para>Those three variables will be transparently passed to the CherryTemplate which
272                 is rendered through the <code>renderTemplate()</code> function. Since we have
273                 defined the templates directory as a global variable of CherryTemplate, we simply
274                 need to provide the name of the template itself to the method.</para>
275               <para>We will then serve to the client the string resulting from the rendering,
276                 although CherryPy allows you to return a string, a list, a generator to serve the
277                 content</para>
278               <para>
279                 <code>index.exposed = True</code> tells CherryPy that this method is exposed and
280                 available to serve content.</para>
281             </callout>
282             <callout arearefs="default">
283               <para>The default method is called when CherryPy failed to map the requested URL to an
284                 exposed object in the published objects tree, but you can also use it to slightly
285                 change CherryPy&apos;s behavior.</para>
286               <para>In this particular context we will use <code>default()</code> allow URL with
287                 positional parameters such as : http://myhost/2005/06/18/78, which would be the
288                 equivalent to :
289                 http://myhost/?year=2005&amp;month=06&amp;day=18&amp;entry=78. The
290                 former URL looks nicer and is more search engine friendly.</para>
291               <para>This behavior is not automatically handled by CherryPy and using the default
292                 method is the most common workaround.</para>
293               <para>First we get the current month calendar then we set the RSS feed path. Then we
294                 check what to do next by testing the number of passed arguments to the request. If
295                 this equals to three then the user asked to view a full day and all its entries. If
296                 it equals to four he specified the entry id as well. Otherwise we simply fallback to
297                 callback <code>index()</code>.</para>
298             </callout>
299           </calloutlist>
300         </example>
301       </section>
302       <section>
303         <title>Serving more content</title>
304         <para>We will define two more exposed methods to our <code>Blog</code> class.</para>
305         <example>
306           <programlisting linenumbering="numbered">
307  <co id="comment"/>
308 def comment(self, id):
309     entryId = id
310     entry = self.em.fetchEntryById(id)
311     comments = self.cm.fetchCommentsByEntry(id)
312     cal = getCurrentCalendar()
313     rssPath = cherrypy.request.base + &apos;/media/feed/rss.xml&apos;
314     return renderTemplate(file = "Blog_comment_add.tmpl")
315 comment.exposed = True
316  
317  <co id="addcomment"/>
318 def addComment(self, entryId, title, author, message):
319     rssPath = cherrypy.request.base + &apos;/media/feed/rss.xml&apos;
320     self.cm.addComment(BlogComment(title, author, message, entryId))
321     return self.comment(entryId)
322 addComment.exposed = True
323           </programlisting>
324           <calloutlist>
325             <callout arearefs="comment">
326               <para>Called when an user wants to read the comments added to an entry.</para>
327             </callout>
328             <callout arearefs="addcomment">
329               <para>Called when an user submitted a new comment through the HTML form.</para>
330             </callout>
331           </calloutlist>
332         </example>
333       </section>
334       <section>
335         <title>Setting up the tree and the server</title>
336         <para>Finally we need to set up the CherryPy tree of published objects, specify what
337           settings we need and finally start the server.</para>
338         <example>
339           <programlisting linenumbering="numbered">
340 <co id="tree"/>
341     cherrypy.root = Blog()
342     cherrypy.root.admin = BlogAdmin()
343
344 <co id="config"/>
345     cherrypy.config.update(file=&apos;media/conf/Blog.conf&apos;)
346 <co id="server"/>
347     cherrypy.server.start()
348           </programlisting>
349           <calloutlist>
350             <callout arearefs="tree">
351               <para>Define the tree structure. Our blogging system only publish two objects.</para>
352             </callout>
353             <callout arearefs="config">
354               <para>Our settings will be defined in a seperate file. We could have defined them
355                 inline as a Python dictionnary.</para>
356             </callout>
357             <callout arearefs="server">
358               <para>Let&apos;s start the server.</para>
359             </callout>
360           </calloutlist>
361         </example>
362       </section>
363     </section>
364     <section>
365       <title>Building the core</title>
366       <para>The core functionnailities will be held in some scripts that we are going to study in
367         the following sections.</para>
368       <section>
369         <title>The backend</title>
370         <para>We will define a file called <filename>backendManager.py</filename> located under the
371             <filename>src</filename> directory. This file will define a class and its set of methods
372           to facilitate access and queries to our backend. Basically we will follow the CRUD
373           interface : Create, Retrieve, Update and Delete. Following is a snippet code from that
374           module:</para>
375         <example>
376           <programlisting linenumbering="numbered">
377           import shelve
378           import threading
379           import time
380           <co id="dblock"/>
381           _dbLocker = threading.Lock()
382          
383           class BlogBackendManager:
384               <co id="fetchall"/>
385               def fetchAll(self):
386                   data = []
387                   try:
388                     _dbLocker.acquire()
389                     db = shelve.open(&apos;media/db/blob.db&apos;, &apos;r&apos;)
390                     data = db.keys()
391                     if db: db.close()
392                   finally:
393                     _dbLocker.release()
394                   return data
395         </programlisting>
396           <calloutlist>
397             <callout arearefs="dblock">We will hold a global lock to ensure the database access will
398               be protected. This is needed since CherryPy is a multithreaded application, each
399               thread handling one user agent request. As you can notice this lock is global to the
400               entire application, therefore no more than one thread will be able to access the
401               database at one given time. This is a very simplistic principle and not a usable one
402               on a larger application, but as we already said, our purpose is to show how to build
403               an application without too much complexity.</callout>
404             <callout arearefs="fetchall">This method will simply connect to the database and fetch
405               all entries. We make sure the lock is always released by calliong the release method
406               within the <code>finally</code>statement, thus whatever happens after the lock has
407               been acquired it will be released.</callout>
408           </calloutlist>
409         </example>
410       </section>
411       <section>
412        
413       </section>
414     </section>
415   </section>
416   <section>
417     <sectioninfo>
418       <abstract>
419         <para/>
420       </abstract>
421     </sectioninfo>
422     <title>Running the application</title>
423   </section>
424 </section>
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets