merge tls-sprint
authorsylvain.thenault@logilab.fr
Tue, 05 May 2009 17:13:30 +0200
branchtls-sprint
changeset 1689 7ff9f0726ee4
parent 1669 04a215f2ff43 (current diff)
parent 1688 1f16db872f92 (diff)
child 1690 e4f7d2ddc99a
merge
--- a/cwconfig.py	Tue May 05 17:12:53 2009 +0200
+++ b/cwconfig.py	Tue May 05 17:13:30 2009 +0200
@@ -677,7 +677,7 @@
         return join(self.instance_data_dir(), self.appid)
 
     def init_cubes(self, cubes):
-        assert self._cubes is None
+        assert self._cubes is None, self._cubes
         self._cubes = self.reorder_cubes(cubes)
         # load cubes'__init__.py file first
         for cube in cubes:
--- a/server/serverctl.py	Tue May 05 17:12:53 2009 +0200
+++ b/server/serverctl.py	Tue May 05 17:13:30 2009 +0200
@@ -139,6 +139,8 @@
             return in_memory_cnx(config, login, pwd)
         except AuthenticationError:
             print 'wrong user/password'
+            # reset cubes else we'll have an assertion error on next retry
+            config._cubes = None
         login, pwd = manager_userpasswd()
 
 # repository specific command handlers ########################################
--- a/web/data/cubicweb.preferences.js	Tue May 05 17:12:53 2009 +0200
+++ b/web/data/cubicweb.preferences.js	Tue May 05 17:13:30 2009 +0200
@@ -4,7 +4,7 @@
  *     move me in a more appropriate place
  */
 
