paul@10 | 1 | # -*- coding: iso-8859-1 -*- |
paul@10 | 2 | """ |
paul@10 | 3 | MoinMoin - EventAggregatorSummary Action |
paul@10 | 4 | |
paul@67 | 5 | @copyright: 2008, 2009, 2010 by Paul Boddie <paul@boddie.org.uk> |
paul@10 | 6 | @copyright: 2000-2004 Juergen Hermann <jh@web.de>, |
paul@24 | 7 | 2003-2008 MoinMoin:ThomasWaldmann, |
paul@24 | 8 | 2004-2006 MoinMoin:AlexanderSchremmer, |
paul@19 | 9 | 2007 MoinMoin:ReimarBauer. |
paul@60 | 10 | 2009 Cristian Rigamonti <rigamonti@fsfeurope.org> |
paul@10 | 11 | @license: GNU GPL (v2 or later), see COPYING.txt for details. |
paul@10 | 12 | """ |
paul@10 | 13 | |
paul@19 | 14 | from MoinMoin.action import ActionBase |
paul@10 | 15 | from MoinMoin import config |
paul@24 | 16 | from MoinMoin.Page import Page |
paul@27 | 17 | import MoinMoin.util # for MoinMoin 1.5.x |
paul@29 | 18 | from MoinMoin import wikiutil |
paul@10 | 19 | import EventAggregatorSupport |
paul@10 | 20 | |
paul@10 | 21 | Dependencies = ['pages'] |
paul@10 | 22 | |
paul@19 | 23 | # Action class and supporting functions. |
paul@19 | 24 | |
paul@109 | 25 | class EventAggregatorSummary(ActionBase, EventAggregatorSupport.ActionSupport): |
paul@19 | 26 | |
paul@19 | 27 | "A summary dialogue requesting various parameters." |
paul@19 | 28 | |
paul@19 | 29 | def get_form_html(self, buttons_html): |
paul@19 | 30 | _ = self._ |
paul@19 | 31 | request = self.request |
paul@109 | 32 | form = self.get_form() |
paul@19 | 33 | |
paul@19 | 34 | category_list = [] |
paul@19 | 35 | |
paul@19 | 36 | for category_name, category_pagename in \ |
paul@19 | 37 | EventAggregatorSupport.getCategoryMapping( |
paul@19 | 38 | EventAggregatorSupport.getCategories(request), |
paul@19 | 39 | request): |
paul@19 | 40 | |
paul@19 | 41 | category_list.append('<option value="%s">%s</option>' % (category_pagename, category_name)) |
paul@19 | 42 | |
paul@23 | 43 | month_list = [] |
paul@23 | 44 | month_list.append('<option value=""></option>') |
paul@23 | 45 | |
paul@23 | 46 | for month in range(1, 13): |
paul@23 | 47 | month_label = _(EventAggregatorSupport.getMonthLabel(month)) |
paul@23 | 48 | month_list.append('<option value="%02d">%s</option>' % (month, month_label)) |
paul@23 | 49 | |
paul@45 | 50 | descriptions_list = [ |
paul@45 | 51 | '<option value="%s">%s</option>' % ("page", _("page")), |
paul@45 | 52 | '<option value="%s">%s</option>' % ("comment", _("comment")) |
paul@45 | 53 | ] |
paul@45 | 54 | |
paul@45 | 55 | format_list = [ |
paul@45 | 56 | '<option value="%s">%s</option>' % ("iCalendar", _("iCalendar")), |
paul@45 | 57 | '<option value="%s">%s</option>' % ("RSS", _("RSS 2.0")) |
paul@45 | 58 | ] |
paul@23 | 59 | |
paul@19 | 60 | d = { |
paul@74 | 61 | "buttons_html" : buttons_html, |
paul@74 | 62 | "category_label" : _("Categories"), |
paul@74 | 63 | "category_list" : "\n".join(category_list), |
paul@74 | 64 | "month_list" : "\n".join(month_list), |
paul@74 | 65 | "start_label" : _("Start year and month"), |
paul@74 | 66 | "start_year_default" : "", |
paul@74 | 67 | "end_label" : _("End year and month"), |
paul@74 | 68 | "end_year_default" : "", |
paul@74 | 69 | "descriptions_label" : _("Use descriptions from..."), |
paul@74 | 70 | "descriptions_list" : "\n".join(descriptions_list), |
paul@74 | 71 | "format_label" : _("Summary format"), |
paul@74 | 72 | "format_list" : "\n".join(format_list), |
paul@74 | 73 | "parent_label" : _("Parent page"), |
paul@74 | 74 | "parent_name" : form.get("parent", [""])[0], |
paul@19 | 75 | } |
paul@10 | 76 | |
paul@19 | 77 | return ''' |
paul@19 | 78 | <table> |
paul@19 | 79 | <tr> |
paul@19 | 80 | <td class="label"><label>%(category_label)s</label></td> |
paul@19 | 81 | <td class="content"> |
paul@19 | 82 | <select multiple="multiple" name="category"> |
paul@19 | 83 | %(category_list)s |
paul@19 | 84 | </select> |
paul@19 | 85 | </td> |
paul@19 | 86 | </tr> |
paul@19 | 87 | <tr> |
paul@19 | 88 | <td class="label"><label>%(start_label)s</label></td> |
paul@22 | 89 | <td> |
paul@23 | 90 | <select name="start-month"> |
paul@23 | 91 | %(month_list)s |
paul@23 | 92 | </select> |
paul@23 | 93 | <input name="start-year" type="text" value="%(start_year_default)s" size="4" /> |
paul@19 | 94 | </td> |
paul@19 | 95 | </tr> |
paul@19 | 96 | <tr> |
paul@19 | 97 | <td class="label"><label>%(end_label)s</label></td> |
paul@22 | 98 | <td> |
paul@23 | 99 | <select name="end-month"> |
paul@23 | 100 | %(month_list)s |
paul@23 | 101 | </select> |
paul@23 | 102 | <input name="end-year" type="text" value="%(end_year_default)s" size="4" /> |
paul@19 | 103 | </td> |
paul@19 | 104 | </tr> |
paul@19 | 105 | <tr> |
paul@45 | 106 | <td class="label"><label>%(descriptions_label)s</label></td> |
paul@45 | 107 | <td class="content"> |
paul@45 | 108 | <select name="descriptions"> |
paul@45 | 109 | %(descriptions_list)s |
paul@45 | 110 | </select> |
paul@45 | 111 | </td> |
paul@45 | 112 | </tr> |
paul@45 | 113 | <tr> |
paul@45 | 114 | <td class="label"><label>%(format_label)s</label></td> |
paul@45 | 115 | <td class="content"> |
paul@45 | 116 | <select name="format"> |
paul@45 | 117 | %(format_list)s |
paul@45 | 118 | </select> |
paul@45 | 119 | </td> |
paul@45 | 120 | </tr> |
paul@45 | 121 | <tr> |
paul@74 | 122 | <td class="label"><label>%(parent_label)s</label></td> |
paul@74 | 123 | <td class="content"> |
paul@74 | 124 | <input name="parent" type="text" size="40" value="%(parent_name)s" /> |
paul@74 | 125 | </td> |
paul@74 | 126 | </tr> |
paul@74 | 127 | <tr> |
paul@19 | 128 | <td></td> |
paul@19 | 129 | <td class="buttons"> |
paul@19 | 130 | %(buttons_html)s |
paul@19 | 131 | </td> |
paul@19 | 132 | </tr> |
paul@19 | 133 | </table> |
paul@19 | 134 | ''' % d |
paul@19 | 135 | |
paul@19 | 136 | def do_action(self): |
paul@19 | 137 | |
paul@19 | 138 | "Write the iCalendar resource." |
paul@19 | 139 | |
paul@19 | 140 | _ = self._ |
paul@109 | 141 | form = self.get_form() |
paul@19 | 142 | |
paul@19 | 143 | # If no category names exist in the request, an error message is |
paul@19 | 144 | # returned. |
paul@19 | 145 | |
paul@23 | 146 | category_names = form.get("category", []) |
paul@19 | 147 | |
paul@19 | 148 | if not category_names: |
paul@19 | 149 | return 0, _("No categories specified.") |
paul@19 | 150 | |
paul@19 | 151 | write_resource(self.request) |
paul@19 | 152 | return 1, None |
paul@19 | 153 | |
paul@27 | 154 | def render_success(self, msg, msgtype=None): |
paul@19 | 155 | |
paul@19 | 156 | """ |
paul@19 | 157 | Render neither 'msg' nor 'msgtype' since a resource has already been |
paul@19 | 158 | produced. |
paul@27 | 159 | NOTE: msgtype is optional because MoinMoin 1.5.x does not support it. |
paul@19 | 160 | """ |
paul@19 | 161 | |
paul@19 | 162 | pass |
paul@19 | 163 | |
paul@24 | 164 | def getQuotedText(text): |
paul@24 | 165 | |
paul@24 | 166 | "Return the 'text' quoted for iCalendar purposes." |
paul@24 | 167 | |
paul@24 | 168 | return text.replace(";", r"\;").replace(",", r"\,") |
paul@24 | 169 | |
paul@19 | 170 | def write_resource(request): |
paul@10 | 171 | |
paul@10 | 172 | """ |
paul@19 | 173 | For the given 'request', write an iCalendar summary of the event data found |
paul@19 | 174 | in the categories specified via the "category" request parameter, using the |
paul@19 | 175 | "start" and "end" parameters (if specified). Multiple "category" parameters |
paul@19 | 176 | can be specified. |
paul@10 | 177 | """ |
paul@10 | 178 | |
paul@109 | 179 | form = EventAggregatorSupport.get_form(request) |
paul@46 | 180 | |
paul@46 | 181 | category_names = form.get("category", []) |
paul@46 | 182 | format = form.get("format", ["iCalendar"])[0] |
paul@46 | 183 | descriptions = form.get("descriptions", ["page"])[0] |
paul@74 | 184 | parent = form.get("parent", [""])[0] |
paul@10 | 185 | |
paul@19 | 186 | # Otherwise, produce an iCalendar resource. |
paul@10 | 187 | |
paul@19 | 188 | calendar_start = EventAggregatorSupport.getFormMonth(request, None, "start") |
paul@19 | 189 | calendar_end = EventAggregatorSupport.getFormMonth(request, None, "end") |
paul@10 | 190 | |
paul@23 | 191 | # Look for separate start and end years and months. |
paul@23 | 192 | |
paul@23 | 193 | if calendar_start is None: |
paul@23 | 194 | calendar_start = EventAggregatorSupport.getFormMonthPair(request, "start-year", "start-month") |
paul@23 | 195 | |
paul@23 | 196 | if calendar_end is None: |
paul@23 | 197 | calendar_end = EventAggregatorSupport.getFormMonthPair(request, "end-year", "end-month") |
paul@23 | 198 | |
paul@10 | 199 | events, shown_events, all_shown_events, earliest, latest = \ |
paul@10 | 200 | EventAggregatorSupport.getEvents(request, category_names, calendar_start, calendar_end) |
paul@10 | 201 | |
paul@29 | 202 | latest_timestamp = EventAggregatorSupport.setEventTimestamps(request, all_shown_events) |
paul@29 | 203 | |
paul@29 | 204 | # Output summary data... |
paul@10 | 205 | |
paul@110 | 206 | if hasattr(request, "http_headers"): |
paul@27 | 207 | send_headers = request.http_headers |
paul@110 | 208 | elif hasattr(request, "emit_http_headers"): |
paul@110 | 209 | send_headers = request.emit_http_headers |
paul@27 | 210 | else: |
paul@110 | 211 | send_headers = EventAggregatorSupport.send_headers(request) |
paul@27 | 212 | |
paul@29 | 213 | # Define headers. |
paul@29 | 214 | |
paul@29 | 215 | if format == "iCalendar": |
paul@29 | 216 | headers = ["Content-Type: text/calendar; charset=%s" % config.charset] |
paul@29 | 217 | elif format == "RSS": |
paul@29 | 218 | headers = ["Content-Type: application/rss+xml; charset=%s" % config.charset] |
paul@29 | 219 | |
paul@29 | 220 | # Define the last modified time. |
paul@10 | 221 | |
paul@38 | 222 | if latest_timestamp is not None: |
paul@38 | 223 | headers.append("Last-Modified: %s" % EventAggregatorSupport.getHTTPTimeString(latest_timestamp)) |
paul@38 | 224 | |
paul@29 | 225 | send_headers(headers) |
paul@29 | 226 | |
paul@29 | 227 | # iCalendar output... |
paul@10 | 228 | |
paul@29 | 229 | if format == "iCalendar": |
paul@29 | 230 | request.write("BEGIN:VCALENDAR\r\n") |
paul@29 | 231 | request.write("PRODID:-//MoinMoin//EventAggregatorSummary\r\n") |
paul@29 | 232 | request.write("VERSION:2.0\r\n") |
paul@24 | 233 | |
paul@69 | 234 | for event in all_shown_events: |
paul@69 | 235 | event_page = event.getPage() |
paul@69 | 236 | event_details = event.getDetails() |
paul@29 | 237 | |
paul@29 | 238 | # Get the summary details. |
paul@10 | 239 | |
paul@74 | 240 | event_summary = event.getSummary(parent) |
paul@67 | 241 | link = event_page.getPageURL(request) |
paul@29 | 242 | |
paul@29 | 243 | # Output the event details. |
paul@24 | 244 | |
paul@29 | 245 | request.write("BEGIN:VEVENT\r\n") |
paul@37 | 246 | request.write("UID:%s\r\n" % link) |
paul@37 | 247 | request.write("URL:%s\r\n" % link) |
paul@29 | 248 | request.write("DTSTAMP:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["created"][:6]) |
paul@29 | 249 | request.write("LAST-MODIFIED:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["last-modified"][:6]) |
paul@29 | 250 | request.write("SEQUENCE:%d\r\n" % event_details["sequence"]) |
paul@92 | 251 | |
paul@92 | 252 | start = event_details["start"] |
paul@92 | 253 | end = event_details["end"] |
paul@92 | 254 | |
paul@92 | 255 | if start.has_time(): |
paul@92 | 256 | request.write("DTSTART") |
paul@93 | 257 | write_calendar_datetime(request, start) |
paul@92 | 258 | else: |
paul@92 | 259 | request.write("DTSTART;VALUE=DATE:%04d%02d%02d\r\n" % start.as_date().as_tuple()) |
paul@92 | 260 | |
paul@92 | 261 | if end.has_time(): |
paul@92 | 262 | request.write("DTEND") |
paul@93 | 263 | write_calendar_datetime(request, end) |
paul@92 | 264 | else: |
paul@92 | 265 | request.write("DTEND;VALUE=DATE:%04d%02d%02d\r\n" % end.next_day().as_date().as_tuple()) |
paul@92 | 266 | |
paul@29 | 267 | request.write("SUMMARY:%s\r\n" % getQuotedText(event_summary)) |
paul@24 | 268 | |
paul@29 | 269 | # Optional details. |
paul@24 | 270 | |
paul@98 | 271 | if event_details.get("topics") or event_details.get("categories"): |
paul@29 | 272 | request.write("CATEGORIES:%s\r\n" % ",".join( |
paul@29 | 273 | [getQuotedText(topic) for topic in event_details.get("topics") or event_details.get("categories")] |
paul@29 | 274 | )) |
paul@29 | 275 | if event_details.has_key("location"): |
paul@29 | 276 | request.write("LOCATION:%s\r\n" % getQuotedText(event_details["location"])) |
paul@29 | 277 | |
paul@29 | 278 | request.write("END:VEVENT\r\n") |
paul@10 | 279 | |
paul@29 | 280 | request.write("END:VCALENDAR\r\n") |
paul@29 | 281 | |
paul@29 | 282 | elif format == "RSS": |
paul@60 | 283 | |
paul@60 | 284 | # Using the page name and the page URL in the title, link and |
paul@60 | 285 | # description. |
paul@60 | 286 | |
paul@110 | 287 | if hasattr(request, "getPathinfo"): |
paul@110 | 288 | path_info = request.getPathinfo() |
paul@110 | 289 | else: |
paul@110 | 290 | path_info = request.path |
paul@110 | 291 | |
paul@29 | 292 | request.write('<rss version="2.0">\r\n') |
paul@29 | 293 | request.write('<channel>\r\n') |
paul@110 | 294 | request.write('<title>%s</title>\r\n' % path_info[1:]) |
paul@110 | 295 | request.write('<link>%s%s</link>\r\n' % (request.getBaseURL(), path_info)) |
paul@110 | 296 | request.write('<description>Events published on %s%s</description>\r\n' % (request.getBaseURL(), path_info)) |
paul@29 | 297 | request.write('<lastBuildDate>%s</lastBuildDate>\r\n' % EventAggregatorSupport.getHTTPTimeString(latest_timestamp)) |
paul@60 | 298 | |
paul@60 | 299 | # Sort all_shown_events by start date, reversed: |
paul@60 | 300 | # |
paul@60 | 301 | # * event_details are dictionaries, with the "start" entry providing |
paul@60 | 302 | # the start date |
paul@60 | 303 | # |
paul@67 | 304 | # So we use as sorting key the "start" key from the event details. |
paul@60 | 305 | |
paul@67 | 306 | ordered_events = EventAggregatorSupport.getOrderedEvents(all_shown_events) |
paul@67 | 307 | ordered_events.reverse() |
paul@24 | 308 | |
paul@69 | 309 | for event in ordered_events: |
paul@69 | 310 | event_page = event.getPage() |
paul@69 | 311 | event_details = event.getDetails() |
paul@29 | 312 | |
paul@29 | 313 | # Get the summary details. |
paul@29 | 314 | |
paul@74 | 315 | event_summary = event.getSummary(parent) |
paul@67 | 316 | link = event_page.getPageURL(request) |
paul@24 | 317 | |
paul@29 | 318 | request.write('<item>\r\n') |
paul@29 | 319 | request.write('<title>%s</title>\r\n' % wikiutil.escape(event_summary)) |
paul@29 | 320 | request.write('<link>%s</link>\r\n' % link) |
paul@45 | 321 | |
paul@45 | 322 | # Write a description according to the preferred source of |
paul@45 | 323 | # descriptions. |
paul@45 | 324 | |
paul@45 | 325 | if descriptions == "page": |
paul@45 | 326 | description = event_details.get("description", "") |
paul@45 | 327 | else: |
paul@45 | 328 | description = event_details["last-comment"] |
paul@45 | 329 | |
paul@45 | 330 | request.write('<description>%s</description>\r\n' % wikiutil.escape(description)) |
paul@29 | 331 | |
paul@29 | 332 | for topic in event_details.get("topics") or event_details.get("categories") or []: |
paul@29 | 333 | request.write('<category>%s</category>\r\n' % topic) |
paul@24 | 334 | |
paul@29 | 335 | request.write('<pubDate>%s</pubDate>\r\n' % EventAggregatorSupport.getHTTPTimeString(event_details["created"])) |
paul@29 | 336 | request.write('<guid>%s#%s</guid>\r\n' % (link, event_details["sequence"])) |
paul@29 | 337 | request.write('</item>\r\n') |
paul@10 | 338 | |
paul@29 | 339 | request.write('</channel>\r\n') |
paul@29 | 340 | request.write('</rss>\r\n') |
paul@10 | 341 | |
paul@27 | 342 | if EventAggregatorSupport.isMoin15(): |
paul@27 | 343 | raise MoinMoin.util.MoinMoinNoFooter |
paul@27 | 344 | |
paul@93 | 345 | def write_calendar_datetime(request, datetime): |
paul@93 | 346 | |
paul@93 | 347 | """ |
paul@93 | 348 | Write to the given 'request' the 'datetime' using appropriate time zone |
paul@93 | 349 | information. |
paul@93 | 350 | """ |
paul@93 | 351 | |
paul@93 | 352 | utc_datetime = datetime.to_utc() |
paul@93 | 353 | if utc_datetime: |
paul@93 | 354 | request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02dZ\r\n" % utc_datetime.padded().as_tuple()[:-1]) |
paul@93 | 355 | else: |
paul@93 | 356 | zone = datetime.time_zone() |
paul@93 | 357 | if zone: |
paul@93 | 358 | request.write(";TZID=/%s" % zone) |
paul@93 | 359 | request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02d\r\n" % datetime.padded().as_tuple()[:-1]) |
paul@93 | 360 | |
paul@19 | 361 | # Action function. |
paul@19 | 362 | |
paul@19 | 363 | def execute(pagename, request): |
paul@19 | 364 | EventAggregatorSummary(pagename, request).render() |
paul@19 | 365 | |
paul@10 | 366 | # vim: tabstop=4 expandtab shiftwidth=4 |