# HG changeset patch # User Paul Boddie # Date 1298081163 -3600 # Node ID 3c602433e6b549fdce35db62c4ed729717313607 # Parent bb420cdfe360a32db981572fd8a23641d9989f11# Parent a818bec1361017fc3c12ac7621314f1b817c1c16 Merged changes from revision 121 on the 0.7 (main) branch. diff -r bb420cdfe360 -r 3c602433e6b5 .hgtags --- a/.hgtags Sun Feb 06 02:38:17 2011 +0100 +++ b/.hgtags Sat Feb 19 03:06:03 2011 +0100 @@ -5,4 +5,5 @@ f1f552421d5e3d23b6fe2d53625d264cfe1d182c rel-0-3 27922520e51f07b289abe18b66537c7481556868 rel-0-4 0c8421ec5a70dc70e93a773f9812564d837cc575 rel-0-5 +e1684a6e8a821ac67ae6d591b703edaefbb66b15 rel-0-6 067b30223eb3db64971ace7dae263e4eae09dce6 rel-0-6-1 diff -r bb420cdfe360 -r 3c602433e6b5 EventAggregatorSupport.py --- a/EventAggregatorSupport.py Sun Feb 06 02:38:17 2011 +0100 +++ b/EventAggregatorSupport.py Sat Feb 19 03:06:03 2011 +0100 @@ -31,7 +31,7 @@ def escattr(s): return escape(s, 1) -__version__ = "0.6.1" +__version__ = "0.6.2" # Date labels. @@ -80,9 +80,6 @@ # Utility functions. -def isMoin15(): - return version.release.startswith("1.5.") - def getCategoryPattern(request): global category_regexp @@ -102,6 +99,147 @@ else: return int(x) +def sort_none_first(x, y): + if x is None: + return -1 + elif y is None: + return 1 + else: + return cmp(x, y) + +# Utility classes and associated functions. + +class Form: + + """ + A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x + environment. + """ + + def __init__(self, form): + self.form = form + + def get(self, name, default=None): + values = self.form.getlist(name) + if not values: + return default + else: + return values + + def __getitem__(self, name): + return self.form.getlist(name) + +class ActionSupport: + + """ + Work around disruptive MoinMoin changes in 1.9, and also provide useful + convenience methods. + """ + + def get_form(self): + return get_form(self.request) + + def _get_selected(self, value, input_value): + + """ + Return the HTML attribute text indicating selection of an option (or + otherwise) if 'value' matches 'input_value'. + """ + + return input_value is not None and value == input_value and 'selected="selected"' or '' + + def _get_selected_for_list(self, value, input_values): + + """ + Return the HTML attribute text indicating selection of an option (or + otherwise) if 'value' matches one of the 'input_values'. + """ + + return value in input_values and 'selected="selected"' or '' + + def _get_input(self, form, name, default=None): + + """ + Return the input from 'form' having the given 'name', returning either + the input converted to an integer or the given 'default' (optional, None + if not specified). + """ + + value = form.get(name, [None])[0] + if not value: # true if 0 obtained + return default + else: + return int(value) + + def get_month_lists(self, default_as_current=0): + + """ + Return two lists of HTML element definitions corresponding to the start + and end month selection controls, with months selected according to any + values that have been specified via request parameters. + """ + + _ = self._ + form = self.get_form() + + # Initialise month lists. + + start_month_list = [] + end_month_list = [] + + start_month = self._get_input(form, "start-month", default_as_current and getCurrentMonth().month() or None) + end_month = self._get_input(form, "end-month", start_month) + + # Prepare month lists, selecting specified months. + + if not default_as_current: + start_month_list.append('') + end_month_list.append('') + + for month in range(1, 13): + month_label = _(getMonthLabel(month)) + selected = self._get_selected(month, start_month) + start_month_list.append('' % (month, selected, month_label)) + selected = self._get_selected(month, end_month) + end_month_list.append('' % (month, selected, month_label)) + + return start_month_list, end_month_list + + def get_year_defaults(self, default_as_current=0): + + "Return defaults for the start and end years." + + form = self.get_form() + + start_year_default = form.get("start-year", [default_as_current and getCurrentYear() or ""])[0] + end_year_default = form.get("end-year", [default_as_current and start_year_default or ""])[0] + + return start_year_default, end_year_default + +def get_form(request): + + "Work around disruptive MoinMoin changes in 1.9." + + if hasattr(request, "values"): + return Form(request.values) + else: + return request.form + +class send_headers: + + """ + A wrapper to preserve MoinMoin 1.8.x (and earlier) request behaviour in a + 1.9.x environment. + """ + + def __init__(self, request): + self.request = request + + def __call__(self, headers): + for header in headers: + parts = header.split(":") + self.request.headers.add(parts[0], ":".join(parts[1:])) + # Textual representations. def getHTTPTimeString(tmtuple): @@ -214,11 +352,7 @@ """ query = search.QueryParser().parse_query('category:%s' % pagename) - if isMoin15(): - results = search.searchPages(request, query) - results.sortByPagename() - else: - results = search.searchPages(request, query, "page_name") + results = search.searchPages(request, query, "page_name") cat_pattern = getCategoryPattern(request) pages = [] @@ -249,21 +383,13 @@ "Using 'request', return the URL of this page." - page = self.page - - if isMoin15(): - return request.getQualifiedURL(page.url(request)) - else: - return request.getQualifiedURL(page.url(request, relative=0)) + return request.getQualifiedURL(self.page.url(request, relative=0)) def getFormat(self): "Get the format used on this page." - if isMoin15(): - return "wiki" # page.pi_format - else: - return self.page.pi["format"] + return self.page.pi["format"] def getRevisions(self): @@ -658,7 +784,7 @@ Using 'request', set timestamp details in the details dictionary of each of the 'events'. - Retutn the latest timestamp found. + Return the latest timestamp found. """ latest = None @@ -728,6 +854,10 @@ return min(first, last), last +# NOTE: Support coverage using times within days. This will involve timespan +# NOTE: objects which can be compared in such a way that set operations will be +# NOTE: able to detect overlapping periods. + def getCoverage(start, end, events): """ @@ -737,7 +867,7 @@ of the form (set of covered days, events). """ - all_events = [] + all_events = {} full_coverage = set() # Get event details. @@ -754,28 +884,39 @@ event_start = max(event_details["start"], start) event_end = min(event_details["end"], end) event_coverage = set(event_start.days_until(event_end)) + event_location = event_details.get("location") # Update the overall coverage. full_coverage.update(event_coverage) - # Try and fit the event into the events list. - - for i, (coverage, covered_events) in enumerate(all_events): - - # Where the event does not overlap with the current - # element, add it alongside existing events. + # Add a new events list for a new location. + # Locations can be unspecified, thus None refers to all unlocalised + # events. - if not coverage.intersection(event_coverage): - covered_events.append(event) - all_events[i] = coverage.union(event_coverage), covered_events - break + if not all_events.has_key(event_location): + all_events[event_location] = [(event_coverage, [event])] - # Make a new element in the list if the event cannot be - # marked alongside existing events. + # Try and fit the event into an events list. else: - all_events.append((event_coverage, [event])) + slot = all_events[event_location] + + for i, (coverage, covered_events) in enumerate(slot): + + # Where the event does not overlap with the current + # element, add it alongside existing events. + + if not coverage.intersection(event_coverage): + covered_events.append(event) + slot[i] = coverage.union(event_coverage), covered_events + break + + # Make a new element in the list if the event cannot be + # marked alongside existing events. + + else: + slot.append((event_coverage, [event])) return full_coverage, all_events @@ -1251,7 +1392,7 @@ in the 'request'. """ - return request.form.get(name, [default])[0] + return get_form(request).get(name, [default])[0] def getQualifiedParameter(request, calendar_name, argname, default=None): @@ -1333,17 +1474,25 @@ else: return None +def getFullMonthLabel(request, year_month): + + """ + Return the full month plus year label using the given 'request' and + 'year_month'. + """ + + _ = request.getText + year, month = year_month.as_tuple() + month_label = _(getMonthLabel(month)) + return "%s %s" % (month_label, year) + # Page-related functions. def getPrettyPageName(page): "Return a nicely formatted title/name for the given 'page'." - if isMoin15(): - title = page.split_title(page.request, force=1) - else: - title = page.split_title(force=1) - + title = page.split_title(force=1) return getPrettyTitle(title) def linkToPage(request, page, text, query_string=None): @@ -1354,14 +1503,7 @@ """ text = wikiutil.escape(text) - - if isMoin15(): - url = wikiutil.quoteWikinameURL(page.page_name) - if query_string is not None: - url = "%s?%s" % (url, query_string) - return wikiutil.link_tag(request, url, text, getattr(page, "formatter", None)) - else: - return page.link_to_raw(request, text, query_string) + return page.link_to_raw(request, text, query_string) def getFullPageName(parent, title): diff -r bb420cdfe360 -r 3c602433e6b5 PKG-INFO --- a/PKG-INFO Sun Feb 06 02:38:17 2011 +0100 +++ b/PKG-INFO Sat Feb 19 03:06:03 2011 +0100 @@ -1,12 +1,12 @@ Metadata-Version: 1.1 Name: EventAggregator -Version: 0.6.1 +Version: 0.6.2 Author: Paul Boddie Author-email: paul at boddie org uk Maintainer: Paul Boddie Maintainer-email: paul at boddie org uk Home-page: http://moinmo.in/MacroMarket/EventAggregator -Download-url: http://moinmo.in/MacroMarket/EventAggregator?action=AttachFile&do=view&target=EventAggregator-0.6.tar.gz +Download-url: http://moinmo.in/MacroMarket/EventAggregator?action=AttachFile&do=view&target=EventAggregator-0.7.tar.gz Summary: Aggregate event data and display it in an event calendar (or summarise it in iCalendar and RSS resources) License: GPL (version 2 or later) Description: The EventAggregator macro for MoinMoin can be used to display event diff -r bb420cdfe360 -r 3c602433e6b5 README.txt --- a/README.txt Sun Feb 06 02:38:17 2011 +0100 +++ b/README.txt Sat Feb 19 03:06:03 2011 +0100 @@ -26,9 +26,14 @@ Important Notices ----------------- -Release 0.6.1 fixes various bugs in HTML production done by the actions. It is +Release 0.6.2 fixes various bugs in HTML production done by the actions. It is strongly recommended to upgrade from earlier versions to this release. +In release 0.6.2, support for MoinMoin 1.5.x has been dropped. Since usage of +the Xapian search software is practically a necessary part of deploying this +solution, and yet Xapian only became integrated with MoinMoin from version 1.6 +onwards, few deployments should have involved MoinMoin 1.5.x. + In release 0.6, support for event times has been introduced. Due to the complicated nature of times, time zones, time regimes, and so on, the behaviour of the software may change in future versions to support common @@ -112,12 +117,7 @@ Using the Macro --------------- -It should now be possible to edit pages and use the macro as follows. For -MoinMoin 1.5: - - [[EventAggregator(CategoryEvents)]] - -For MoinMoin 1.6 and above: +It should now be possible to edit pages and use the macro as follows: <> @@ -227,6 +227,22 @@ time zone information for the correct interpretation of time information in those summaries. Thus, it is highly recommended that pytz be installed. +New in EventAggregator 0.6.2 (Changes since EventAggregator 0.6.1) +------------------------------------------------------------------ + + * Dropped MoinMoin 1.5.x support, since Xapian search is not available for + that version and is virtually a necessity. + * Fixed form handling to be compatible with MoinMoin 1.9.x, since that + particular release series introduced an incompatible request API that + breaks existing code (no longer providing access to query string + parameters via the form attribute, and only returning single values + unless the new getlist method on form-like objects is used). + * Fixed the direct writing of requests to be compatible with MoinMoin 1.9. + * Added pop-up elements showing information about the calendar/view + resources available for download or subscription. + * Added download/subscription links which open the form associated with the + EventAggregatorSummary action and permit editing of the supplied values. + New in EventAggregator 0.6.1 (Changes since EventAggregator 0.6) ---------------------------------------------------------------- diff -r bb420cdfe360 -r 3c602433e6b5 actions/EventAggregatorNewEvent.py --- a/actions/EventAggregatorNewEvent.py Sun Feb 06 02:38:17 2011 +0100 +++ b/actions/EventAggregatorNewEvent.py Sat Feb 19 03:06:03 2011 +0100 @@ -27,27 +27,14 @@ # Action class and supporting functions. -class EventAggregatorNewEvent(ActionBase): +class EventAggregatorNewEvent(ActionBase, EventAggregatorSupport.ActionSupport): "An event creation dialogue requesting various parameters." - def _get_selected(self, value, input_value): - return input_value is not None and value == input_value and 'selected="selected"' or '' - - def _get_selected_for_list(self, value, input_values): - return value in input_values and 'selected="selected"' or '' - - def _get_input(self, form, name, default=None): - value = form.get(name, [None])[0] - if not value: # true if 0 obtained - return default - else: - return int(value) - def get_form_html(self, buttons_html): _ = self._ request = self.request - form = request.form + form = self.get_form() # Handle advanced and basic forms, and enable/disable certain fields. @@ -99,20 +86,8 @@ # Initialise month lists. - start_month_list = [] - end_month_list = [] - - start_month = self._get_input(form, "start-month", EventAggregatorSupport.getCurrentMonth().month()) - end_month = self._get_input(form, "end-month", start_month) - - # Prepare month lists, selecting specified months. - - for month in range(1, 13): - month_label = _(EventAggregatorSupport.getMonthLabel(month)) - selected = self._get_selected(month, start_month) - start_month_list.append('' % (month, selected, escape(month_label))) - selected = self._get_selected(month, end_month) - end_month_list.append('' % (month, selected, escape(month_label))) + start_month_list, end_month_list = self.get_month_lists(default_as_current=1) + start_year_default, end_year_default = self.get_year_defaults(default_as_current=1) # Initialise regime lists. @@ -155,7 +130,7 @@ "start_label" : escape(_("Start date (day, month, year)")), "start_day_default" : escattr(form.get("start-day", [""])[0]), - "start_year_default" : escattr(form.get("start-year", [""])[0] or EventAggregatorSupport.getCurrentYear()), + "start_year_default" : escattr(start_year_default), "start_time_label" : escape(_("Start time (hour, minute, second)")), "start_hour_default" : escattr(form.get("start-hour", [""])[0]), "start_minute_default" : escattr(form.get("start-minute", [""])[0]), @@ -164,7 +139,7 @@ "end_label" : escape(_("End date (day, month, year) - if different")), "end_day_default" : escattr(form.get("end-day", [""])[0] or form.get("start-day", [""])[0]), - "end_year_default" : escattr(form.get("end-year", [""])[0] or form.get("start-year", [""])[0]), + "end_year_default" : escattr(end_year_default), "end_time_label" : escape(_("End time (hour, minute, second)")), "end_hour_default" : escattr(form.get("end-hour", [""])[0]), "end_minute_default" : escattr(form.get("end-minute", [""])[0]), @@ -469,7 +444,7 @@ "Create the new event." _ = self._ - form = self.request.form + form = self.get_form() # If no title exists in the request, an error message is returned. @@ -499,7 +474,7 @@ "Create an event page using the 'request'." _ = request.getText - form = request.form + form = self.get_form() category_pagenames = form.get("category", []) description = form.get("description", [None])[0] diff -r bb420cdfe360 -r 3c602433e6b5 actions/EventAggregatorSummary.py --- a/actions/EventAggregatorSummary.py Sun Feb 06 02:38:17 2011 +0100 +++ b/actions/EventAggregatorSummary.py Sat Feb 19 03:06:03 2011 +0100 @@ -14,7 +14,6 @@ from MoinMoin.action import ActionBase from MoinMoin import config from MoinMoin.Page import Page -import MoinMoin.util # for MoinMoin 1.5.x from MoinMoin import wikiutil import EventAggregatorSupport @@ -25,50 +24,82 @@ # Action class and supporting functions. -class EventAggregatorSummary(ActionBase): +class EventAggregatorSummary(ActionBase, EventAggregatorSupport.ActionSupport): "A summary dialogue requesting various parameters." def get_form_html(self, buttons_html): _ = self._ request = self.request - form = request.form + form = self.get_form() category_list = [] + category_pagenames = form.get("category", []) for category_name, category_pagename in \ EventAggregatorSupport.getCategoryMapping( EventAggregatorSupport.getCategories(request), request): - category_list.append('' % (escattr(category_pagename), escape(category_name))) + selected = self._get_selected_for_list(category_pagename, category_pagenames) + category_list.append('' % (escattr(category_pagename), selected, escape(category_name))) + + # Initialise month lists. + + start_month_list, end_month_list = self.get_month_lists() + start_year_default, end_year_default = self.get_year_defaults() + + # Criteria instead of months and years. - month_list = [] - month_list.append('') + start_criteria_default = form.get("start", [""])[0] + end_criteria_default = form.get("end", [""])[0] + + start_criteria_evaluated = EventAggregatorSupport.getParameterMonth(start_criteria_default) + end_criteria_evaluated = EventAggregatorSupport.getParameterMonth(end_criteria_default) - for month in range(1, 13): - month_label = _(EventAggregatorSupport.getMonthLabel(month)) - month_list.append('' % (month, escape(month_label))) + start_criteria_evaluated = start_criteria_evaluated and \ + EventAggregatorSupport.getFullMonthLabel(request, start_criteria_evaluated) or "" + end_criteria_evaluated = end_criteria_evaluated and \ + EventAggregatorSupport.getFullMonthLabel(request, end_criteria_evaluated) or "" + + # Descriptions. + + descriptions = form.get("descriptions", [None])[0] descriptions_list = [ - '' % ("page", escape(_("page"))), - '' % ("comment", escape(_("comment"))) + '' % ("page", self._get_selected("page", descriptions), escape(_("page"))), + '' % ("comment", self._get_selected("comment", descriptions), escape(_("comment"))) ] + # Format. + + format = form.get("format", [None])[0] + format_list = [ - '' % ("iCalendar", escape(_("iCalendar"))), - '' % ("RSS", escape(_("RSS 2.0"))) + '' % ("iCalendar", self._get_selected("iCalendar", format), escape(_("iCalendar"))), + '' % ("RSS", self._get_selected("RSS", format), escape(_("RSS 2.0"))) ] + right_arrow = unicode('\xe2\x86\x92', "utf-8") + d = { "buttons_html" : buttons_html, "category_label" : escape(_("Categories")), "category_list" : "\n".join(category_list), - "month_list" : "\n".join(month_list), + "start_month_list" : "\n".join(start_month_list), "start_label" : escape(_("Start year and month")), - "start_year_default" : "", + "start_year_default" : escattr(start_year_default), + "start_criteria_label" : escape(_("or special criteria")), + "start_criteria_default": escattr(start_criteria_default), + "start_eval_label" : escape(right_arrow), + "start_criteria_eval" : escape(start_criteria_evaluated), + "end_month_list" : "\n".join(end_month_list), "end_label" : escape(_("End year and month")), - "end_year_default" : "", + "end_year_default" : escattr(end_year_default), + "end_criteria_label" : escape(_("or special criteria")), + "end_criteria_default" : escattr(end_criteria_default), + "end_eval_label" : escape(right_arrow), + "end_criteria_eval" : escape(end_criteria_evaluated), "descriptions_label" : escape(_("Use descriptions from...")), "descriptions_list" : "\n".join(descriptions_list), "format_label" : escape(_("Summary format")), @@ -91,21 +122,37 @@ + + + + + %(start_criteria_eval)s + + + + + + + + %(end_criteria_eval)s + + +