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