EventAggregator

macros/EventAggregator.py

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