-function toggleVisibility(elemId) {
+function toggleVisibility(elemId, cookiename) {
     _clearPreviousMessages();
     jqNode(elemId).toggleClass('hidden');
     asyncRemoteExec('set_cookie', cookiename,
--- a/web/form.py	Tue May 05 17:12:53 2009 +0200
+++ b/web/form.py	Tue May 05 17:13:30 2009 +0200
@@ -20,7 +20,7 @@
 from cubicweb.web.controller import NAV_FORM_PARAMETERS
 from cubicweb.web.formfields import (Field, StringField, RelationField,
                                      HiddenInitialValueField)
-from cubicweb.web.formrenderers import FormRenderer
+from cubicweb.web import formrenderers
 from cubicweb.web import formwidgets as fwdgs
 
 class FormViewMixIn(object):
@@ -220,6 +220,7 @@
     __registry__ = 'forms'
     __select__ = yes()
 
+    renderer_cls = formrenderers.FormRenderer
     is_subform = False
 
     # attributes overrideable through __init__
@@ -319,7 +320,7 @@
         """render this form, using the renderer given in args or the default
         FormRenderer()
         """
-        renderer = values.pop('renderer', FormRenderer())
+        renderer = values.pop('renderer', self.renderer_cls())
         return renderer.render(self, values)
 
     def form_build_context(self, rendervalues=None):
--- a/web/formrenderers.py	Tue May 05 17:12:53 2009 +0200
+++ b/web/formrenderers.py	Tue May 05 17:13:30 2009 +0200
@@ -328,7 +328,7 @@
             super(EntityFormRenderer, self).render_buttons(w, form)
 
     def relations_form(self, w, form):
-        srels_by_cat = form.srelations_by_category(('generic', 'metadata'), 'add')
+        srels_by_cat = form.srelations_by_category('generic', 'add')
         if not srels_by_cat:
             return u''
         req = form.req
--- a/web/views/baseviews.py	Tue May 05 17:12:53 2009 +0200
+++ b/web/views/baseviews.py	Tue May 05 17:13:30 2009 +0200
@@ -16,7 +16,7 @@
 
 from rql import nodes
 
-from logilab.mtconverter import TransformError, html_escape
+from logilab.mtconverter import TransformError, html_escape, xml_escape
 
 from cubicweb import NoSelectableObject
 from cubicweb.selectors import yes, empty_rset
@@ -338,7 +338,7 @@
         highlighted = '<b>%s</b>' % searched
         for attr in entity.e_schema.indexable_attributes():
             try:
-                value = html_escape(entity.printable_value(attr, format='text/plain').lower())
+                value = xml_escape(entity.printable_value(attr, format='text/plain').lower())
             except TransformError, ex:
                 continue
             except:
--- a/web/views/boxes.py	Tue May 05 17:12:53 2009 +0200
+++ b/web/views/boxes.py	Tue May 05 17:13:30 2009 +0200
@@ -136,10 +136,10 @@
         eschema = entity.e_schema
         for rschema, teschema, x in self.add_related_schemas(entity):
             if x == 'subject':
-                label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
+                label = '%s %s %s %s' % (eschema, rschema, teschema, x)
                 url = self.linkto_url(entity, rschema, teschema, 'object')
             else:
-                label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
+                label = '%s %s %s %s' % (teschema, rschema, eschema, x)
                 url = self.linkto_url(entity, rschema, teschema, 'subject')
             actions.append(self.mk_action(_(label), url))
         return actions
--- a/web/views/cwproperties.py	Tue May 05 17:12:53 2009 +0200
+++ b/web/views/cwproperties.py	Tue May 05 17:13:30 2009 +0200
@@ -50,7 +50,7 @@
 
 def make_togglable_link(nodeid, label, cookiename):
     """builds a HTML link that switches the visibility & remembers it"""
-    action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \
+    action = u"javascript: toggleVisibility('%s', '%s')" % \
         (nodeid, cookiename)
     return u'<a href="%s">%s</a>' % (action, label)
 
--- a/web/views/editforms.py	Tue May 05 17:12:53 2009 +0200
+++ b/web/views/editforms.py	Tue May 05 17:13:30 2009 +0200
@@ -18,7 +18,7 @@
 from cubicweb.utils import make_uid
 from cubicweb.view import EntityView
 from cubicweb.common import tags
-from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs
+from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
 from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
 from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton
 from cubicweb.web.formrenderers import (FormRenderer, EntityFormRenderer,
@@ -228,19 +228,21 @@
         """fetch and render the form"""
         # make a copy of entity to avoid altering the entity in the
         # request's cache.
+        entity.complete()
         self.newentity = copy(entity)
         self.copying = self.newentity.eid
-        self.newentity.eid = None
+        self.initialize_varmaker()
+        self.newentity.eid = self.varmaker.next()
         self.w(u'<script type="text/javascript">updateMessage("%s");</script>\n'
                % self.req._('Please note that this is only a shallow copy'))
-        super(CopyFormView, self).render_form(entity)
+        super(CopyFormView, self).render_form(self.newentity)
         del self.newentity
 
     def init_form(self, form, entity):
         """customize your form before rendering here"""
         super(CopyFormView, self).init_form(form, entity)
         if entity.eid == self.newentity.eid:
-            form.form_add_hidden('__cloned_eid', self.copying, eidparam=True)
+            form.form_add_hidden(eid_param('__cloned_eid', entity.eid), self.copying)
 
     def submited_message(self):
         """return the message that will be displayed on successful edition"""
--- a/web/views/massmailing.py	Tue May 05 17:12:53 2009 +0200
+++ b/web/views/massmailing.py	Tue May 05 17:13:30 2009 +0200
@@ -13,7 +13,8 @@
 from cubicweb.view import EntityView
 from cubicweb.web import stdmsgs
 from cubicweb.web.action import Action
-from cubicweb.web.form import FieldsForm, FormRenderer, FormViewMixIn
+from cubicweb.web.form import FieldsForm, FormViewMixIn
+from cubicweb.web.formrenderers import FormRenderer
 from cubicweb.web.formfields import StringField
 from cubicweb.web.formwidgets import CheckBox, TextInput, AjaxWidget, ImgButton
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/old_calendar.py	Tue May 05 17:13:30 2009 +0200
@@ -0,0 +1,543 @@
+"""html calendar views
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+from datetime import datetime, date, time, timedelta
+#from datetime import datetime, RelativeDateTime, date, time, Sunday
+
+from logilab.mtconverter import html_escape
+
+from cubicweb.interfaces import ICalendarViews
+from cubicweb.utils import date_range
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView
+
+# Define some useful constants
+#ONE_MONTH = RelativeDateTime(months=1)
+ONE_MONTH = timedelta(days=31)
+TODAY = date.today()
+THIS_MONTH = TODAY.month
+THIS_YEAR = TODAY.year
+# mx.DateTime and ustrftime could be used to build WEEKDAYS
+WEEKDAYS = [_("monday"), _("tuesday"), _("wednesday"), _("thursday"),
+            _("friday"), _("saturday"), _("sunday")]
+
+# used by i18n tools
+MONTHNAMES = [ _('january'), _('february'), _('march'), _('april'), _('may'),
+               _('june'), _('july'), _('august'), _('september'), _('october'),
+               _('november'), _('december')
+               ]
+
+class _CalendarView(EntityView):
+    """base calendar view containing helpful methods to build calendar views"""
+    __select__ = implements(ICalendarViews,)
+    need_navigation = False
+
+    # Navigation building methods / views ####################################
+
+    PREV = u'<a href="%s">&lt;&lt;</a>&nbsp;&nbsp;<a href="%s">&lt;</a>'
+    NEXT = u'<a href="%s">&gt;</a>&nbsp;&nbsp;<a href="%s">&gt;&gt;</a>'
+    NAV_HEADER = u"""<table class="calendarPageHeader">
+<tr><td class="prev">%s</td><td class="next">%s</td></tr>
+</table>
+""" % (PREV, NEXT)
+    
+    def nav_header(self, date, smallshift=3, bigshift=9):
+        """prints shortcut links to go to previous/next steps (month|week)"""
+        prev1 = date - RelativeDateTime(months=smallshift)
+        prev2 = date - RelativeDateTime(months=bigshift)
+        next1 = date + RelativeDateTime(months=smallshift)
+        next2 = date + RelativeDateTime(months=bigshift)
+        rql, vid = self.rset.printable_rql(), self.id
+        return self.NAV_HEADER % (
+            html_escape(self.build_url(rql=rql, vid=vid, year=prev2.year, month=prev2.month)),
+            html_escape(self.build_url(rql=rql, vid=vid, year=prev1.year, month=prev1.month)),
+            html_escape(self.build_url(rql=rql, vid=vid, year=next1.year, month=next1.month)),
+            html_escape(self.build_url(rql=rql, vid=vid, year=next2.year, month=next2.month)))
+        
+    
+    # Calendar building methods ##############################################
+    
+    def build_calendars(self, schedule, begin, end):
+        """build several HTML calendars at once, one for each month
+        between begin and end
+        """
+        return [self.build_calendar(schedule, date)
+                for date in date_range(begin, end, incr=ONE_MONTH)]
+    
+    def build_calendar(self, schedule, first_day):
+        """method responsible for building *one* HTML calendar"""
+        # FIXME  iterates between [first_day-first_day.day_of_week ;
+        #                          last_day+6-last_day.day_of_week]
+        umonth = self.format_date(first_day, '%B %Y') # localized month name
+        rows = []
+        current_row = [NO_CELL] * first_day.day_of_week
+        for daynum in xrange(0, first_day.days_in_month):
+            # build cell day
+            day = first_day + daynum
+            events = schedule.get(day)
+            if events:
+                events = [u'\n'.join(event) for event in events.values()]
+                current_row.append(CELL % (daynum+1, '\n'.join(events)))
+            else:
+                current_row.append(EMPTY_CELL % (daynum+1))
+            # store & reset current row on Sundays
+            if day.day_of_week == Sunday:
+                rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
+                current_row = []
+        current_row.extend([NO_CELL] * (Sunday-day.day_of_week))
+        rql = self.rset.printable_rql()
+        if day.day_of_week != Sunday:
+            rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
+        url = self.build_url(rql=rql, vid='calendarmonth',
+                             year=first_day.year, month=first_day.month)
+        monthlink = u'<a href="%s">%s</a>' % (html_escape(url), umonth)
+        return CALENDAR(self.req) % (monthlink, '\n'.join(rows))
+
+    def _mk_schedule(self, begin, end, itemvid='calendaritem'):
+        """private method that gathers information from resultset
+        and builds calendars according to it
+
+        :param begin: begin of date range
+        :param end: end of date rangs
+        :param itemvid: which view to call to render elements in cells
+
+        returns { day1 : { hour : [views] },
+                  day2 : { hour : [views] } ... }
+        """
+        # put this here since all sub views are calling this method        
+        self.req.add_css('cubicweb.calendar.css') 
+        schedule = {}
+        for row in xrange(len(self.rset.rows)):
+            entity = self.entity(row)
+            infos = u'<div class="event">'
+            infos += self.view(itemvid, self.rset, row=row)
+            infos += u'</div>'
+            for date in entity.matching_dates(begin, end):
+                day = Date(date.year, date.month, date.day)
+                time = Time(date.hour, date.minute, date.second) 
+                schedule.setdefault(day, {})
+                schedule[day].setdefault(time, []).append(infos)
+        return schedule
+        
+
+    @staticmethod
+    def get_date_range(day=TODAY, shift=4):
+        """returns a couple (begin, end)
+
+        <begin> is the first day of current_month - shift
+        <end> is the last day of current_month + (shift+1)
+        """
+        first_day_in_month = DateTime(day.year, day.month, 1)
+        begin = first_day_in_month - RelativeDateTime(months=shift)
+        end = (first_day_in_month + RelativeDateTime(months=shift+1)) - 1
+        return begin, end
+
+
+    def _build_ampm_cells(self, daynum, events):
+        """create a view without any hourly details.
+
+        :param daynum: day of the built cell
+        :param events: dictionnary with all events classified by hours"""
+        # split events according am/pm
+        am_events = [event for e_time, e_list in events.iteritems()
+                     if 0 <= e_time.hour < 12
+                     for event in e_list]
+        pm_events = [event for e_time, e_list in events.iteritems()
+                     if 12 <= e_time.hour < 24
+                     for event in e_list]
+        # format each am/pm cell
+        if am_events:
+            am_content = AMPM_CONTENT % ("amCell", "am", '\n'.join(am_events))
+        else:
+            am_content = AMPM_EMPTY % ("amCell", "am")
+        if pm_events:
+            pm_content = AMPM_CONTENT % ("pmCell", "pm", '\n'.join(pm_events))
+        else:
+            pm_content = AMPM_EMPTY % ("pmCell", "pm")
+        return am_content, pm_content
+
+
+
+class YearCalendarView(_CalendarView):
+    id = 'calendaryear'
+    title = _('calendar (year)')
+
+    def call(self, year=THIS_YEAR, month=THIS_MONTH):
+        """this view renders a 3x3 calendars' table"""
+        year = int(self.req.form.get('year', year))
+        month = int(self.req.form.get('month', month))
+        center_date = DateTime(year, month)
+        begin, end = self.get_date_range(day=center_date)
+        schedule = self._mk_schedule(begin, end)
+        self.w(self.nav_header(center_date))
+        calendars = tuple(self.build_calendars(schedule, begin, end))
+        self.w(SMALL_CALENDARS_PAGE % calendars)
+
+
+class SemesterCalendarView(_CalendarView):
+    """this view renders three semesters as three rows of six columns,
+    one column per month
+    """
+    id = 'calendarsemester'
+    title = _('calendar (semester)')
+
+    def call(self, year=THIS_YEAR, month=THIS_MONTH):
+        year = int(self.req.form.get('year', year))
+        month = int(self.req.form.get('month', month))
+        begin = DateTime(year, month) - RelativeDateTime(months=2)
+        end = DateTime(year, month) + RelativeDateTime(months=3)
+        schedule = self._mk_schedule(begin, end)
+        self.w(self.nav_header(DateTime(year, month), 1, 6))
+        self.w(u'<table class="semesterCalendar">')
+        self.build_calendars(schedule, begin, end)
+        self.w(u'</table>')
+        self.w(self.nav_header(DateTime(year, month), 1, 6))
+
+    def build_calendars(self, schedule, begin, end):
+        self.w(u'<tr>')
+        rql = self.rset.printable_rql()
+        for cur_month in date_range(begin, end, incr=ONE_MONTH):
+            umonth = u'%s&nbsp;%s' % (self.format_date(cur_month, '%B'), cur_month.year)
+            url = self.build_url(rql=rql, vid=self.id,
+                                 year=cur_month.year, month=cur_month.month)
+            self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (html_escape(url),
+                                                                  umonth))
+        self.w(u'</tr>')
+        _ = self.req._
+        for day_num in xrange(31):
+            self.w(u'<tr>')
+            for cur_month in date_range(begin, end, incr=ONE_MONTH):
+                if day_num >= cur_month.days_in_month:
+                    self.w(u'%s%s' % (NO_CELL, NO_CELL))
+                else:
+                    day = DateTime(cur_month.year, cur_month.month, day_num+1)
+                    events = schedule.get(day)
+                    self.w(u'<td>%s&nbsp;%s</td>\n' % (_(WEEKDAYS[day.day_of_week])[0].upper(), day_num+1))
+                    self.format_day_events(day, events)
+            self.w(u'</tr>')
+            
+    def format_day_events(self, day, events):
+        if events:
+            events = ['\n'.join(event) for event in events.values()]
+            self.w(WEEK_CELL % '\n'.join(events))
+        else:
+            self.w(WEEK_EMPTY_CELL)
+        
+
+class MonthCalendarView(_CalendarView):
+    """this view renders a 3x1 calendars' table"""
+    id = 'calendarmonth'
+    title = _('calendar (month)')
+    
+    def call(self, year=THIS_YEAR, month=THIS_MONTH):
+        year = int(self.req.form.get('year', year))
+        month = int(self.req.form.get('month', month))
+        center_date = DateTime(year, month)
+        begin, end = self.get_date_range(day=center_date, shift=1)
+        schedule = self._mk_schedule(begin, end)
+        calendars = self.build_calendars(schedule, begin, end)
+        self.w(self.nav_header(center_date, 1, 3))
+        self.w(BIG_CALENDARS_PAGE % tuple(calendars))
+        self.w(self.nav_header(center_date, 1, 3))
+
+        
+class WeekCalendarView(_CalendarView):
+    """this view renders a calendar for week events"""
+    id = 'calendarweek'
+    title = _('calendar (week)')
+    
+    def call(self, year=THIS_YEAR, week=TODAY.isocalendar()[1]):
+        year = int(self.req.form.get('year', year))
+        week = int(self.req.form.get('week', week))
+        day0 = DateTime(year)
+        first_day_of_week = (day0-day0.day_of_week) + 7*week
+        begin, end = first_day_of_week-7, first_day_of_week+14
+        schedule = self._mk_schedule(begin, end, itemvid='calendarlargeitem')
+        self.w(self.nav_header(first_day_of_week))
+        self.w(u'<table class="weekCalendar">')
+        _weeks = [(first_day_of_week-7, first_day_of_week-1),
+                  (first_day_of_week, first_day_of_week+6),
+                  (first_day_of_week+7, first_day_of_week+13)]
+        self.build_calendar(schedule, _weeks)
+        self.w(u'</table>')
+        self.w(self.nav_header(first_day_of_week))
+ 
+    def build_calendar(self, schedule, weeks):
+        rql = self.rset.printable_rql()
+        _ = self.req._
+        for monday, sunday in weeks:            
+            umonth = self.format_date(monday, '%B %Y')
+            url = self.build_url(rql=rql, vid='calendarmonth',
+                                 year=monday.year, month=monday.month)
+            monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+            self.w(u'<tr><th colspan="3">%s %s (%s)</th></tr>' \
+                  % (_('week'), monday.isocalendar()[1], monthlink))
+            for day in date_range(monday, sunday):
+                self.w(u'<tr>')
+                self.w(u'<td>%s</td>' % _(WEEKDAYS[day.day_of_week]))
+                self.w(u'<td>%s</td>' % (day.strftime('%Y-%m-%d')))
+                events = schedule.get(day)
+                if events:
+                    events = ['\n'.join(event) for event in events.values()]
+                    self.w(WEEK_CELL % '\n'.join(events))
+                else:
+                    self.w(WEEK_EMPTY_CELL)
+                self.w(u'</tr>')
+        
+    def nav_header(self, date, smallshift=1, bigshift=3):
+        """prints shortcut links to go to previous/next steps (month|week)"""
+        prev1 = date - RelativeDateTime(weeks=smallshift)
+        prev2 = date - RelativeDateTime(weeks=bigshift)
+        next1 = date + RelativeDateTime(weeks=smallshift)
+        next2 = date + RelativeDateTime(weeks=bigshift)
+        rql, vid = self.rset.printable_rql(), self.id
+        return self.NAV_HEADER % (
+            html_escape(self.build_url(rql=rql, vid=vid, year=prev2.year, week=prev2.isocalendar()[1])),
+            html_escape(self.build_url(rql=rql, vid=vid, year=prev1.year, week=prev1.isocalendar()[1])),
+            html_escape(self.build_url(rql=rql, vid=vid, year=next1.year, week=next1.isocalendar()[1])),
+            html_escape(self.build_url(rql=rql, vid=vid, year=next2.year, week=next2.isocalendar()[1])))
+
+
+        
+class AMPMYearCalendarView(YearCalendarView):
+    id = 'ampmcalendaryear'
+    title = _('am/pm calendar (year)')
+    
+    def build_calendar(self, schedule, first_day):
+        """method responsible for building *one* HTML calendar"""
+        umonth = self.format_date(first_day, '%B %Y') # localized month name
+        rows = [] # each row is: (am,pm), (am,pm) ... week_title
+        current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.day_of_week
+        rql = self.rset.printable_rql()
+        for daynum in xrange(0, first_day.days_in_month):
+            # build cells day
+            day = first_day + daynum
+            events = schedule.get(day)
+            if events:
+                current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
+            else:
+                current_row.append((AMPM_DAY % (daynum+1),
+                                    AMPM_EMPTY % ("amCell", "am"),
+                                    AMPM_EMPTY % ("pmCell", "pm")))
+            # store & reset current row on Sundays
+            if day.day_of_week == Sunday:
+                url = self.build_url(rql=rql, vid='ampmcalendarweek',
+                                     year=day.year, week=day.isocalendar()[1])
+                weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+                                                    day.isocalendar()[1])
+                current_row.append(WEEKNUM_CELL % weeklink)
+                rows.append(current_row)
+                current_row = []
+        current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (Sunday-day.day_of_week))
+        url = self.build_url(rql=rql, vid='ampmcalendarweek',
+                             year=day.year, week=day.isocalendar()[1])
+        weeklink = '<a href="%s">%s</a>' % (html_escape(url), day.isocalendar()[1])
+        current_row.append(WEEKNUM_CELL % weeklink)
+        rows.append(current_row)
+        # build two rows for each week: am & pm
+        formatted_rows = []
+        for row in rows:
+            week_title = row.pop()
+            day_row = [day for day, am, pm in row]
+            am_row = [am for day, am, pm in row]
+            pm_row = [pm for day, am, pm in row]
+            formatted_rows.append('<tr>%s%s</tr>'% (week_title, '\n'.join(day_row)))
+            formatted_rows.append('<tr class="amRow"><td>&nbsp;</td>%s</tr>'% '\n'.join(am_row))
+            formatted_rows.append('<tr class="pmRow"><td>&nbsp;</td>%s</tr>'% '\n'.join(pm_row))
+        # tigh everything together
+        url = self.build_url(rql=rql, vid='ampmcalendarmonth',
+                             year=first_day.year, month=first_day.month)
+        monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+        return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
+        
+
+
+class AMPMSemesterCalendarView(SemesterCalendarView):
+    """this view renders a 3x1 calendars' table"""
+    id = 'ampmcalendarsemester'
+    title = _('am/pm calendar (semester)')
+
+    def build_calendars(self, schedule, begin, end):
+        self.w(u'<tr>')
+        rql = self.rset.printable_rql()
+        for cur_month in date_range(begin, end, incr=ONE_MONTH):
+            umonth = u'%s&nbsp;%s' % (self.format_date(cur_month, '%B'), cur_month.year)
+            url = self.build_url(rql=rql, vid=self.id,
+                                 year=cur_month.year, month=cur_month.month)
+            self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (html_escape(url),
+                                                                  umonth))
+        self.w(u'</tr>')
+        _ = self.req._
+        for day_num in xrange(31):
+            self.w(u'<tr>')
+            for cur_month in date_range(begin, end, incr=ONE_MONTH):
+                if day_num >= cur_month.days_in_month:
+                    self.w(u'%s%s%s' % (NO_CELL, NO_CELL, NO_CELL))
+                else:
+                    day = DateTime(cur_month.year, cur_month.month, day_num+1)
+                    events = schedule.get(day)
+                    self.w(u'<td>%s&nbsp;%s</td>\n' % (_(WEEKDAYS[day.day_of_week])[0].upper(),
+                                                       day_num+1))
+                    self.format_day_events(day, events)
+            self.w(u'</tr>')
+    
+    def format_day_events(self, day, events):
+        if events:
+            self.w(u'\n'.join(self._build_ampm_cells(day, events)))
+        else:
+            self.w(u'%s %s'% (AMPM_EMPTY % ("amCell", "am"), 
+                              AMPM_EMPTY % ("pmCell", "pm")))
+
+
+class AMPMMonthCalendarView(MonthCalendarView):
+    """this view renders a 3x1 calendars' table"""
+    id = 'ampmcalendarmonth'
+    title = _('am/pm calendar (month)')
+
+    def build_calendar(self, schedule, first_day):
+        """method responsible for building *one* HTML calendar"""
+        umonth = self.format_date(first_day, '%B %Y') # localized month name
+        rows = [] # each row is: (am,pm), (am,pm) ... week_title
+        current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.day_of_week
+        rql = self.rset.printable_rql()
+        for daynum in xrange(0, first_day.days_in_month):
+            # build cells day
+            day = first_day + daynum
+            events = schedule.get(day)
+            if events:
+                current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
+            else:
+                current_row.append((AMPM_DAY % (daynum+1),
+                                    AMPM_EMPTY % ("amCell", "am"),
+                                    AMPM_EMPTY % ("pmCell", "pm")))
+            # store & reset current row on Sundays
+            if day.day_of_week == Sunday:
+                url = self.build_url(rql=rql, vid='ampmcalendarweek',
+                                     year=day.year, week=day.isocalendar()[1])
+                weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+                                                    day.isocalendar()[1])
+                current_row.append(WEEKNUM_CELL % weeklink)
+                rows.append(current_row)
+                current_row = []
+        current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (Sunday-day.day_of_week))
+        url = self.build_url(rql=rql, vid='ampmcalendarweek',
+                             year=day.year, week=day.isocalendar()[1])
+        weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+                                            day.isocalendar()[1])
+        current_row.append(WEEKNUM_CELL % weeklink)
+        rows.append(current_row)
+        # build two rows for each week: am & pm
+        formatted_rows = []
+        for row in rows:
+            week_title = row.pop()
+            day_row = [day for day, am, pm in row]
+            am_row = [am for day, am, pm in row]
+            pm_row = [pm for day, am, pm in row]
+            formatted_rows.append('<tr>%s%s</tr>'% (week_title, '\n'.join(day_row)))
+            formatted_rows.append('<tr class="amRow"><td>&nbsp;</td>%s</tr>'% '\n'.join(am_row))
+            formatted_rows.append('<tr class="pmRow"><td>&nbsp;</td>%s</tr>'% '\n'.join(pm_row))
+        # tigh everything together
+        url = self.build_url(rql=rql, vid='ampmcalendarmonth',
+                             year=first_day.year, month=first_day.month)
+        monthlink = '<a href="%s">%s</a>' % (html_escape(url),
+                                             umonth)
+        return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))      
+    
+
+    
+class AMPMWeekCalendarView(WeekCalendarView):
+    """this view renders a 3x1 calendars' table"""
+    id = 'ampmcalendarweek'
+    title = _('am/pm calendar (week)')
+
+    def build_calendar(self, schedule, weeks):
+        rql = self.rset.printable_rql()
+        w = self.w
+        _ = self.req._
+        for monday, sunday in weeks:
+            umonth = self.format_date(monday, '%B %Y')
+            url = self.build_url(rql=rql, vid='ampmcalendarmonth',
+                                 year=monday.year, month=monday.month)
+            monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+            w(u'<tr>%s</tr>' % (
+                WEEK_TITLE % (_('week'), monday.isocalendar()[1], monthlink)))
+            w(u'<tr><th>%s</th><th>&nbsp;</th></tr>'% _(u'Date'))
+            for day in date_range(monday, sunday):
+                events = schedule.get(day)
+                style = day.day_of_week % 2 and "even" or "odd"
+                w(u'<tr class="%s">' % style)
+                if events:
+                    hours = events.keys()
+                    hours.sort()
+                    w(AMPM_DAYWEEK % (
+                        len(hours), _(WEEKDAYS[day.day_of_week]),
+                        self.format_date(day)))
+                    w(AMPM_WEEK_CELL % (
+                        hours[0].hour, hours[0].minute,
+                        '\n'.join(events[hours[0]])))
+                    w(u'</tr>')
+                    for hour in hours[1:]:
+                        w(u'<tr class="%s">%s</tr>'% (
+                            style, AMPM_WEEK_CELL % (hour.hour, hour.minute,
+                                                     '\n'.join(events[hour]))))
+                else:
+                    w(AMPM_DAYWEEK_EMPTY % (
+                        _(WEEKDAYS[day.day_of_week]),
+                        self.format_date(day)))
+                    w(WEEK_EMPTY_CELL)
+                    w(u'</tr>')
+
+
+SMALL_CALENDARS_PAGE = u"""<table class="smallCalendars">
+<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
+<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
+<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
+</table>
+"""
+
+BIG_CALENDARS_PAGE = u"""<table class="bigCalendars">
+<tr><td class="calendar">%s</td></tr>
+<tr><td class="calendar">%s</td></tr>
+<tr><td class="calendar">%s</td></tr>
+</table>
+"""
+
+WEEKNUM_CELL = u'<td class="weeknum">%s</td>'
+
+def CALENDAR(req):
+    _ = req._
+    WEEKNUM_HEADER = u'<th class="weeknum">%s</th>' % _('week')
+    CAL_HEADER = WEEKNUM_HEADER + u' \n'.join([u'<th class="weekday">%s</th>' % _(day)[0].upper()
+                                               for day in WEEKDAYS])
+    return u"""<table>
+<tr><th class="month" colspan="8">%%s</th></tr>
+<tr>
+  %s
+</tr>
+%%s
+</table>
+""" % (CAL_HEADER,)
+
+
+DAY_TEMPLATE = """<tr><td class="weekday">%(daylabel)s</td><td>%(dmydate)s</td><td>%(dayschedule)s</td>
+"""
+
+NO_CELL = u'<td class="noday"></td>'
+EMPTY_CELL = u'<td class="cellEmpty"><span class="cellTitle">%s</span></td>'
+CELL = u'<td class="cell"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'
+
+AMPM_DAY = u'<td class="cellDay">%d</td>'
+AMPM_EMPTY = u'<td class="%sEmpty"><span class="cellTitle">%s</span></td>'
+AMPM_CONTENT = u'<td class="%s"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'
+
+WEEK_TITLE = u'<th class="weekTitle" colspan="2">%s %s (%s)</th>'
+WEEK_EMPTY_CELL = u'<td class="weekEmptyCell">&nbsp;</td>'
+WEEK_CELL = u'<td class="weekCell"><div class="cellContent">%s</div></td>'
+
+AMPM_DAYWEEK_EMPTY = u'<td>%s&nbsp;%s</td>'
+AMPM_DAYWEEK = u'<td rowspan="%d">%s&nbsp;%s</td>'
+AMPM_WEEK_CELL = u'<td class="ampmWeekCell"><div class="cellContent">%02d:%02d - %s</div></td>'