ctdo-trac/TracRendezVous/tracrendezvous/event/web_ui.py

1000 lines
45 KiB
Python
Raw Normal View History

2012-03-31 15:45:24 +00:00
# -*- 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, local_to_utc
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 = datetime.now(utc)
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 = datetime(int(year), int(month), int(day), 17, tzinfo=utc)
except ValueError:
new_date = date_now
event = Event(myenv, 0, "", req.authname, date_now, date_now, new_date, 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)
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)
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 = datetime.now(utc)
event.update()
req.session["edited"] = True
req.session.save()
req.redirect(req.href.event("edit", event.e_id))
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 = selected_tz.fromutc(datetime.fromtimestamp(time_begin, utc))
time_end = selected_tz.fromutc(datetime.fromtimestamp(time_end, utc))
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')