HTML templates

I'm still not sure this is better solution, but here it is

I've written small parsing/rendering model which takes HTML template with inserted Python code and renders it. I named it uPytestry, by Java Tapestry Framework which I've been using for 2 years for now. Tapestry is very complex framework and there are plenty if existing Python templating frameworks. But my intention was to write engine with only Python code allowed within HTML templates, ie no special commands or processing instructions.

Whole source code is in upytestry.py file from attached archive evcal.tar.gz.

Description

There are 3 forms to include Python code

  1. <py code="py:python code here"/> just executes python code

  2. <py code="py:python code here"> ... body ... </py> whereas 3 local variables can be used in pyton code

    1. body local variable refers to internal body

    2. formatter is current formatter used (may be buffer or null file)

    3. renderBody is convinient method call variable (see example below)

  3. <anytag anyparam="py:python code here"> evaluates python expression and sets value of parameter

Quick example

Next template

<table>
  <py code="py:for self.c in ['a','b','c']: renderBody(body,formatter)">
  <tr class="py:self.c+'Class'">
    <td>
      <p> A,B or C - <py code="py:formatter.write(self.c)"/> ?
    </td>
  <tr>
  </py>

  <py code="py:for self.c in ['d','e','f']: self.buffers.render(formatter, body)">
  <tr class="header">
    <py code="py:self.buffers.renderBlock('headerblock', body)"><td>
      <p> A,B or C - <py code="py:formatter.write(self.c)"/> ?
    </td></py>
  <tr>
  </py>
  <py code="py:self.buffers.flush()"/>

</table>

generates this HTML {{{<table>

</table> }}} with next python code used

   1 p = Parser()
   2 p.feed(open("test.html.tmpl").read())
   3 
   4 print p.html
   5 
   6 class TestRenderer(Renderer):
   7     def __init__(self, template):
   8         Renderer.__init__(self, template)
   9         self.buffers = Buffers(self)
  10         
  11 r = TestRenderer(template=p.html)
  12 r.render(formatter=sys.stdout)

How it works

Parser class takes HTML template, parses it and creates tree of strings and tuples where each tuple is in form (tagName, attributes, body).

The above example parsed template looks this way

['\n<table>\n  ', ('py', [('code', <code object <interactive> at 0xb7dddba0, file "errors", line 1>)], ['\n  ', ('tr', [('class', <code object <expression> at 0xb7db77e0, file "errors", line 1>)], ['\n    <td>\n      <p> A,B or C - ', ('py', [('code', <code object <interactive> at 0xb7db7820, file "errors", line 1>)], None), ' ?\n    </td>\n  <tr>\n  '])]), '\n\n  ', ('py', [('code', <code object <interactive> at 0xb7a8b160, file "errors", line 1>)], ['\n  <tr class="header">\n    ', ('py', [('code', <code object <interactive> at 0xb7a8b720, file "errors", line 1>)], ['<td>\n      <p> A,B or C - ', ('py', [('code', <code object <interactive> at 0xb7dddee0, file "errors", line 1>)], None), ' ?\n    </td>']), '\n  <tr>\n  ']), '\n  ', ('py', [('code', <code object <interactive> at 0xb7a8b460, file "errors", line 1>)], None), '\n\n</table>']

Renderer class takes this tree, outputs strings to formatter and executes compiled Python code within its renderBody() method.

And there is yet another class Buffers which is extension of Renderer class. It accumulates output to internal anonymous and named buffers and writes it to formatter when it's flush() method is called. Code is written to named buffer when there is buffers.renderBlock(bufferName, body) call and all other code is written to anonymous buffers. HTML that goes to anonymous buffers is rendered only ONCE between flush() calls, so as can be seen from example above we can iterate over TR tags and accumulate TD tags to buffers.

EventCalendar templates

Of course I would not post here if I hadn't any progress with EventCalendar templates. Currently I have 2 templates for Month and Week calendar. They both use EventTable class, described later and generate HTML similar to original, but still incomplete. You could see them in attached archive evcal.tar.gz.

(!) To get output run evcaltest.py and it will create two HTML files: monthly.html and weekly.html.

More ideas

Tapestry Framework main idea is component - reused piece of code and template. There are some HTML in EventCalendar which is used in several places, so it is worth adding something similar to uPytestry.

Refactor to OOP

While developing uPytestry I also created some helper and model classes.

(!) Currently I give very brief description of classes.

Model? classes

Event is superclass for all event classes. Event class examples can be MoinWikiEvent - collects events from MoinMoin pages, DatabaseEvent - reads event from some database, ExternalICalEvent - reads from external site using iCal protocol.

For each event there is EventSource class which collects and returns events for some period specified in constructor, eg MoinWikiEventSource, DatabaseEventSource.

All helper and other classes work with ONLY Event class, ie they cannot use any features of Event subclasses.

Helper classes

Events holds and manages events, it takes list of EventSource objects and requests events for desired period. Internally it creates two event lists sorted by start and end dates.

EventIterator class takes Events object, start/end dates and can iterate over this period with some time step. On every iteration it returns set of events active in current step. (!) This class implementation can be greatly optimized by using Events class sorted lists, I just wrote most simple one.

EventTable class constructs table of events for some period using EventIterator class. This class is used for month and week calendar tables to render HTML. EventTable consists of rows, row groups and special column group. I will write more on this when someone needs it.

Calendar render classes

MonthCalendarRenderer and WeekCalendarRenderer classes are just renderers which use EventTable class and HTML templates. They are really short, eg 2/3 of MonthCalendarRenderer takes initialization code which calculates start date for given month calendar.

Refactoring steps

It's dificult to divide refactoring into steps, so I think we need to do it all at once. Main ideas are:

  1. All HTML render code should be moved to evcal.render module classes (MonthCalendarRenderer etc) and HTML templates.

  2. Event search code should be moved to evcal.moin module classes (MoinEventSource)

  3. All Global, Params and helper functions should be moved to evcal or evcal.util modules.

To get overview of current EventCalendar implementation I wrote small program, which parses EventCalendar.py and creates function call graph. I removed some functions like debug(), formatDate() and loadEvents() and attached generated showcal.png.

And before I take any refactor steps I need comments on all these ideas :) OlegBatrashev

MoinMoin: MacroMarket/EventCalendar/RefactoringProposals (last edited 2007-10-29 19:11:01 by localhost)