# -*- coding: utf-8 -*- import re, math from datetime import datetime, timedelta, time from os import mkdir from os.path import join from re import match, sub from collections import defaultdict from sqlite3 import IntegrityError from operator import attrgetter from trac.config import * from trac.mimeview.api import Mimeview, IContentConverter, Context from trac.core import Component, implements, TracError from trac.resource import IResourceManager, Resource, get_resource_url from trac.perm import PermissionError, IPermissionRequestor from trac.search import ISearchSource, search_to_sql, shorten_result from trac.util import get_reporter_id, Ranges from trac.util.text import to_unicode from trac.util.html import html from trac.util.datefmt import get_timezone, utc, format_time, localtz #from trac.util.translation import _, add_domain from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_warning, add_notice, add_ctxtnav, add_script, add_link, Chrome from trac.web import IRequestHandler from trac.wiki import IWikiSyntaxProvider from dateutil import rrule from genshi.builder import tag from genshi.template import TemplateLoader, NewTextTemplate from tractags.api import TagSystem, ITagProvider from tracrendezvous.location.model import ItemLocation from tracrendezvous.event.model import Event, EventRRule, EventRDate #from tracrendezvous.event.parse_ical import parse_ical from ctdotools.utils import gen_wiki_page, validate_id, time_parse, datetime_parse, date_parse, get_tz from trac.util.translation import domain_functions add_domain, _, tag_ = domain_functions('tracrendezvous', ('add_domain', '_', 'tag_')) import tracrendezvous __all__ = ['EventModule','EventTagProvider'] class EventTagProvider(Component): """A tag provider using Events tag field as sources of tags. """ implements(ITagProvider) # ITagProvider methods def get_taggable_realm(self): return 'event' def get_tagged_resources(self, req, tags): split_into_tags = TagSystem(self.env).split_into_tags db = self.env.get_db_cnx() cursor = db.cursor() args = [] sql = "SELECT * FROM (SELECT e_id, tags, COALESCE(tags, '') as fields FROM events)" constraints = [] if tags: constraints.append( "(" + ' OR '.join(["fields LIKE %s" for t in tags]) + ")") args += ['%' + t + '%' for t in tags] else: constraints.append("fields != ''") if constraints: sql += " WHERE %s" % " AND ".join(constraints) sql += " ORDER BY e_id" self.env.log.debug(sql) cursor.execute(sql, args) for row in cursor: id, ttags = row[0], ' '.join([f for f in row[1:-1] if f]) event_tags = split_into_tags(ttags) tags = set([to_unicode(x) for x in tags]) if (not tags or event_tags.intersection(tags)): yield Resource('event', id), event_tags def get_resource_tags(self, req, resource): if 'EVENTS_VIEW' not in req.perm(resource): return event = Event.fetch_one(self.env, resource.id) tags = self.__event_tags(event) return tags def set_resource_tags(self, req, resource, tags): req.perm.require('EVENTS_MODIFY', resource) split_into_tags = TagSystem(self.env).split_into_tags event = Event.fetch_one(self.env, resource.id) all_ = self.__event_tags(event) tags.difference_update(all_.difference(event.tags)) event.tags = u' '.join(sorted(map(to_unicode, tags))) event.update() def remove_resource_tags(self, req, resource): req.perm.require('EVENTS_MODIFY', resource) event = Event.fetch_one(self.env, resource.id) event.tags = None event.update() # Private methods def __event_tags(self, rendezvous): return TagSystem(self.env).split_into_tags(rendezvous.tags) class EventModule(Component): '''The web ui frontend for the event management system''' Option("event", "upcoming_name", _(u"Upcoming Events")) Option("event", "upcoming_desc", _(u"Hier erfährst Du direkt, was in den nächsten 6 Monaten im Chaostreff los ist.")) Option("event", "wiki_content", _(u"Feel free to fill this outer space with content descibing the actual event.[[BR]]If the event is recurrent, describe the overall topics or similarities here and put the date dependant stuff into the wikipages you find via the links on the right side.")) implements(IRequestHandler, IResourceManager, IWikiSyntaxProvider, IContentConverter, IPermissionRequestor, INavigationContributor, ISearchSource, ITemplateProvider) m1 = re.compile(r'^/event/(\d+)$') m2 = re.compile(r'^/event/edit/(\d+)$') m3 = re.compile(r'^/event/recurrency/(\d+)$') m4 = re.compile(r'^/event/by-day/(\d{4})-(\d{1,2})-(\d{1,2})$') m5 = re.compile(r'^/event/new/(\d{4})-(\d{1,2})-(\d{1,2})$') m6 = re.compile(r'^/event/createpage/(\d+)/(\d{4})-(\d{1,2})-(\d{1,2})$') def __init__(self, parent=None): from pkg_resources import resource_filename locale_dir = resource_filename(tracrendezvous.__name__, 'locale') add_domain(self.env.path, locale_dir) # INavigationContributor methods def get_active_navigation_item(self, req): return 'event' def get_navigation_items(self, req): if "EVENTS_VIEW" in req.perm: yield ('mainnav', 'event', html.A(_('Upcoming Events'), href=req.href.event("upcoming"))) def get_permission_actions(self): '''returns all permissions this component provides''' return ['EVENTS_ADD', 'EVENTS_DELETE', 'EVENTS_MODIFY', 'EVENTS_VIEW', ('EVENTS_ADMIN', ('EVENTS_ADD', 'EVENTS_DELETE', 'EVENTS_MODIFY', 'EVENTS_VIEW'))] def match_request(self, req): self.env.log.debug("EventModule.match_request %s\n" % req) key = req.path_info simple_paths = ("/event/upcoming", '/event', "/event/new") if key in simple_paths: return True m = self.m1.match(key) if m: req.args["e_id"] = int(m.group(1)) return True m = self.m2.match(key) if m: req.args["e_id"] = int(m.group(1)) return True m = self.m3.match(key) if m: req.args["e_id"] = int(m.group(1)) return True m = self.m4.match(key) if m: req.args['arg_date'] = datetime(int(m.group(1)),int(m.group(2)),int(m.group(3)), 17, tzinfo=utc) return True m = self.m5.match(key) if m: req.args['arg_date'] = datetime(int(m.group(1)),int(m.group(2)),int(m.group(3)), 17, tzinfo=utc) return True m = self.m6.match(key) if m: req.args["e_id"] = int(m.group(1)) req.args['arg_date'] = datetime(int(m.group(2)),int(m.group(3)),int(m.group(4)), tzinfo=utc) return True return False # ITemplateProvider methods def get_templates_dirs(self): from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] def get_htdocs_dirs(self): """Return a list of directories with static resources (such as style sheets, images, etc.) Each item in the list must be a `(prefix, abspath)` tuple. The `prefix` part defines the path in the URL that requests to these resources are prefixed with. The `abspath` is the absolute path to the directory containing the resources on the local file system. """ from pkg_resources import resource_filename return [('hw', resource_filename(__name__, 'htdocs')), ('parent', resource_filename(tracrendezvous.__name__, 'htdocs'))] def process_request(self, req): query = req.path_info if "/event/upcoming" == query: return self.__display_upcoming_events(req) elif "/event/by-day" in query: return self.__display_events_by_day(req) elif "/event/new" in query: return self.__process_event(req) elif "/event/recurrency" in query: return self.__process_recurrency(req) elif "/event/edit" in query: return self.__process_event(req) elif "/event/createpage" in query: return self.__create_wiki_page(req) elif "/event" in query: return self.__display_events(req) else: return self.__display_upcoming_events(req) # IContentConverter methods def get_supported_conversions(self): #yield ('rss', _('RSS Feed'), 'xml', #'tracrendezvous.Event', 'application/rss+xml', 8) yield ('ical', _('Ical Feed'), 'ics', 'tracrendezvous.Event', 'text/calendar', 8) def convert_content(self, req, mimetype, cal, key): #if key == 'rss': #return self._export_upcoming_events_rss(req, ticket) if key == 'ical': return self.__export_upcoming_events_ical(req, cal) # IResourceManager methods def get_resource_realms(self): yield 'event' def get_resource_description(self, resource, format=None, context=None, **kwargs): if format == 'compact': return 'Event #%s' % resource.id from tracrendezvous.event.model import Event event = Event.fetch_one(self.env, resource.id) # TODO: really UTC? return "Event #%d - %s (%s UTC - %s UTC)" % (event.e_id, event.name, event.time_begin, event.time_end) # ISearchSource methods def get_search_filters(self, req): if 'EVENTS_VIEW' in req.perm: yield ('event', _('Events')) def get_search_results(self, req, terms, filters): if not 'event' in filters: return db = self.env.get_db_cnx() sql_query, args = search_to_sql(db, ['e1.name','e1.author'], terms) cursor = db.cursor() cursor.execute("SELECT e1.e_id, e1.name, e1.author, e1.time_modified " "FROM events e1 " "WHERE " + sql_query, args) event_realm = Resource('event') for e_id, name, author, ts in cursor: event = event_realm(id=e_id) yield (get_resource_url(self.env, event, req.href), name, datetime.fromtimestamp(ts, utc), author, _("Click the link")) # IWikiSyntaxProvider methods def get_wiki_syntax(self): return [] def get_link_resolvers(self): yield ('event', self._format_event_link) # private methods def __process_recurrency(self, req): req.perm.require("EVENTS_MODIFY") e_id = req.args["e_id"] add_ctxtnav(req, _('Back to') + _('Overview'), req.href.event()) add_ctxtnav(req, _('Back to') + _('Event') + ' #%s' % e_id, req.href.event('edit', e_id)) add_stylesheet (req, 'hw/css/ui.all.css') add_stylesheet (req, 'hw/css/event.css') add_script (req, 'hw/scripts/jquery-ui-1.6.custom.min.js') if req.session.has_key("edited"): #add_notice(req, tag.p(_("Recurrency rule created successfully. Back to "), tag.a(_("Event"), href=req.href.event(event.e_id)))) del req.session["edited"] req.session.save() if req.session.has_key("added"): #add_notice(req, tag.p(_("Recurrency rule created successfully. Back to "), tag.a(_("Event"), href=req.href.event(event.e_id)))) del req.session["added"] req.session.save() if req.session.has_key("event_added"): add_notice(req, tag.p(_("Event created successfully.") + _("Back to "), tag.a(_("Event"), href=req.href.event(event.e_id)))) del req.session["event_added"] req.session.save() if req.session.has_key("deleted"): #add_notice(req, tag.p(_("Recurrency rule deleted successfully. Back to "), tag.a(_("Event"), href=req.href.event(event.e_id)))) del req.session["deleted"] req.session.save() session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None)) event = Event.fetch_one(self.env, e_id) if not event: raise TracError(_("Event not found")) try: period = EventRRule.fetch_by_event(self.env, e_id)[0] exists = True except IndexError: period = EventRRule(self.env, e_id=event.e_id) exists = False freq = 0 if req.method == "POST": if req.args.has_key("save"): is_valid = True is_periodic = req.args.has_key("is_periodic") if event.is_periodic and not is_periodic: EventRRule.delete(self.env, event.e_id) EventRDate.delete(self.env, event.e_id) event.is_periodic = False event.update() req.redirect(req.href.event("recurrency", event.e_id)) elif not event.is_periodic and is_periodic: event.is_periodic = True event.update() def get_bymonth(req, name, period): try: period.bymonth = int(req.args[name]) if not (0 <= period.bymonth < 12): raise Exception() except Exception: add_warning(req, _("Wrong Value for 'month'.")) raise def get_bymonthday(req, name, period): try: period.bymonthday = int(req.args[name]) if not (-33 <= period.bymonthday < 32): raise Exception() except Exception, e: add_warning(req, _("Wrong Value for 'monthday'.")) raise def get_byweekday(req, name, period): try: period.byweekday = set((int(req.args[name]),)) if filter(lambda x: not (0 <= x < 7), period.byweekday): raise Exception() except Exception, e: add_warning(req, _("Wrong Value for 'weekday'.")) raise def get_byweekdayocc(req, name, period): byweekdayocc = req.args[name] if byweekdayocc: try: period.byweekdayocc = int(byweekdayocc) except Exception, e: add_warning(req, _("Wrong Value for 'weekday occurence'.")) raise else: period.byweekdayocc = None def get_interval(req, name, period): try: period.interval = int(req.args[name]) if not (0 <= period.interval < 1000): raise Exception() except Exception, e: add_warning(req, _("Wrong Value for interval")) raise # std parameters count = req.args["count"] if count: try: period.count = int(count) except Exception, e: is_valid = False add_warning(req, _("Wrong Value for count")) else: period.count = None until_date = req.args["until_date"] if until_date: try: until = date_parse(until_date) period.until = datetime.combine(until, time(12,tzinfo=utc)) except Exception, e: add_warning(req, _("Wrong Value for until_date or until_time")) raise else: period.until = None # selective parameters try: freq = int(req.args["freq"]) except KeyError: add_warning(req, _("Please select one of the recurrency types")) is_valid = False if freq == 0: period.freq = rrule.YEARLY period.byweekday = list() period.byweekdayocc = None period.byyearday = None try: get_interval(req, "yearinterval", period) get_bymonthday(req, "monthday-yearly-0", period) get_bymonth(req, "monthname-yearly-0", period) except Exception: is_valid = False elif freq == 1: period.freq = rrule.YEARLY get_interval(req, "yearinterval", period) period.bymonthday = None period.byyearday = None try: get_byweekdayocc(req, "dayocc-yearly-1", period) get_byweekday(req, "weekday-yearly-1", period) get_bymonth(req, "monthname-yearly-1", period) except Exception: is_valid = False elif freq == 2: period.freq = rrule.YEARLY try: get_interval(req, "yearinterval", period) except Exception: is_valid = False period.bymonthday = None period.bymonth = None period.byweekday = list() period.byweekdayocc = None try: period.byyearday = int(req.args["yearday"]) if not (0 <= period.byyearday < 366): raise Exception() except Exception, e: add_warning(req, _("Wrong Value for 'weekday'.")) is_valid = False elif freq == 3: period.freq = rrule.MONTHLY try: get_interval(req, "monthinterval", period) get_bymonthday(req, "monthday-monthly-0", period) except Exception: is_valid = False period.byweekday = None period.byweekdayocc = None period.byyearday = None period.bymonth = None elif freq == 4: period.freq = rrule.MONTHLY period.byyearday = None period.bymonth = None period.bymonthday = None try: get_interval(req, "monthinterval", period) get_byweekdayocc(req, "dayocc-monthly-1", period) get_byweekday(req, "weekday-monthly-1", period) except Exception: is_valid = False elif freq == 5: period.freq = rrule.WEEKLY try: get_interval(req, "weekinterval", period) except Exception: is_valid = False period.byweekday = list() period.byweekdayocc = None period.byyearday = None period.bymonth = None period.bymonthday = None try: weekdays = set(map(int, req.args["weekday-weekly"])) if any(map(lambda x: not (0 <= x < 7), weekdays)): is_valid = False if weekdays != period.byweekday: period.byweekday = weekdays except KeyError: add_warning(req, _("Any days selected.")) is_valid = False elif freq == 6: period.freq = rrule.DAILY try: get_interval(req, "dayinterval", period) except Exception: is_valid = False period.byweekday = None period.byweekdayocc = None period.byyearday = None period.bymonth = None period.bymonthday = None if is_valid: if exists: period.update() else: period.commit() req.redirect(req.href.event("recurrency", e_id)) elif req.args.has_key("exception-add"): try: exception = datetime_parse("%s 12:00" % req.args["exception-name"], selected_tz) exception = exception.replace(hour=event.time_begin.hour, minute=event.time_begin.minute) except ValueError: add_warning(req, _("Wrong recurrency exception date format")) req.redirect(req.href.event("recurrency", e_id)) rdate = EventRDate(self.env, 0, event.e_id, True, exception) rdate.commit() req.session["added"] = True req.redirect(req.href.event("recurrency", e_id)) elif req.args.has_key("exception-edit"): for k in req.args.iterkeys(): try: marker, erd_id = k.split(":", 1) except Exception,e: continue try: erd_id = int(erd_id) validate_id(erd_id) except ValueError,e: continue if marker != "exception": continue rdate = EventRDate.fetch_one(self.env, erd_id) try: exception = datetime_parse("%s 12:00" % req.args["exception-name"], selected_tz) exception = exception.replace(hour=event.time_begin.hour, minute=event.time_begin.minute) except KeyError: add_warning(req, _("You have to specify a valid recurrency exception date for that operation!")) req.redirect(req.href.event("recurrency", e_id)) if exception: rdate.erd_datetime = exception rdate.update() req.session["edited"] = True req.redirect(req.href.event("recurrency", e_id)) req.redirect(req.href.event("recurrency", e_id)) elif req.args.has_key("exception-delete"): for k in req.args.iterkeys(): try: marker, erd_id = k.split(":", 1) except Exception,e: continue try: erd_id = int(erd_id) validate_id(erd_id) except ValueError,e: continue if marker != "exception": continue EventRDate.delete(self.env, erd_id) req.session["deleted"] = True req.redirect(req.href.event("recurrency", e_id)) else: if period.bymonthday!=None and period.bymonth!=None: freq = 0 elif period.byweekday and period.bymonth!=None: freq = 1 elif period.byyearday!=None: freq = 2 elif period.bymonthday!=None: freq = 3 elif period.byweekdayocc!=None and period.byweekday: freq = 4 elif period.freq == rrule.WEEKLY: freq = 5 elif period.freq == rrule.DAILY: freq = 6 exceptions = EventRDate.fetch_by_event(self.env, event.e_id) return "recur_edit.html", { "event" : event, "freq" : freq, "period" : period, "session_tzname" : session_tzname, "selected_tz" : selected_tz, "exceptions" : exceptions, "default_location" : self.config.getint("location", "default_location"), "locations" : ItemLocation.fetch_all(self.env), "month_names" : EventRRule.month_names, "day_names" : EventRRule.day_names, "weekday_names" : EventRRule.weekday_names, "monthday_names" : EventRRule.monthday_names}, None def __process_event(self, req): '''process add, change and delete rendezvous''' if 'EVENTS_ADD' not in req.perm and 'EVENTS_MODIFY' not in req.perm and 'EVENTS_DELETE' not in req.perm: raise PermissionError() add_stylesheet (req, 'hw/css/event.css') add_stylesheet (req, 'hw/css/ui.all.css') add_script (req, 'hw/scripts/jquery-ui-1.6.custom.min.js') # set information after redirects if req.session.has_key("edited"): add_notice(req, tag.p(_("Event edited successfully. Back to "), tag.a(_("Overview"), href=req.href.event()))) del req.session["edited"] req.session.save() if req.session.has_key("added"): add_notice(req, tag.p(_("Event created successfully. Back to "), tag.a(_("Overview"), href=req.href.event()))) del req.session["added"] req.session.save() if req.session.has_key("deleted"): add_notice(req, tag.p(_("Event deleted successfully. Back to "), tag.a(_("Overview"), href=req.href.event()))) del req.session["deleted"] req.session.save() myenv = self.env date_now = utc.localize(datetime.utcnow()) session_tzname, selected_tz = get_tz(req.session.get('tz', self.config.get("trac", "default_timezone") or None)) if req.args.has_key("e_id"): event = Event.fetch_one(myenv, req.args["e_id"]) if not event: raise TracError(_("Event not found")) add_ctxtnav(req, _('Events Overview'), req.href.event()) add_ctxtnav(req, _('Upcoming Events'), req.href.event("upcoming")) add_ctxtnav(req, _('Add New Event'), req.href.event("new")) else: try: pi = req.path_info crap, new_date = pi.split("new/", 1) year, month, day = re.match("(\d{4})-(\d{1,2})-(\d{1,2})", new_date).groups() new_date = utc.localize(datetime(int(year), int(month), int(day), 17, tzinfo=selected_tz)) except ValueError: new_date = date_now event = Event(myenv, 0, "", req.authname, date_now, date_now, new_date, selected_tz.normalize(new_date + timedelta(0, 10800)), 1) add_ctxtnav(req, _('Events Overview'), req.href.event()) add_ctxtnav(req, _('Upcoming Events'), req.href.event("upcoming")) if req.method == "POST": if req.args.has_key("delete"): req.perm.require("EVENTS_DELETE") EventRRule.delete(myenv, event.e_id) EventRDate.delete(myenv, event.e_id) Event.delete(myenv, event.e_id) db = myenv.get_db_cnx() cursor = db.cursor() # TODO: that is hackish. find out how to use trac arg formatting # of "select * from foo where bar like 'baz%';" like = "delete from wiki where name like 'events/%s/%%'" % int(event.e_id) cursor.execute(like) db.commit() req.session["deleted"] = True req.redirect(req.href.event("new")) is_valid = True event.name = req.args.get("name", None) try: event.old_time_begin = event.time_begin event.time_begin = datetime_parse("%s %s" % (req.args["date_begin"], req.args["time_begin"]), selected_tz) print "time_begin", event.time_begin, req.args["time_begin"] except Exception: add_warning(req, _("Wrong format in 'Date begin'.")) is_valid = False try: event.old_time_end = event.time_end event.time_end = datetime_parse("%s %s" % (req.args["date_end"], req.args["time_end"]), selected_tz) print "time_end", event.time_end, req.args["time_end"] except Exception, e: add_warning(req, _("Wrong format in 'Date end'.")) is_valid = False try: event.location_id = int(req.args["location_id"]) location = ItemLocation.fetch_one(myenv, event.location_id) except Exception, e: add_warning(req, _("Could not find location")) is_valid = False tags = req.args.get("tags", None) if tags: event.tags = sub("[,;.:]", " ", tags) attendees = req.args.get("attendees", None) if attendees: attendees = sub("[,;.:]", " ", attendees) if is_valid: if not req.args.has_key("e_id"): req.perm.require("EVENTS_ADD") try: self.__validate_event(event) event.commit() except Exception, e: add_warning(req, str(e)) raise event.wikipage = "events/%d" % event.e_id event.update() wikicontent = u" = %s =\n[[EventHeader(%d)]]\n\n%s" % (event.name, event.e_id, self.config.get("event", "wiki_content")) try: gen_wiki_page(myenv, req.authname, event.wikipage, wikicontent, req.remote_addr) except IntegrityError, e: add_warning(req, _("Wikipage already exists.")) if req.args.has_key("show_recurrency"): req.redirect(req.href.event("recurrency", event.e_id)) req.session["event_added"] = True req.session.save() if req.args.has_key("show_recurrency"): req.session["added"] = True req.session.save() req.redirect(req.href.event("edit", event.e_id)) else: req.perm.require("EVENTS_MODIFY") try: self.__validate_event(event) except Exception, e: add_warning(req, str(e)) raise else: event.time_modified = utc.localize(datetime.utcnow()) event.update() req.session["edited"] = True req.session.save() req.redirect(req.href.event("edit", event.e_id)) print print "selected_tz", type(selected_tz) print "session_tzname", type(session_tzname) print "vars", selected_tz.__dict__ print "vars", vars(event.time_begin.tzinfo) print "time_begin", event.time_begin.astimezone(selected_tz) print "time_end", event.time_end.astimezone(selected_tz) return "event_edit.html", {"event" : event, "session_tzname" : session_tzname, "selected_tz" : selected_tz, "default_location" : self.config.getint("location", "default_location"), "locations" : ItemLocation.fetch_all(myenv)}, None def __display_events(self, req): '''process add,change,delete actions''' add_stylesheet (req, 'hw/css/event.css') if "EVENTS_ADD" in req.perm: add_ctxtnav(req, _('Add New Event'), req.href.event("new")) add_ctxtnav(req, _('Upcoming Events'), req.href.event("upcoming")) if req.args.has_key("e_id"): events = [Event.fetch_one(self.env, req.args["e_id"], show_next=True)] title = _("Event Details") else: events = Event.fetch_all_with_rrule(self.env) events = sorted(events, key=attrgetter("time_begin")) title = _("Event Overview") session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None)) return "event_list.html", {"events" : events, "title" : title, "session_tzname" : session_tzname, "selected_tz" : selected_tz}, None def __display_upcoming_events(self, req): if req.args.get("format") == 'ical': events = Event.fetch_as_ical(self.env) Mimeview(self.env).send_converted(req, 'tracrendezvous.Event', events, "ical", _("CTDO: Upcoming Events")) add_stylesheet (req, 'hw/css/event.css') if req.locale is not None: add_script(req, 'parent/tracrendezvous/%s.js' % req.locale) if "EVENTS_ADD" in req.perm: add_ctxtnav(req, _('Add New Event'), req.href.event("new")) add_ctxtnav(req, _('Events Overview'), req.href.event()) for conversion in Mimeview(self.env).get_supported_conversions("tracrendezvous.Event"): conversion_href = req.href.event("upcoming", format=conversion[0]) add_link(req, 'alternate', conversion_href, conversion[1], conversion[3], conversion[0]) session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None)) n = datetime.now(utc) n = n.replace(hour=0, minute=0, second=0, microsecond=0) end = n + timedelta(183) end = end.replace(hour=23, minute=0, second=59, microsecond=999) table, headers = self.__get_upcoming_table(n, end) #ical_file = file("/home/hotshelf/icalout.ics") #ical = parse_ical(self.env, ical_file) return "events.html", {"table" : table, "headers" : headers, "session_tzname" : session_tzname, "format" : "%a, %d.%m.%Y", "selected_tz" : selected_tz, "title" : _("Upcoming Events for"), "title2" : "%s - %s" % (n.strftime('%A, %d.%m.%Y %H:%M'), end.strftime('%A, %d.%m.%Y %H:%M')), "now" : n, "end" : end}, None def __display_events_by_day(self, req): add_stylesheet (req, 'hw/css/event.css') add_ctxtnav(req, _('Back to Upcoming Events'), req.href.event("upcoming")) session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None)) # do datetime calc only in utc !!! n = title = req.args["arg_date"] n = datetime(n.year, n.month, n.day, tzinfo=utc) #n = local_to_utc(n, selected_tz) e = n + timedelta(1) if "EVENTS_ADD" in req.perm: add_ctxtnav(req, _('Add New Event'), req.href.event("new", title.strftime("%Y-%m-%d"))) table, headers = self.__get_day_table(n, e) return "event_details.html", {"table" : table, "headers" : headers, "format" : "%H:%M", "session_tzname" : session_tzname, "selected_tz" : selected_tz, "title" : _("Details of %s") % (title.strftime('%A, %d.%m.%Y'),), "now" : n, "end" : e}, None def __export_upcoming_events_rss(self, req, foo): now = datetime.now(utc) now.replace(hour=0,second=0,microsecond=0) end = now + timedelta(183) end.replace(hour=23,second=59,microsecond=999) events = Event.fetch_all(self.env) data = {"events" : sorted(events, key=attrgetter("time_begin")), "today" : datetime.now(utc)} output = Chrome(self.env).render_template(req, 'upcoming_events.rss', data, 'application/rss+xml') return output, 'application/rss+xml' def __export_upcoming_events_ical(self, req, events): data = {"stamp" : datetime.now(utc).strftime("%Y%m%dT%H%M%SZ"), "events" : sorted(events, key=attrgetter("time_begin")), "today" : datetime.now(utc), "calname" : self.env.config.get("events", "upcoming_name"), "caldesc" : self.env.config.get("events", "upcoming_desc")} ch = Chrome(self.env) ds = ch.get_all_templates_dirs() data = ch.populate_data(req, data) templates = TemplateLoader(ds, auto_reload=self.env.config.getbool('trac', 'auto_reload'), variable_lookup='lenient') template = templates.load("ical.txt", cls=NewTextTemplate) try: stream = template.generate(**data) output = stream.render() return output, 'text/calendar' except Exception, e: # give some hints when hitting a Genshi unicode error if isinstance(e, UnicodeError): pos = self._stream_location(stream) if pos: location = "'%s', line %s, char %s" % pos else: location = _("(unknown template location)") raise TracError(_("Genshi %(error)s error while rendering " "template %(location)s", error=e.__class__.__name__, location=location)) raise def __validate_event(self, event): if not event.name: raise TypeError(_("EventValidationError: Title is empty")) if event.time_end <= event.time_begin: raise TypeError(_("EventValidationError: end time before or equal begin time")) def __get_upcoming_table(self, timerange_begin, timerange_end): # ongoing events goes in here referenced by column index res = Event.fetch_by_period_dict(self.env, timerange_begin, timerange_end) ongoing = {} # column index -> event table = [] timerange_begin = timerange_begin.date() timerange_end = timerange_end.date() dt = timedelta(1) column_count = 0 def get_next(d): count = 1 while 1: if not d.has_key(count): return count count += 1 while timerange_begin < timerange_end: row = {0 : timerange_begin} done = [] for key,event in ongoing.iteritems(): if event.time_end.date() < timerange_begin: done.append(key) for i in done: del ongoing[i] for key in ongoing.iterkeys(): row[key] = False events = res[timerange_begin] for event in events: dt2 = event.time_end - event.time_begin dt2_days = dt2.days + 1 if dt2.seconds > 43200: dt2_days += 1 slot = get_next(ongoing) event.rowspan = dt2_days ongoing[slot] = event row[slot] = [event,] column_count = max(column_count, len(ongoing)) table.append(row) timerange_begin += dt table2 = [] mbase = [True for x in xrange(column_count+1)] for row in table: myrow = mbase[:] for key,value in row.iteritems(): myrow[key] = value table2.append(myrow) mbase = ["" for x in xrange(column_count+1)] mbase[0] = _("date") return table2, mbase def __create_wiki_page(self, req): '''Programatically create a new wiki page tailored to a given occurence of an event - one date of a recurrence set. ''' req.perm.require("WIKI_CREATE") dt = req.args["arg_date"] e_id = req.args["e_id"] wikipage = "events/%d/%s" % (e_id, dt.strftime("%Y-%m-%d")) event = Event.fetch_one(self.env, e_id) wikicontent = u" = %s =\n[[EventHeader(%d)]]\n\n%s" % (event.name, event.e_id, self.env.config.get("event", "wiki_content", "")) try: gen_wiki_page(self.env, req.authname, wikipage, wikicontent, req.remote_addr) except IntegrityError, e: pass req.redirect(req.href.wiki(wikipage)) def __get_day_table(self, timerange_begin, timerange_end, dt=timedelta(0, 1800)): ''' Calculates and arranges events during a day period which can be defined by start datetime 'timerange_begin' and end datetime 'timerange_end'. The resolution of the "table" prepared for html or other UIs can be controlled by timedelta 'dt'. The first column contains the time slots, additional columns contain destinct locations. It returns a row list of cells.''' class UList(list): def ___init__(self): list.__init__(self) self.rowspan = None events = Event.fetch_by_period_list(self.env, timerange_begin, timerange_end) if not events: return None, None timerange_begin = min(events, key=lambda x:x.time_begin).time_begin timerange_end = max(events, key=lambda x:x.time_end).time_end # now we calcutate the value of previous "slot" beginning as datetime to beautify # the slot time beginnings. Try to uncomment the next few lines and reload to see # what happens to the table without;-) dts = dt.days * 86400 + dt.seconds tbs = ((timerange_begin.hour*3600+timerange_begin.minute*60)/dts)*dts minutes, seconds = divmod(tbs, 60) hours, minutes = divmod(minutes, 60) timerange_begin = timerange_begin.replace(hour=hours, minute=minutes) #timerange_end += dt # we want one slot after the last event ends column_count = 1 # column 0 is always timerange_begin locmap = dict() # location_id -> column index ongoing = dict() # column index of actual used column -> eventlist locs = dict() # column index -> location name slotcount = 0 table = [] done = [] while timerange_begin < timerange_end: row = {0 : timerange_begin} for k,e in done: events.remove(e) done = [] slot_end = timerange_begin + dt for event in events: if event.time_begin >= timerange_begin and event.time_begin < slot_end: if not locmap.has_key(event.location_id): index = column_count column_count += 1 locmap[event.location_id] = index locs[event.location_id] = event.location.name else: index = locmap[event.location_id] if not hasattr(event, "rowspan_begin"): # event starts in that timeslot event.rowspan_begin = slotcount if not row.has_key(index): row[index] = UList() if ongoing.has_key(index): ongoing[index][0]+=1 refc, event_list = ongoing[index] else: event_list = row[index] ongoing[index] = [1, event_list] event_list.append(event) if event.time_end <= slot_end: # event ends index = locmap[event.location_id] done.append((locmap[event.location_id], event)) event.rowspan_end = slotcount+1 refc, el = ongoing[index] if refc<=1: del ongoing[index] else: refc-=1 ongoing[index] = [refc, el] if hasattr(event, "rowspan_begin") and event.time_begin < timerange_begin and event.time_end > timerange_begin: # marking cell as occupied due to longer lasting event than time slot length index = locmap[event.location_id] row[index] = False table.append(row) timerange_begin += dt slotcount += 1 final_table = [] header = [True for x in xrange(column_count)] for row in table: trow = header[:] for index, item in row.iteritems(): trow[index] = item if item and isinstance(item, UList): rowspan_begin = min(item, key=lambda x:x.rowspan_begin).rowspan_begin rowspan_end = max(item, key=lambda x:x.rowspan_end).rowspan_end item.rowspan = rowspan_end - rowspan_begin final_table.append(trow) for location_id, index in locmap.iteritems(): header[index] = locs[location_id] header[0] = _("Start") return final_table, header def _format_event_link(self, formatter, ns, target, label, fullmatch=None): link, params, fragment = formatter.split_link(target) r = Ranges(link) if len(r) == 1: num = r.a validate_id(num) cursor = formatter.db.cursor() cursor.execute("SELECT name,time_begin,time_end " "FROM events WHERE e_id=%s", (num,)) session_tzname, selected_tz = get_tz(formatter.req.session.get("tz", self.env.config.get("trac", "default_timezone") or None)) for name, time_begin, time_end in cursor: time_begin = utc.localize(datetime.fromtimestamp(time_begin, utc)).astimezone(selected_tz) time_end = utc.localize(datetime.datetime.fromtimestamp(time_end, utc)).astimezone(selected_tz) title = "%s (%s - %s %s)" % (name, time_begin.strftime('%d.%m.%Y %H:%M'), time_end.strftime('%d.%m.%Y %H:%M'), time_begin.tzinfo.tzname(None)) if label == link: label = title href = formatter.href.event(num) return tag.a(label, title=title, href=href) return tag.a(label, class_='missing event')