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

Changeset 813

Show
Ignore:
Timestamp:
11/11/05 10:24:31
Author:
lawouach
Message:

started from scratch Chapter 3

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/docs/book/xml/buildingblog.xml

    r749 r813  
    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> 
     1<?xml version='1.0'?> 
     2<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3b2/docbookx.dtd"> 
     3<section xml:id="buildingblog" xmlns:db="http://docbook.org/docbook-ng" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
     4 
     5  <title>A real case application : A blog</title> 
     6 
    57  <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> 
     8 
     9    <title>Overview</title> 
     10 
     11    <para>In this section we will be working around a blog system in  
     12    order to have an overview of how to build applications with  
     13    CherryPy. Of course we do not pretend to define the unique way to  
     14    layout an application with CherryPy, this is a practical  
     15    introduction to CherryPy.</para> 
     16 
     17    <para>We will cover the following topics:</para> 
     18 
     19    <para> 
     20 
     21      <itemizedlist> 
     22 
     23        <listitem> 
     24 
     25          <para>Layout of the package. In that section we will see how  
     26          to setup a generic structure for the package.</para> 
     27 
     28        </listitem> 
     29 
     30        <listitem> 
     31 
     32          <para>Backend support. In that section we will discuss the  
     33          different backend once can use for an application and the  
     34          implication of such choices.</para> 
     35 
     36        </listitem> 
     37 
     38        <listitem> 
     39 
     40          <para>Template support. That section will explain how  
     41          CherryPy works with templating systems.</para> 
     42 
     43        </listitem> 
     44 
     45      </itemizedlist> 
     46 
     47    </para> 
     48 
    2449  </section> 
     50 
    2551  <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> 
     52 
     53    <title>Requirements</title> 
     54 
     55    <para>We will be describing the product opkee! in its version 1.0.  
     56    You will therefore need to download it at  
     57    <link>http://www.defuze.org/oss/opkee/</link>.</para> 
     58 
    8859  </section> 
     60 
    8961  <section> 
    90     <title>Design consideration</title> 
    91     <para/> 
     62 
     63    <title>Layout of the package</title> 
     64 
    9265  </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 
    16566 
    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() 
     67</section> 
    34368 
    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> 

Hosted by WebFaction

Log in as guest/cpguest to create tickets