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