hi there,

i've recently started to use webware and cheetah templates in a new way.
since i'm experimenting successfully with such approach i feel it's useful to illustrate the process, mostly to get feedback.


we start by marking a master page with zones to be replaced. the master page will dictate the layout of the site, it can contains HTML markup, flash objects, images and so on.

in the following example we just have a "title" and "content" zones that site's servlets are likely to replace with dynamic content:

<!-- file: site.html -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";>
<html xmlns="http://www.w3.org/1999/xhtml";>
<head>
<title>$page.title</title>
</head>

<body>

<div id="main">

  <div id="header">
    <h1>$page.title</h1>
  </div>

  <div id="content">
    $page.content($page)  <!-- Note: pass $page to content too -->
  </div>

</div>

</body>
</html>

servlets of our site will, as usual, inherit from a root servlet, commonly named SitePage. a SitePage class might looks like this:

class SitePage(Page):

  def title(self):
    return 'Some title'

  def content(self):
    return '[content placeholder]'

  def awake(self, transaction):
    Page.awake(self, transaction)  # needed

#@@ acquire pooled DB connection

self.load(transaction.request())

  # sub classes are free to override load
  def load(self, request):
      pass

  def sleep(self, transaction):
    Page.sleep(self, transaction)  # needed

#@@ release pooled DB connection

  def writeHTML(self):
    self.write(self.html(self))

  # master page template
  html = DiskTemplate('site.html')

  def preAction(self, actionName):
    """Override default Page behavior."""
    pass

  def postAction(self, actionName):
    """Render a page after action."""
    self.writeHTML()

a DiskTemplate object (more on that later) will be asked to load the HTML file from disk. this will occur once, when servlet will be loaded in memory by webware. internally DiskTemplate will compile the HTML
file (using Cheeatah) and store it in the "html" class variabile.


the writeHTML method will ask to such template to render itself
as a string and then the usual self.write method will send template
output to the browser. note how the servlet passes itself as a namespace
to the DiskTemplate, this will allow us to access all the servlet internals via the $page template's variable.


this is the code of DiskTemplate:

class DiskTemplate(object):

    # template cache
    cache = {}

    def __init__(self, path):
        self.path = path

def __call__(self, page):

        if self.path in self.cache:
            tmpl = self.cache[self.path]
        else:
            app = page.application()
            s = app.serverSidePath('www/t/%s' % self.path)

            # if file changes reload app (only with AutoReload = True)
            #@@ modloader.watchFile(s)
            tmpl = Template(file=s, filter=Unicode2Latin1)

            # store in cache
            self.cache[self.path] = tmpl

        # pass caller servlet as namespace
        tmpl.page = page
        return str(tmpl)

DiskTemplate keeps a cache of the loaded templates. actually i'm
not sure this is really needed in a normal scenario.

once we have setup your SitePage we start to subclass it
to provide dynamic contents for all *or some* of the marked zones. to bring back the original example if we miss to re-implement "title" and "content" methods python will fall-back to use SitePage corresponding implementations. one of my servlets looks like this:


from base import SitePage, MemoryTemplate
import catalogdb # data-access module

class index(SitePage):

    def load(self, request):
        #@@ let's pretend self._cnn holds an active DB connection
        #@@ get POST/GET params via request('someparam', '')
        self._categories = catalogdb.rootCategories(self._cnn)

    def content(self):
        '''
        #for $category in $page._categories
          <div class="entry">
            <p>
              <a href="/cat?id=$category.node_id">$category.title</a>
            </p>
            <p class="dtm">$category.description</p>
          </div>
        #end for
        #unless $page._categories
          <h2>Oops!</h2>\n<p>Category not found.</p>
        #end unless
        '''

content = MemoryTemplate(content)

the above servlet loads some catalogue categories from db and saves
the list in self._categories. servlet re-implements SitePage "content"
to provide some custom HTML markup bound to loaded DB records. content method uses the docstring as cheetah code to generate markup.


immediately after the docstring we rebind "content" to a MemoryTemplate object. again MemoryTemplate will compile only once he Cheetah template and keep it in memory.

this is the code of MemoryTemplate:

class MemoryTemplate(object):

    def __init__(self, fn):
        self.tmpl = Template(fn.__doc__, filter=Unicode2Latin1)

    def __call__(self, page):
        # pass caller servlet as namespace
        self.tmpl.page = page
        return str(self.tmpl)

the code is similar in purpose to the DiskTemplate one. if we need
to load from disk the Cheetah code we just write:

content = DiskTemplate('content-for-index.html')

the reason to keep the templates on disk or inside servlets it's really a matter of how much logic VS markup holds our templates. if we need
to allow our peers to keep tweaking the markup so let's keep that on disk, otherwise keep 'em within the servlet.


so everything is fine and dandy, but what if you have one or more
servlets that need to have a different master page (an home page
is a typical use-case). well, we would write:

class home(SitePage):

    # master template
    html = DiskTemplate('home.html')

    def content(self):
        '''
        ...your custom cheetah page...
        '''

content = MemoryTemplate(content)

the above code replaces both "html" and "content" templates.

there's a little gotcha to make everything to work correctly:
in the master template you have to pass $page to the marked zone
in order to be able to access $page again in sub-classes.
that's why in master page i've written:

  <div id="content">
    $page.content($page)
  </div>

right now i'm not able to elimite this quirk. maybe with some
sys._getframe(1) hacking i could grab the caller instance but
it's risky to rely on sys._getframe.

note that zones are really only required to return strings, nothing prevent us to just return strings for simpler task--just check SitePage.title implementation.

finally, with the new function decorators planned for python 2.4
authors will be able to collapse content / content = (Disk|Memory)Template(content) on a single line:


def content(self) [MemoryTemplate]:

but don't trust me on the syntax sugar to do that, there's
still some debate in the air.

hope this helps.

cheers,
deelan

---
http://www.deelan.com/









-------------------------------------------------------
This SF.Net email is sponsored by Sleepycat Software
Learn developer strategies Cisco, Motorola, Ericsson & Lucent use to deliver higher performing products faster, at low TCO.
http://www.sleepycat.com/telcomwpreg.php?From=osdnemail3
_______________________________________________
Webware-discuss mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/webware-discuss

Reply via email to