EventAggregator

macros/EventAggregator.py

56:4b0e0c5022a1
2009-10-31 Paul Boddie Updated release notes.
     1 # -*- coding: iso-8859-1 -*-     2 """     3     MoinMoin - EventAggregator Macro     4      5     @copyright: 2008, 2009 by Paul Boddie <paul@boddie.org.uk>     6     @copyright: 2000-2004 Juergen Hermann <jh@web.de>,     7                 2005-2008 MoinMoin:ThomasWaldmann.     8     @license: GNU GPL (v2 or later), see COPYING.txt for details.     9 """    10     11 from MoinMoin import wikiutil    12 import EventAggregatorSupport    13 import calendar    14     15 linkToPage = EventAggregatorSupport.linkToPage    16     17 try:    18     set    19 except NameError:    20     from sets import Set as set    21     22 Dependencies = ['pages']    23     24 # Abstractions.    25     26 class View:    27     28     "A view of the event calendar."    29     30     def __init__(self, page, calendar_name, first, last):    31     32         """    33         Initialise the view with the current 'page', a 'calendar_name' (which    34         may be None), and the 'first' and 'last' months.    35         """    36     37         self.page = page    38         self.calendar_name = calendar_name    39     40         if self.calendar_name is not None:    41     42             # Store the view parameters.    43     44             span = EventAggregatorSupport.span(first, last)    45             self.number_of_months = span[0] * 12 + span[1] + 1    46     47             self.previous_month_start = EventAggregatorSupport.prevmonth(first)    48             self.next_month_start = EventAggregatorSupport.nextmonth(first)    49             self.previous_month_end = EventAggregatorSupport.prevmonth(last)    50             self.next_month_end = EventAggregatorSupport.nextmonth(last)    51     52             self.previous_set_start = EventAggregatorSupport.monthupdate(first, -self.number_of_months)    53             self.next_set_start = EventAggregatorSupport.monthupdate(first, self.number_of_months)    54             self.previous_set_end = EventAggregatorSupport.monthupdate(last, -self.number_of_months)    55             self.next_set_end = EventAggregatorSupport.monthupdate(last, self.number_of_months)    56     57     def getMonthQueryString(self, argname, month):    58         if month is not None:    59             return "%s-%s=%04d-%02d" % ((self.calendar_name, argname) + month)    60         else:    61             return ""    62     63     def writeMonthHeading(self, year, month):    64         page = self.page    65         request = page.request    66         fmt = page.formatter    67         _ = request.getText    68     69         output = []    70     71         month_label = _(EventAggregatorSupport.getMonthLabel(month))    72     73         # Prepare navigation links.    74     75         if self.calendar_name is not None:    76             calendar_name = self.calendar_name    77     78             # Links to the previous set of months and to a calendar shifted    79             # back one month.    80     81             previous_set_link = "%s&%s" % (    82                 self.getMonthQueryString("start", self.previous_set_start),    83                 self.getMonthQueryString("end", self.previous_set_end)    84                 )    85             previous_month_link = "%s&%s" % (    86                 self.getMonthQueryString("start", self.previous_month_start),    87                 self.getMonthQueryString("end", self.previous_month_end)    88                 )    89     90             # Links to the next set of months and to a calendar shifted    91             # forward one month.    92     93             next_set_link = "%s&%s" % (    94                 self.getMonthQueryString("start", self.next_set_start),    95                 self.getMonthQueryString("end", self.next_set_end)    96                 )    97             next_month_link = "%s&%s" % (    98                 self.getMonthQueryString("start", self.next_month_start),    99                 self.getMonthQueryString("end", self.next_month_end)   100                 )   101    102             # A link leading to this month being at the top of the calendar.   103    104             full_month_label = "%s %s" % (month_label, year)   105             end_month = EventAggregatorSupport.monthupdate((year, month), self.number_of_months - 1)   106    107             month_link = "%s&%s" % (   108                 self.getMonthQueryString("start", (year, month)),   109                 self.getMonthQueryString("end", end_month)   110                 )   111    112             output.append(fmt.span(on=1, css_class="previous-month"))   113             output.append(linkToPage(request, page, "<<", previous_set_link))   114             output.append(fmt.text(" "))   115             output.append(linkToPage(request, page, "<", previous_month_link))   116             output.append(fmt.span(on=0))   117    118             output.append(fmt.span(on=1, css_class="next-month"))   119             output.append(linkToPage(request, page, ">", next_month_link))   120             output.append(fmt.text(" "))   121             output.append(linkToPage(request, page, ">>", next_set_link))   122             output.append(fmt.span(on=0))   123    124             output.append(linkToPage(request, page, full_month_label, month_link))   125    126         else:   127             output.append(fmt.span(on=1))   128             output.append(fmt.text(month_label))   129             output.append(fmt.span(on=0))   130             output.append(fmt.text(" "))   131             output.append(fmt.span(on=1))   132             output.append(fmt.text(unicode(year)))   133             output.append(fmt.span(on=0))   134    135         return "".join(output)   136    137 # HTML-related functions.   138    139 def getColour(s):   140     colour = [0, 0, 0]   141     digit = 0   142     for c in s:   143         colour[digit] += ord(c)   144         colour[digit] = colour[digit] % 256   145         digit += 1   146         digit = digit % 3   147     return tuple(colour)   148    149 def getBlackOrWhite(colour):   150     if sum(colour) / 3.0 > 127:   151         return (0, 0, 0)   152     else:   153         return (255, 255, 255)   154    155 def getMonthActionQueryString(argname, month):   156     if month is not None:   157         return "%s=%04d-%02d" % ((argname,) + month)   158     else:   159         return ""   160    161 # Macro functions.   162    163 def execute(macro, args):   164    165     """   166     Execute the 'macro' with the given 'args': an optional list of selected   167     category names (categories whose pages are to be shown), together with   168     optional named arguments of the following forms:   169    170       start=YYYY-MM     shows event details starting from the specified month   171       start=current-N   shows event details relative to the current month   172       end=YYYY-MM       shows event details ending at the specified month   173       end=current+N     shows event details relative to the current month   174    175       mode=calendar     shows a calendar view of events   176       mode=list         shows a list of events by month   177       mode=ics          provides iCalendar data for the events   178    179       names=daily       shows the name of an event on every day of that event   180       names=weekly      shows the name of an event once per week   181    182       calendar=NAME     uses the given NAME to provide request parameters which   183                         can be used to control the calendar view   184    185       template=PAGE     uses the given PAGE as the default template for new   186                         events (or the default template from the configuration   187                         if not specified)   188    189       parent=PAGE       uses the given PAGE as the parent of any new event page   190     """   191    192     request = macro.request   193     fmt = macro.formatter   194     page = fmt.page   195     _ = request.getText   196    197     # Interpret the arguments.   198    199     try:   200         parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or []   201     except AttributeError:   202         parsed_args = args.split(",")   203    204     parsed_args = [arg for arg in parsed_args if arg]   205    206     # Get special arguments.   207    208     category_names = []   209     calendar_start = None   210     calendar_end = None   211     mode = "calendar"   212     name_usage = "weekly"   213     calendar_name = None   214     template_name = getattr(request.cfg, "event_aggregator_new_event_template", "EventTemplate")   215     parent_name = None   216    217     for arg in parsed_args:   218         if arg.startswith("start="):   219             calendar_start = EventAggregatorSupport.getParameterMonth(arg[6:])   220    221         elif arg.startswith("end="):   222             calendar_end = EventAggregatorSupport.getParameterMonth(arg[4:])   223    224         elif arg.startswith("mode="):   225             mode = arg[5:]   226    227         elif arg.startswith("names="):   228             name_usage = arg[6:]   229    230         elif arg.startswith("calendar="):   231             calendar_name = arg[9:]   232    233         elif arg.startswith("template="):   234             template_name = arg[9:]   235    236         elif arg.startswith("parent="):   237             parent_name = arg[7:]   238    239         else:   240             category_names.append(arg)   241    242     # Find request parameters to override settings.   243    244     if calendar_name is not None:   245         calendar_start = EventAggregatorSupport.getFormMonth(request, calendar_name, "start") or calendar_start   246         calendar_end = EventAggregatorSupport.getFormMonth(request, calendar_name, "end") or calendar_end   247    248     # HTML link fragments.   249    250     category_name_parameters = "&".join([("category=%s" % name) for name in category_names])   251    252     # Get the events.   253    254     events, shown_events, all_shown_events, earliest, latest = \   255         EventAggregatorSupport.getEvents(request, category_names, calendar_start, calendar_end)   256    257     # Get a concrete period of time.   258    259     first, last = EventAggregatorSupport.getConcretePeriod(calendar_start, calendar_end, earliest, latest)   260    261     # Define a view of the calendar, retaining useful navigational information.   262    263     view = View(page, calendar_name, first, last)   264    265     # Make a calendar.   266    267     output = []   268    269     # Output download controls.   270    271     download_all_link = "action=EventAggregatorSummary&doit=1&%s" % category_name_parameters   272     download_link = download_all_link + ("&%s&%s" % (   273         getMonthActionQueryString("start", calendar_start),   274         getMonthActionQueryString("end", calendar_end)   275         ))   276     subscribe_all_link = download_all_link + "&format=RSS"   277     subscribe_link = download_link + "&format=RSS"   278    279     output.append(fmt.div(on=1, css_class="event-controls"))   280     output.append(fmt.span(on=1, css_class="event-download"))   281     output.append(linkToPage(request, page, _("Download this view"), download_link))   282     output.append(fmt.span(on=0))   283     output.append(fmt.span(on=1, css_class="event-download"))   284     output.append(linkToPage(request, page, _("Download this calendar"), download_all_link))   285     output.append(fmt.span(on=0))   286     output.append(fmt.span(on=1, css_class="event-download"))   287     output.append(linkToPage(request, page, _("Subscribe to this view"), subscribe_link))   288     output.append(fmt.span(on=0))   289     output.append(fmt.span(on=1, css_class="event-download"))   290     output.append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link))   291     output.append(fmt.span(on=0))   292     output.append(fmt.div(on=0))   293    294     # Output top-level information.   295    296     # Start of list view output.   297    298     if mode == "list":   299         output.append(fmt.bullet_list(on=1, attr={"class" : "event-listings"}))   300    301     # Start of table view output.   302    303     elif mode == "table":   304    305         # Output a table.   306    307         output.append(fmt.table(on=1, attrs={"tableclass" : "event-table"}))   308    309         output.append(fmt.table_row(on=1))   310         output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"}))   311         output.append(fmt.text(_("Event dates")))   312         output.append(fmt.table_cell(on=0))   313         output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"}))   314         output.append(fmt.text(_("Event location")))   315         output.append(fmt.table_cell(on=0))   316         output.append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"}))   317         output.append(fmt.text(_("Event details")))   318         output.append(fmt.table_cell(on=0))   319         output.append(fmt.table_row(on=0))   320    321     # Visit all months in the requested range, or across known events.   322    323     for year, month in EventAggregatorSupport.daterange(first, last):   324    325         # Either output a calendar view...   326    327         if mode == "calendar":   328    329             # Output a month.   330    331             output.append(fmt.table(on=1, attrs={"tableclass" : "event-month"}))   332    333             output.append(fmt.table_row(on=1))   334             output.append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "21"}))   335    336             # Either write a month heading or produce links for navigable   337             # calendars.   338    339             output.append(view.writeMonthHeading(year, month))   340    341             output.append(fmt.table_cell(on=0))   342             output.append(fmt.table_row(on=0))   343    344             # Weekday headings.   345    346             output.append(fmt.table_row(on=1))   347    348             for weekday in range(0, 7):   349                 output.append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading", "colspan" : "3"}))   350                 output.append(fmt.text(_(EventAggregatorSupport.getDayLabel(weekday))))   351                 output.append(fmt.table_cell(on=0))   352    353             output.append(fmt.table_row(on=0))   354    355             # Process the days of the month.   356    357             start_weekday, number_of_days = calendar.monthrange(year, month)   358    359             # The start weekday is the weekday of day number 1.   360             # Find the first day of the week, counting from below zero, if   361             # necessary, in order to land on the first day of the month as   362             # day number 1.   363    364             first_day = 1 - start_weekday   365    366             while first_day <= number_of_days:   367    368                 # Find events in this week and determine how to mark them on the   369                 # calendar.   370    371                 week_start = (year, month, max(first_day, 1))   372                 week_end = (year, month, min(first_day + 6, number_of_days))   373    374                 week_coverage, week_events = EventAggregatorSupport.getCoverage(   375                     week_start, week_end, shown_events.get((year, month), []))   376    377                 # Output a week, starting with the day numbers.   378    379                 output.append(fmt.table_row(on=1))   380    381                 for weekday in range(0, 7):   382                     day = first_day + weekday   383                     date = (year, month, day)   384    385                     # Output out-of-month days.   386    387                     if day < 1 or day > number_of_days:   388                         output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-excluded", "colspan" : "3"}))   389                         output.append(fmt.table_cell(on=0))   390    391                     # Output normal days.   392    393                     else:   394                         if date in week_coverage:   395                             output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-busy", "colspan" : "3"}))   396                         else:   397                             output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-heading event-day-empty", "colspan" : "3"}))   398    399                         # Make a link to a new event action.   400    401                         new_event_link = "action=EventAggregatorNewEvent&start-day=%d&start-month=%d&start-year=%d" \   402                             "&%s&template=%s&parent=%s" % (   403                             day, month, year, category_name_parameters, template_name, parent_name or "")   404    405                         # Output the day number.   406    407                         output.append(fmt.div(on=1))   408                         output.append(fmt.span(on=1, css_class="event-day-number"))   409                         output.append(linkToPage(request, page, unicode(day), new_event_link))   410                         output.append(fmt.span(on=0))   411                         output.append(fmt.div(on=0))   412    413                         # End of day.   414    415                         output.append(fmt.table_cell(on=0))   416    417                 # End of day numbers.   418    419                 output.append(fmt.table_row(on=0))   420    421                 # Either generate empty days...   422    423                 if not week_events:   424                     output.append(fmt.table_row(on=1))   425    426                     for weekday in range(0, 7):   427                         day = first_day + weekday   428                         date = (year, month, day)   429    430                         # Output out-of-month days.   431    432                         if day < 1 or day > number_of_days:   433                             output.append(fmt.table_cell(on=1,   434                                 attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"}))   435                             output.append(fmt.table_cell(on=0))   436    437                         # Output empty days.   438    439                         else:   440                             output.append(fmt.table_cell(on=1,   441                                 attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"}))   442    443                     output.append(fmt.table_row(on=0))   444    445                 # Or visit each set of scheduled events...   446    447                 else:   448                     for coverage, events in week_events:   449    450                         # Output each set.   451    452                         output.append(fmt.table_row(on=1))   453    454                         # Then, output day details.   455    456                         for weekday in range(0, 7):   457                             day = first_day + weekday   458                             date = (year, month, day)   459    460                             # Skip out-of-month days.   461    462                             if day < 1 or day > number_of_days:   463                                 output.append(fmt.table_cell(on=1,   464                                     attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"}))   465                                 output.append(fmt.table_cell(on=0))   466                                 continue   467    468                             # Output the day.   469    470                             if date not in coverage:   471                                 output.append(fmt.table_cell(on=1,   472                                     attrs={"class" : "event-day-content event-day-empty", "colspan" : "3"}))   473    474                             # Get event details for the current day.   475    476                             for event_page, event_details in events:   477                                 if not (event_details["start"] <= date <= event_details["end"]):   478                                     continue   479    480                                 # Get basic properties of the event.   481    482                                 starts_today = event_details["start"] == date   483                                 ends_today = event_details["end"] == date   484                                 event_summary = EventAggregatorSupport.getEventSummary(event_page, event_details, parent_name)   485    486                                 # Generate a colour for the event.   487    488                                 bg = getColour(event_page.page_name)   489                                 fg = getBlackOrWhite(bg)   490                                 style = ("background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg))   491    492                                 # Determine if the event name should be shown.   493    494                                 start_of_period = starts_today or weekday == 0 or day == 1   495    496                                 if name_usage == "daily" or start_of_period:   497                                     hide_text = 0   498                                 else:   499                                     hide_text = 1   500    501                                 # Output start of day gap and determine whether   502                                 # any event content should be explicitly output   503                                 # for this day.   504    505                                 if starts_today:   506    507                                     # Single day events...   508    509                                     if ends_today:   510                                         colspan = 3   511                                         event_day_type = "event-day-single"   512    513                                     # Events starting today...   514    515                                     else:   516                                         output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-start-gap"}))   517                                         output.append(fmt.table_cell(on=0))   518    519                                         # Calculate the span of this cell.   520                                         # Events whose names appear on every day...   521    522                                         if name_usage == "daily":   523                                             colspan = 2   524                                             event_day_type = "event-day-starting"   525    526                                         # Events whose names appear once per week...   527    528                                         else:   529                                             if event_details["end"] <= week_end:   530                                                 event_length = event_details["end"][2] - day + 1   531                                                 colspan = (event_length - 2) * 3 + 4   532                                             else:   533                                                 event_length = week_end[2] - day + 1   534                                                 colspan = (event_length - 1) * 3 + 2   535    536                                             event_day_type = "event-day-multiple"   537    538                                 # Events continuing from a previous week...   539    540                                 elif start_of_period:   541    542                                     # End of continuing event...   543    544                                     if ends_today:   545                                         colspan = 2   546                                         event_day_type = "event-day-ending"   547    548                                     # Events continuing for at least one more day...   549    550                                     else:   551    552                                         # Calculate the span of this cell.   553                                         # Events whose names appear on every day...   554    555                                         if name_usage == "daily":   556                                             colspan = 3   557                                             event_day_type = "event-day-full"   558    559                                         # Events whose names appear once per week...   560    561                                         else:   562                                             if event_details["end"] <= week_end:   563                                                 event_length = event_details["end"][2] - day + 1   564                                                 colspan = (event_length - 1) * 3 + 2   565                                             else:   566                                                 event_length = week_end[2] - day + 1   567                                                 colspan = event_length * 3   568    569                                             event_day_type = "event-day-multiple"   570    571                                 # Continuing events whose names appear on every day...   572    573                                 elif name_usage == "daily":   574                                     if ends_today:   575                                         colspan = 2   576                                         event_day_type = "event-day-ending"   577                                     else:   578                                         colspan = 3   579                                         event_day_type = "event-day-full"   580    581                                 # Continuing events whose names appear once per week...   582    583                                 else:   584                                     colspan = None   585    586                                 # Output the main content only if it is not   587                                 # continuing from a previous day.   588    589                                 if colspan is not None:   590    591                                     # Colour the cell for continuing events.   592    593                                     attrs={   594                                         "class" : "event-day-content event-day-busy %s" % event_day_type,   595                                         "colspan" : str(colspan)   596                                         }   597    598                                     if not (starts_today and ends_today):   599                                         attrs["style"] = style   600    601                                     output.append(fmt.table_cell(on=1, attrs=attrs))   602    603                                     # Output the event.   604    605                                     if starts_today and ends_today or not hide_text:   606    607                                         output.append(fmt.div(on=1, css_class="event-summary-box"))   608                                         output.append(fmt.div(on=1, css_class="event-summary", style=style))   609                                         output.append(linkToPage(request, event_page, event_summary))   610                                         output.append(fmt.div(on=0))   611    612                                         # Add a pop-up element for long summaries.   613    614                                         output.append(fmt.div(on=1, css_class="event-summary-popup", style=style))   615                                         output.append(linkToPage(request, event_page, event_summary))   616                                         output.append(fmt.div(on=0))   617    618                                         output.append(fmt.div(on=0))   619    620                                     # Output end of day content.   621    622                                     output.append(fmt.div(on=0))   623    624                                 # Output end of day gap.   625    626                                 if ends_today and not starts_today:   627                                     output.append(fmt.table_cell(on=1, attrs={"class" : "event-day-end-gap"}))   628                                     output.append(fmt.table_cell(on=0))   629    630                             # End of day.   631    632                             output.append(fmt.table_cell(on=0))   633    634                         # End of set.   635    636                         output.append(fmt.table_row(on=0))   637    638                         # Add a spacer.   639    640                         output.append(fmt.table_row(on=1))   641    642                         for weekday in range(0, 7):   643                             day = first_day + weekday   644                             css_classes = "event-day-spacer"   645    646                             # Skip out-of-month days.   647    648                             if day < 1 or day > number_of_days:   649                                 css_classes += " event-day-excluded"   650    651                             output.append(fmt.table_cell(on=1, attrs={"class" : css_classes, "colspan" : "3"}))   652                             output.append(fmt.table_cell(on=0))   653    654                         output.append(fmt.table_row(on=0))   655    656                 # Process the next week...   657    658                 first_day += 7   659    660             # End of month.   661    662             output.append(fmt.table(on=0))   663    664         # Or output a summary view...   665    666         elif mode == "list":   667    668             # Output a list.   669    670             output.append(fmt.listitem(on=1, attr={"class" : "event-listings-month"}))   671             output.append(fmt.div(on=1, attr={"class" : "event-listings-month-heading"}))   672    673             # Either write a month heading or produce links for navigable   674             # calendars.   675    676             output.append(view.writeMonthHeading(year, month))   677    678             output.append(fmt.div(on=0))   679    680             output.append(fmt.bullet_list(on=1, attr={"class" : "event-month-listings"}))   681    682             # Get the events in order.   683    684             ordered_events = EventAggregatorSupport.getOrderedEvents(shown_events.get((year, month), []))   685    686             # Show the events in order.   687    688             for event_page, event_details in ordered_events:   689                 event_summary = EventAggregatorSupport.getEventSummary(event_page, event_details, parent_name)   690    691                 output.append(fmt.listitem(on=1, attr={"class" : "event-listing"}))   692    693                 # Link to the page using the summary.   694    695                 output.append(fmt.paragraph(on=1))   696                 output.append(linkToPage(request, event_page, event_summary))   697                 output.append(fmt.paragraph(on=0))   698    699                 # Start and end dates.   700    701                 output.append(fmt.paragraph(on=1))   702                 output.append(fmt.span(on=1))   703                 output.append(fmt.text("%04d-%02d-%02d" % event_details["start"]))   704                 output.append(fmt.span(on=0))   705                 output.append(fmt.text(" - "))   706                 output.append(fmt.span(on=1))   707                 output.append(fmt.text("%04d-%02d-%02d" % event_details["end"]))   708                 output.append(fmt.span(on=0))   709                 output.append(fmt.paragraph(on=0))   710    711                 # Location.   712    713                 if event_details.has_key("location"):   714                     output.append(fmt.paragraph(on=1))   715                     output.append(fmt.text(event_details["location"]))   716                     output.append(fmt.paragraph(on=1))   717    718                 # Topics.   719    720                 if event_details.has_key("topics") or event_details.has_key("categories"):   721                     output.append(fmt.bullet_list(on=1, attr={"class" : "event-topics"}))   722    723                     for topic in event_details.get("topics") or event_details.get("categories"):   724                         output.append(fmt.listitem(on=1))   725                         output.append(fmt.text(topic))   726                         output.append(fmt.listitem(on=0))   727    728                     output.append(fmt.bullet_list(on=0))   729    730                 output.append(fmt.listitem(on=0))   731    732             output.append(fmt.bullet_list(on=0))   733    734         # Or output a table of events...   735    736         elif mode == "table":   737    738             # Get the events in order.   739    740             ordered_events = EventAggregatorSupport.getOrderedEvents(shown_events.get((year, month), []))   741    742             # Show the events in order.   743    744             for event_page, event_details in ordered_events:   745                 event_summary = EventAggregatorSupport.getEventSummary(event_page, event_details, parent_name)   746    747                 # Prepare CSS classes with category-related styling.   748    749                 css_classes = ["event-table-details"]   750    751                 for topic in event_details.get("topics") or event_details.get("categories"):   752    753                     # Filter the category text to avoid illegal characters.   754    755                     css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic)))   756    757                 attrs = {"class" : " ".join(css_classes)}   758    759                 output.append(fmt.table_row(on=1))   760    761                 # Start and end dates.   762    763                 output.append(fmt.table_cell(on=1, attrs=attrs))   764                 output.append(fmt.span(on=1))   765                 output.append(fmt.text("%04d-%02d-%02d" % event_details["start"]))   766                 output.append(fmt.span(on=0))   767    768                 if event_details["start"] != event_details["end"]:   769                     output.append(fmt.text(" - "))   770                     output.append(fmt.span(on=1))   771                     output.append(fmt.text("%04d-%02d-%02d" % event_details["end"]))   772                     output.append(fmt.span(on=0))   773    774                 output.append(fmt.table_cell(on=0))   775    776                 # Location.   777    778                 output.append(fmt.table_cell(on=1, attrs=attrs))   779    780                 if event_details.has_key("location"):   781                     output.append(fmt.text(event_details["location"]))   782    783                 output.append(fmt.table_cell(on=0))   784    785                 # Link to the page using the summary.   786    787                 output.append(fmt.table_cell(on=1, attrs=attrs))   788                 output.append(linkToPage(request, event_page, event_summary))   789                 output.append(fmt.table_cell(on=0))   790    791                 output.append(fmt.table_row(on=0))   792    793     # Output top-level information.   794    795     # End of list view output.   796    797     if mode == "list":   798         output.append(fmt.bullet_list(on=0))   799    800     # End of table view output.   801    802     elif mode == "table":   803         output.append(fmt.table(on=0))   804    805     return ''.join(output)   806    807 # vim: tabstop=4 expandtab shiftwidth=4