web/views/calendar.py
changeset 0 b97547f5f1fa
child 237 3df2e0ae2eba
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """html calendar views
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 
       
     8 from mx.DateTime import DateTime, RelativeDateTime, today, ISO
       
     9 from datetime import datetime
       
    10 
       
    11 from vobject import iCalendar, icalendar
       
    12 
       
    13 from logilab.mtconverter import html_escape
       
    14 
       
    15 from cubicweb.interfaces import ICalendarable
       
    16 from cubicweb.common.utils import date_range
       
    17 from cubicweb.common.uilib import ajax_replace_url
       
    18 from cubicweb.common.selectors import interface_selector, anyrset_selector
       
    19 from cubicweb.common.registerers import priority_registerer
       
    20 from cubicweb.common.view import EntityView
       
    21 
       
    22 
       
    23 # For backward compatibility
       
    24 from cubicweb.interfaces import ICalendarViews, ITimetableViews
       
    25 try:
       
    26     from cubicweb.web.views.old_calendar import _CalendarView, AMPMWeekCalendarView
       
    27 except ImportError:
       
    28     import logging
       
    29     logger = logging.getLogger('cubicweb.registry')
       
    30     logger.info("old calendar views could not be found and won't be registered")
       
    31 
       
    32 _ = unicode
       
    33 
       
    34 # useful constants & functions
       
    35 def mkdt(mxdate):
       
    36     """
       
    37     Build a stdlib datetime date from a mx.datetime 
       
    38     """
       
    39     d = mxdate
       
    40     return datetime(d.year, d.month, d.day, d.hour, d.minute,
       
    41                     tzinfo=icalendar.utc)
       
    42 def iso(mxdate):
       
    43     """
       
    44     Format a ms datetime in ISO 8601 string 
       
    45     """
       
    46     # XXX What about timezone?
       
    47     return ISO.str(mxdate)
       
    48 
       
    49 # mx.DateTime and ustrftime could be used to build WEEKDAYS
       
    50 WEEKDAYS = (_("monday"), _("tuesday"), _("wednesday"), _("thursday"),
       
    51             _("friday"), _("saturday"), _("sunday"))
       
    52 
       
    53 # used by i18n tools
       
    54 MONTHNAMES = ( _('january'), _('february'), _('march'), _('april'), _('may'),
       
    55                _('june'), _('july'), _('august'), _('september'), _('october'),
       
    56                _('november'), _('december')
       
    57                )
       
    58 
       
    59 #################
       
    60 # In calendar views (views used as calendar cell item) 
       
    61 
       
    62 
       
    63 class CalendarItemView(EntityView):
       
    64     id = 'calendaritem'
       
    65 
       
    66     def cell_call(self, row, col, dates=False):
       
    67         task = self.complete_entity(row)
       
    68         task.view('oneline', w=self.w)
       
    69         if dates:
       
    70             if task.start and task.stop:
       
    71                 self.w('<br/>from %s'%self.format_date(task.start))
       
    72                 self.w('<br/>to %s'%self.format_date(task.stop))
       
    73                 
       
    74 class CalendarLargeItemView(CalendarItemView):
       
    75     id = 'calendarlargeitem'
       
    76         
       
    77 #################
       
    78 # Calendar views
       
    79 
       
    80 class iCalView(EntityView):
       
    81     """A calendar view that generates a iCalendar file (RFC 2445)
       
    82 
       
    83     Does apply to ICalendarable compatible entities
       
    84     """
       
    85     __registerer__ = priority_registerer
       
    86     __selectors__ = (interface_selector,)
       
    87     accepts_interfaces = (ICalendarable,)
       
    88     need_navigation = False
       
    89     content_type = 'text/calendar'
       
    90     title = _('iCalendar')
       
    91     templatable = False
       
    92     id = 'ical'
       
    93 
       
    94     def call(self):
       
    95         ical = iCalendar()
       
    96         for i in range(len(self.rset.rows)):
       
    97             task = self.complete_entity(i)
       
    98             event = ical.add('vevent')
       
    99             event.add('summary').value = task.dc_title()
       
   100             event.add('description').value = task.dc_description()
       
   101             if task.start:
       
   102                 event.add('dtstart').value = mkdt(task.start)
       
   103             if task.stop:
       
   104                 event.add('dtend').value = mkdt(task.stop)
       
   105 
       
   106         buff = ical.serialize()
       
   107         if not isinstance(buff, unicode):
       
   108             buff = unicode(buff, self.req.encoding)
       
   109         self.w(buff)
       
   110 
       
   111 class hCalView(EntityView):
       
   112     """A calendar view that generates a hCalendar file
       
   113 
       
   114     Does apply to ICalendarable compatible entities
       
   115     """
       
   116     __registerer__ = priority_registerer
       
   117     __selectors__ = (interface_selector,)
       
   118     accepts_interfaces = (ICalendarable,)
       
   119     need_navigation = False
       
   120     title = _('hCalendar')
       
   121     templatable = False
       
   122     id = 'hcal'
       
   123 
       
   124     def call(self):
       
   125         self.w(u'<div class="hcalendar">')
       
   126         for i in range(len(self.rset.rows)):
       
   127             task = self.complete_entity(i)
       
   128             self.w(u'<div class="vevent">')
       
   129             self.w(u'<h3 class="summary">%s</h3>' % html_escape(task.dc_title()))
       
   130             self.w(u'<div class="description">%s</div>' % html_escape(task.dc_description()))
       
   131             if task.start:
       
   132                 self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (iso(task.start), self.format_date(task.start)))
       
   133             if task.stop:
       
   134                 self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (iso(task.stop), self.format_date(task.stop)))
       
   135             self.w(u'</div>')
       
   136         self.w(u'</div>')
       
   137 
       
   138     
       
   139 class _TaskEntry(object):
       
   140     def __init__(self, task, color, index=0):
       
   141         self.task = task
       
   142         self.color = color
       
   143         self.index = index
       
   144         self.length = 1
       
   145 
       
   146 class OneMonthCal(EntityView):
       
   147     """At some point, this view will probably replace ampm calendars"""
       
   148     __registerer__ = priority_registerer
       
   149     __selectors__ = (interface_selector, anyrset_selector)
       
   150     accepts_interfaces = (ICalendarable,)
       
   151     need_navigation = False
       
   152     id = 'onemonthcal'
       
   153     title = _('one month')
       
   154 
       
   155     def call(self):
       
   156         self.req.add_js('cubicweb.ajax.js')
       
   157         self.req.add_css('cubicweb.calendar.css')
       
   158         # XXX: restrict courses directy with RQL
       
   159         _today =  today()
       
   160 
       
   161         if 'year' in self.req.form:
       
   162             year = int(self.req.form['year'])
       
   163         else:
       
   164             year = _today.year
       
   165         if 'month' in self.req.form:
       
   166             month = int(self.req.form['month'])
       
   167         else:
       
   168             month = _today.month
       
   169 
       
   170         first_day_of_month = DateTime(year, month, 1)
       
   171         lastday = first_day_of_month + RelativeDateTime(months=1,weekday=(6,1))
       
   172         firstday= first_day_of_month + RelativeDateTime(months=-1,weekday=(0,-1))
       
   173         month_dates = list(date_range(firstday, lastday))
       
   174         dates = {}
       
   175         users = []
       
   176         task_max = 0
       
   177         for row in xrange(self.rset.rowcount):
       
   178             task = self.rset.get_entity(row,0)
       
   179             if len(self.rset[row]) > 1 and self.rset.description[row][1] == 'EUser':
       
   180                 user = self.rset.get_entity(row,1)
       
   181             else:
       
   182                 user = None
       
   183             the_dates = []
       
   184             if task.start:
       
   185                 if task.start > lastday:
       
   186                     continue
       
   187                 the_dates = [task.start]
       
   188             if task.stop:
       
   189                 if task.stop < firstday:
       
   190                     continue
       
   191                 the_dates = [task.stop]
       
   192             if task.start and task.stop:
       
   193                 if task.start.absdate == task.stop.absdate:
       
   194                     date = task.start
       
   195                     if firstday<= date <= lastday:
       
   196                         the_dates = [date]
       
   197                 else:
       
   198                     the_dates = date_range(max(task.start,firstday),
       
   199                                            min(task.stop,lastday))
       
   200             if not the_dates:
       
   201                 continue
       
   202             
       
   203             for d in the_dates:
       
   204                 d_tasks = dates.setdefault((d.year, d.month, d.day), {})
       
   205                 t_users = d_tasks.setdefault(task,set())
       
   206                 t_users.add( user )
       
   207                 if len(d_tasks)>task_max:
       
   208                     task_max = len(d_tasks)
       
   209 
       
   210         days = []
       
   211         nrows = max(3,task_max)
       
   212         # colors here are class names defined in cubicweb.css
       
   213         colors = [ "col%x"%i for i in range(12) ]
       
   214         next_color_index = 0
       
   215 
       
   216         visited_tasks = {} # holds a description of a task
       
   217         task_colors = {}   # remember a color assigned to a task
       
   218         for date in month_dates:
       
   219             d_tasks = dates.get((date.year, date.month, date.day), {})
       
   220             rows = [None] * nrows
       
   221             # every task that is "visited" for the first time
       
   222             # require a special treatment, so we put them in
       
   223             # 'postpone'
       
   224             postpone = []
       
   225             for task in d_tasks:
       
   226                 if task in visited_tasks:
       
   227                     task_descr = visited_tasks[ task ]
       
   228                     rows[task_descr.index] = task_descr
       
   229                 else:
       
   230                     postpone.append(task)
       
   231             for task in postpone:
       
   232                 # to every 'new' task we must affect a color
       
   233                 # (which must be the same for every user concerned
       
   234                 # by the task)
       
   235                 for i,t in enumerate(rows):
       
   236                     if t is None:
       
   237                         if task in task_colors:
       
   238                             color = task_colors[task]
       
   239                         else:
       
   240                             color = colors[next_color_index]
       
   241                             next_color_index = (next_color_index+1)%len(colors)
       
   242                             task_colors[task] = color
       
   243                         task_descr = _TaskEntry(task, color, i)
       
   244                         rows[i] = task_descr
       
   245                         visited_tasks[task] = task_descr
       
   246                         break
       
   247                 else:
       
   248                     raise RuntimeError("is it possible we got it wrong?")
       
   249 
       
   250             days.append( rows )
       
   251 
       
   252         curdate = first_day_of_month
       
   253         self.w(u'<div id="onemonthcalid">')
       
   254         # build schedule
       
   255         self.w(u'<table class="omcalendar">')
       
   256         prevlink, nextlink = self._prevnext_links(curdate)  # XXX
       
   257         self.w(u'<tr><th><a href="%s">&lt;&lt;</a></th><th colspan="5">%s %s</th>'
       
   258                u'<th><a href="%s">&gt;&gt;</a></th></tr>' %
       
   259                (html_escape(prevlink), self.req._(curdate.strftime('%B').lower()),
       
   260                 curdate.year, html_escape(nextlink)))
       
   261 
       
   262         # output header
       
   263         self.w(u'<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>' %
       
   264                tuple(self.req._(day) for day in WEEKDAYS))
       
   265         
       
   266         # build calendar
       
   267         for date, task_rows in zip(month_dates, days):
       
   268             if date.day_of_week == 0:
       
   269                 self.w(u'<tr>')
       
   270             self._build_calendar_cell(date, task_rows, curdate)
       
   271             if date.day_of_week == 6:
       
   272                 self.w(u'</tr>')
       
   273         self.w(u'</table></div>')
       
   274 
       
   275     def _prevnext_links(self, curdate):
       
   276         prevdate = curdate - RelativeDateTime(months=1)
       
   277         nextdate = curdate + RelativeDateTime(months=1)
       
   278         rql = self.rset.rql
       
   279         prevlink = ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
       
   280                                     year=prevdate.year, month=prevdate.month)
       
   281         nextlink = ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
       
   282                                     year=nextdate.year, month=nextdate.month)
       
   283         return prevlink, nextlink
       
   284 
       
   285     def _build_calendar_cell(self, date, rows, curdate):
       
   286         curmonth = curdate.month
       
   287         classes = ""
       
   288         if date.month != curmonth:
       
   289             classes += " outOfRange"
       
   290         if date == today():
       
   291             classes += " today"
       
   292         self.w(u'<td class="cell%s">' % classes)
       
   293         self.w(u'<div class="calCellTitle%s">' % classes)
       
   294         self.w(u'<div class="day">%s</div>' % date.day)
       
   295         
       
   296         if len(self.rset.column_types(0)) == 1:
       
   297             etype = list(self.rset.column_types(0))[0]
       
   298             url = self.build_url(vid='creation', etype=etype,
       
   299                                  schedule=True,
       
   300                                  start=self.format_date(date), stop=self.format_date(date),
       
   301                                  __redirectrql=self.rset.rql,
       
   302                                  __redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
       
   303                                  __redirectvid=self.id
       
   304                                  )
       
   305             self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (html_escape(url), self.req._(u'add')))
       
   306             self.w(u'&nbsp;')
       
   307         self.w(u'</div>')
       
   308         self.w(u'<div class="cellContent">')
       
   309         for task_descr in rows:
       
   310             if task_descr:
       
   311                 task = task_descr.task
       
   312                 self.w(u'<div class="task %s">' % task_descr.color)
       
   313                 task.view('calendaritem', w=self.w )
       
   314                 url = task.absolute_url(vid='edition',
       
   315                                         __redirectrql=self.rset.rql,
       
   316                                         __redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
       
   317                                         __redirectvid=self.id
       
   318                                         )
       
   319 
       
   320                 self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % html_escape(url))
       
   321                 task.view('tooltip', w=self.w )
       
   322                 self.w(u'</div>')
       
   323             else:
       
   324                 self.w(u'<div class="task">')
       
   325                 self.w(u"&nbsp;")
       
   326             self.w(u'</div>')
       
   327         self.w(u'</div>')
       
   328         self.w(u'</td>')
       
   329 
       
   330 
       
   331 class OneWeekCal(EntityView):
       
   332     """At some point, this view will probably replace ampm calendars"""
       
   333     __registerer__ = priority_registerer
       
   334     __selectors__ = (interface_selector, anyrset_selector)
       
   335     accepts_interfaces = (ICalendarable,)
       
   336     need_navigation = False
       
   337     id = 'oneweekcal'
       
   338     title = _('one week')
       
   339     
       
   340     def call(self):
       
   341         self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.calendar.js') )
       
   342         self.req.add_css('cubicweb.calendar.css')
       
   343         # XXX: restrict courses directy with RQL
       
   344         _today =  today()
       
   345 
       
   346         if 'year' in self.req.form:
       
   347             year = int(self.req.form['year'])
       
   348         else:
       
   349             year = _today.year
       
   350         if 'week' in self.req.form:
       
   351             week = int(self.req.form['week'])
       
   352         else:
       
   353             week = _today.iso_week[1]        
       
   354 
       
   355         first_day_of_week = ISO.ParseWeek("%s-W%s-1"%(year, week))
       
   356         lastday = first_day_of_week + RelativeDateTime(days=6)
       
   357         firstday= first_day_of_week
       
   358         dates = [[] for i in range(7)]
       
   359         task_max = 0
       
   360         task_colors = {}   # remember a color assigned to a task
       
   361         # colors here are class names defined in cubicweb.css
       
   362         colors = [ "col%x"%i for i in range(12) ]
       
   363         next_color_index = 0
       
   364         done_tasks = []
       
   365         for row in xrange(self.rset.rowcount):
       
   366             task = self.rset.get_entity(row,0)
       
   367             if task in done_tasks:
       
   368                 continue
       
   369             done_tasks.append(task)
       
   370             the_dates = []
       
   371             if task.start:
       
   372                 if task.start > lastday:
       
   373                     continue
       
   374                 the_dates = [task.start]
       
   375             if task.stop:
       
   376                 if task.stop < firstday:
       
   377                     continue
       
   378                 the_dates = [task.stop]
       
   379             if task.start and task.stop:
       
   380                 the_dates = date_range(max(task.start,firstday),
       
   381                                        min(task.stop,lastday))
       
   382             if not the_dates:
       
   383                 continue
       
   384                 
       
   385             if task not in task_colors:
       
   386                 task_colors[task] = colors[next_color_index]
       
   387                 next_color_index = (next_color_index+1)%len(colors)
       
   388             
       
   389             for d in the_dates:
       
   390                 day = d.day_of_week
       
   391                 task_descr = _TaskEntry(task, task_colors[task])  
       
   392                 dates[day].append(task_descr)
       
   393             
       
   394         self.w(u'<div id="oneweekcalid">')
       
   395         # build schedule
       
   396         self.w(u'<table class="omcalendar" id="week">')
       
   397         prevlink, nextlink = self._prevnext_links(first_day_of_week)  # XXX
       
   398         self.w(u'<tr><th class="transparent"></th>')
       
   399         self.w(u'<th><a href="%s">&lt;&lt;</a></th><th colspan="5">%s %s %s</th>'
       
   400                u'<th><a href="%s">&gt;&gt;</a></th></tr>' %
       
   401                (html_escape(prevlink), first_day_of_week.year,
       
   402                 self.req._(u'week'), first_day_of_week.iso_week[1],
       
   403                 html_escape(nextlink)))
       
   404 
       
   405         # output header
       
   406         self.w(u'<tr>')
       
   407         self.w(u'<th class="transparent"></th>') # column for hours
       
   408         _today = today()
       
   409         for i, day in enumerate(WEEKDAYS):
       
   410             date = first_day_of_week + i
       
   411             if date.absdate == _today.absdate:
       
   412                 self.w(u'<th class="today">%s<br/>%s</th>' % (self.req._(day), self.format_date(date)))
       
   413             else:
       
   414                 self.w(u'<th>%s<br/>%s</th>' % (self.req._(day), self.format_date(date)))
       
   415         self.w(u'</tr>')
       
   416 
       
   417         
       
   418         # build week calendar
       
   419         self.w(u'<tr>')
       
   420         self.w(u'<td style="width:5em;">') # column for hours
       
   421         extra = ""
       
   422         for h in range(8, 20):
       
   423             self.w(u'<div class="hour" %s>'%extra)
       
   424             self.w(u'%02d:00'%h)
       
   425             self.w(u'</div>')            
       
   426         self.w(u'</td>')
       
   427         
       
   428         for i, day in enumerate(WEEKDAYS):
       
   429             date = first_day_of_week + i
       
   430             classes = ""
       
   431             if date.absdate == _today.absdate:
       
   432                 classes = " today"
       
   433             self.w(u'<td class="column %s" id="%s">'%(classes, day))
       
   434             if len(self.rset.column_types(0)) == 1:
       
   435                 etype = list(self.rset.column_types(0))[0]
       
   436                 url = self.build_url(vid='creation', etype=etype,
       
   437                                      schedule=True,
       
   438                                      __redirectrql=self.rset.rql,
       
   439                                      __redirectparams=self.req.build_url_params(year=year, week=week),
       
   440                                      __redirectvid=self.id
       
   441                                      )
       
   442                 extra = ' ondblclick="addCalendarItem(event, hmin=%s, hmax=%s, year=%s, month=%s, day=%s, duration=%s, baseurl=\'%s\')"' % (8,20,date.year, date.month, date.day, 2, html_escape(url))
       
   443             else:
       
   444                 extra = ""
       
   445             self.w(u'<div class="columndiv"%s>'% extra)
       
   446             for h in range(8, 20):
       
   447                 self.w(u'<div class="hourline" style="top:%sex;">'%((h-7)*8))
       
   448                 self.w(u'</div>')            
       
   449             if dates[i]:
       
   450                 self._build_calendar_cell(date, dates[i])
       
   451             self.w(u'</div>')
       
   452             self.w(u'</td>')
       
   453         self.w(u'</tr>')
       
   454         self.w(u'</table></div>')
       
   455         self.w(u'<div id="coord"></div>')
       
   456         self.w(u'<div id="debug">&nbsp;</div>')
       
   457  
       
   458     def _one_day_task(self, task):
       
   459         """
       
   460         Return true if the task is a "one day" task; ie it have a start and a stop the same day
       
   461         """
       
   462         if task.start and task.stop:
       
   463             if task.start.absdate ==  task.stop.absdate:
       
   464                 return True
       
   465         return False
       
   466         
       
   467     def _build_calendar_cell(self, date, task_descrs):
       
   468         inday_tasks = [t for t in task_descrs if self._one_day_task(t.task) and  t.task.start.hour<20 and t.task.stop.hour>7]
       
   469         wholeday_tasks = [t for t in task_descrs if not self._one_day_task(t.task)]
       
   470 
       
   471         inday_tasks.sort(key=lambda t:t.task.start)
       
   472         sorted_tasks = []
       
   473         for i, t in enumerate(wholeday_tasks):
       
   474             t.index = i
       
   475         ncols = len(wholeday_tasks)
       
   476         while inday_tasks:
       
   477             t = inday_tasks.pop(0)
       
   478             for i, c in enumerate(sorted_tasks):
       
   479                 if not c or c[-1].task.stop <= t.task.start:
       
   480                     c.append(t)
       
   481                     t.index = i+ncols
       
   482                     break
       
   483             else:
       
   484                 t.index = len(sorted_tasks) + ncols
       
   485                 sorted_tasks.append([t])
       
   486         ncols += len(sorted_tasks)
       
   487         if ncols == 0:
       
   488             return
       
   489 
       
   490         inday_tasks = []
       
   491         for tasklist in sorted_tasks:
       
   492             inday_tasks += tasklist
       
   493         width = 100.0/ncols
       
   494         for task_desc in wholeday_tasks + inday_tasks:
       
   495             task = task_desc.task
       
   496             start_hour = 8
       
   497             start_min = 0
       
   498             stop_hour = 20
       
   499             stop_min = 0
       
   500             if task.start:
       
   501                 if date < task.start < date + 1:
       
   502                     start_hour = max(8, task.start.hour)
       
   503                     start_min = task.start.minute
       
   504             if task.stop:
       
   505                 if date < task.stop < date + 1:
       
   506                     stop_hour = min(20, task.stop.hour)
       
   507                     if stop_hour < 20:
       
   508                         stop_min = task.stop.minute
       
   509                     
       
   510             height = 100.0*(stop_hour+stop_min/60.0-start_hour-start_min/60.0)/(20-8)
       
   511             top = 100.0*(start_hour+start_min/60.0-8)/(20-8)
       
   512             left = width*task_desc.index
       
   513             style = "height: %s%%; width: %s%%; top: %s%%; left: %s%%; " % \
       
   514                 (height, width, top, left)
       
   515             self.w(u'<div class="task %s" style="%s">' % \
       
   516                        (task_desc.color, style))
       
   517             task.view('calendaritem', dates=False, w=self.w)
       
   518             url = task.absolute_url(vid='edition',
       
   519                                     __redirectrql=self.rset.rql,
       
   520                                     __redirectparams=self.req.build_url_params(year=date.year, week=date.iso_week[1]),
       
   521                                     __redirectvid=self.id
       
   522                                  )
       
   523 
       
   524             self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % html_escape(url))
       
   525             task.view('tooltip', w=self.w)
       
   526             self.w(u'</div>')
       
   527             if task.start is None:
       
   528                 self.w(u'<div class="bottommarker">')
       
   529                 self.w(u'<div class="bottommarkerline" style="margin: 0px 3px 0px 3px; height: 1px;">')
       
   530                 self.w(u'</div>')
       
   531                 self.w(u'<div class="bottommarkerline" style="margin: 0px 2px 0px 2px; height: 1px;">')
       
   532                 self.w(u'</div>')
       
   533                 self.w(u'<div class="bottommarkerline" style="margin: 0px 1px 0px 1px; height: 3ex; color: white; font-size: x-small; vertical-align: center; text-align: center;">')
       
   534                 self.w(u'end')
       
   535                 self.w(u'</div>')
       
   536                 self.w(u'</div>')
       
   537             self.w(u'</div>')
       
   538 
       
   539             
       
   540     def _prevnext_links(self, curdate):
       
   541         prevdate = curdate - RelativeDateTime(days=7)
       
   542         nextdate = curdate + RelativeDateTime(days=7)
       
   543         rql = self.rset.rql
       
   544         prevlink = ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
       
   545                                     year=prevdate.year, week=prevdate.iso_week[1])
       
   546         nextlink = ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
       
   547                                     year=nextdate.year, week=nextdate.iso_week[1])
       
   548         return prevlink, nextlink
       
   549