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