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