1.1 --- a/EventAggregatorSupport.py Sat Apr 02 19:07:50 2011 +0200
1.2 +++ b/EventAggregatorSupport.py Sun Apr 03 02:06:01 2011 +0200
1.3 @@ -374,6 +374,49 @@
1.4 pages.append(page)
1.5 return pages
1.6
1.7 +def getAllCategoryPages(category_names, request):
1.8 +
1.9 + """
1.10 + Return all pages belonging to the categories having the given
1.11 + 'category_names', using the given 'request'.
1.12 + """
1.13 +
1.14 + pages = []
1.15 + pagenames = set()
1.16 +
1.17 + for category_name in category_names:
1.18 +
1.19 + # Get the pages and page names in the category.
1.20 +
1.21 + pages_in_category = getCategoryPages(category_name, request)
1.22 +
1.23 + # Visit each page in the category.
1.24 +
1.25 + for page_in_category in pages_in_category:
1.26 + pagename = page_in_category.page_name
1.27 +
1.28 + # Only process each page once.
1.29 +
1.30 + if pagename in pagenames:
1.31 + continue
1.32 + else:
1.33 + pagenames.add(pagename)
1.34 +
1.35 + pages.append(page_in_category)
1.36 +
1.37 + return pages
1.38 +
1.39 +def getPagesFromResults(result_pages, request):
1.40 +
1.41 + "Return genuine pages for the given 'result_pages' using the 'request'."
1.42 +
1.43 + return [Page(request, page.page_name) for page in result_pages]
1.44 +
1.45 +# Interfaces.
1.46 +
1.47 +class ActsAsTimespan:
1.48 + pass
1.49 +
1.50 # The main activity functions.
1.51
1.52 class EventPage:
1.53 @@ -628,9 +671,6 @@
1.54
1.55 return linkToPage(request, self.page, text, query_string)
1.56
1.57 -class ActsAsTimespan:
1.58 - pass
1.59 -
1.60 class Event(ActsAsTimespan):
1.61
1.62 "A description of an event."
1.63 @@ -715,112 +755,49 @@
1.64 def as_limits(self):
1.65 return self.as_timespan().as_limits()
1.66
1.67 -def getEvents(request, category_names, calendar_start=None, calendar_end=None, resolution="month"):
1.68 -
1.69 - """
1.70 - Using the 'request', generate a list of events found on pages belonging to
1.71 - the specified 'category_names', using the optional 'calendar_start' and
1.72 - 'calendar_end' values to indicate a window of interest.
1.73 -
1.74 - The optional 'resolution' determines the unit of time used in providing the
1.75 - results:
1.76 -
1.77 - * a list of events
1.78 - * a dictionary mapping time units to event lists (within the window of
1.79 - interest), usable as a kind of index to groups of events
1.80 - * a list of all events within the window of interest
1.81 - * the earliest time value of an event within the window of interest
1.82 - * the latest time value of an event within the window of interest.
1.83 - """
1.84 -
1.85 - # Dates need to comply with the requested resolution.
1.86 - # Here, None values need to be preserved when converting.
1.87 -
1.88 - if resolution == "month":
1.89 - convert = lambda x: x and x.as_month()
1.90 - get_values = lambda x, y: x.months_until(y)
1.91 - else:
1.92 - convert = lambda x: x and x.as_date()
1.93 - get_values = lambda x, y: x.days_until(y)
1.94 -
1.95 - # Re-order the window, if appropriate.
1.96 -
1.97 - if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end:
1.98 - calendar_start, calendar_end = map(convert, (calendar_end, calendar_start))
1.99 -
1.100 - # Otherwise, just convert the calendar limits.
1.101 -
1.102 - else:
1.103 - calendar_start, calendar_end = map(convert, (calendar_start, calendar_end))
1.104 -
1.105 - calendar_period = Timespan(calendar_start, calendar_end)
1.106 +def getEventsFromPages(pages):
1.107 +
1.108 + "Return a list of events found on the given 'pages'."
1.109
1.110 events = []
1.111 - shown_events = {}
1.112 - all_shown_events = []
1.113 - processed_pages = set()
1.114 -
1.115 - earliest = None
1.116 - latest = None
1.117 -
1.118 - for category_name in category_names:
1.119 -
1.120 - # Get the pages and page names in the category.
1.121 -
1.122 - pages_in_category = getCategoryPages(category_name, request)
1.123 -
1.124 - # Visit each page in the category.
1.125 -
1.126 - for page_in_category in pages_in_category:
1.127 - pagename = page_in_category.page_name
1.128 -
1.129 - # Only process each page once.
1.130 -
1.131 - if pagename in processed_pages:
1.132 - continue
1.133 - else:
1.134 - processed_pages.add(pagename)
1.135 -
1.136 - # Get a real page, not a result page.
1.137 -
1.138 - event_page = EventPage(Page(request, pagename))
1.139 -
1.140 - # Get all events described in the page.
1.141 -
1.142 - for event in event_page.getEvents():
1.143 - event_details = event.getDetails()
1.144 -
1.145 - # Remember the event.
1.146 -
1.147 - events.append(event)
1.148 -
1.149 - # Test for the suitability of the event.
1.150 -
1.151 - if event.as_timespan() is not None:
1.152 - start, end = map(convert, event.as_timespan().as_limits())
1.153 -
1.154 - # Compare the dates to the requested calendar window, if any.
1.155 -
1.156 - if event in calendar_period:
1.157 -
1.158 - all_shown_events.append(event)
1.159 -
1.160 - if earliest is None or start < earliest:
1.161 - earliest = start
1.162 - if latest is None or end > latest:
1.163 - latest = end
1.164 -
1.165 - # Store the event in the time-specific dictionary.
1.166 -
1.167 - first = max(start, calendar_start or start)
1.168 - last = min(end, calendar_end or end)
1.169 -
1.170 - for event_time_value in get_values(first, last):
1.171 - if not shown_events.has_key(event_time_value):
1.172 - shown_events[event_time_value] = []
1.173 - shown_events[event_time_value].append(event)
1.174 -
1.175 - return events, shown_events, all_shown_events, earliest, latest
1.176 +
1.177 + for page in pages:
1.178 +
1.179 + # Get a real page, not a result page.
1.180 +
1.181 + event_page = EventPage(page)
1.182 +
1.183 + # Get all events described in the page.
1.184 +
1.185 + for event in event_page.getEvents():
1.186 +
1.187 + # Remember the event.
1.188 +
1.189 + events.append(event)
1.190 +
1.191 + return events
1.192 +
1.193 +def getEventsInPeriod(events, calendar_period, resolution):
1.194 +
1.195 + """
1.196 + Return a collection containing those of the given 'events' which occur
1.197 + within the given 'calendar_period' at the given 'resolution'.
1.198 + """
1.199 +
1.200 + all_shown_events = TimespanCollection(resolution)
1.201 +
1.202 + for event in events:
1.203 +
1.204 + # Test for the suitability of the event.
1.205 +
1.206 + if event.as_timespan() is not None:
1.207 +
1.208 + # Compare the dates to the requested calendar window, if any.
1.209 +
1.210 + if event in calendar_period:
1.211 + all_shown_events.insert_in_order(event)
1.212 +
1.213 + return all_shown_events
1.214
1.215 def setEventTimestamps(request, events):
1.216
1.217 @@ -867,6 +844,23 @@
1.218 ordered_events.sort()
1.219 return ordered_events
1.220
1.221 +def getCalendarPeriod(calendar_start, calendar_end, resolution):
1.222 +
1.223 + """
1.224 + Return a calendar period for the given 'calendar_start' and 'calendar_end',
1.225 + employing the given 'resolution'. The 'calendar_start' and 'calendar_end'
1.226 + parameters can be given as None.
1.227 + """
1.228 +
1.229 + # Re-order the window, if appropriate.
1.230 +
1.231 + if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end:
1.232 + calendar_start, calendar_end = calendar_end, calendar_start
1.233 +
1.234 + # Return a timespan at the given resolution.
1.235 +
1.236 + return Timespan(calendar_start, calendar_end).convert(resolution)
1.237 +
1.238 def getConcretePeriod(calendar_start, calendar_end, earliest, latest):
1.239
1.240 """
1.241 @@ -903,70 +897,63 @@
1.242
1.243 return min(first, last), last
1.244
1.245 -def getCoverage(start, end, events, resolution="date"):
1.246 +def getCoverage(events, resolution="date"):
1.247
1.248 """
1.249 - Within the period defined by the 'start' and 'end' dates, determine the
1.250 - coverage of the days in the period by the given 'events', returning a
1.251 - collection of timespans, along with a dictionary mapping locations to
1.252 - collections of slots, where each slot contains a tuple of the form
1.253 - (timespans, events).
1.254 + Determine the coverage of the given 'events', returning a collection of
1.255 + timespans, along with a dictionary mapping locations to collections of
1.256 + slots, where each slot contains a tuple of the form (timespans, events).
1.257 """
1.258
1.259 all_events = {}
1.260 full_coverage = TimespanCollection(resolution)
1.261 - coverage_period = full_coverage.convert(Timespan(start, end))
1.262
1.263 # Get event details.
1.264
1.265 for event in events:
1.266 event_details = event.getDetails()
1.267
1.268 - # Test for the event in the period.
1.269 -
1.270 - if event in coverage_period:
1.271 -
1.272 - # Find the coverage of this period for the event.
1.273 -
1.274 - # For day views, each location has its own slot, but for month
1.275 - # views, all locations are pooled together since having separate
1.276 - # slots for each location can lead to poor usage of vertical space.
1.277 -
1.278 - if resolution == "datetime":
1.279 - event_location = event_details.get("location")
1.280 - else:
1.281 - event_location = None
1.282 -
1.283 - # Update the overall coverage.
1.284 -
1.285 - full_coverage.insert_in_order(event)
1.286 -
1.287 - # Add a new events list for a new location.
1.288 - # Locations can be unspecified, thus None refers to all unlocalised
1.289 - # events.
1.290 -
1.291 - if not all_events.has_key(event_location):
1.292 - all_events[event_location] = [TimespanCollection(resolution, [event])]
1.293 -
1.294 - # Try and fit the event into an events list.
1.295 + # Find the coverage of this period for the event.
1.296 +
1.297 + # For day views, each location has its own slot, but for month
1.298 + # views, all locations are pooled together since having separate
1.299 + # slots for each location can lead to poor usage of vertical space.
1.300 +
1.301 + if resolution == "datetime":
1.302 + event_location = event_details.get("location")
1.303 + else:
1.304 + event_location = None
1.305 +
1.306 + # Update the overall coverage.
1.307 +
1.308 + full_coverage.insert_in_order(event)
1.309 +
1.310 + # Add a new events list for a new location.
1.311 + # Locations can be unspecified, thus None refers to all unlocalised
1.312 + # events.
1.313 +
1.314 + if not all_events.has_key(event_location):
1.315 + all_events[event_location] = [TimespanCollection(resolution, [event])]
1.316 +
1.317 + # Try and fit the event into an events list.
1.318 +
1.319 + else:
1.320 + slot = all_events[event_location]
1.321 +
1.322 + for slot_events in slot:
1.323 +
1.324 + # Where the event does not overlap with the events in the
1.325 + # current collection, add it alongside these events.
1.326 +
1.327 + if not event in slot_events:
1.328 + slot_events.insert_in_order(event)
1.329 + break
1.330 +
1.331 + # Make a new element in the list if the event cannot be
1.332 + # marked alongside existing events.
1.333
1.334 else:
1.335 - slot = all_events[event_location]
1.336 -
1.337 - for slot_events in slot:
1.338 -
1.339 - # Where the event does not overlap with the events in the
1.340 - # current collection, add it alongside these events.
1.341 -
1.342 - if not event in slot_events:
1.343 - slot_events.insert_in_order(event)
1.344 - break
1.345 -
1.346 - # Make a new element in the list if the event cannot be
1.347 - # marked alongside existing events.
1.348 -
1.349 - else:
1.350 - slot.append(TimespanCollection(resolution, [event]))
1.351 + slot.append(TimespanCollection(resolution, [event]))
1.352
1.353 return full_coverage, all_events
1.354
1.355 @@ -1041,7 +1028,21 @@
1.356 def months(self):
1.357 return self.data[0] * 12 + self.data[1]
1.358
1.359 -class Temporal:
1.360 +class Convertible:
1.361 +
1.362 + "Support for converting temporal objects."
1.363 +
1.364 + def _get_converter(self, resolution):
1.365 + if resolution == "month":
1.366 + return lambda x: x and x.as_month()
1.367 + elif resolution == "date":
1.368 + return lambda x: x and x.as_date()
1.369 + elif resolution == "datetime":
1.370 + return lambda x: x and x.as_datetime_or_date()
1.371 + else:
1.372 + return lambda x: x
1.373 +
1.374 +class Temporal(Convertible):
1.375
1.376 "A simple temporal representation, common to dates and times."
1.377
1.378 @@ -1057,6 +1058,9 @@
1.379 def as_tuple(self):
1.380 return tuple(self.data)
1.381
1.382 + def convert(self, resolution):
1.383 + return self._get_converter(resolution)(self)
1.384 +
1.385 def __cmp__(self, other):
1.386
1.387 """
1.388 @@ -1167,6 +1171,8 @@
1.389
1.390 return self.until(self.as_month(), end.as_month(), Month.next_month, Month.previous_month)
1.391
1.392 + step_to = months_until
1.393 +
1.394 class Date(Month):
1.395
1.396 "A simple year-month-day representation."
1.397 @@ -1241,6 +1247,8 @@
1.398
1.399 return self.until(self.as_date(), end.as_date(), Date.next_day, Date.previous_day)
1.400
1.401 + step_to = days_until
1.402 +
1.403 class DateTime(Date):
1.404
1.405 "A simple date plus time representation."
1.406 @@ -1522,7 +1530,7 @@
1.407
1.408 return 0
1.409
1.410 -class Timespan(ActsAsTimespan):
1.411 +class Timespan(ActsAsTimespan, Convertible):
1.412
1.413 """
1.414 A period of time which can be compared against others to check for overlaps.
1.415 @@ -1544,6 +1552,9 @@
1.416 def as_limits(self):
1.417 return self.start, self.end
1.418
1.419 + def convert(self, resolution):
1.420 + return Timespan(*map(self._get_converter(resolution), self.as_limits()))
1.421 +
1.422 def is_before(self, a, b):
1.423
1.424 """
1.425 @@ -1607,25 +1618,31 @@
1.426 """
1.427
1.428 def __init__(self, resolution, values=None):
1.429 -
1.430 - # Timespans need to be given converted start and end dates/times.
1.431 -
1.432 - if resolution == "date":
1.433 - self.convert_time = lambda x: x.as_date()
1.434 - elif resolution == "datetime":
1.435 - self.convert_time = lambda x: x.as_datetime_or_date()
1.436 + self.resolution = resolution
1.437 + self.values = values or []
1.438 +
1.439 + def as_timespan(self):
1.440 + return Timespan(*self.as_limits())
1.441 +
1.442 + def as_limits(self):
1.443 +
1.444 + "Return the earliest and latest points in time for this collection."
1.445 +
1.446 + if not self.values:
1.447 + return None, None
1.448 else:
1.449 - self.convert_time = lambda x: x
1.450 -
1.451 - self.values = values or []
1.452 + first, last = self.values[0], self.values[-1]
1.453 + if isinstance(first, ActsAsTimespan):
1.454 + first = first.as_timespan().start
1.455 + if isinstance(last, ActsAsTimespan):
1.456 + last = last.as_timespan().end
1.457 + return first, last
1.458
1.459 def convert(self, value):
1.460 if isinstance(value, ActsAsTimespan):
1.461 - value = value.as_timespan()
1.462 - start, end = map(self.convert_time, value.as_limits())
1.463 - return Timespan(start, end)
1.464 + return value.as_timespan().convert(self.resolution)
1.465 else:
1.466 - return self.convert_time(value)
1.467 + return value.convert(self.resolution)
1.468
1.469 def __iter__(self):
1.470 return iter(self.values)
1.471 @@ -1655,6 +1672,11 @@
1.472 def insert_in_order(self, value):
1.473 bisect.insort_left(self, value)
1.474
1.475 + def items_in_range(self, start, end):
1.476 + slice_start = bisect.bisect_left(self, start)
1.477 + slice_end = bisect.bisect_right(self, end, slice_start)
1.478 + return self.values[slice_start:slice_end]
1.479 +
1.480 def getCountry(s):
1.481
1.482 "Find a country code in the given string 's'."