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