1.1 --- a/EventAggregatorSupport.py Sun May 05 20:34:14 2013 +0200
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,3638 +0,0 @@
1.4 -# -*- coding: iso-8859-1 -*-
1.5 -"""
1.6 - MoinMoin - EventAggregator library
1.7 -
1.8 - @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk>
1.9 - @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
1.10 - 2005-2008 MoinMoin:ThomasWaldmann.
1.11 - @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.12 -"""
1.13 -
1.14 -from ContentTypeSupport import getContentTypeAndEncoding
1.15 -from GeneralSupport import *
1.16 -from LocationSupport import *
1.17 -from MoinDateSupport import *
1.18 -from MoinRemoteSupport import *
1.19 -from MoinSupport import *
1.20 -from ViewSupport import *
1.21 -
1.22 -from MoinMoin.Page import Page
1.23 -from MoinMoin.action import AttachFile
1.24 -from MoinMoin import wikiutil
1.25 -
1.26 -import codecs
1.27 -import re
1.28 -import urllib
1.29 -
1.30 -try:
1.31 - from cStringIO import StringIO
1.32 -except ImportError:
1.33 - from StringIO import StringIO
1.34 -
1.35 -try:
1.36 - set
1.37 -except NameError:
1.38 - from sets import Set as set
1.39 -
1.40 -try:
1.41 - import vCalendar
1.42 -except ImportError:
1.43 - vCalendar = None
1.44 -
1.45 -escape = wikiutil.escape
1.46 -
1.47 -__version__ = "0.9.1"
1.48 -
1.49 -# Page parsing.
1.50 -
1.51 -definition_list_regexp = re.compile(ur'(?P<wholeterm>^(?P<optcomment>#*)\s+(?P<term>.*?):: )(?P<desc>.*?)$', re.UNICODE | re.MULTILINE)
1.52 -category_membership_regexp = re.compile(ur"^\s*(?:(Category\S+)(?:\s+(Category\S+))*)\s*$", re.MULTILINE | re.UNICODE)
1.53 -
1.54 -# Value parsing.
1.55 -
1.56 -country_code_regexp = re.compile(ur'(?:^|\W)(?P<code>[A-Z]{2})(?:$|\W+$)', re.UNICODE)
1.57 -
1.58 -# Utility functions.
1.59 -
1.60 -def to_plain_text(s, request):
1.61 -
1.62 - "Convert 's' to plain text."
1.63 -
1.64 - fmt = getFormatterClass(request, "plain")(request)
1.65 - fmt.setPage(request.page)
1.66 - return formatText(s, request, fmt)
1.67 -
1.68 -def getLocationPosition(location, locations):
1.69 -
1.70 - """
1.71 - Attempt to return the position of the given 'location' using the 'locations'
1.72 - dictionary provided. If no position can be found, return a latitude of None
1.73 - and a longitude of None.
1.74 - """
1.75 -
1.76 - latitude, longitude = None, None
1.77 -
1.78 - if location is not None:
1.79 - try:
1.80 - latitude, longitude = map(getMapReference, locations[location].split())
1.81 - except (KeyError, ValueError):
1.82 - pass
1.83 -
1.84 - return latitude, longitude
1.85 -
1.86 -# Utility classes and associated functions.
1.87 -
1.88 -class ActionSupport(ActionSupport):
1.89 -
1.90 - "Extend the generic action support."
1.91 -
1.92 - def get_month_lists(self, default_as_current=0):
1.93 -
1.94 - """
1.95 - Return two lists of HTML element definitions corresponding to the start
1.96 - and end month selection controls, with months selected according to any
1.97 - values that have been specified via request parameters.
1.98 - """
1.99 -
1.100 - _ = self._
1.101 - form = self.get_form()
1.102 -
1.103 - # Initialise month lists.
1.104 -
1.105 - start_month_list = []
1.106 - end_month_list = []
1.107 -
1.108 - start_month = self._get_input(form, "start-month", default_as_current and getCurrentMonth().month() or None)
1.109 - end_month = self._get_input(form, "end-month", start_month)
1.110 -
1.111 - # Prepare month lists, selecting specified months.
1.112 -
1.113 - if not default_as_current:
1.114 - start_month_list.append('<option value=""></option>')
1.115 - end_month_list.append('<option value=""></option>')
1.116 -
1.117 - for month in range(1, 13):
1.118 - month_label = escape(_(getMonthLabel(month)))
1.119 - selected = self._get_selected(month, start_month)
1.120 - start_month_list.append('<option value="%02d" %s>%s</option>' % (month, selected, month_label))
1.121 - selected = self._get_selected(month, end_month)
1.122 - end_month_list.append('<option value="%02d" %s>%s</option>' % (month, selected, month_label))
1.123 -
1.124 - return start_month_list, end_month_list
1.125 -
1.126 - def get_year_defaults(self, default_as_current=0):
1.127 -
1.128 - "Return defaults for the start and end years."
1.129 -
1.130 - form = self.get_form()
1.131 -
1.132 - start_year_default = form.get("start-year", [default_as_current and getCurrentYear() or ""])[0]
1.133 - end_year_default = form.get("end-year", [default_as_current and start_year_default or ""])[0]
1.134 -
1.135 - return start_year_default, end_year_default
1.136 -
1.137 - def get_day_defaults(self, default_as_current=0):
1.138 -
1.139 - "Return defaults for the start and end days."
1.140 -
1.141 - form = self.get_form()
1.142 -
1.143 - start_day_default = form.get("start-day", [default_as_current and getCurrentDate().day() or ""])[0]
1.144 - end_day_default = form.get("end-day", [default_as_current and start_day_default or ""])[0]
1.145 -
1.146 - return start_day_default, end_day_default
1.147 -
1.148 -# Event parsing from page texts.
1.149 -
1.150 -def parseEvents(text, event_page, fragment=None):
1.151 -
1.152 - """
1.153 - Parse events in the given 'text', returning a list of event objects for the
1.154 - given 'event_page'. An optional 'fragment' can be specified to indicate a
1.155 - specific region of the event page.
1.156 -
1.157 - If the optional 'fragment' identifier is provided, the first heading may
1.158 - also be used to provide an event summary/title.
1.159 - """
1.160 -
1.161 - template_details = {}
1.162 - if fragment:
1.163 - template_details["fragment"] = fragment
1.164 -
1.165 - details = {}
1.166 - details.update(template_details)
1.167 - raw_details = {}
1.168 -
1.169 - # Obtain a heading, if requested.
1.170 -
1.171 - if fragment:
1.172 - for level, title, (start, end) in getHeadings(text):
1.173 - raw_details["title"] = text[start:end]
1.174 - details["title"] = getSimpleWikiText(title.strip())
1.175 - break
1.176 -
1.177 - # Start populating events.
1.178 -
1.179 - events = [Event(event_page, details, raw_details)]
1.180 -
1.181 - for match in definition_list_regexp.finditer(text):
1.182 -
1.183 - # Skip commented-out items.
1.184 -
1.185 - if match.group("optcomment"):
1.186 - continue
1.187 -
1.188 - # Permit case-insensitive list terms.
1.189 -
1.190 - term = match.group("term").lower()
1.191 - raw_desc = match.group("desc")
1.192 -
1.193 - # Special value type handling.
1.194 -
1.195 - # Dates.
1.196 -
1.197 - if term in Event.date_terms:
1.198 - desc = getDateTime(raw_desc)
1.199 -
1.200 - # Lists (whose elements may be quoted).
1.201 -
1.202 - elif term in Event.list_terms:
1.203 - desc = map(getSimpleWikiText, to_list(raw_desc, ","))
1.204 -
1.205 - # Position details.
1.206 -
1.207 - elif term == "geo":
1.208 - try:
1.209 - desc = map(getMapReference, to_list(raw_desc, None))
1.210 - if len(desc) != 2:
1.211 - continue
1.212 - except (KeyError, ValueError):
1.213 - continue
1.214 -
1.215 - # Labels which may well be quoted.
1.216 -
1.217 - elif term in Event.title_terms:
1.218 - desc = getSimpleWikiText(raw_desc.strip())
1.219 -
1.220 - # Plain Wiki text terms.
1.221 -
1.222 - elif term in Event.other_terms:
1.223 - desc = raw_desc.strip()
1.224 -
1.225 - else:
1.226 - desc = raw_desc
1.227 -
1.228 - if desc is not None:
1.229 -
1.230 - # Handle apparent duplicates by creating a new set of
1.231 - # details.
1.232 -
1.233 - if details.has_key(term):
1.234 -
1.235 - # Make a new event.
1.236 -
1.237 - details = {}
1.238 - details.update(template_details)
1.239 - raw_details = {}
1.240 - events.append(Event(event_page, details, raw_details))
1.241 -
1.242 - details[term] = desc
1.243 - raw_details[term] = raw_desc
1.244 -
1.245 - return events
1.246 -
1.247 -# Event resources providing collections of events.
1.248 -
1.249 -class EventResource:
1.250 -
1.251 - "A resource providing event information."
1.252 -
1.253 - def __init__(self, url):
1.254 - self.url = url
1.255 -
1.256 - def getPageURL(self):
1.257 -
1.258 - "Return the URL of this page."
1.259 -
1.260 - return self.url
1.261 -
1.262 - def getFormat(self):
1.263 -
1.264 - "Get the format used by this resource."
1.265 -
1.266 - return "plain"
1.267 -
1.268 - def getMetadata(self):
1.269 -
1.270 - """
1.271 - Return a dictionary containing items describing the page's "created"
1.272 - time, "last-modified" time, "sequence" (or revision number) and the
1.273 - "last-comment" made about the last edit.
1.274 - """
1.275 -
1.276 - return {}
1.277 -
1.278 - def getEvents(self):
1.279 -
1.280 - "Return a list of events from this resource."
1.281 -
1.282 - return []
1.283 -
1.284 - def linkToPage(self, request, text, query_string=None, anchor=None):
1.285 -
1.286 - """
1.287 - Using 'request', return a link to this page with the given link 'text'
1.288 - and optional 'query_string' and 'anchor'.
1.289 - """
1.290 -
1.291 - return linkToResource(self.url, request, text, query_string, anchor)
1.292 -
1.293 - # Formatting-related functions.
1.294 -
1.295 - def formatText(self, text, fmt):
1.296 -
1.297 - """
1.298 - Format the given 'text' using the specified formatter 'fmt'.
1.299 - """
1.300 -
1.301 - # Assume plain text which is then formatted appropriately.
1.302 -
1.303 - return fmt.text(text)
1.304 -
1.305 -class EventCalendar(EventResource):
1.306 -
1.307 - "An iCalendar resource."
1.308 -
1.309 - def __init__(self, url, calendar):
1.310 - EventResource.__init__(self, url)
1.311 - self.calendar = calendar
1.312 - self.events = None
1.313 -
1.314 - def getEvents(self):
1.315 -
1.316 - "Return a list of events from this resource."
1.317 -
1.318 - if self.events is None:
1.319 - self.events = []
1.320 -
1.321 - _calendar, _empty, calendar = self.calendar
1.322 -
1.323 - for objtype, attrs, obj in calendar:
1.324 -
1.325 - # Read events.
1.326 -
1.327 - if objtype == "VEVENT":
1.328 - details = {}
1.329 -
1.330 - for property, attrs, value in obj:
1.331 -
1.332 - # Convert dates.
1.333 -
1.334 - if property in ("DTSTART", "DTEND", "CREATED", "DTSTAMP", "LAST-MODIFIED"):
1.335 - if property in ("DTSTART", "DTEND"):
1.336 - property = property[2:]
1.337 - if attrs.get("VALUE") == "DATE":
1.338 - value = getDateFromCalendar(value)
1.339 - if value and property == "END":
1.340 - value = value.previous_day()
1.341 - else:
1.342 - value = getDateTimeFromCalendar(value)
1.343 -
1.344 - # Convert numeric data.
1.345 -
1.346 - elif property == "SEQUENCE":
1.347 - value = int(value)
1.348 -
1.349 - # Convert lists.
1.350 -
1.351 - elif property == "CATEGORIES":
1.352 - value = to_list(value, ",")
1.353 -
1.354 - # Convert positions (using decimal values).
1.355 -
1.356 - elif property == "GEO":
1.357 - try:
1.358 - value = map(getMapReferenceFromDecimal, to_list(value, ";"))
1.359 - if len(value) != 2:
1.360 - continue
1.361 - except (KeyError, ValueError):
1.362 - continue
1.363 -
1.364 - # Accept other textual data as it is.
1.365 -
1.366 - elif property in ("LOCATION", "SUMMARY", "URL"):
1.367 - value = value or None
1.368 -
1.369 - # Ignore other properties.
1.370 -
1.371 - else:
1.372 - continue
1.373 -
1.374 - property = property.lower()
1.375 - details[property] = value
1.376 -
1.377 - self.events.append(CalendarEvent(self, details))
1.378 -
1.379 - return self.events
1.380 -
1.381 -class EventPage:
1.382 -
1.383 - "An event page acting as an event resource."
1.384 -
1.385 - def __init__(self, page):
1.386 - self.page = page
1.387 - self.events = None
1.388 - self.body = None
1.389 - self.categories = None
1.390 - self.metadata = None
1.391 -
1.392 - def copyPage(self, page):
1.393 -
1.394 - "Copy the body of the given 'page'."
1.395 -
1.396 - self.body = page.getBody()
1.397 -
1.398 - def getPageURL(self):
1.399 -
1.400 - "Return the URL of this page."
1.401 -
1.402 - return getPageURL(self.page)
1.403 -
1.404 - def getFormat(self):
1.405 -
1.406 - "Get the format used on this page."
1.407 -
1.408 - return getFormat(self.page)
1.409 -
1.410 - def getMetadata(self):
1.411 -
1.412 - """
1.413 - Return a dictionary containing items describing the page's "created"
1.414 - time, "last-modified" time, "sequence" (or revision number) and the
1.415 - "last-comment" made about the last edit.
1.416 - """
1.417 -
1.418 - if self.metadata is None:
1.419 - self.metadata = getMetadata(self.page)
1.420 - return self.metadata
1.421 -
1.422 - def getRevisions(self):
1.423 -
1.424 - "Return a list of page revisions."
1.425 -
1.426 - return self.page.getRevList()
1.427 -
1.428 - def getPageRevision(self):
1.429 -
1.430 - "Return the revision details dictionary for this page."
1.431 -
1.432 - return getPageRevision(self.page)
1.433 -
1.434 - def getPageName(self):
1.435 -
1.436 - "Return the page name."
1.437 -
1.438 - return self.page.page_name
1.439 -
1.440 - def getPrettyPageName(self):
1.441 -
1.442 - "Return a nicely formatted title/name for this page."
1.443 -
1.444 - return getPrettyPageName(self.page)
1.445 -
1.446 - def getBody(self):
1.447 -
1.448 - "Get the current page body."
1.449 -
1.450 - if self.body is None:
1.451 - self.body = self.page.get_raw_body()
1.452 - return self.body
1.453 -
1.454 - def getEvents(self):
1.455 -
1.456 - "Return a list of events from this page."
1.457 -
1.458 - if self.events is None:
1.459 - self.events = []
1.460 - if self.getFormat() == "wiki":
1.461 - for format, attributes, region in getFragments(self.getBody(), True):
1.462 - self.events += parseEvents(region, self, attributes.get("fragment"))
1.463 -
1.464 - return self.events
1.465 -
1.466 - def setEvents(self, events):
1.467 -
1.468 - "Set the given 'events' on this page."
1.469 -
1.470 - self.events = events
1.471 -
1.472 - def getCategoryMembership(self):
1.473 -
1.474 - "Get the category names from this page."
1.475 -
1.476 - if self.categories is None:
1.477 - body = self.getBody()
1.478 - match = category_membership_regexp.search(body)
1.479 - self.categories = match and [x for x in match.groups() if x] or []
1.480 -
1.481 - return self.categories
1.482 -
1.483 - def setCategoryMembership(self, category_names):
1.484 -
1.485 - """
1.486 - Set the category membership for the page using the specified
1.487 - 'category_names'.
1.488 - """
1.489 -
1.490 - self.categories = category_names
1.491 -
1.492 - def flushEventDetails(self):
1.493 -
1.494 - "Flush the current event details to this page's body text."
1.495 -
1.496 - new_body_parts = []
1.497 - end_of_last_match = 0
1.498 - body = self.getBody()
1.499 -
1.500 - events = iter(self.getEvents())
1.501 -
1.502 - event = events.next()
1.503 - event_details = event.getDetails()
1.504 - replaced_terms = set()
1.505 -
1.506 - for match in definition_list_regexp.finditer(body):
1.507 -
1.508 - # Permit case-insensitive list terms.
1.509 -
1.510 - term = match.group("term").lower()
1.511 - desc = match.group("desc")
1.512 -
1.513 - # Check that the term has not already been substituted. If so,
1.514 - # get the next event.
1.515 -
1.516 - if term in replaced_terms:
1.517 - try:
1.518 - event = events.next()
1.519 -
1.520 - # No more events.
1.521 -
1.522 - except StopIteration:
1.523 - break
1.524 -
1.525 - event_details = event.getDetails()
1.526 - replaced_terms = set()
1.527 -
1.528 - # Add preceding text to the new body.
1.529 -
1.530 - new_body_parts.append(body[end_of_last_match:match.start()])
1.531 -
1.532 - # Get the matching regions, adding the term to the new body.
1.533 -
1.534 - new_body_parts.append(match.group("wholeterm"))
1.535 -
1.536 - # Special value type handling.
1.537 -
1.538 - if event_details.has_key(term):
1.539 -
1.540 - # Dates.
1.541 -
1.542 - if term in event.date_terms:
1.543 - desc = desc.replace("YYYY-MM-DD", str(event_details[term]))
1.544 -
1.545 - # Lists (whose elements may be quoted).
1.546 -
1.547 - elif term in event.list_terms:
1.548 - desc = ", ".join([getEncodedWikiText(item) for item in event_details[term]])
1.549 -
1.550 - # Labels which must be quoted.
1.551 -
1.552 - elif term in event.title_terms:
1.553 - desc = getEncodedWikiText(event_details[term])
1.554 -
1.555 - # Position details.
1.556 -
1.557 - elif term == "geo":
1.558 - desc = " ".join(map(str, event_details[term]))
1.559 -
1.560 - # Text which need not be quoted, but it will be Wiki text.
1.561 -
1.562 - elif term in event.other_terms:
1.563 - desc = event_details[term]
1.564 -
1.565 - replaced_terms.add(term)
1.566 -
1.567 - # Add the replaced value.
1.568 -
1.569 - new_body_parts.append(desc)
1.570 -
1.571 - # Remember where in the page has been processed.
1.572 -
1.573 - end_of_last_match = match.end()
1.574 -
1.575 - # Write the rest of the page.
1.576 -
1.577 - new_body_parts.append(body[end_of_last_match:])
1.578 -
1.579 - self.body = "".join(new_body_parts)
1.580 -
1.581 - def flushCategoryMembership(self):
1.582 -
1.583 - "Flush the category membership to the page body."
1.584 -
1.585 - body = self.getBody()
1.586 - category_names = self.getCategoryMembership()
1.587 - match = category_membership_regexp.search(body)
1.588 -
1.589 - if match:
1.590 - self.body = "".join([body[:match.start()], " ".join(category_names), body[match.end():]])
1.591 -
1.592 - def saveChanges(self):
1.593 -
1.594 - "Save changes to the event."
1.595 -
1.596 - self.flushEventDetails()
1.597 - self.flushCategoryMembership()
1.598 - self.page.saveText(self.getBody(), 0)
1.599 -
1.600 - def linkToPage(self, request, text, query_string=None, anchor=None):
1.601 -
1.602 - """
1.603 - Using 'request', return a link to this page with the given link 'text'
1.604 - and optional 'query_string' and 'anchor'.
1.605 - """
1.606 -
1.607 - return linkToPage(request, self.page, text, query_string, anchor)
1.608 -
1.609 - # Formatting-related functions.
1.610 -
1.611 - def getParserClass(self, format):
1.612 -
1.613 - """
1.614 - Return a parser class for the given 'format', returning a plain text
1.615 - parser if no parser can be found for the specified 'format'.
1.616 - """
1.617 -
1.618 - return getParserClass(self.page.request, format)
1.619 -
1.620 - def formatText(self, text, fmt):
1.621 -
1.622 - """
1.623 - Format the given 'text' using the specified formatter 'fmt'.
1.624 - """
1.625 -
1.626 - fmt.page = page = self.page
1.627 - request = page.request
1.628 -
1.629 - parser_cls = self.getParserClass(self.getFormat())
1.630 - return formatText(text, request, fmt, parser_cls)
1.631 -
1.632 -# Event details.
1.633 -
1.634 -class Event(ActsAsTimespan):
1.635 -
1.636 - "A description of an event."
1.637 -
1.638 - title_terms = "title", "summary"
1.639 - date_terms = "start", "end"
1.640 - list_terms = "topics", "categories"
1.641 - other_terms = "description", "location", "link"
1.642 - geo_terms = "geo",
1.643 - all_terms = title_terms + date_terms + list_terms + other_terms + geo_terms
1.644 -
1.645 - def __init__(self, page, details, raw_details=None):
1.646 - self.page = page
1.647 - self.details = details
1.648 - self.raw_details = raw_details
1.649 -
1.650 - # Permit omission of the end of the event by duplicating the start.
1.651 -
1.652 - if self.details.has_key("start") and not self.details.get("end"):
1.653 - end = self.details["start"]
1.654 -
1.655 - # Make any end time refer to the day instead.
1.656 -
1.657 - if isinstance(end, DateTime):
1.658 - end = end.as_date()
1.659 -
1.660 - self.details["end"] = end
1.661 -
1.662 - def __repr__(self):
1.663 - return "<Event %r %r>" % (self.getSummary(), self.as_limits())
1.664 -
1.665 - def __hash__(self):
1.666 -
1.667 - """
1.668 - Return a dictionary hash, avoiding mistaken equality of events in some
1.669 - situations (notably membership tests) by including the URL as well as
1.670 - the summary.
1.671 - """
1.672 -
1.673 - return hash(self.getSummary() + self.getEventURL())
1.674 -
1.675 - def getPage(self):
1.676 -
1.677 - "Return the page describing this event."
1.678 -
1.679 - return self.page
1.680 -
1.681 - def setPage(self, page):
1.682 -
1.683 - "Set the 'page' describing this event."
1.684 -
1.685 - self.page = page
1.686 -
1.687 - def getEventURL(self):
1.688 -
1.689 - "Return the URL of this event."
1.690 -
1.691 - fragment = self.details.get("fragment")
1.692 - return self.page.getPageURL() + (fragment and "#" + fragment or "")
1.693 -
1.694 - def linkToEvent(self, request, text, query_string=None):
1.695 -
1.696 - """
1.697 - Using 'request', return a link to this event with the given link 'text'
1.698 - and optional 'query_string'.
1.699 - """
1.700 -
1.701 - return self.page.linkToPage(request, text, query_string, self.details.get("fragment"))
1.702 -
1.703 - def getMetadata(self):
1.704 -
1.705 - """
1.706 - Return a dictionary containing items describing the event's "created"
1.707 - time, "last-modified" time, "sequence" (or revision number) and the
1.708 - "last-comment" made about the last edit.
1.709 - """
1.710 -
1.711 - # Delegate this to the page.
1.712 -
1.713 - return self.page.getMetadata()
1.714 -
1.715 - def getSummary(self, event_parent=None):
1.716 -
1.717 - """
1.718 - Return either the given title or summary of the event according to the
1.719 - event details, or a summary made from using the pretty version of the
1.720 - page name.
1.721 -
1.722 - If the optional 'event_parent' is specified, any page beneath the given
1.723 - 'event_parent' page in the page hierarchy will omit this parent information
1.724 - if its name is used as the summary.
1.725 - """
1.726 -
1.727 - event_details = self.details
1.728 -
1.729 - if event_details.has_key("title"):
1.730 - return event_details["title"]
1.731 - elif event_details.has_key("summary"):
1.732 - return event_details["summary"]
1.733 - else:
1.734 - # If appropriate, remove the parent details and "/" character.
1.735 -
1.736 - title = self.page.getPageName()
1.737 -
1.738 - if event_parent and title.startswith(event_parent):
1.739 - title = title[len(event_parent.rstrip("/")) + 1:]
1.740 -
1.741 - return getPrettyTitle(title)
1.742 -
1.743 - def getDetails(self):
1.744 -
1.745 - "Return the details for this event."
1.746 -
1.747 - return self.details
1.748 -
1.749 - def setDetails(self, event_details):
1.750 -
1.751 - "Set the 'event_details' for this event."
1.752 -
1.753 - self.details = event_details
1.754 -
1.755 - def getRawDetails(self):
1.756 -
1.757 - "Return the details for this event as they were written in a page."
1.758 -
1.759 - return self.raw_details
1.760 -
1.761 - # Timespan-related methods.
1.762 -
1.763 - def __contains__(self, other):
1.764 - return self == other
1.765 -
1.766 - def __eq__(self, other):
1.767 - if isinstance(other, Event):
1.768 - return self.getSummary() == other.getSummary() and self.getEventURL() == other.getEventURL() and self._cmp(other)
1.769 - else:
1.770 - return self._cmp(other) == 0
1.771 -
1.772 - def __ne__(self, other):
1.773 - return not self.__eq__(other)
1.774 -
1.775 - def __lt__(self, other):
1.776 - return self._cmp(other) == -1
1.777 -
1.778 - def __le__(self, other):
1.779 - return self._cmp(other) in (-1, 0)
1.780 -
1.781 - def __gt__(self, other):
1.782 - return self._cmp(other) == 1
1.783 -
1.784 - def __ge__(self, other):
1.785 - return self._cmp(other) in (0, 1)
1.786 -
1.787 - def _cmp(self, other):
1.788 -
1.789 - "Compare this event to an 'other' event purely by their timespans."
1.790 -
1.791 - if isinstance(other, Event):
1.792 - return cmp(self.as_timespan(), other.as_timespan())
1.793 - else:
1.794 - return cmp(self.as_timespan(), other)
1.795 -
1.796 - def as_timespan(self):
1.797 - details = self.details
1.798 - if details.has_key("start") and details.has_key("end"):
1.799 - return Timespan(details["start"], details["end"])
1.800 - else:
1.801 - return None
1.802 -
1.803 - def as_limits(self):
1.804 - ts = self.as_timespan()
1.805 - return ts and ts.as_limits()
1.806 -
1.807 -class CalendarEvent(Event):
1.808 -
1.809 - "An event from a remote calendar."
1.810 -
1.811 - def getEventURL(self):
1.812 -
1.813 - "Return the URL of this event."
1.814 -
1.815 - return self.details.get("url") or self.page.getPageURL()
1.816 -
1.817 - def linkToEvent(self, request, text, query_string=None, anchor=None):
1.818 -
1.819 - """
1.820 - Using 'request', return a link to this event with the given link 'text'
1.821 - and optional 'query_string' and 'anchor'.
1.822 - """
1.823 -
1.824 - return linkToResource(self.getEventURL(), request, text, query_string, anchor)
1.825 -
1.826 - def getMetadata(self):
1.827 -
1.828 - """
1.829 - Return a dictionary containing items describing the event's "created"
1.830 - time, "last-modified" time, "sequence" (or revision number) and the
1.831 - "last-comment" made about the last edit.
1.832 - """
1.833 -
1.834 - return {
1.835 - "created" : self.details.get("created") or self.details["dtstamp"],
1.836 - "last-modified" : self.details.get("last-modified") or self.details["dtstamp"],
1.837 - "sequence" : self.details.get("sequence") or 0,
1.838 - "last-comment" : ""
1.839 - }
1.840 -
1.841 -# Obtaining event containers and events from such containers.
1.842 -
1.843 -def getEventPages(pages):
1.844 -
1.845 - "Return a list of events found on the given 'pages'."
1.846 -
1.847 - # Get real pages instead of result pages.
1.848 -
1.849 - return map(EventPage, pages)
1.850 -
1.851 -def getAllEventSources(request):
1.852 -
1.853 - "Return all event sources defined in the Wiki using the 'request'."
1.854 -
1.855 - sources_page = getattr(request.cfg, "event_aggregator_sources_page", "EventSourcesDict")
1.856 -
1.857 - # Remote sources are accessed via dictionary page definitions.
1.858 -
1.859 - return getWikiDict(sources_page, request)
1.860 -
1.861 -def getEventResources(sources, calendar_start, calendar_end, request):
1.862 -
1.863 - """
1.864 - Return resource objects for the given 'sources' using the given
1.865 - 'calendar_start' and 'calendar_end' to parameterise requests to the sources,
1.866 - and the 'request' to access configuration settings in the Wiki.
1.867 - """
1.868 -
1.869 - sources_dict = getAllEventSources(request)
1.870 - if not sources_dict:
1.871 - return []
1.872 -
1.873 - # Use dates for the calendar limits.
1.874 -
1.875 - if isinstance(calendar_start, Date):
1.876 - pass
1.877 - elif isinstance(calendar_start, Month):
1.878 - calendar_start = calendar_start.as_date(1)
1.879 -
1.880 - if isinstance(calendar_end, Date):
1.881 - pass
1.882 - elif isinstance(calendar_end, Month):
1.883 - calendar_end = calendar_end.as_date(-1)
1.884 -
1.885 - resources = []
1.886 -
1.887 - for source in sources:
1.888 - try:
1.889 - details = sources_dict[source].split()
1.890 - url = details[0]
1.891 - format = (details[1:] or ["ical"])[0]
1.892 - except (KeyError, ValueError):
1.893 - pass
1.894 - else:
1.895 - # Prevent local file access.
1.896 -
1.897 - if url.startswith("file:"):
1.898 - continue
1.899 -
1.900 - # Parameterise the URL.
1.901 - # Where other parameters are used, care must be taken to encode them
1.902 - # properly.
1.903 -
1.904 - url = url.replace("{start}", urllib.quote_plus(calendar_start and str(calendar_start) or ""))
1.905 - url = url.replace("{end}", urllib.quote_plus(calendar_end and str(calendar_end) or ""))
1.906 -
1.907 - # Get a parser.
1.908 - # NOTE: This could be done reactively by choosing a parser based on
1.909 - # NOTE: the content type provided by the URL.
1.910 -
1.911 - if format == "ical" and vCalendar is not None:
1.912 - parser = vCalendar.parse
1.913 - resource_cls = EventCalendar
1.914 - required_content_type = "text/calendar"
1.915 - else:
1.916 - continue
1.917 -
1.918 - # Obtain the resource, using a cached version if appropriate.
1.919 -
1.920 - max_cache_age = int(getattr(request.cfg, "event_aggregator_max_cache_age", "300"))
1.921 - data = getCachedResource(request, url, "EventAggregator", "wiki", max_cache_age)
1.922 - if not data:
1.923 - continue
1.924 -
1.925 - # Process the entry, parsing the content.
1.926 -
1.927 - f = StringIO(data)
1.928 - try:
1.929 - url = f.readline()
1.930 -
1.931 - # Get the content type and encoding, making sure that the data
1.932 - # can be parsed.
1.933 -
1.934 - content_type, encoding = getContentTypeAndEncoding(f.readline())
1.935 - if content_type != required_content_type:
1.936 - continue
1.937 -
1.938 - # Send the data to the parser.
1.939 -
1.940 - uf = codecs.getreader(encoding or "utf-8")(f)
1.941 - try:
1.942 - resources.append(resource_cls(url, parser(uf)))
1.943 - finally:
1.944 - uf.close()
1.945 - finally:
1.946 - f.close()
1.947 -
1.948 - return resources
1.949 -
1.950 -def getEventsFromResources(resources):
1.951 -
1.952 - "Return a list of events supplied by the given event 'resources'."
1.953 -
1.954 - events = []
1.955 -
1.956 - for resource in resources:
1.957 -
1.958 - # Get all events described by the resource.
1.959 -
1.960 - for event in resource.getEvents():
1.961 -
1.962 - # Remember the event.
1.963 -
1.964 - events.append(event)
1.965 -
1.966 - return events
1.967 -
1.968 -# Event filtering and limits.
1.969 -
1.970 -def getEventsInPeriod(events, calendar_period):
1.971 -
1.972 - """
1.973 - Return a collection containing those of the given 'events' which occur
1.974 - within the given 'calendar_period'.
1.975 - """
1.976 -
1.977 - all_shown_events = []
1.978 -
1.979 - for event in events:
1.980 -
1.981 - # Test for the suitability of the event.
1.982 -
1.983 - if event.as_timespan() is not None:
1.984 -
1.985 - # Compare the dates to the requested calendar window, if any.
1.986 -
1.987 - if event in calendar_period:
1.988 - all_shown_events.append(event)
1.989 -
1.990 - return all_shown_events
1.991 -
1.992 -def getEventLimits(events):
1.993 -
1.994 - "Return the earliest and latest of the given 'events'."
1.995 -
1.996 - earliest = None
1.997 - latest = None
1.998 -
1.999 - for event in events:
1.1000 -
1.1001 - # Test for the suitability of the event.
1.1002 -
1.1003 - if event.as_timespan() is not None:
1.1004 - ts = event.as_timespan()
1.1005 - if earliest is None or ts.start < earliest:
1.1006 - earliest = ts.start
1.1007 - if latest is None or ts.end > latest:
1.1008 - latest = ts.end
1.1009 -
1.1010 - return earliest, latest
1.1011 -
1.1012 -def getLatestEventTimestamp(events):
1.1013 -
1.1014 - """
1.1015 - Return the latest timestamp found from the given 'events'.
1.1016 - """
1.1017 -
1.1018 - latest = None
1.1019 -
1.1020 - for event in events:
1.1021 - metadata = event.getMetadata()
1.1022 -
1.1023 - if latest is None or latest < metadata["last-modified"]:
1.1024 - latest = metadata["last-modified"]
1.1025 -
1.1026 - return latest
1.1027 -
1.1028 -def getOrderedEvents(events):
1.1029 -
1.1030 - """
1.1031 - Return a list with the given 'events' ordered according to their start and
1.1032 - end dates.
1.1033 - """
1.1034 -
1.1035 - ordered_events = events[:]
1.1036 - ordered_events.sort()
1.1037 - return ordered_events
1.1038 -
1.1039 -def getCalendarPeriod(calendar_start, calendar_end):
1.1040 -
1.1041 - """
1.1042 - Return a calendar period for the given 'calendar_start' and 'calendar_end'.
1.1043 - These parameters can be given as None.
1.1044 - """
1.1045 -
1.1046 - # Re-order the window, if appropriate.
1.1047 -
1.1048 - if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end:
1.1049 - calendar_start, calendar_end = calendar_end, calendar_start
1.1050 -
1.1051 - return Timespan(calendar_start, calendar_end)
1.1052 -
1.1053 -def getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution):
1.1054 -
1.1055 - """
1.1056 - From the requested 'calendar_start' and 'calendar_end', which may be None,
1.1057 - indicating that no restriction is imposed on the period for each of the
1.1058 - boundaries, use the 'earliest' and 'latest' event months to define a
1.1059 - specific period of interest.
1.1060 - """
1.1061 -
1.1062 - # Define the period as starting with any specified start month or the
1.1063 - # earliest event known, ending with any specified end month or the latest
1.1064 - # event known.
1.1065 -
1.1066 - first = calendar_start or earliest
1.1067 - last = calendar_end or latest
1.1068 -
1.1069 - # If there is no range of months to show, perhaps because there are no
1.1070 - # events in the requested period, and there was no start or end month
1.1071 - # specified, show only the month indicated by the start or end of the
1.1072 - # requested period. If all events were to be shown but none were found show
1.1073 - # the current month.
1.1074 -
1.1075 - if resolution == "date":
1.1076 - get_current = getCurrentDate
1.1077 - else:
1.1078 - get_current = getCurrentMonth
1.1079 -
1.1080 - if first is None:
1.1081 - first = last or get_current()
1.1082 - if last is None:
1.1083 - last = first or get_current()
1.1084 -
1.1085 - if resolution == "month":
1.1086 - first = first.as_month()
1.1087 - last = last.as_month()
1.1088 -
1.1089 - # Permit "expiring" periods (where the start date approaches the end date).
1.1090 -
1.1091 - return min(first, last), last
1.1092 -
1.1093 -def getCoverage(events, resolution="date"):
1.1094 -
1.1095 - """
1.1096 - Determine the coverage of the given 'events', returning a collection of
1.1097 - timespans, along with a dictionary mapping locations to collections of
1.1098 - slots, where each slot contains a tuple of the form (timespans, events).
1.1099 - """
1.1100 -
1.1101 - all_events = {}
1.1102 - full_coverage = TimespanCollection(resolution)
1.1103 -
1.1104 - # Get event details.
1.1105 -
1.1106 - for event in events:
1.1107 - event_details = event.getDetails()
1.1108 -
1.1109 - # Find the coverage of this period for the event.
1.1110 -
1.1111 - # For day views, each location has its own slot, but for month
1.1112 - # views, all locations are pooled together since having separate
1.1113 - # slots for each location can lead to poor usage of vertical space.
1.1114 -
1.1115 - if resolution == "datetime":
1.1116 - event_location = event_details.get("location")
1.1117 - else:
1.1118 - event_location = None
1.1119 -
1.1120 - # Update the overall coverage.
1.1121 -
1.1122 - full_coverage.insert_in_order(event)
1.1123 -
1.1124 - # Add a new events list for a new location.
1.1125 - # Locations can be unspecified, thus None refers to all unlocalised
1.1126 - # events.
1.1127 -
1.1128 - if not all_events.has_key(event_location):
1.1129 - all_events[event_location] = [TimespanCollection(resolution, [event])]
1.1130 -
1.1131 - # Try and fit the event into an events list.
1.1132 -
1.1133 - else:
1.1134 - slot = all_events[event_location]
1.1135 -
1.1136 - for slot_events in slot:
1.1137 -
1.1138 - # Where the event does not overlap with the events in the
1.1139 - # current collection, add it alongside these events.
1.1140 -
1.1141 - if not event in slot_events:
1.1142 - slot_events.insert_in_order(event)
1.1143 - break
1.1144 -
1.1145 - # Make a new element in the list if the event cannot be
1.1146 - # marked alongside existing events.
1.1147 -
1.1148 - else:
1.1149 - slot.append(TimespanCollection(resolution, [event]))
1.1150 -
1.1151 - return full_coverage, all_events
1.1152 -
1.1153 -def getCoverageScale(coverage):
1.1154 -
1.1155 - """
1.1156 - Return a scale for the given coverage so that the times involved are
1.1157 - exposed. The scale consists of a list of non-overlapping timespans forming
1.1158 - a contiguous period of time.
1.1159 - """
1.1160 -
1.1161 - times = set()
1.1162 - for timespan in coverage:
1.1163 - start, end = timespan.as_limits()
1.1164 -
1.1165 - # Add either genuine times or dates converted to times.
1.1166 -
1.1167 - if isinstance(start, DateTime):
1.1168 - times.add(start)
1.1169 - else:
1.1170 - times.add(start.as_start_of_day())
1.1171 -
1.1172 - if isinstance(end, DateTime):
1.1173 - times.add(end)
1.1174 - else:
1.1175 - times.add(end.as_date().next_day())
1.1176 -
1.1177 - times = list(times)
1.1178 - times.sort(cmp_dates_as_day_start)
1.1179 -
1.1180 - scale = []
1.1181 - first = 1
1.1182 - start = None
1.1183 - for time in times:
1.1184 - if not first:
1.1185 - scale.append(Timespan(start, time))
1.1186 - else:
1.1187 - first = 0
1.1188 - start = time
1.1189 -
1.1190 - return scale
1.1191 -
1.1192 -# Event sorting.
1.1193 -
1.1194 -def sort_start_first(x, y):
1.1195 - x_ts = x.as_limits()
1.1196 - if x_ts is not None:
1.1197 - x_start, x_end = x_ts
1.1198 - y_ts = y.as_limits()
1.1199 - if y_ts is not None:
1.1200 - y_start, y_end = y_ts
1.1201 - start_order = cmp(x_start, y_start)
1.1202 - if start_order == 0:
1.1203 - return cmp(x_end, y_end)
1.1204 - else:
1.1205 - return start_order
1.1206 - return 0
1.1207 -
1.1208 -# Country code parsing.
1.1209 -
1.1210 -def getCountry(s):
1.1211 -
1.1212 - "Find a country code in the given string 's'."
1.1213 -
1.1214 - match = country_code_regexp.search(s)
1.1215 -
1.1216 - if match:
1.1217 - return match.group("code")
1.1218 - else:
1.1219 - return None
1.1220 -
1.1221 -# Page-related functions.
1.1222 -
1.1223 -def fillEventPageFromTemplate(template_page, new_page, event_details, category_pagenames):
1.1224 -
1.1225 - """
1.1226 - Using the given 'template_page', complete the 'new_page' by copying the
1.1227 - template and adding the given 'event_details' (a dictionary of event
1.1228 - fields), setting also the 'category_pagenames' to define category
1.1229 - membership.
1.1230 - """
1.1231 -
1.1232 - event_page = EventPage(template_page)
1.1233 - new_event_page = EventPage(new_page)
1.1234 - new_event_page.copyPage(event_page)
1.1235 -
1.1236 - if new_event_page.getFormat() == "wiki":
1.1237 - new_event = Event(new_event_page, event_details)
1.1238 - new_event_page.setEvents([new_event])
1.1239 - new_event_page.setCategoryMembership(category_pagenames)
1.1240 - new_event_page.flushEventDetails()
1.1241 -
1.1242 - return new_event_page.getBody()
1.1243 -
1.1244 -def getMapsPage(request):
1.1245 - return getattr(request.cfg, "event_aggregator_maps_page", "EventMapsDict")
1.1246 -
1.1247 -def getLocationsPage(request):
1.1248 - return getattr(request.cfg, "event_aggregator_locations_page", "EventLocationsDict")
1.1249 -
1.1250 -class Location:
1.1251 -
1.1252 - """
1.1253 - A representation of a location acquired from the locations dictionary.
1.1254 -
1.1255 - The locations dictionary is a mapping from location to a string containing
1.1256 - white-space-separated values describing...
1.1257 -
1.1258 - * The latitude and longitude of the location.
1.1259 - * Optionally, the time regime used by the location.
1.1260 - """
1.1261 -
1.1262 - def __init__(self, location, locations):
1.1263 -
1.1264 - """
1.1265 - Initialise the given 'location' using the 'locations' dictionary
1.1266 - provided.
1.1267 - """
1.1268 -
1.1269 - self.location = location
1.1270 -
1.1271 - try:
1.1272 - self.data = locations[location].split()
1.1273 - except KeyError:
1.1274 - self.data = []
1.1275 -
1.1276 - def getPosition(self):
1.1277 -
1.1278 - """
1.1279 - Attempt to return the position of this location. If no position can be
1.1280 - found, return a latitude of None and a longitude of None.
1.1281 - """
1.1282 -
1.1283 - try:
1.1284 - latitude, longitude = map(getMapReference, self.data[:2])
1.1285 - return latitude, longitude
1.1286 - except ValueError:
1.1287 - return None, None
1.1288 -
1.1289 - def getTimeRegime(self):
1.1290 -
1.1291 - """
1.1292 - Attempt to return the time regime employed at this location. If no
1.1293 - regime has been specified, return None.
1.1294 - """
1.1295 -
1.1296 - try:
1.1297 - return self.data[2]
1.1298 - except IndexError:
1.1299 - return None
1.1300 -
1.1301 -# User interface abstractions.
1.1302 -
1.1303 -class View:
1.1304 -
1.1305 - "A view of the event calendar."
1.1306 -
1.1307 - def __init__(self, page, calendar_name,
1.1308 - raw_calendar_start, raw_calendar_end,
1.1309 - original_calendar_start, original_calendar_end,
1.1310 - calendar_start, calendar_end,
1.1311 - wider_calendar_start, wider_calendar_end,
1.1312 - first, last, category_names, remote_sources, search_pattern, template_name,
1.1313 - parent_name, mode, resolution, name_usage, map_name):
1.1314 -
1.1315 - """
1.1316 - Initialise the view with the current 'page', a 'calendar_name' (which
1.1317 - may be None), the 'raw_calendar_start' and 'raw_calendar_end' (which
1.1318 - are the actual start and end values provided by the request), the
1.1319 - calculated 'original_calendar_start' and 'original_calendar_end' (which
1.1320 - are the result of calculating the calendar's limits from the raw start
1.1321 - and end values), the requested, calculated 'calendar_start' and
1.1322 - 'calendar_end' (which may involve different start and end values due to
1.1323 - navigation in the user interface), and the requested
1.1324 - 'wider_calendar_start' and 'wider_calendar_end' (which indicate a wider
1.1325 - view used when navigating out of the day view), along with the 'first'
1.1326 - and 'last' months of event coverage.
1.1327 -
1.1328 - The additional 'category_names', 'remote_sources', 'search_pattern',
1.1329 - 'template_name', 'parent_name' and 'mode' parameters are used to
1.1330 - configure the links employed by the view.
1.1331 -
1.1332 - The 'resolution' affects the view for certain modes and is also used to
1.1333 - parameterise links.
1.1334 -
1.1335 - The 'name_usage' parameter controls how names are shown on calendar mode
1.1336 - events, such as how often labels are repeated.
1.1337 -
1.1338 - The 'map_name' parameter provides the name of a map to be used in the
1.1339 - map mode.
1.1340 - """
1.1341 -
1.1342 - self.page = page
1.1343 - self.calendar_name = calendar_name
1.1344 - self.raw_calendar_start = raw_calendar_start
1.1345 - self.raw_calendar_end = raw_calendar_end
1.1346 - self.original_calendar_start = original_calendar_start
1.1347 - self.original_calendar_end = original_calendar_end
1.1348 - self.calendar_start = calendar_start
1.1349 - self.calendar_end = calendar_end
1.1350 - self.wider_calendar_start = wider_calendar_start
1.1351 - self.wider_calendar_end = wider_calendar_end
1.1352 - self.template_name = template_name
1.1353 - self.parent_name = parent_name
1.1354 - self.mode = mode
1.1355 - self.resolution = resolution
1.1356 - self.name_usage = name_usage
1.1357 - self.map_name = map_name
1.1358 -
1.1359 - # Search-related parameters for links.
1.1360 -
1.1361 - self.category_name_parameters = "&".join([("category=%s" % name) for name in category_names])
1.1362 - self.remote_source_parameters = "&".join([("source=%s" % source) for source in remote_sources])
1.1363 - self.search_pattern = search_pattern
1.1364 -
1.1365 - # Calculate the duration in terms of the highest common unit of time.
1.1366 -
1.1367 - self.first = first
1.1368 - self.last = last
1.1369 - self.duration = abs(last - first) + 1
1.1370 -
1.1371 - if self.calendar_name:
1.1372 -
1.1373 - # Store the view parameters.
1.1374 -
1.1375 - self.previous_start = first.previous()
1.1376 - self.next_start = first.next()
1.1377 - self.previous_end = last.previous()
1.1378 - self.next_end = last.next()
1.1379 -
1.1380 - self.previous_set_start = first.update(-self.duration)
1.1381 - self.next_set_start = first.update(self.duration)
1.1382 - self.previous_set_end = last.update(-self.duration)
1.1383 - self.next_set_end = last.update(self.duration)
1.1384 -
1.1385 - def getIdentifier(self):
1.1386 -
1.1387 - "Return a unique identifier to be used to refer to this view."
1.1388 -
1.1389 - # NOTE: Nasty hack to get a unique identifier if no name is given.
1.1390 -
1.1391 - return self.calendar_name or str(id(self))
1.1392 -
1.1393 - def getQualifiedParameterName(self, argname):
1.1394 -
1.1395 - "Return the 'argname' qualified using the calendar name."
1.1396 -
1.1397 - return getQualifiedParameterName(self.calendar_name, argname)
1.1398 -
1.1399 - def getDateQueryString(self, argname, date, prefix=1):
1.1400 -
1.1401 - """
1.1402 - Return a query string fragment for the given 'argname', referring to the
1.1403 - month given by the specified 'year_month' object, appropriate for this
1.1404 - calendar.
1.1405 -
1.1406 - If 'prefix' is specified and set to a false value, the parameters in the
1.1407 - query string will not be calendar-specific, but could be used with the
1.1408 - summary action.
1.1409 - """
1.1410 -
1.1411 - suffixes = ["year", "month", "day"]
1.1412 -
1.1413 - if date is not None:
1.1414 - args = []
1.1415 - for suffix, value in zip(suffixes, date.as_tuple()):
1.1416 - suffixed_argname = "%s-%s" % (argname, suffix)
1.1417 - if prefix:
1.1418 - suffixed_argname = self.getQualifiedParameterName(suffixed_argname)
1.1419 - args.append("%s=%s" % (suffixed_argname, value))
1.1420 - return "&".join(args)
1.1421 - else:
1.1422 - return ""
1.1423 -
1.1424 - def getRawDateQueryString(self, argname, date, prefix=1):
1.1425 -
1.1426 - """
1.1427 - Return a query string fragment for the given 'argname', referring to the
1.1428 - date given by the specified 'date' value, appropriate for this
1.1429 - calendar.
1.1430 -
1.1431 - If 'prefix' is specified and set to a false value, the parameters in the
1.1432 - query string will not be calendar-specific, but could be used with the
1.1433 - summary action.
1.1434 - """
1.1435 -
1.1436 - if date is not None:
1.1437 - if prefix:
1.1438 - argname = self.getQualifiedParameterName(argname)
1.1439 - return "%s=%s" % (argname, wikiutil.url_quote_plus(date))
1.1440 - else:
1.1441 - return ""
1.1442 -
1.1443 - def getNavigationLink(self, start, end, mode=None, resolution=None, wider_start=None, wider_end=None):
1.1444 -
1.1445 - """
1.1446 - Return a query string fragment for navigation to a view showing months
1.1447 - from 'start' to 'end' inclusive, with the optional 'mode' indicating the
1.1448 - view style and the optional 'resolution' indicating the resolution of a
1.1449 - view, if configurable.
1.1450 -
1.1451 - If the 'wider_start' and 'wider_end' arguments are given, parameters
1.1452 - indicating a wider calendar view (when returning from a day view, for
1.1453 - example) will be included in the link.
1.1454 - """
1.1455 -
1.1456 - return "%s&%s&%s=%s&%s=%s&%s&%s" % (
1.1457 - self.getRawDateQueryString("start", start),
1.1458 - self.getRawDateQueryString("end", end),
1.1459 - self.getQualifiedParameterName("mode"), mode or self.mode,
1.1460 - self.getQualifiedParameterName("resolution"), resolution or self.resolution,
1.1461 - self.getRawDateQueryString("wider-start", wider_start),
1.1462 - self.getRawDateQueryString("wider-end", wider_end),
1.1463 - )
1.1464 -
1.1465 - def getUpdateLink(self, start, end, mode=None, resolution=None, wider_start=None, wider_end=None):
1.1466 -
1.1467 - """
1.1468 - Return a query string fragment for navigation to a view showing months
1.1469 - from 'start' to 'end' inclusive, with the optional 'mode' indicating the
1.1470 - view style and the optional 'resolution' indicating the resolution of a
1.1471 - view, if configurable. This link differs from the conventional
1.1472 - navigation link in that it is sufficient to activate the update action
1.1473 - and produce an updated region of the page without needing to locate and
1.1474 - process the page or any macro invocation.
1.1475 -
1.1476 - If the 'wider_start' and 'wider_end' arguments are given, parameters
1.1477 - indicating a wider calendar view (when returning from a day view, for
1.1478 - example) will be included in the link.
1.1479 - """
1.1480 -
1.1481 - parameters = [
1.1482 - self.getRawDateQueryString("start", start, 0),
1.1483 - self.getRawDateQueryString("end", end, 0),
1.1484 - self.category_name_parameters,
1.1485 - self.remote_source_parameters,
1.1486 - self.getRawDateQueryString("wider-start", wider_start, 0),
1.1487 - self.getRawDateQueryString("wider-end", wider_end, 0),
1.1488 - ]
1.1489 -
1.1490 - pairs = [
1.1491 - ("calendar", self.calendar_name or ""),
1.1492 - ("calendarstart", self.raw_calendar_start or ""),
1.1493 - ("calendarend", self.raw_calendar_end or ""),
1.1494 - ("mode", mode or self.mode),
1.1495 - ("resolution", resolution or self.resolution),
1.1496 - ("parent", self.parent_name or ""),
1.1497 - ("template", self.template_name or ""),
1.1498 - ("names", self.name_usage),
1.1499 - ("map", self.map_name or ""),
1.1500 - ("search", self.search_pattern or ""),
1.1501 - ]
1.1502 -
1.1503 - url = self.page.url(self.page.request,
1.1504 - "action=EventAggregatorUpdate&%s" % (
1.1505 - "&".join([("%s=%s" % pair) for pair in pairs] + parameters)
1.1506 - ), relative=True)
1.1507 -
1.1508 - return "return replaceCalendar('EventAggregator-%s', '%s')" % (self.getIdentifier(), url)
1.1509 -
1.1510 - def getNewEventLink(self, start):
1.1511 -
1.1512 - """
1.1513 - Return a query string activating the new event form, incorporating the
1.1514 - calendar parameters, specialising the form for the given 'start' date or
1.1515 - month.
1.1516 - """
1.1517 -
1.1518 - if start is not None:
1.1519 - details = start.as_tuple()
1.1520 - pairs = zip(["start-year=%d", "start-month=%d", "start-day=%d"], details)
1.1521 - args = [(param % value) for (param, value) in pairs]
1.1522 - args = "&".join(args)
1.1523 - else:
1.1524 - args = ""
1.1525 -
1.1526 - # Prepare navigation details for the calendar shown with the new event
1.1527 - # form.
1.1528 -
1.1529 - navigation_link = self.getNavigationLink(
1.1530 - self.calendar_start, self.calendar_end
1.1531 - )
1.1532 -
1.1533 - return "action=EventAggregatorNewEvent%s%s&template=%s&parent=%s&%s" % (
1.1534 - args and "&%s" % args,
1.1535 - self.category_name_parameters and "&%s" % self.category_name_parameters,
1.1536 - self.template_name, self.parent_name or "",
1.1537 - navigation_link)
1.1538 -
1.1539 - def getFullDateLabel(self, date):
1.1540 - return getFullDateLabel(self.page.request, date)
1.1541 -
1.1542 - def getFullMonthLabel(self, year_month):
1.1543 - return getFullMonthLabel(self.page.request, year_month)
1.1544 -
1.1545 - def getFullLabel(self, arg):
1.1546 - return self.resolution == "date" and self.getFullDateLabel(arg) or self.getFullMonthLabel(arg)
1.1547 -
1.1548 - def _getCalendarPeriod(self, start_label, end_label, default_label):
1.1549 -
1.1550 - """
1.1551 - Return a label describing a calendar period in terms of the given
1.1552 - 'start_label' and 'end_label', with the 'default_label' being used where
1.1553 - the supplied start and end labels fail to produce a meaningful label.
1.1554 - """
1.1555 -
1.1556 - output = []
1.1557 - append = output.append
1.1558 -
1.1559 - if start_label:
1.1560 - append(start_label)
1.1561 - if end_label and start_label != end_label:
1.1562 - if output:
1.1563 - append(" - ")
1.1564 - append(end_label)
1.1565 - return "".join(output) or default_label
1.1566 -
1.1567 - def getCalendarPeriod(self):
1.1568 - _ = self.page.request.getText
1.1569 - return self._getCalendarPeriod(
1.1570 - self.calendar_start and self.getFullLabel(self.calendar_start),
1.1571 - self.calendar_end and self.getFullLabel(self.calendar_end),
1.1572 - _("All events")
1.1573 - )
1.1574 -
1.1575 - def getOriginalCalendarPeriod(self):
1.1576 - _ = self.page.request.getText
1.1577 - return self._getCalendarPeriod(
1.1578 - self.original_calendar_start and self.getFullLabel(self.original_calendar_start),
1.1579 - self.original_calendar_end and self.getFullLabel(self.original_calendar_end),
1.1580 - _("All events")
1.1581 - )
1.1582 -
1.1583 - def getRawCalendarPeriod(self):
1.1584 - _ = self.page.request.getText
1.1585 - return self._getCalendarPeriod(
1.1586 - self.raw_calendar_start,
1.1587 - self.raw_calendar_end,
1.1588 - _("No period specified")
1.1589 - )
1.1590 -
1.1591 - def writeDownloadControls(self):
1.1592 -
1.1593 - """
1.1594 - Return a representation of the download controls, featuring links for
1.1595 - view, calendar and customised downloads and subscriptions.
1.1596 - """
1.1597 -
1.1598 - page = self.page
1.1599 - request = page.request
1.1600 - fmt = request.formatter
1.1601 - _ = request.getText
1.1602 -
1.1603 - output = []
1.1604 - append = output.append
1.1605 -
1.1606 - # The full URL is needed for webcal links.
1.1607 -
1.1608 - full_url = "%s%s" % (request.getBaseURL(), getPathInfo(request))
1.1609 -
1.1610 - # Generate the links.
1.1611 -
1.1612 - download_dialogue_link = "action=EventAggregatorSummary&parent=%s&resolution=%s&search=%s%s%s" % (
1.1613 - self.parent_name or "",
1.1614 - self.resolution,
1.1615 - self.search_pattern or "",
1.1616 - self.category_name_parameters and "&%s" % self.category_name_parameters,
1.1617 - self.remote_source_parameters and "&%s" % self.remote_source_parameters
1.1618 - )
1.1619 - download_all_link = download_dialogue_link + "&doit=1"
1.1620 - download_link = download_all_link + ("&%s&%s" % (
1.1621 - self.getDateQueryString("start", self.calendar_start, prefix=0),
1.1622 - self.getDateQueryString("end", self.calendar_end, prefix=0)
1.1623 - ))
1.1624 -
1.1625 - # Subscription links just explicitly select the RSS format.
1.1626 -
1.1627 - subscribe_dialogue_link = download_dialogue_link + "&format=RSS"
1.1628 - subscribe_all_link = download_all_link + "&format=RSS"
1.1629 - subscribe_link = download_link + "&format=RSS"
1.1630 -
1.1631 - # Adjust the "download all" and "subscribe all" links if the calendar
1.1632 - # has an inherent period associated with it.
1.1633 -
1.1634 - period_limits = []
1.1635 -
1.1636 - if self.raw_calendar_start:
1.1637 - period_limits.append("&%s" %
1.1638 - self.getRawDateQueryString("start", self.raw_calendar_start, prefix=0)
1.1639 - )
1.1640 - if self.raw_calendar_end:
1.1641 - period_limits.append("&%s" %
1.1642 - self.getRawDateQueryString("end", self.raw_calendar_end, prefix=0)
1.1643 - )
1.1644 -
1.1645 - period_limits = "".join(period_limits)
1.1646 -
1.1647 - download_dialogue_link += period_limits
1.1648 - download_all_link += period_limits
1.1649 - subscribe_dialogue_link += period_limits
1.1650 - subscribe_all_link += period_limits
1.1651 -
1.1652 - # Pop-up descriptions of the downloadable calendars.
1.1653 -
1.1654 - calendar_period = self.getCalendarPeriod()
1.1655 - original_calendar_period = self.getOriginalCalendarPeriod()
1.1656 - raw_calendar_period = self.getRawCalendarPeriod()
1.1657 -
1.1658 - # Write the controls.
1.1659 -
1.1660 - # Download controls.
1.1661 -
1.1662 - append(fmt.div(on=1, css_class="event-download-controls"))
1.1663 -
1.1664 - append(fmt.span(on=1, css_class="event-download"))
1.1665 - append(fmt.text(_("Download...")))
1.1666 - append(fmt.div(on=1, css_class="event-download-popup"))
1.1667 -
1.1668 - append(fmt.div(on=1, css_class="event-download-item"))
1.1669 - append(fmt.span(on=1, css_class="event-download-types"))
1.1670 - append(fmt.span(on=1, css_class="event-download-webcal"))
1.1671 - append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_link))
1.1672 - append(fmt.span(on=0))
1.1673 - append(fmt.span(on=1, css_class="event-download-http"))
1.1674 - append(linkToPage(request, page, _("http"), download_link))
1.1675 - append(fmt.span(on=0))
1.1676 - append(fmt.span(on=0)) # end types
1.1677 - append(fmt.span(on=1, css_class="event-download-label"))
1.1678 - append(fmt.text(_("Download this view")))
1.1679 - append(fmt.span(on=0)) # end label
1.1680 - append(fmt.span(on=1, css_class="event-download-period"))
1.1681 - append(fmt.text(calendar_period))
1.1682 - append(fmt.span(on=0))
1.1683 - append(fmt.div(on=0))
1.1684 -
1.1685 - append(fmt.div(on=1, css_class="event-download-item"))
1.1686 - append(fmt.span(on=1, css_class="event-download-types"))
1.1687 - append(fmt.span(on=1, css_class="event-download-webcal"))
1.1688 - append(linkToResource(full_url.replace("http", "webcal", 1), request, _("webcal"), download_all_link))
1.1689 - append(fmt.span(on=0))
1.1690 - append(fmt.span(on=1, css_class="event-download-http"))
1.1691 - append(linkToPage(request, page, _("http"), download_all_link))
1.1692 - append(fmt.span(on=0))
1.1693 - append(fmt.span(on=0)) # end types
1.1694 - append(fmt.span(on=1, css_class="event-download-label"))
1.1695 - append(fmt.text(_("Download this calendar")))
1.1696 - append(fmt.span(on=0)) # end label
1.1697 - append(fmt.span(on=1, css_class="event-download-period"))
1.1698 - append(fmt.text(original_calendar_period))
1.1699 - append(fmt.span(on=0))
1.1700 - append(fmt.span(on=1, css_class="event-download-period-raw"))
1.1701 - append(fmt.text(raw_calendar_period))
1.1702 - append(fmt.span(on=0))
1.1703 - append(fmt.div(on=0))
1.1704 -
1.1705 - append(fmt.div(on=1, css_class="event-download-item"))
1.1706 - append(fmt.span(on=1, css_class="event-download-link"))
1.1707 - append(linkToPage(request, page, _("Edit download options..."), download_dialogue_link))
1.1708 - append(fmt.span(on=0)) # end label
1.1709 - append(fmt.div(on=0))
1.1710 -
1.1711 - append(fmt.div(on=0)) # end of pop-up
1.1712 - append(fmt.span(on=0)) # end of download
1.1713 -
1.1714 - # Subscription controls.
1.1715 -
1.1716 - append(fmt.span(on=1, css_class="event-download"))
1.1717 - append(fmt.text(_("Subscribe...")))
1.1718 - append(fmt.div(on=1, css_class="event-download-popup"))
1.1719 -
1.1720 - append(fmt.div(on=1, css_class="event-download-item"))
1.1721 - append(fmt.span(on=1, css_class="event-download-label"))
1.1722 - append(linkToPage(request, page, _("Subscribe to this view"), subscribe_link))
1.1723 - append(fmt.span(on=0)) # end label
1.1724 - append(fmt.span(on=1, css_class="event-download-period"))
1.1725 - append(fmt.text(calendar_period))
1.1726 - append(fmt.span(on=0))
1.1727 - append(fmt.div(on=0))
1.1728 -
1.1729 - append(fmt.div(on=1, css_class="event-download-item"))
1.1730 - append(fmt.span(on=1, css_class="event-download-label"))
1.1731 - append(linkToPage(request, page, _("Subscribe to this calendar"), subscribe_all_link))
1.1732 - append(fmt.span(on=0)) # end label
1.1733 - append(fmt.span(on=1, css_class="event-download-period"))
1.1734 - append(fmt.text(original_calendar_period))
1.1735 - append(fmt.span(on=0))
1.1736 - append(fmt.span(on=1, css_class="event-download-period-raw"))
1.1737 - append(fmt.text(raw_calendar_period))
1.1738 - append(fmt.span(on=0))
1.1739 - append(fmt.div(on=0))
1.1740 -
1.1741 - append(fmt.div(on=1, css_class="event-download-item"))
1.1742 - append(fmt.span(on=1, css_class="event-download-link"))
1.1743 - append(linkToPage(request, page, _("Edit subscription options..."), subscribe_dialogue_link))
1.1744 - append(fmt.span(on=0)) # end label
1.1745 - append(fmt.div(on=0))
1.1746 -
1.1747 - append(fmt.div(on=0)) # end of pop-up
1.1748 - append(fmt.span(on=0)) # end of download
1.1749 -
1.1750 - append(fmt.div(on=0)) # end of controls
1.1751 -
1.1752 - return "".join(output)
1.1753 -
1.1754 - def writeViewControls(self):
1.1755 -
1.1756 - """
1.1757 - Return a representation of the view mode controls, permitting viewing of
1.1758 - aggregated events in calendar, list or table form.
1.1759 - """
1.1760 -
1.1761 - page = self.page
1.1762 - request = page.request
1.1763 - fmt = request.formatter
1.1764 - _ = request.getText
1.1765 -
1.1766 - output = []
1.1767 - append = output.append
1.1768 -
1.1769 - # For day view links to other views, the wider view parameters should
1.1770 - # be used in order to be able to return to those other views.
1.1771 -
1.1772 - specific_start = self.calendar_start
1.1773 - specific_end = self.calendar_end
1.1774 -
1.1775 - start = self.wider_calendar_start or self.original_calendar_start and specific_start
1.1776 - end = self.wider_calendar_end or self.original_calendar_end and specific_end
1.1777 -
1.1778 - help_page = Page(request, "HelpOnEventAggregator")
1.1779 -
1.1780 - calendar_link = self.getNavigationLink(start and start.as_month(), end and end.as_month(), "calendar", "month")
1.1781 - calendar_update_link = self.getUpdateLink(start and start.as_month(), end and end.as_month(), "calendar", "month")
1.1782 - list_link = self.getNavigationLink(start, end, "list", "month")
1.1783 - list_update_link = self.getUpdateLink(start, end, "list", "month")
1.1784 - table_link = self.getNavigationLink(start, end, "table", "month")
1.1785 - table_update_link = self.getUpdateLink(start, end, "table", "month")
1.1786 - map_link = self.getNavigationLink(start, end, "map", "month")
1.1787 - map_update_link = self.getUpdateLink(start, end, "map", "month")
1.1788 -
1.1789 - # Specific links permit date-level navigation.
1.1790 -
1.1791 - specific_day_link = self.getNavigationLink(specific_start, specific_end, "day", wider_start=start, wider_end=end)
1.1792 - specific_day_update_link = self.getUpdateLink(specific_start, specific_end, "day", wider_start=start, wider_end=end)
1.1793 - specific_list_link = self.getNavigationLink(specific_start, specific_end, "list", wider_start=start, wider_end=end)
1.1794 - specific_list_update_link = self.getUpdateLink(specific_start, specific_end, "list", wider_start=start, wider_end=end)
1.1795 - specific_table_link = self.getNavigationLink(specific_start, specific_end, "table", wider_start=start, wider_end=end)
1.1796 - specific_table_update_link = self.getUpdateLink(specific_start, specific_end, "table", wider_start=start, wider_end=end)
1.1797 - specific_map_link = self.getNavigationLink(specific_start, specific_end, "map", wider_start=start, wider_end=end)
1.1798 - specific_map_update_link = self.getUpdateLink(specific_start, specific_end, "map", wider_start=start, wider_end=end)
1.1799 -
1.1800 - new_event_link = self.getNewEventLink(start)
1.1801 -
1.1802 - # Write the controls.
1.1803 -
1.1804 - append(fmt.div(on=1, css_class="event-view-controls"))
1.1805 -
1.1806 - append(fmt.span(on=1, css_class="event-view"))
1.1807 - append(linkToPage(request, help_page, _("Help")))
1.1808 - append(fmt.span(on=0))
1.1809 -
1.1810 - append(fmt.span(on=1, css_class="event-view"))
1.1811 - append(linkToPage(request, page, _("New event"), new_event_link))
1.1812 - append(fmt.span(on=0))
1.1813 -
1.1814 - if self.mode != "calendar":
1.1815 - view_label = self.resolution == "date" and _("View day in calendar") or _("View as calendar")
1.1816 - append(fmt.span(on=1, css_class="event-view"))
1.1817 - append(linkToPage(request, page, view_label, calendar_link, onclick=calendar_update_link))
1.1818 - append(fmt.span(on=0))
1.1819 -
1.1820 - if self.resolution == "date" and self.mode != "day":
1.1821 - append(fmt.span(on=1, css_class="event-view"))
1.1822 - append(linkToPage(request, page, _("View day as calendar"), specific_day_link, onclick=specific_day_update_link))
1.1823 - append(fmt.span(on=0))
1.1824 -
1.1825 - if self.resolution != "date" and self.mode != "list" or self.resolution == "date":
1.1826 - view_label = self.resolution == "date" and _("View day in list") or _("View as list")
1.1827 - append(fmt.span(on=1, css_class="event-view"))
1.1828 - append(linkToPage(request, page, view_label, list_link, onclick=list_update_link))
1.1829 - append(fmt.span(on=0))
1.1830 -
1.1831 - if self.resolution == "date" and self.mode != "list":
1.1832 - append(fmt.span(on=1, css_class="event-view"))
1.1833 - append(linkToPage(request, page, _("View day as list"),
1.1834 - specific_list_link, onclick=specific_list_update_link
1.1835 - ))
1.1836 - append(fmt.span(on=0))
1.1837 -
1.1838 - if self.resolution != "date" and self.mode != "table" or self.resolution == "date":
1.1839 - view_label = self.resolution == "date" and _("View day in table") or _("View as table")
1.1840 - append(fmt.span(on=1, css_class="event-view"))
1.1841 - append(linkToPage(request, page, view_label, table_link, onclick=table_update_link))
1.1842 - append(fmt.span(on=0))
1.1843 -
1.1844 - if self.resolution == "date" and self.mode != "table":
1.1845 - append(fmt.span(on=1, css_class="event-view"))
1.1846 - append(linkToPage(request, page, _("View day as table"),
1.1847 - specific_table_link, onclick=specific_table_update_link
1.1848 - ))
1.1849 - append(fmt.span(on=0))
1.1850 -
1.1851 - if self.map_name:
1.1852 - if self.resolution != "date" and self.mode != "map" or self.resolution == "date":
1.1853 - view_label = self.resolution == "date" and _("View day in map") or _("View as map")
1.1854 - append(fmt.span(on=1, css_class="event-view"))
1.1855 - append(linkToPage(request, page, view_label, map_link, onclick=map_update_link))
1.1856 - append(fmt.span(on=0))
1.1857 -
1.1858 - if self.resolution == "date" and self.mode != "map":
1.1859 - append(fmt.span(on=1, css_class="event-view"))
1.1860 - append(linkToPage(request, page, _("View day as map"),
1.1861 - specific_map_link, onclick=specific_map_update_link
1.1862 - ))
1.1863 - append(fmt.span(on=0))
1.1864 -
1.1865 - append(fmt.div(on=0))
1.1866 -
1.1867 - return "".join(output)
1.1868 -
1.1869 - def writeMapHeading(self):
1.1870 -
1.1871 - """
1.1872 - Return the calendar heading for the current calendar, providing links
1.1873 - permitting navigation to other periods.
1.1874 - """
1.1875 -
1.1876 - label = self.getCalendarPeriod()
1.1877 -
1.1878 - if self.raw_calendar_start is None or self.raw_calendar_end is None:
1.1879 - fmt = self.page.request.formatter
1.1880 - output = []
1.1881 - append = output.append
1.1882 - append(fmt.span(on=1))
1.1883 - append(fmt.text(label))
1.1884 - append(fmt.span(on=0))
1.1885 - return "".join(output)
1.1886 - else:
1.1887 - return self._writeCalendarHeading(label, self.calendar_start, self.calendar_end)
1.1888 -
1.1889 - def writeDateHeading(self, date):
1.1890 - if isinstance(date, Date):
1.1891 - return self.writeDayHeading(date)
1.1892 - else:
1.1893 - return self.writeMonthHeading(date)
1.1894 -
1.1895 - def writeMonthHeading(self, year_month):
1.1896 -
1.1897 - """
1.1898 - Return the calendar heading for the given 'year_month' (a Month object)
1.1899 - providing links permitting navigation to other months.
1.1900 - """
1.1901 -
1.1902 - full_month_label = self.getFullMonthLabel(year_month)
1.1903 - end_month = year_month.update(self.duration - 1)
1.1904 - return self._writeCalendarHeading(full_month_label, year_month, end_month)
1.1905 -
1.1906 - def writeDayHeading(self, date):
1.1907 -
1.1908 - """
1.1909 - Return the calendar heading for the given 'date' (a Date object)
1.1910 - providing links permitting navigation to other dates.
1.1911 - """
1.1912 -
1.1913 - full_date_label = self.getFullDateLabel(date)
1.1914 - end_date = date.update(self.duration - 1)
1.1915 - return self._writeCalendarHeading(full_date_label, date, end_date)
1.1916 -
1.1917 - def _writeCalendarHeading(self, label, start, end):
1.1918 -
1.1919 - """
1.1920 - Write a calendar heading providing links permitting navigation to other
1.1921 - periods, using the given 'label' along with the 'start' and 'end' dates
1.1922 - to provide a link to a particular period.
1.1923 - """
1.1924 -
1.1925 - page = self.page
1.1926 - request = page.request
1.1927 - fmt = request.formatter
1.1928 - _ = request.getText
1.1929 -
1.1930 - output = []
1.1931 - append = output.append
1.1932 -
1.1933 - # Prepare navigation links.
1.1934 -
1.1935 - if self.calendar_name:
1.1936 - calendar_name = self.calendar_name
1.1937 -
1.1938 - # Links to the previous set of months and to a calendar shifted
1.1939 - # back one month.
1.1940 -
1.1941 - previous_set_link = self.getNavigationLink(
1.1942 - self.previous_set_start, self.previous_set_end
1.1943 - )
1.1944 - previous_link = self.getNavigationLink(
1.1945 - self.previous_start, self.previous_end
1.1946 - )
1.1947 - previous_set_update_link = self.getUpdateLink(
1.1948 - self.previous_set_start, self.previous_set_end
1.1949 - )
1.1950 - previous_update_link = self.getUpdateLink(
1.1951 - self.previous_start, self.previous_end
1.1952 - )
1.1953 -
1.1954 - # Links to the next set of months and to a calendar shifted
1.1955 - # forward one month.
1.1956 -
1.1957 - next_set_link = self.getNavigationLink(
1.1958 - self.next_set_start, self.next_set_end
1.1959 - )
1.1960 - next_link = self.getNavigationLink(
1.1961 - self.next_start, self.next_end
1.1962 - )
1.1963 - next_set_update_link = self.getUpdateLink(
1.1964 - self.next_set_start, self.next_set_end
1.1965 - )
1.1966 - next_update_link = self.getUpdateLink(
1.1967 - self.next_start, self.next_end
1.1968 - )
1.1969 -
1.1970 - # A link leading to this date being at the top of the calendar.
1.1971 -
1.1972 - date_link = self.getNavigationLink(start, end)
1.1973 - date_update_link = self.getUpdateLink(start, end)
1.1974 -
1.1975 - append(fmt.span(on=1, css_class="previous"))
1.1976 - append(linkToPage(request, page, "<<", previous_set_link, onclick=previous_set_update_link))
1.1977 - append(fmt.text(" "))
1.1978 - append(linkToPage(request, page, "<", previous_link, onclick=previous_update_link))
1.1979 - append(fmt.span(on=0))
1.1980 -
1.1981 - append(fmt.span(on=1, css_class="next"))
1.1982 - append(linkToPage(request, page, ">", next_link, onclick=next_update_link))
1.1983 - append(fmt.text(" "))
1.1984 - append(linkToPage(request, page, ">>", next_set_link, onclick=next_set_update_link))
1.1985 - append(fmt.span(on=0))
1.1986 -
1.1987 - append(linkToPage(request, page, label, date_link, onclick=date_update_link))
1.1988 -
1.1989 - else:
1.1990 - append(fmt.span(on=1))
1.1991 - append(fmt.text(label))
1.1992 - append(fmt.span(on=0))
1.1993 -
1.1994 - return "".join(output)
1.1995 -
1.1996 - def writeDayNumberHeading(self, date, busy):
1.1997 -
1.1998 - """
1.1999 - Return a link for the given 'date' which will activate the new event
1.2000 - action for the given day. If 'busy' is given as a true value, the
1.2001 - heading will be marked as busy.
1.2002 - """
1.2003 -
1.2004 - page = self.page
1.2005 - request = page.request
1.2006 - fmt = request.formatter
1.2007 - _ = request.getText
1.2008 -
1.2009 - output = []
1.2010 - append = output.append
1.2011 -
1.2012 - year, month, day = date.as_tuple()
1.2013 - new_event_link = self.getNewEventLink(date)
1.2014 -
1.2015 - # Prepare a link to the day view for this day.
1.2016 -
1.2017 - day_view_link = self.getNavigationLink(date, date, "day", "date", self.calendar_start, self.calendar_end)
1.2018 - day_view_update_link = self.getUpdateLink(date, date, "day", "date", self.calendar_start, self.calendar_end)
1.2019 -
1.2020 - # Output the heading class.
1.2021 -
1.2022 - today_attr = date == getCurrentDate() and "event-day-current" or ""
1.2023 -
1.2024 - append(
1.2025 - fmt.table_cell(on=1, attrs={
1.2026 - "class" : "event-day-heading event-day-%s %s" % (busy and "busy" or "empty", today_attr),
1.2027 - "colspan" : "3"
1.2028 - }))
1.2029 -
1.2030 - # Output the number and pop-up menu.
1.2031 -
1.2032 - append(fmt.div(on=1, css_class="event-day-box"))
1.2033 -
1.2034 - append(fmt.span(on=1, css_class="event-day-number-popup"))
1.2035 - append(fmt.span(on=1, css_class="event-day-number-link"))
1.2036 - append(linkToPage(request, page, _("View day"), day_view_link, onclick=day_view_update_link))
1.2037 - append(fmt.span(on=0))
1.2038 - append(fmt.span(on=1, css_class="event-day-number-link"))
1.2039 - append(linkToPage(request, page, _("New event"), new_event_link))
1.2040 - append(fmt.span(on=0))
1.2041 - append(fmt.span(on=0))
1.2042 -
1.2043 - append(fmt.span(on=1, css_class="event-day-number"))
1.2044 - append(fmt.text(unicode(day)))
1.2045 - append(fmt.span(on=0))
1.2046 -
1.2047 - append(fmt.div(on=0))
1.2048 -
1.2049 - # End of heading.
1.2050 -
1.2051 - append(fmt.table_cell(on=0))
1.2052 -
1.2053 - return "".join(output)
1.2054 -
1.2055 - # Common layout methods.
1.2056 -
1.2057 - def getEventStyle(self, colour_seed):
1.2058 -
1.2059 - "Generate colour style information using the given 'colour_seed'."
1.2060 -
1.2061 - bg = getColour(colour_seed)
1.2062 - fg = getBlackOrWhite(bg)
1.2063 - return "background-color: rgb(%d, %d, %d); color: rgb(%d, %d, %d);" % (bg + fg)
1.2064 -
1.2065 - def writeEventSummaryBox(self, event):
1.2066 -
1.2067 - "Return an event summary box linking to the given 'event'."
1.2068 -
1.2069 - page = self.page
1.2070 - request = page.request
1.2071 - fmt = request.formatter
1.2072 -
1.2073 - output = []
1.2074 - append = output.append
1.2075 -
1.2076 - event_details = event.getDetails()
1.2077 - event_summary = event.getSummary(self.parent_name)
1.2078 -
1.2079 - is_ambiguous = event.as_timespan().ambiguous()
1.2080 - style = self.getEventStyle(event_summary)
1.2081 -
1.2082 - # The event box contains the summary, alongside
1.2083 - # other elements.
1.2084 -
1.2085 - append(fmt.div(on=1, css_class="event-summary-box"))
1.2086 - append(fmt.div(on=1, css_class="event-summary", style=style))
1.2087 -
1.2088 - if is_ambiguous:
1.2089 - append(fmt.icon("/!\\"))
1.2090 -
1.2091 - append(event.linkToEvent(request, event_summary))
1.2092 - append(fmt.div(on=0))
1.2093 -
1.2094 - # Add a pop-up element for long summaries.
1.2095 -
1.2096 - append(fmt.div(on=1, css_class="event-summary-popup", style=style))
1.2097 -
1.2098 - if is_ambiguous:
1.2099 - append(fmt.icon("/!\\"))
1.2100 -
1.2101 - append(event.linkToEvent(request, event_summary))
1.2102 - append(fmt.div(on=0))
1.2103 -
1.2104 - append(fmt.div(on=0))
1.2105 -
1.2106 - return "".join(output)
1.2107 -
1.2108 - # Calendar layout methods.
1.2109 -
1.2110 - def writeMonthTableHeading(self, year_month):
1.2111 - page = self.page
1.2112 - fmt = page.request.formatter
1.2113 -
1.2114 - output = []
1.2115 - append = output.append
1.2116 -
1.2117 - append(fmt.table_row(on=1))
1.2118 - append(fmt.table_cell(on=1, attrs={"class" : "event-month-heading", "colspan" : "21"}))
1.2119 -
1.2120 - append(self.writeMonthHeading(year_month))
1.2121 -
1.2122 - append(fmt.table_cell(on=0))
1.2123 - append(fmt.table_row(on=0))
1.2124 -
1.2125 - return "".join(output)
1.2126 -
1.2127 - def writeWeekdayHeadings(self):
1.2128 - page = self.page
1.2129 - request = page.request
1.2130 - fmt = request.formatter
1.2131 - _ = request.getText
1.2132 -
1.2133 - output = []
1.2134 - append = output.append
1.2135 -
1.2136 - append(fmt.table_row(on=1))
1.2137 -
1.2138 - for weekday in range(0, 7):
1.2139 - append(fmt.table_cell(on=1, attrs={"class" : "event-weekday-heading", "colspan" : "3"}))
1.2140 - append(fmt.text(_(getDayLabel(weekday))))
1.2141 - append(fmt.table_cell(on=0))
1.2142 -
1.2143 - append(fmt.table_row(on=0))
1.2144 - return "".join(output)
1.2145 -
1.2146 - def writeDayNumbers(self, first_day, number_of_days, month, coverage):
1.2147 - page = self.page
1.2148 - fmt = page.request.formatter
1.2149 -
1.2150 - output = []
1.2151 - append = output.append
1.2152 -
1.2153 - append(fmt.table_row(on=1))
1.2154 -
1.2155 - for weekday in range(0, 7):
1.2156 - day = first_day + weekday
1.2157 - date = month.as_date(day)
1.2158 -
1.2159 - # Output out-of-month days.
1.2160 -
1.2161 - if day < 1 or day > number_of_days:
1.2162 - append(fmt.table_cell(on=1,
1.2163 - attrs={"class" : "event-day-heading event-day-excluded", "colspan" : "3"}))
1.2164 - append(fmt.table_cell(on=0))
1.2165 -
1.2166 - # Output normal days.
1.2167 -
1.2168 - else:
1.2169 - # Output the day heading, making a link to a new event
1.2170 - # action.
1.2171 -
1.2172 - append(self.writeDayNumberHeading(date, date in coverage))
1.2173 -
1.2174 - # End of day numbers.
1.2175 -
1.2176 - append(fmt.table_row(on=0))
1.2177 - return "".join(output)
1.2178 -
1.2179 - def writeEmptyWeek(self, first_day, number_of_days, month):
1.2180 - page = self.page
1.2181 - fmt = page.request.formatter
1.2182 -
1.2183 - output = []
1.2184 - append = output.append
1.2185 -
1.2186 - append(fmt.table_row(on=1))
1.2187 -
1.2188 - for weekday in range(0, 7):
1.2189 - day = first_day + weekday
1.2190 - date = month.as_date(day)
1.2191 -
1.2192 - today_attr = date == getCurrentDate() and "event-day-current" or ""
1.2193 -
1.2194 - # Output out-of-month days.
1.2195 -
1.2196 - if day < 1 or day > number_of_days:
1.2197 - append(fmt.table_cell(on=1,
1.2198 - attrs={"class" : "event-day-content event-day-excluded %s" % today_attr, "colspan" : "3"}))
1.2199 - append(fmt.table_cell(on=0))
1.2200 -
1.2201 - # Output empty days.
1.2202 -
1.2203 - else:
1.2204 - append(fmt.table_cell(on=1,
1.2205 - attrs={"class" : "event-day-content event-day-empty %s" % today_attr, "colspan" : "3"}))
1.2206 -
1.2207 - append(fmt.table_row(on=0))
1.2208 - return "".join(output)
1.2209 -
1.2210 - def writeWeekSlots(self, first_day, number_of_days, month, week_end, week_slots):
1.2211 - output = []
1.2212 - append = output.append
1.2213 -
1.2214 - locations = week_slots.keys()
1.2215 - locations.sort(sort_none_first)
1.2216 -
1.2217 - # Visit each slot corresponding to a location (or no location).
1.2218 -
1.2219 - for location in locations:
1.2220 -
1.2221 - # Visit each coverage span, presenting the events in the span.
1.2222 -
1.2223 - for events in week_slots[location]:
1.2224 -
1.2225 - # Output each set.
1.2226 -
1.2227 - append(self.writeWeekSlot(first_day, number_of_days, month, week_end, events))
1.2228 -
1.2229 - # Add a spacer.
1.2230 -
1.2231 - append(self.writeWeekSpacer(first_day, number_of_days, month))
1.2232 -
1.2233 - return "".join(output)
1.2234 -
1.2235 - def writeWeekSlot(self, first_day, number_of_days, month, week_end, events):
1.2236 - page = self.page
1.2237 - request = page.request
1.2238 - fmt = request.formatter
1.2239 -
1.2240 - output = []
1.2241 - append = output.append
1.2242 -
1.2243 - append(fmt.table_row(on=1))
1.2244 -
1.2245 - # Then, output day details.
1.2246 -
1.2247 - for weekday in range(0, 7):
1.2248 - day = first_day + weekday
1.2249 - date = month.as_date(day)
1.2250 -
1.2251 - # Skip out-of-month days.
1.2252 -
1.2253 - if day < 1 or day > number_of_days:
1.2254 - append(fmt.table_cell(on=1,
1.2255 - attrs={"class" : "event-day-content event-day-excluded", "colspan" : "3"}))
1.2256 - append(fmt.table_cell(on=0))
1.2257 - continue
1.2258 -
1.2259 - # Output the day.
1.2260 - # Where a day does not contain an event, a single cell is used.
1.2261 - # Otherwise, multiple cells are used to provide space before, during
1.2262 - # and after events.
1.2263 -
1.2264 - today_attr = date == getCurrentDate() and "event-day-current" or ""
1.2265 -
1.2266 - if date not in events:
1.2267 - append(fmt.table_cell(on=1,
1.2268 - attrs={"class" : "event-day-content event-day-empty %s" % today_attr, "colspan" : "3"}))
1.2269 -
1.2270 - # Get event details for the current day.
1.2271 -
1.2272 - for event in events:
1.2273 - event_details = event.getDetails()
1.2274 -
1.2275 - if date not in event:
1.2276 - continue
1.2277 -
1.2278 - # Get basic properties of the event.
1.2279 -
1.2280 - starts_today = event_details["start"] == date
1.2281 - ends_today = event_details["end"] == date
1.2282 - event_summary = event.getSummary(self.parent_name)
1.2283 -
1.2284 - style = self.getEventStyle(event_summary)
1.2285 -
1.2286 - # Determine if the event name should be shown.
1.2287 -
1.2288 - start_of_period = starts_today or weekday == 0 or day == 1
1.2289 -
1.2290 - if self.name_usage == "daily" or start_of_period:
1.2291 - hide_text = 0
1.2292 - else:
1.2293 - hide_text = 1
1.2294 -
1.2295 - # Output start of day gap and determine whether
1.2296 - # any event content should be explicitly output
1.2297 - # for this day.
1.2298 -
1.2299 - if starts_today:
1.2300 -
1.2301 - # Single day events...
1.2302 -
1.2303 - if ends_today:
1.2304 - colspan = 3
1.2305 - event_day_type = "event-day-single"
1.2306 -
1.2307 - # Events starting today...
1.2308 -
1.2309 - else:
1.2310 - append(fmt.table_cell(on=1, attrs={"class" : "event-day-start-gap %s" % today_attr}))
1.2311 - append(fmt.table_cell(on=0))
1.2312 -
1.2313 - # Calculate the span of this cell.
1.2314 - # Events whose names appear on every day...
1.2315 -
1.2316 - if self.name_usage == "daily":
1.2317 - colspan = 2
1.2318 - event_day_type = "event-day-starting"
1.2319 -
1.2320 - # Events whose names appear once per week...
1.2321 -
1.2322 - else:
1.2323 - if event_details["end"] <= week_end:
1.2324 - event_length = event_details["end"].day() - day + 1
1.2325 - colspan = (event_length - 2) * 3 + 4
1.2326 - else:
1.2327 - event_length = week_end.day() - day + 1
1.2328 - colspan = (event_length - 1) * 3 + 2
1.2329 -
1.2330 - event_day_type = "event-day-multiple"
1.2331 -
1.2332 - # Events continuing from a previous week...
1.2333 -
1.2334 - elif start_of_period:
1.2335 -
1.2336 - # End of continuing event...
1.2337 -
1.2338 - if ends_today:
1.2339 - colspan = 2
1.2340 - event_day_type = "event-day-ending"
1.2341 -
1.2342 - # Events continuing for at least one more day...
1.2343 -
1.2344 - else:
1.2345 -
1.2346 - # Calculate the span of this cell.
1.2347 - # Events whose names appear on every day...
1.2348 -
1.2349 - if self.name_usage == "daily":
1.2350 - colspan = 3
1.2351 - event_day_type = "event-day-full"
1.2352 -
1.2353 - # Events whose names appear once per week...
1.2354 -
1.2355 - else:
1.2356 - if event_details["end"] <= week_end:
1.2357 - event_length = event_details["end"].day() - day + 1
1.2358 - colspan = (event_length - 1) * 3 + 2
1.2359 - else:
1.2360 - event_length = week_end.day() - day + 1
1.2361 - colspan = event_length * 3
1.2362 -
1.2363 - event_day_type = "event-day-multiple"
1.2364 -
1.2365 - # Continuing events whose names appear on every day...
1.2366 -
1.2367 - elif self.name_usage == "daily":
1.2368 - if ends_today:
1.2369 - colspan = 2
1.2370 - event_day_type = "event-day-ending"
1.2371 - else:
1.2372 - colspan = 3
1.2373 - event_day_type = "event-day-full"
1.2374 -
1.2375 - # Continuing events whose names appear once per week...
1.2376 -
1.2377 - else:
1.2378 - colspan = None
1.2379 -
1.2380 - # Output the main content only if it is not
1.2381 - # continuing from a previous day.
1.2382 -
1.2383 - if colspan is not None:
1.2384 -
1.2385 - # Colour the cell for continuing events.
1.2386 -
1.2387 - attrs={
1.2388 - "class" : "event-day-content event-day-busy %s %s" % (event_day_type, today_attr),
1.2389 - "colspan" : str(colspan)
1.2390 - }
1.2391 -
1.2392 - if not (starts_today and ends_today):
1.2393 - attrs["style"] = style
1.2394 -
1.2395 - append(fmt.table_cell(on=1, attrs=attrs))
1.2396 -
1.2397 - # Output the event.
1.2398 -
1.2399 - if starts_today and ends_today or not hide_text:
1.2400 - append(self.writeEventSummaryBox(event))
1.2401 -
1.2402 - append(fmt.table_cell(on=0))
1.2403 -
1.2404 - # Output end of day gap.
1.2405 -
1.2406 - if ends_today and not starts_today:
1.2407 - append(fmt.table_cell(on=1, attrs={"class" : "event-day-end-gap %s" % today_attr}))
1.2408 - append(fmt.table_cell(on=0))
1.2409 -
1.2410 - # End of set.
1.2411 -
1.2412 - append(fmt.table_row(on=0))
1.2413 - return "".join(output)
1.2414 -
1.2415 - def writeWeekSpacer(self, first_day, number_of_days, month):
1.2416 - page = self.page
1.2417 - fmt = page.request.formatter
1.2418 -
1.2419 - output = []
1.2420 - append = output.append
1.2421 -
1.2422 - append(fmt.table_row(on=1))
1.2423 -
1.2424 - for weekday in range(0, 7):
1.2425 - day = first_day + weekday
1.2426 - date = month.as_date(day)
1.2427 - today_attr = date == getCurrentDate() and "event-day-current" or ""
1.2428 -
1.2429 - css_classes = "event-day-spacer %s" % today_attr
1.2430 -
1.2431 - # Skip out-of-month days.
1.2432 -
1.2433 - if day < 1 or day > number_of_days:
1.2434 - css_classes += " event-day-excluded"
1.2435 -
1.2436 - append(fmt.table_cell(on=1, attrs={"class" : css_classes, "colspan" : "3"}))
1.2437 - append(fmt.table_cell(on=0))
1.2438 -
1.2439 - append(fmt.table_row(on=0))
1.2440 - return "".join(output)
1.2441 -
1.2442 - # Day layout methods.
1.2443 -
1.2444 - def writeDayTableHeading(self, date, colspan=1):
1.2445 - page = self.page
1.2446 - fmt = page.request.formatter
1.2447 -
1.2448 - output = []
1.2449 - append = output.append
1.2450 -
1.2451 - append(fmt.table_row(on=1))
1.2452 -
1.2453 - append(fmt.table_cell(on=1, attrs={"class" : "event-full-day-heading", "colspan" : str(colspan)}))
1.2454 - append(self.writeDayHeading(date))
1.2455 - append(fmt.table_cell(on=0))
1.2456 -
1.2457 - append(fmt.table_row(on=0))
1.2458 - return "".join(output)
1.2459 -
1.2460 - def writeEmptyDay(self, date):
1.2461 - page = self.page
1.2462 - fmt = page.request.formatter
1.2463 -
1.2464 - output = []
1.2465 - append = output.append
1.2466 -
1.2467 - append(fmt.table_row(on=1))
1.2468 -
1.2469 - append(fmt.table_cell(on=1,
1.2470 - attrs={"class" : "event-day-content event-day-empty"}))
1.2471 -
1.2472 - append(fmt.table_row(on=0))
1.2473 - return "".join(output)
1.2474 -
1.2475 - def writeDaySlots(self, date, full_coverage, day_slots):
1.2476 -
1.2477 - """
1.2478 - Given a 'date', non-empty 'full_coverage' for the day concerned, and a
1.2479 - non-empty mapping of 'day_slots' (from locations to event collections),
1.2480 - output the day slots for the day.
1.2481 - """
1.2482 -
1.2483 - page = self.page
1.2484 - fmt = page.request.formatter
1.2485 -
1.2486 - output = []
1.2487 - append = output.append
1.2488 -
1.2489 - locations = day_slots.keys()
1.2490 - locations.sort(sort_none_first)
1.2491 -
1.2492 - # Traverse the time scale of the full coverage, visiting each slot to
1.2493 - # determine whether it provides content for each period.
1.2494 -
1.2495 - scale = getCoverageScale(full_coverage)
1.2496 -
1.2497 - # Define a mapping of events to rowspans.
1.2498 -
1.2499 - rowspans = {}
1.2500 -
1.2501 - # Populate each period with event details, recording how many periods
1.2502 - # each event populates.
1.2503 -
1.2504 - day_rows = []
1.2505 -
1.2506 - for period in scale:
1.2507 -
1.2508 - # Ignore timespans before this day.
1.2509 -
1.2510 - if period != date:
1.2511 - continue
1.2512 -
1.2513 - # Visit each slot corresponding to a location (or no location).
1.2514 -
1.2515 - day_row = []
1.2516 -
1.2517 - for location in locations:
1.2518 -
1.2519 - # Visit each coverage span, presenting the events in the span.
1.2520 -
1.2521 - for events in day_slots[location]:
1.2522 - event = self.getActiveEvent(period, events)
1.2523 - if event is not None:
1.2524 - if not rowspans.has_key(event):
1.2525 - rowspans[event] = 1
1.2526 - else:
1.2527 - rowspans[event] += 1
1.2528 - day_row.append((location, event))
1.2529 -
1.2530 - day_rows.append((period, day_row))
1.2531 -
1.2532 - # Output the locations.
1.2533 -
1.2534 - append(fmt.table_row(on=1))
1.2535 -
1.2536 - # Add a spacer.
1.2537 -
1.2538 - append(self.writeDaySpacer(colspan=2, cls="location"))
1.2539 -
1.2540 - for location in locations:
1.2541 -
1.2542 - # Add spacers to the column spans.
1.2543 -
1.2544 - columns = len(day_slots[location]) * 2 - 1
1.2545 - append(fmt.table_cell(on=1, attrs={"class" : "event-location-heading", "colspan" : str(columns)}))
1.2546 - append(fmt.text(location or ""))
1.2547 - append(fmt.table_cell(on=0))
1.2548 -
1.2549 - # Add a trailing spacer.
1.2550 -
1.2551 - append(self.writeDaySpacer(cls="location"))
1.2552 -
1.2553 - append(fmt.table_row(on=0))
1.2554 -
1.2555 - # Output the periods with event details.
1.2556 -
1.2557 - period = None
1.2558 - events_written = set()
1.2559 -
1.2560 - for period, day_row in day_rows:
1.2561 -
1.2562 - # Write an empty heading for the start of the day where the first
1.2563 - # applicable timespan starts before this day.
1.2564 -
1.2565 - if period.start < date:
1.2566 - append(fmt.table_row(on=1))
1.2567 - append(self.writeDayScaleHeading(""))
1.2568 -
1.2569 - # Otherwise, write a heading describing the time.
1.2570 -
1.2571 - else:
1.2572 - append(fmt.table_row(on=1))
1.2573 - append(self.writeDayScaleHeading(period.start.time_string()))
1.2574 -
1.2575 - append(self.writeDaySpacer())
1.2576 -
1.2577 - # Visit each slot corresponding to a location (or no location).
1.2578 -
1.2579 - for location, event in day_row:
1.2580 -
1.2581 - # Output each location slot's contribution.
1.2582 -
1.2583 - if event is None or event not in events_written:
1.2584 - append(self.writeDaySlot(period, event, event is None and 1 or rowspans[event]))
1.2585 - if event is not None:
1.2586 - events_written.add(event)
1.2587 -
1.2588 - # Add a trailing spacer.
1.2589 -
1.2590 - append(self.writeDaySpacer())
1.2591 -
1.2592 - append(fmt.table_row(on=0))
1.2593 -
1.2594 - # Write a final time heading if the last period ends in the current day.
1.2595 -
1.2596 - if period is not None:
1.2597 - if period.end == date:
1.2598 - append(fmt.table_row(on=1))
1.2599 - append(self.writeDayScaleHeading(period.end.time_string()))
1.2600 -
1.2601 - for slot in day_row:
1.2602 - append(self.writeDaySpacer())
1.2603 - append(self.writeEmptyDaySlot())
1.2604 -
1.2605 - append(fmt.table_row(on=0))
1.2606 -
1.2607 - return "".join(output)
1.2608 -
1.2609 - def writeDayScaleHeading(self, heading):
1.2610 - page = self.page
1.2611 - fmt = page.request.formatter
1.2612 -
1.2613 - output = []
1.2614 - append = output.append
1.2615 -
1.2616 - append(fmt.table_cell(on=1, attrs={"class" : "event-scale-heading"}))
1.2617 - append(fmt.text(heading))
1.2618 - append(fmt.table_cell(on=0))
1.2619 -
1.2620 - return "".join(output)
1.2621 -
1.2622 - def getActiveEvent(self, period, events):
1.2623 - for event in events:
1.2624 - if period not in event:
1.2625 - continue
1.2626 - return event
1.2627 - else:
1.2628 - return None
1.2629 -
1.2630 - def writeDaySlot(self, period, event, rowspan):
1.2631 - page = self.page
1.2632 - fmt = page.request.formatter
1.2633 -
1.2634 - output = []
1.2635 - append = output.append
1.2636 -
1.2637 - if event is not None:
1.2638 - event_summary = event.getSummary(self.parent_name)
1.2639 - style = self.getEventStyle(event_summary)
1.2640 -
1.2641 - append(fmt.table_cell(on=1, attrs={
1.2642 - "class" : "event-timespan-content event-timespan-busy",
1.2643 - "style" : style,
1.2644 - "rowspan" : str(rowspan)
1.2645 - }))
1.2646 - append(self.writeEventSummaryBox(event))
1.2647 - append(fmt.table_cell(on=0))
1.2648 - else:
1.2649 - append(self.writeEmptyDaySlot())
1.2650 -
1.2651 - return "".join(output)
1.2652 -
1.2653 - def writeEmptyDaySlot(self):
1.2654 - page = self.page
1.2655 - fmt = page.request.formatter
1.2656 -
1.2657 - output = []
1.2658 - append = output.append
1.2659 -
1.2660 - append(fmt.table_cell(on=1,
1.2661 - attrs={"class" : "event-timespan-content event-timespan-empty"}))
1.2662 - append(fmt.table_cell(on=0))
1.2663 -
1.2664 - return "".join(output)
1.2665 -
1.2666 - def writeDaySpacer(self, colspan=1, cls="timespan"):
1.2667 - page = self.page
1.2668 - fmt = page.request.formatter
1.2669 -
1.2670 - output = []
1.2671 - append = output.append
1.2672 -
1.2673 - append(fmt.table_cell(on=1, attrs={
1.2674 - "class" : "event-%s-spacer" % cls,
1.2675 - "colspan" : str(colspan)}))
1.2676 - append(fmt.table_cell(on=0))
1.2677 - return "".join(output)
1.2678 -
1.2679 - # Map layout methods.
1.2680 -
1.2681 - def writeMapTableHeading(self):
1.2682 - page = self.page
1.2683 - fmt = page.request.formatter
1.2684 -
1.2685 - output = []
1.2686 - append = output.append
1.2687 -
1.2688 - append(fmt.table_cell(on=1, attrs={"class" : "event-map-heading"}))
1.2689 - append(self.writeMapHeading())
1.2690 - append(fmt.table_cell(on=0))
1.2691 -
1.2692 - return "".join(output)
1.2693 -
1.2694 - def showDictError(self, text, pagename):
1.2695 - page = self.page
1.2696 - request = page.request
1.2697 - fmt = request.formatter
1.2698 -
1.2699 - output = []
1.2700 - append = output.append
1.2701 -
1.2702 - append(fmt.div(on=1, attrs={"class" : "event-aggregator-error"}))
1.2703 - append(fmt.paragraph(on=1))
1.2704 - append(fmt.text(text))
1.2705 - append(fmt.paragraph(on=0))
1.2706 - append(fmt.paragraph(on=1))
1.2707 - append(linkToPage(request, Page(request, pagename), pagename))
1.2708 - append(fmt.paragraph(on=0))
1.2709 -
1.2710 - return "".join(output)
1.2711 -
1.2712 - def writeMapMarker(self, marker_x, marker_y, map_x_scale, map_y_scale, location, events):
1.2713 -
1.2714 - "Put a marker on the map."
1.2715 -
1.2716 - page = self.page
1.2717 - request = page.request
1.2718 - fmt = request.formatter
1.2719 -
1.2720 - output = []
1.2721 - append = output.append
1.2722 -
1.2723 - append(fmt.listitem(on=1, css_class="event-map-label"))
1.2724 -
1.2725 - # Have a positioned marker for the print mode.
1.2726 -
1.2727 - append(fmt.div(on=1, css_class="event-map-label-only",
1.2728 - style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % (
1.2729 - marker_x, marker_y, map_x_scale, map_y_scale))
1.2730 - append(fmt.div(on=0))
1.2731 -
1.2732 - # Have a marker containing a pop-up when using the screen mode,
1.2733 - # providing a normal block when using the print mode.
1.2734 -
1.2735 - append(fmt.div(on=1, css_class="event-map-label",
1.2736 - style="left:%dpx; top:%dpx; min-width:%dpx; min-height:%dpx") % (
1.2737 - marker_x, marker_y, map_x_scale, map_y_scale))
1.2738 - append(fmt.div(on=1, css_class="event-map-details"))
1.2739 - append(fmt.div(on=1, css_class="event-map-shadow"))
1.2740 - append(fmt.div(on=1, css_class="event-map-location"))
1.2741 -
1.2742 - # The location may have been given as formatted text, but this will not
1.2743 - # be usable in a heading, so it must be first converted to plain text.
1.2744 -
1.2745 - append(fmt.heading(on=1, depth=2))
1.2746 - append(fmt.text(to_plain_text(location, request)))
1.2747 - append(fmt.heading(on=0, depth=2))
1.2748 -
1.2749 - append(self.writeMapEventSummaries(events))
1.2750 -
1.2751 - append(fmt.div(on=0))
1.2752 - append(fmt.div(on=0))
1.2753 - append(fmt.div(on=0))
1.2754 - append(fmt.div(on=0))
1.2755 - append(fmt.listitem(on=0))
1.2756 -
1.2757 - return "".join(output)
1.2758 -
1.2759 - def writeMapEventSummaries(self, events):
1.2760 -
1.2761 - "Write summaries of the given 'events' for the map."
1.2762 -
1.2763 - page = self.page
1.2764 - request = page.request
1.2765 - fmt = request.formatter
1.2766 -
1.2767 - # Sort the events by date.
1.2768 -
1.2769 - events.sort(sort_start_first)
1.2770 -
1.2771 - # Write out a self-contained list of events.
1.2772 -
1.2773 - output = []
1.2774 - append = output.append
1.2775 -
1.2776 - append(fmt.bullet_list(on=1, attr={"class" : "event-map-location-events"}))
1.2777 -
1.2778 - for event in events:
1.2779 -
1.2780 - # Get the event details.
1.2781 -
1.2782 - event_summary = event.getSummary(self.parent_name)
1.2783 - start, end = event.as_limits()
1.2784 - event_period = self._getCalendarPeriod(
1.2785 - start and self.getFullDateLabel(start),
1.2786 - end and self.getFullDateLabel(end),
1.2787 - "")
1.2788 -
1.2789 - append(fmt.listitem(on=1))
1.2790 -
1.2791 - # Link to the page using the summary.
1.2792 -
1.2793 - append(event.linkToEvent(request, event_summary))
1.2794 -
1.2795 - # Add the event period.
1.2796 -
1.2797 - append(fmt.text(" "))
1.2798 - append(fmt.span(on=1, css_class="event-map-period"))
1.2799 - append(fmt.text(event_period))
1.2800 - append(fmt.span(on=0))
1.2801 -
1.2802 - append(fmt.listitem(on=0))
1.2803 -
1.2804 - append(fmt.bullet_list(on=0))
1.2805 -
1.2806 - return "".join(output)
1.2807 -
1.2808 - def render(self, all_shown_events):
1.2809 -
1.2810 - """
1.2811 - Render the view, returning the rendered representation as a string.
1.2812 - The view will show a list of 'all_shown_events'.
1.2813 - """
1.2814 -
1.2815 - page = self.page
1.2816 - request = page.request
1.2817 - fmt = request.formatter
1.2818 - _ = request.getText
1.2819 -
1.2820 - # Make a calendar.
1.2821 -
1.2822 - output = []
1.2823 - append = output.append
1.2824 -
1.2825 - append(fmt.div(on=1, css_class="event-calendar", id=("EventAggregator-%s" % self.getIdentifier())))
1.2826 -
1.2827 - # Output download controls.
1.2828 -
1.2829 - append(fmt.div(on=1, css_class="event-controls"))
1.2830 - append(self.writeDownloadControls())
1.2831 - append(fmt.div(on=0))
1.2832 -
1.2833 - # Output a table.
1.2834 -
1.2835 - if self.mode == "table":
1.2836 -
1.2837 - # Start of table view output.
1.2838 -
1.2839 - append(fmt.table(on=1, attrs={"tableclass" : "event-table"}))
1.2840 -
1.2841 - append(fmt.table_row(on=1))
1.2842 - append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"}))
1.2843 - append(fmt.text(_("Event dates")))
1.2844 - append(fmt.table_cell(on=0))
1.2845 - append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"}))
1.2846 - append(fmt.text(_("Event location")))
1.2847 - append(fmt.table_cell(on=0))
1.2848 - append(fmt.table_cell(on=1, attrs={"class" : "event-table-heading"}))
1.2849 - append(fmt.text(_("Event details")))
1.2850 - append(fmt.table_cell(on=0))
1.2851 - append(fmt.table_row(on=0))
1.2852 -
1.2853 - # Show the events in order.
1.2854 -
1.2855 - all_shown_events.sort(sort_start_first)
1.2856 -
1.2857 - for event in all_shown_events:
1.2858 - event_page = event.getPage()
1.2859 - event_summary = event.getSummary(self.parent_name)
1.2860 - event_details = event.getDetails()
1.2861 -
1.2862 - # Prepare CSS classes with category-related styling.
1.2863 -
1.2864 - css_classes = ["event-table-details"]
1.2865 -
1.2866 - for topic in event_details.get("topics") or event_details.get("categories") or []:
1.2867 -
1.2868 - # Filter the category text to avoid illegal characters.
1.2869 -
1.2870 - css_classes.append("event-table-category-%s" % "".join(filter(lambda c: c.isalnum(), topic)))
1.2871 -
1.2872 - attrs = {"class" : " ".join(css_classes)}
1.2873 -
1.2874 - append(fmt.table_row(on=1))
1.2875 -
1.2876 - # Start and end dates.
1.2877 -
1.2878 - append(fmt.table_cell(on=1, attrs=attrs))
1.2879 - append(fmt.span(on=1))
1.2880 - append(fmt.text(str(event_details["start"])))
1.2881 - append(fmt.span(on=0))
1.2882 -
1.2883 - if event_details["start"] != event_details["end"]:
1.2884 - append(fmt.text(" - "))
1.2885 - append(fmt.span(on=1))
1.2886 - append(fmt.text(str(event_details["end"])))
1.2887 - append(fmt.span(on=0))
1.2888 -
1.2889 - append(fmt.table_cell(on=0))
1.2890 -
1.2891 - # Location.
1.2892 -
1.2893 - append(fmt.table_cell(on=1, attrs=attrs))
1.2894 -
1.2895 - if event_details.has_key("location"):
1.2896 - append(event_page.formatText(event_details["location"], fmt))
1.2897 -
1.2898 - append(fmt.table_cell(on=0))
1.2899 -
1.2900 - # Link to the page using the summary.
1.2901 -
1.2902 - append(fmt.table_cell(on=1, attrs=attrs))
1.2903 - append(event.linkToEvent(request, event_summary))
1.2904 - append(fmt.table_cell(on=0))
1.2905 -
1.2906 - append(fmt.table_row(on=0))
1.2907 -
1.2908 - # End of table view output.
1.2909 -
1.2910 - append(fmt.table(on=0))
1.2911 -
1.2912 - # Output a map view.
1.2913 -
1.2914 - elif self.mode == "map":
1.2915 -
1.2916 - # Special dictionary pages.
1.2917 -
1.2918 - maps_page = getMapsPage(request)
1.2919 - locations_page = getLocationsPage(request)
1.2920 -
1.2921 - map_image = None
1.2922 -
1.2923 - # Get the maps and locations.
1.2924 -
1.2925 - maps = getWikiDict(maps_page, request)
1.2926 - locations = getWikiDict(locations_page, request)
1.2927 -
1.2928 - # Get the map image definition.
1.2929 -
1.2930 - if maps is not None and self.map_name:
1.2931 - try:
1.2932 - map_details = maps[self.map_name].split()
1.2933 -
1.2934 - map_bottom_left_latitude, map_bottom_left_longitude, map_top_right_latitude, map_top_right_longitude = \
1.2935 - map(getMapReference, map_details[:4])
1.2936 - map_width, map_height = map(int, map_details[4:6])
1.2937 - map_image = map_details[6]
1.2938 -
1.2939 - map_x_scale = map_width / (map_top_right_longitude - map_bottom_left_longitude).to_degrees()
1.2940 - map_y_scale = map_height / (map_top_right_latitude - map_bottom_left_latitude).to_degrees()
1.2941 -
1.2942 - except (KeyError, ValueError):
1.2943 - pass
1.2944 -
1.2945 - # Report errors.
1.2946 -
1.2947 - if maps is None:
1.2948 - append(self.showDictError(
1.2949 - _("You do not have read access to the maps page:"),
1.2950 - maps_page))
1.2951 -
1.2952 - elif not self.map_name:
1.2953 - append(self.showDictError(
1.2954 - _("Please specify a valid map name corresponding to an entry on the following page:"),
1.2955 - maps_page))
1.2956 -
1.2957 - elif map_image is None:
1.2958 - append(self.showDictError(
1.2959 - _("Please specify a valid entry for %s on the following page:") % self.map_name,
1.2960 - maps_page))
1.2961 -
1.2962 - elif locations is None:
1.2963 - append(self.showDictError(
1.2964 - _("You do not have read access to the locations page:"),
1.2965 - locations_page))
1.2966 -
1.2967 - # Attempt to show the map.
1.2968 -
1.2969 - else:
1.2970 -
1.2971 - # Get events by position.
1.2972 -
1.2973 - events_by_location = {}
1.2974 - event_locations = {}
1.2975 -
1.2976 - for event in all_shown_events:
1.2977 - event_details = event.getDetails()
1.2978 -
1.2979 - location = event_details.get("location")
1.2980 - geo = event_details.get("geo")
1.2981 -
1.2982 - # Make a temporary location if an explicit position is given
1.2983 - # but not a location name.
1.2984 -
1.2985 - if not location and geo:
1.2986 - location = "%s %s" % tuple(geo)
1.2987 -
1.2988 - # Map the location to a position.
1.2989 -
1.2990 - if location is not None and not event_locations.has_key(location):
1.2991 -
1.2992 - # Get any explicit position of an event.
1.2993 -
1.2994 - if geo:
1.2995 - latitude, longitude = geo
1.2996 -
1.2997 - # Or look up the position of a location using the locations
1.2998 - # page.
1.2999 -
1.3000 - else:
1.3001 - latitude, longitude = Location(location, locations).getPosition()
1.3002 -
1.3003 - # Use a normalised location if necessary.
1.3004 -
1.3005 - if latitude is None and longitude is None:
1.3006 - normalised_location = getNormalisedLocation(location)
1.3007 - if normalised_location is not None:
1.3008 - latitude, longitude = getLocationPosition(normalised_location, locations)
1.3009 - if latitude is not None and longitude is not None:
1.3010 - location = normalised_location
1.3011 -
1.3012 - # Only remember positioned locations.
1.3013 -
1.3014 - if latitude is not None and longitude is not None:
1.3015 - event_locations[location] = latitude, longitude
1.3016 -
1.3017 - # Record events according to location.
1.3018 -
1.3019 - if not events_by_location.has_key(location):
1.3020 - events_by_location[location] = []
1.3021 -
1.3022 - events_by_location[location].append(event)
1.3023 -
1.3024 - # Get the map image URL.
1.3025 -
1.3026 - map_image_url = AttachFile.getAttachUrl(maps_page, map_image, request)
1.3027 -
1.3028 - # Start of map view output.
1.3029 -
1.3030 - map_identifier = "map-%s" % self.getIdentifier()
1.3031 - append(fmt.div(on=1, css_class="event-map", id=map_identifier))
1.3032 -
1.3033 - append(fmt.table(on=1))
1.3034 -
1.3035 - append(fmt.table_row(on=1))
1.3036 - append(self.writeMapTableHeading())
1.3037 - append(fmt.table_row(on=0))
1.3038 -
1.3039 - append(fmt.table_row(on=1))
1.3040 - append(fmt.table_cell(on=1))
1.3041 -
1.3042 - append(fmt.div(on=1, css_class="event-map-container"))
1.3043 - append(fmt.image(map_image_url))
1.3044 - append(fmt.number_list(on=1))
1.3045 -
1.3046 - # Events with no location are unpositioned.
1.3047 -
1.3048 - if events_by_location.has_key(None):
1.3049 - unpositioned_events = events_by_location[None]
1.3050 - del events_by_location[None]
1.3051 - else:
1.3052 - unpositioned_events = []
1.3053 -
1.3054 - # Events whose location is unpositioned are themselves considered
1.3055 - # unpositioned.
1.3056 -
1.3057 - for location in set(events_by_location.keys()).difference(event_locations.keys()):
1.3058 - unpositioned_events += events_by_location[location]
1.3059 -
1.3060 - # Sort the locations before traversing them.
1.3061 -
1.3062 - event_locations = event_locations.items()
1.3063 - event_locations.sort()
1.3064 -
1.3065 - # Show the events in the map.
1.3066 -
1.3067 - for location, (latitude, longitude) in event_locations:
1.3068 - events = events_by_location[location]
1.3069 -
1.3070 - # Skip unpositioned locations and locations outside the map.
1.3071 -
1.3072 - if latitude is None or longitude is None or \
1.3073 - latitude < map_bottom_left_latitude or \
1.3074 - longitude < map_bottom_left_longitude or \
1.3075 - latitude > map_top_right_latitude or \
1.3076 - longitude > map_top_right_longitude:
1.3077 -
1.3078 - unpositioned_events += events
1.3079 - continue
1.3080 -
1.3081 - # Get the position and dimensions of the map marker.
1.3082 - # NOTE: Use one degree as the marker size.
1.3083 -
1.3084 - marker_x, marker_y = getPositionForCentrePoint(
1.3085 - getPositionForReference(map_top_right_latitude, longitude, latitude, map_bottom_left_longitude,
1.3086 - map_x_scale, map_y_scale),
1.3087 - map_x_scale, map_y_scale)
1.3088 -
1.3089 - # Add the map marker.
1.3090 -
1.3091 - append(self.writeMapMarker(marker_x, marker_y, map_x_scale, map_y_scale, location, events))
1.3092 -
1.3093 - append(fmt.number_list(on=0))
1.3094 - append(fmt.div(on=0))
1.3095 - append(fmt.table_cell(on=0))
1.3096 - append(fmt.table_row(on=0))
1.3097 -
1.3098 - # Write unpositioned events.
1.3099 -
1.3100 - if unpositioned_events:
1.3101 - unpositioned_identifier = "unpositioned-%s" % self.getIdentifier()
1.3102 -
1.3103 - append(fmt.table_row(on=1, css_class="event-map-unpositioned",
1.3104 - id=unpositioned_identifier))
1.3105 - append(fmt.table_cell(on=1))
1.3106 -
1.3107 - append(fmt.heading(on=1, depth=2))
1.3108 - append(fmt.text(_("Events not shown on the map")))
1.3109 - append(fmt.heading(on=0, depth=2))
1.3110 -
1.3111 - # Show and hide controls.
1.3112 -
1.3113 - append(fmt.div(on=1, css_class="event-map-show-control"))
1.3114 - append(fmt.anchorlink(on=1, name=unpositioned_identifier))
1.3115 - append(fmt.text(_("Show unpositioned events")))
1.3116 - append(fmt.anchorlink(on=0))
1.3117 - append(fmt.div(on=0))
1.3118 -
1.3119 - append(fmt.div(on=1, css_class="event-map-hide-control"))
1.3120 - append(fmt.anchorlink(on=1, name=map_identifier))
1.3121 - append(fmt.text(_("Hide unpositioned events")))
1.3122 - append(fmt.anchorlink(on=0))
1.3123 - append(fmt.div(on=0))
1.3124 -
1.3125 - append(self.writeMapEventSummaries(unpositioned_events))
1.3126 -
1.3127 - # End of map view output.
1.3128 -
1.3129 - append(fmt.table_cell(on=0))
1.3130 - append(fmt.table_row(on=0))
1.3131 - append(fmt.table(on=0))
1.3132 - append(fmt.div(on=0))
1.3133 -
1.3134 - # Output a list.
1.3135 -
1.3136 - elif self.mode == "list":
1.3137 -
1.3138 - # Start of list view output.
1.3139 -
1.3140 - append(fmt.bullet_list(on=1, attr={"class" : "event-listings"}))
1.3141 -
1.3142 - # Output a list.
1.3143 -
1.3144 - for period in self.first.until(self.last):
1.3145 -
1.3146 - append(fmt.listitem(on=1, attr={"class" : "event-listings-period"}))
1.3147 - append(fmt.div(on=1, attr={"class" : "event-listings-heading"}))
1.3148 -
1.3149 - # Either write a date heading or produce links for navigable
1.3150 - # calendars.
1.3151 -
1.3152 - append(self.writeDateHeading(period))
1.3153 -
1.3154 - append(fmt.div(on=0))
1.3155 -
1.3156 - append(fmt.bullet_list(on=1, attr={"class" : "event-period-listings"}))
1.3157 -
1.3158 - # Show the events in order.
1.3159 -
1.3160 - events_in_period = getEventsInPeriod(all_shown_events, getCalendarPeriod(period, period))
1.3161 - events_in_period.sort(sort_start_first)
1.3162 -
1.3163 - for event in events_in_period:
1.3164 - event_page = event.getPage()
1.3165 - event_details = event.getDetails()
1.3166 - event_summary = event.getSummary(self.parent_name)
1.3167 -
1.3168 - append(fmt.listitem(on=1, attr={"class" : "event-listing"}))
1.3169 -
1.3170 - # Link to the page using the summary.
1.3171 -
1.3172 - append(fmt.paragraph(on=1))
1.3173 - append(event.linkToEvent(request, event_summary))
1.3174 - append(fmt.paragraph(on=0))
1.3175 -
1.3176 - # Start and end dates.
1.3177 -
1.3178 - append(fmt.paragraph(on=1))
1.3179 - append(fmt.span(on=1))
1.3180 - append(fmt.text(str(event_details["start"])))
1.3181 - append(fmt.span(on=0))
1.3182 - append(fmt.text(" - "))
1.3183 - append(fmt.span(on=1))
1.3184 - append(fmt.text(str(event_details["end"])))
1.3185 - append(fmt.span(on=0))
1.3186 - append(fmt.paragraph(on=0))
1.3187 -
1.3188 - # Location.
1.3189 -
1.3190 - if event_details.has_key("location"):
1.3191 - append(fmt.paragraph(on=1))
1.3192 - append(event_page.formatText(event_details["location"], fmt))
1.3193 - append(fmt.paragraph(on=1))
1.3194 -
1.3195 - # Topics.
1.3196 -
1.3197 - if event_details.has_key("topics") or event_details.has_key("categories"):
1.3198 - append(fmt.bullet_list(on=1, attr={"class" : "event-topics"}))
1.3199 -
1.3200 - for topic in event_details.get("topics") or event_details.get("categories") or []:
1.3201 - append(fmt.listitem(on=1))
1.3202 - append(event_page.formatText(topic, fmt))
1.3203 - append(fmt.listitem(on=0))
1.3204 -
1.3205 - append(fmt.bullet_list(on=0))
1.3206 -
1.3207 - append(fmt.listitem(on=0))
1.3208 -
1.3209 - append(fmt.bullet_list(on=0))
1.3210 -
1.3211 - # End of list view output.
1.3212 -
1.3213 - append(fmt.bullet_list(on=0))
1.3214 -
1.3215 - # Output a month calendar. This shows month-by-month data.
1.3216 -
1.3217 - elif self.mode == "calendar":
1.3218 -
1.3219 - # Visit all months in the requested range, or across known events.
1.3220 -
1.3221 - for month in self.first.months_until(self.last):
1.3222 -
1.3223 - # Output a month.
1.3224 -
1.3225 - append(fmt.table(on=1, attrs={"tableclass" : "event-month"}))
1.3226 -
1.3227 - # Either write a month heading or produce links for navigable
1.3228 - # calendars.
1.3229 -
1.3230 - append(self.writeMonthTableHeading(month))
1.3231 -
1.3232 - # Weekday headings.
1.3233 -
1.3234 - append(self.writeWeekdayHeadings())
1.3235 -
1.3236 - # Process the days of the month.
1.3237 -
1.3238 - start_weekday, number_of_days = month.month_properties()
1.3239 -
1.3240 - # The start weekday is the weekday of day number 1.
1.3241 - # Find the first day of the week, counting from below zero, if
1.3242 - # necessary, in order to land on the first day of the month as
1.3243 - # day number 1.
1.3244 -
1.3245 - first_day = 1 - start_weekday
1.3246 -
1.3247 - while first_day <= number_of_days:
1.3248 -
1.3249 - # Find events in this week and determine how to mark them on the
1.3250 - # calendar.
1.3251 -
1.3252 - week_start = month.as_date(max(first_day, 1))
1.3253 - week_end = month.as_date(min(first_day + 6, number_of_days))
1.3254 -
1.3255 - full_coverage, week_slots = getCoverage(
1.3256 - getEventsInPeriod(all_shown_events, getCalendarPeriod(week_start, week_end)))
1.3257 -
1.3258 - # Output a week, starting with the day numbers.
1.3259 -
1.3260 - append(self.writeDayNumbers(first_day, number_of_days, month, full_coverage))
1.3261 -
1.3262 - # Either generate empty days...
1.3263 -
1.3264 - if not week_slots:
1.3265 - append(self.writeEmptyWeek(first_day, number_of_days, month))
1.3266 -
1.3267 - # Or generate each set of scheduled events...
1.3268 -
1.3269 - else:
1.3270 - append(self.writeWeekSlots(first_day, number_of_days, month, week_end, week_slots))
1.3271 -
1.3272 - # Process the next week...
1.3273 -
1.3274 - first_day += 7
1.3275 -
1.3276 - # End of month.
1.3277 -
1.3278 - append(fmt.table(on=0))
1.3279 -
1.3280 - # Output a day view.
1.3281 -
1.3282 - elif self.mode == "day":
1.3283 -
1.3284 - # Visit all days in the requested range, or across known events.
1.3285 -
1.3286 - for date in self.first.days_until(self.last):
1.3287 -
1.3288 - append(fmt.table(on=1, attrs={"tableclass" : "event-calendar-day"}))
1.3289 -
1.3290 - full_coverage, day_slots = getCoverage(
1.3291 - getEventsInPeriod(all_shown_events, getCalendarPeriod(date, date)), "datetime")
1.3292 -
1.3293 - # Work out how many columns the day title will need.
1.3294 - # Include spacers after the scale and each event column.
1.3295 -
1.3296 - colspan = sum(map(len, day_slots.values())) * 2 + 2
1.3297 -
1.3298 - append(self.writeDayTableHeading(date, colspan))
1.3299 -
1.3300 - # Either generate empty days...
1.3301 -
1.3302 - if not day_slots:
1.3303 - append(self.writeEmptyDay(date))
1.3304 -
1.3305 - # Or generate each set of scheduled events...
1.3306 -
1.3307 - else:
1.3308 - append(self.writeDaySlots(date, full_coverage, day_slots))
1.3309 -
1.3310 - # End of day.
1.3311 -
1.3312 - append(fmt.table(on=0))
1.3313 -
1.3314 - # Output view controls.
1.3315 -
1.3316 - append(fmt.div(on=1, css_class="event-controls"))
1.3317 - append(self.writeViewControls())
1.3318 - append(fmt.div(on=0))
1.3319 -
1.3320 - # Close the calendar region.
1.3321 -
1.3322 - append(fmt.div(on=0))
1.3323 -
1.3324 - # Add any scripts.
1.3325 -
1.3326 - if isinstance(fmt, request.html_formatter.__class__):
1.3327 - append(self.update_script)
1.3328 -
1.3329 - return ''.join(output)
1.3330 -
1.3331 - update_script = """\
1.3332 -<script type="text/javascript">
1.3333 -function replaceCalendar(name, url) {
1.3334 - var calendar = document.getElementById(name);
1.3335 -
1.3336 - if (calendar == null) {
1.3337 - return true;
1.3338 - }
1.3339 -
1.3340 - var xmlhttp = new XMLHttpRequest();
1.3341 - xmlhttp.open("GET", url, false);
1.3342 - xmlhttp.send(null);
1.3343 -
1.3344 - var newCalendar = xmlhttp.responseText;
1.3345 -
1.3346 - if (newCalendar != null) {
1.3347 - calendar.innerHTML = newCalendar;
1.3348 - return false;
1.3349 - }
1.3350 -
1.3351 - return true;
1.3352 -}
1.3353 -</script>
1.3354 -"""
1.3355 -
1.3356 -# Event selection from request parameters.
1.3357 -
1.3358 -def getEventsUsingParameters(category_names, search_pattern, remote_sources,
1.3359 - calendar_start, calendar_end, resolution, request):
1.3360 -
1.3361 - "Get the events according to the resolution of the calendar."
1.3362 -
1.3363 - if search_pattern:
1.3364 - results = getPagesForSearch(search_pattern, request)
1.3365 - else:
1.3366 - results = []
1.3367 -
1.3368 - results += getAllCategoryPages(category_names, request)
1.3369 - pages = getPagesFromResults(results, request)
1.3370 - events = getEventsFromResources(getEventPages(pages))
1.3371 - events += getEventsFromResources(getEventResources(remote_sources, calendar_start, calendar_end, request))
1.3372 - all_shown_events = getEventsInPeriod(events, getCalendarPeriod(calendar_start, calendar_end))
1.3373 - earliest, latest = getEventLimits(all_shown_events)
1.3374 -
1.3375 - # Get a concrete period of time.
1.3376 -
1.3377 - first, last = getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution)
1.3378 -
1.3379 - return all_shown_events, first, last
1.3380 -
1.3381 -# Event-only formatting.
1.3382 -
1.3383 -def formatEvent(event, request, fmt, write=None):
1.3384 -
1.3385 - """
1.3386 - Format the given 'event' using the 'request' and formatter 'fmt'. If the
1.3387 - 'write' parameter is specified, use it to write output.
1.3388 - """
1.3389 -
1.3390 - details = event.getDetails()
1.3391 - raw_details = event.getRawDetails()
1.3392 - write = write or request.write
1.3393 -
1.3394 - if details.has_key("fragment"):
1.3395 - write(fmt.anchordef(details["fragment"]))
1.3396 -
1.3397 - # Promote any title to a heading above the event details.
1.3398 -
1.3399 - if raw_details.has_key("title"):
1.3400 - write(formatText(raw_details["title"], request, fmt))
1.3401 - elif details.has_key("title"):
1.3402 - write(fmt.heading(on=1, depth=1))
1.3403 - write(fmt.text(details["title"]))
1.3404 - write(fmt.heading(on=0, depth=1))
1.3405 -
1.3406 - # Produce a definition list for the rest of the details.
1.3407 -
1.3408 - write(fmt.definition_list(on=1))
1.3409 -
1.3410 - for term in event.all_terms:
1.3411 - if term == "title":
1.3412 - continue
1.3413 -
1.3414 - raw_value = raw_details.get(term)
1.3415 - value = details.get(term)
1.3416 -
1.3417 - if raw_value or value:
1.3418 - write(fmt.definition_term(on=1))
1.3419 - write(fmt.text(term))
1.3420 - write(fmt.definition_term(on=0))
1.3421 - write(fmt.definition_desc(on=1))
1.3422 -
1.3423 - # Try and use the raw details, if available.
1.3424 -
1.3425 - if raw_value:
1.3426 - write(formatText(raw_value, request, fmt))
1.3427 -
1.3428 - # Otherwise, format the processed details.
1.3429 -
1.3430 - else:
1.3431 - if term in event.list_terms:
1.3432 - write(", ".join([formatText(str(v), request, fmt) for v in value]))
1.3433 - else:
1.3434 - write(fmt.text(str(value)))
1.3435 -
1.3436 - write(fmt.definition_desc(on=0))
1.3437 -
1.3438 - write(fmt.definition_list(on=0))
1.3439 -
1.3440 -def formatEventsForOutputType(events, request, mimetype, parent=None, descriptions=None, latest_timestamp=None, write=None):
1.3441 -
1.3442 - """
1.3443 - Format the given 'events' using the 'request' for the given 'mimetype'.
1.3444 -
1.3445 - The optional 'parent' indicates the "natural" parent page of the events. Any
1.3446 - event pages residing beneath the parent page will have their names
1.3447 - reproduced as relative to the parent page.
1.3448 -
1.3449 - The optional 'descriptions' indicates the nature of any description given
1.3450 - for events in the output resource.
1.3451 -
1.3452 - The optional 'latest_timestamp' indicates the timestamp of the latest edit
1.3453 - of the page or event collection.
1.3454 -
1.3455 - If the 'write' parameter is specified, use it to write output.
1.3456 - """
1.3457 -
1.3458 - write = write or request.write
1.3459 -
1.3460 - # Start the collection.
1.3461 -
1.3462 - if mimetype == "text/calendar" and vCalendar is not None:
1.3463 - write = vCalendar.iterwrite(write=write).write
1.3464 - write("BEGIN", {}, "VCALENDAR")
1.3465 - write("PRODID", {}, "-//MoinMoin//EventAggregatorSummary")
1.3466 - write("VERSION", {}, "2.0")
1.3467 -
1.3468 - elif mimetype == "application/rss+xml":
1.3469 -
1.3470 - # Using the page name and the page URL in the title, link and
1.3471 - # description.
1.3472 -
1.3473 - path_info = getPathInfo(request)
1.3474 -
1.3475 - write('<rss version="2.0">\r\n')
1.3476 - write('<channel>\r\n')
1.3477 - write('<title>%s</title>\r\n' % path_info[1:])
1.3478 - write('<link>%s%s</link>\r\n' % (request.getBaseURL(), path_info))
1.3479 - write('<description>Events published on %s%s</description>\r\n' % (request.getBaseURL(), path_info))
1.3480 -
1.3481 - if latest_timestamp is not None:
1.3482 - write('<lastBuildDate>%s</lastBuildDate>\r\n' % latest_timestamp.as_HTTP_datetime_string())
1.3483 -
1.3484 - # Sort the events by start date, reversed.
1.3485 -
1.3486 - ordered_events = getOrderedEvents(events)
1.3487 - ordered_events.reverse()
1.3488 - events = ordered_events
1.3489 -
1.3490 - elif mimetype == "text/html":
1.3491 - write('<html>')
1.3492 - write('<body>')
1.3493 -
1.3494 - # Output the collection one by one.
1.3495 -
1.3496 - for event in events:
1.3497 - formatEventForOutputType(event, request, mimetype, parent, descriptions)
1.3498 -
1.3499 - # End the collection.
1.3500 -
1.3501 - if mimetype == "text/calendar" and vCalendar is not None:
1.3502 - write("END", {}, "VCALENDAR")
1.3503 -
1.3504 - elif mimetype == "application/rss+xml":
1.3505 - write('</channel>\r\n')
1.3506 - write('</rss>\r\n')
1.3507 -
1.3508 - elif mimetype == "text/html":
1.3509 - write('</body>')
1.3510 - write('</html>')
1.3511 -
1.3512 -def formatEventForOutputType(event, request, mimetype, parent=None, descriptions=None, write=None):
1.3513 -
1.3514 - """
1.3515 - Format the given 'event' using the 'request' for the given 'mimetype'.
1.3516 -
1.3517 - The optional 'parent' indicates the "natural" parent page of the events. Any
1.3518 - event pages residing beneath the parent page will have their names
1.3519 - reproduced as relative to the parent page.
1.3520 -
1.3521 - The optional 'descriptions' indicates the nature of any description given
1.3522 - for events in the output resource.
1.3523 -
1.3524 - If the 'write' parameter is specified, use it to write output.
1.3525 - """
1.3526 -
1.3527 - write = write or request.write
1.3528 - event_details = event.getDetails()
1.3529 - event_metadata = event.getMetadata()
1.3530 -
1.3531 - if mimetype == "text/calendar" and vCalendar is not None:
1.3532 -
1.3533 - # NOTE: A custom formatter making attributes for links and plain
1.3534 - # NOTE: text for values could be employed here.
1.3535 -
1.3536 - write = vCalendar.iterwrite(write=write).write
1.3537 -
1.3538 - # Get the summary details.
1.3539 -
1.3540 - event_summary = event.getSummary(parent)
1.3541 - link = event.getEventURL()
1.3542 -
1.3543 - # Output the event details.
1.3544 -
1.3545 - write("BEGIN", {}, "VEVENT")
1.3546 - write("UID", {}, link)
1.3547 - write("URL", {}, link)
1.3548 - write("DTSTAMP", {}, "%04d%02d%02dT%02d%02d%02dZ" % event_metadata["created"].as_tuple()[:6])
1.3549 - write("LAST-MODIFIED", {}, "%04d%02d%02dT%02d%02d%02dZ" % event_metadata["last-modified"].as_tuple()[:6])
1.3550 - write("SEQUENCE", {}, "%d" % event_metadata["sequence"])
1.3551 -
1.3552 - start = event_details["start"]
1.3553 - end = event_details["end"]
1.3554 -
1.3555 - if isinstance(start, DateTime):
1.3556 - params, value = get_calendar_datetime(start)
1.3557 - else:
1.3558 - params, value = {"VALUE" : "DATE"}, "%04d%02d%02d" % start.as_date().as_tuple()
1.3559 - write("DTSTART", params, value)
1.3560 -
1.3561 - if isinstance(end, DateTime):
1.3562 - params, value = get_calendar_datetime(end)
1.3563 - else:
1.3564 - params, value = {"VALUE" : "DATE"}, "%04d%02d%02d" % end.next_day().as_date().as_tuple()
1.3565 - write("DTEND", params, value)
1.3566 -
1.3567 - write("SUMMARY", {}, event_summary)
1.3568 -
1.3569 - # Optional details.
1.3570 -
1.3571 - if event_details.get("topics") or event_details.get("categories"):
1.3572 - write("CATEGORIES", {}, event_details.get("topics") or event_details.get("categories"))
1.3573 - if event_details.has_key("location"):
1.3574 - write("LOCATION", {}, event_details["location"])
1.3575 - if event_details.has_key("geo"):
1.3576 - write("GEO", {}, tuple([str(ref.to_degrees()) for ref in event_details["geo"]]))
1.3577 -
1.3578 - write("END", {}, "VEVENT")
1.3579 -
1.3580 - elif mimetype == "application/rss+xml":
1.3581 -
1.3582 - event_page = event.getPage()
1.3583 - event_details = event.getDetails()
1.3584 -
1.3585 - # Get a parser and formatter for the formatting of some attributes.
1.3586 -
1.3587 - fmt = request.html_formatter
1.3588 -
1.3589 - # Get the summary details.
1.3590 -
1.3591 - event_summary = event.getSummary(parent)
1.3592 - link = event.getEventURL()
1.3593 -
1.3594 - write('<item>\r\n')
1.3595 - write('<title>%s</title>\r\n' % escape(event_summary))
1.3596 - write('<link>%s</link>\r\n' % link)
1.3597 -
1.3598 - # Write a description according to the preferred source of
1.3599 - # descriptions.
1.3600 -
1.3601 - if descriptions == "page":
1.3602 - description = event_details.get("description", "")
1.3603 - else:
1.3604 - description = event_metadata["last-comment"]
1.3605 -
1.3606 - write('<description>%s</description>\r\n' %
1.3607 - fmt.text(event_page.formatText(description, fmt)))
1.3608 -
1.3609 - for topic in event_details.get("topics") or event_details.get("categories") or []:
1.3610 - write('<category>%s</category>\r\n' %
1.3611 - fmt.text(event_page.formatText(topic, fmt)))
1.3612 -
1.3613 - write('<pubDate>%s</pubDate>\r\n' % event_metadata["created"].as_HTTP_datetime_string())
1.3614 - write('<guid>%s#%s</guid>\r\n' % (link, event_metadata["sequence"]))
1.3615 - write('</item>\r\n')
1.3616 -
1.3617 - elif mimetype == "text/html":
1.3618 - fmt = request.html_formatter
1.3619 - fmt.setPage(request.page)
1.3620 - formatEvent(event, request, fmt, write=write)
1.3621 -
1.3622 -# iCalendar format helper functions.
1.3623 -
1.3624 -def get_calendar_datetime(datetime):
1.3625 -
1.3626 - """
1.3627 - Write to the given 'request' the 'datetime' using appropriate time zone
1.3628 - information.
1.3629 - """
1.3630 -
1.3631 - utc_datetime = datetime.to_utc()
1.3632 - if utc_datetime:
1.3633 - return {"VALUE" : "DATE-TIME"}, "%04d%02d%02dT%02d%02d%02dZ" % utc_datetime.padded().as_tuple()[:-1]
1.3634 - else:
1.3635 - zone = datetime.time_zone()
1.3636 - params = {"VALUE" : "DATE-TIME"}
1.3637 - if zone:
1.3638 - params["TZID"] = zone
1.3639 - return params, "%04d%02d%02dT%02d%02d%02d" % datetime.padded().as_tuple()[:-1]
1.3640 -
1.3641 -# vim: tabstop=4 expandtab shiftwidth=4