# -*- coding: utf-8 -*- from datetime import datetime, timedelta 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.core import Component, implements, TracError from trac.perm import PermissionError from trac.util import get_reporter_id from trac.util.text import to_unicode from trac.util.datefmt import utc, get_timezone, localtz, timezone from trac.util.html import html from trac.util.translation import _ from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet, add_warning, add_notice, add_ctxtnav, add_script from trac.web import IRequestHandler from genshi.builder import tag from tracrendezvous.location.loc_xml import * from tracrendezvous.rendezvous import api from tracrendezvous.rendezvous.model import * from tracrendezvous.location.utils import * from tracrendezvous.location.model import ItemLocation from ctdotools.utils import * from tracrendezvous.rendezvous.utils import * __all__ = ['RendezVousModule',] class RendezVousModule(Component): '''The web ui frontend for the rendezvous system''' implements(INavigationContributor, IRequestHandler, ITemplateProvider) IntOption("rendezvous", "default_location", 1, doc=u"used for rendezvous' location") Option("rendezvous", "default_status", u"open") IntOption("rendezvous", "default_rendezvous_type", 1, doc=u"default RendezVous type") BoolOption("rendezvous", "show_location_map", True, doc="if set to True displays an openmaps iframe in the rendezvous properties") BoolOption("rendezvous", "show_vote_graph", False, doc=u"if set to True displays a vote graph for every date") # INavigationContributor methods def get_active_navigation_item(self, req): return 'rendezvous' def get_navigation_items(self, req): if "RENDEZVOUS_VIEW" in req.perm: yield ('mainnav', 'rendezvous', html.A('RendezVous', href=req.href.rendezvouses())) def match_request(self, req): key = req.path_info if key == u"/rendezvouses": return True m = match(r'/rendezvous/(\d+)$', key) if m: req.args['rendezvous_id'] = int(m.group(1)) return True m = match(r'/quickvote/(\d+)$', key) if m: req.args['date_id'] = int(m.group(1)) return True m = match(r'/vote/(\d+)$', key) if m: req.args['date_id'] = int(m.group(1)) return True m = match(r'/rendezvous/(\d+)/comment/(\d+)$', key) if m: req.args['rendezvous_id'] = int(m.group(1)) req.args['comment_id'] = int(m.group(2)) return True if key == u"/newrendezvous": return True m = match(r"/location(/(\d+))?$", key) if m: if m.groups(1) != None: req.args['rendezvous_id'] = int(m.group(2)) return True m = match(r'/editrendezvous/(\d+)$', key) if m: req.args["rendezvous_id"] = int(m.group(1)) return True m = match(r'/date/(\d+)$', key) if m: req.args["rendezvous_id"] = int(m.group(1)) return True return False def process_request(self, req): query = req.path_info if "/rendezvouses" in query: return self._display_overview(req) elif "/rendezvous" in query: return self._display_rendezvous(req) elif "/quickvote" in query: return self._process_quickvote(req) elif "/vote" in query: return self._process_vote(req) elif "/date" in query: return self._process_date(req) elif "/comment" in query: return self._process_rendezvous(req) elif "/newrendezvous" in query: return self._process_rendezvous(req) elif "/location" in query: return self._process_location(req) elif "/editrendezvous" in query: return self._process_rendezvous(req) raise TracError("unknown request") # ITemplateProvider methods # Used to add the plugin's templates and htdocs 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'))] def _process_comment(self, req): '''display an overview of active rendezvous or details about one if requested''' req.perm.require("RENDEZVOUS_COMMENT_VIEW") add_stylesheet (req, 'hw/css/rendezvous.css') comment_id = int(req.args["comment_id"]) comment = RendezVousComment.fetch_one(self.env, comment_id) data = {"comment" : comment} add_ctxtnav(req, "Back to RendezVous # '%s'" % comment.rendezvous_id, req.href.rendezvous(comment.rendezvous_id)) if not comment: raise ValueError("RendezVousComment not found") if req.method == "POST" and \ req.args.has_key("comment"): req.perm.require('RENDEZVOUS_COMMENT_ADD') comment.comment = to_unicode(req.args["comment"]) if not req.args.has_key("preview"): comment.update() req.redirect(req.href.comment(comment_id)) else: data["preview"] = True return 'comment.html', data, None def _process_date(self, req): '''process add,change,delete actions for dates''' req.perm.require("RENDEZVOUS_DATE_VIEW") add_stylesheet (req, 'hw/css/rendezvous.css') add_stylesheet (req, 'hw/css/ui.all.css') add_script (req, 'hw/scripts/jquery-ui-1.6.custom.min.js') rendezvous_id = int(req.args["rendezvous_id"]) validate_id(rendezvous_id) now = datetime.now(utc) rd = RendezVousDate(self.env, 0, rendezvous_id, req.authname, req.args.get("email", None), datetime.now(utc), now, now + timedelta(seconds=7200), False, False) session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None)) if req.session.has_key("notice"): add_notice(req, req.session["notice"]) del req.session["notice"] req.session.save() data = {'settings': {'session': req.session, 'session_id': req.session.sid}, 'localtz': localtz, "new_date" : rd, "selected_tz" : selected_tz, "session_tzname" : session_tzname} del now ren = RendezVous.fetch_one(self.env, rendezvous_id, fetch_dates=True) if not ren: add_warning(req, tag.span("RendezVous not found. You can create a new RendezVous ", tag.a("here", href=req.href.newrendezvous()))) return 'rendezvous.html', data, None data["rendezvous"] = ren data["dates"] = RendezVousDate.fetch_by_rendezvous(self.env, rendezvous_id) updated_dates = set() add_ctxtnav(req, 'Back to Overview', req.href.rendezvouses()) add_ctxtnav(req, 'Back to RendezVous #%d'% rendezvous_id, req.href.rendezvous(rendezvous_id)) add_ctxtnav(req, 'Edit RendezVous #%d'% rendezvous_id, req.href.editrendezvous(rendezvous_id)) if req.method == "POST": # perhaps we can permit certain changes e.g time_*. # Tell me your point of view on that subject as ticket if ren.elected: add_warning(req, "RendezVous is already scheduled. No changes allowed!") return 'date.html', data, None if req.args.has_key("newdate") and \ req.args.has_key("date_begin") and \ req.args.has_key("time_begin") and \ req.args.has_key("date_end") and \ req.args.has_key("time_end"): req.perm.require('RENDEZVOUS_DATE_ADD') is_valid = True try: rd.time_begin = datetime_parse("%s %s" % (req.args["date_begin"], req.args["time_begin"]), selected_tz) except Exception, e: add_warning(req, str(e)) is_valid = False try: rd.time_end = datetime_parse("%s %s" % (req.args["date_end"], req.args["time_end"]) , selected_tz) except Exception, e: add_warning(req, str(e)) is_valid = False try: self._validate_date(rd) except Exception, e: add_warning(req, str(e)) is_valid = False if RendezVousDate.exists(self.env, rd.time_begin, rd.time_end): add_warning(req, "RendezVousDate already exists") is_valid = False try: check_date_collision(rd, data["dates"]) except Exception, e: add_warning(req, "RendezVousDate collides with other dates") is_valid = False if is_valid: rd.commit() if req.args.has_key("autovoting"): self._do_quickvote(req, rd.date_id) req.session["notice"] = "new RendezVousDate saved successfully" req.redirect(req.href.date(rendezvous_id)) elif req.args.has_key("savedates"): req.perm.require("RENDEZVOUS_DATE_MODIFY") dates = {} for i in ren.dates: i.changes = {} dates[i.date_id] = i deleted = set() changed = set() deleted_dates = req.args.get("delete", []) if isinstance(deleted_dates, basestring): deleted_dates = [deleted_dates,] for date_id in deleted_dates: did = int(date_id) RendezVousDate.delete(self.env, did) try: del dates[did] deleted.add(did) except Exception: pass del deleted_dates for key in req.args: kind = date_id = None try: kind, date_id = key.split(":", 1) except ValueError: continue date_id = int(date_id) try: validate_id(date_id) except Exception, e: add_warning(str(e)) continue if date_id in deleted: continue if not dates.has_key(date_id): add_warning(req, "could not find RendezVousDate with date_id '%d" % date_id) continue vd = dates[date_id] if vd.author != req.authname: req.perm.require("RENDEZVOUS_ADMIN") if kind == "email": email = unicode(req.args[key]) if email != vd.email: vd.email = email changed.add(date_id) elif kind == "time_begin": try: tmp = time_parse(req.args[key]) tmp = local_to_utc(vd.time_begin.replace(hour=tmp.hour, minute=tmp.minute), selected_tz) except Exception, e: add_warning(req, str(e)) continue else: if vd.time_begin != tmp: ch = vd.changes.get("time_begin", dict()) if ch: ch["new"].update({"old" : vd.time_begin.time(), "new" : tmp}) else: ch.update({"old" : vd.time_begin.time(), "new" : tmp}) vd.time_begin = tmp changed.add(date_id) elif kind == "time_end": try: tmp = time_parse(req.args[key]) tmp = local_to_utc(vd.time_end.replace(hour=tmp.hour, minute=tmp.minute), selected_tz) except ValueError, e: add_warning(req, str(e)) continue else: if vd.time_end != tmp: ch = vd.changes.get("time_end", dict()) if ch: ch.update({"old" : vd.time_end, "new" : tmp}) else: ch["new"] = tmp vd.time_end = tmp changed.add(date_id) elif kind == "date_begin": try: tmp = date_parse(req.args[key]) tmp = vd.time_begin.replace(year=tmp.year, month=tmp.month, day=tmp.day) except Exception, e: add_warning(req, str(e)) continue else: if vd.time_begin != tmp: ch = vd.changes.get("time_begin", dict()) if ch: ch["new"] = tmp else: ch.update({"old" : vd.time_begin, "new" : tmp}) vd.time_begin = tmp changed.add(date_id) elif kind == "date_end": try: tmp = date_parse(req.args[key]) tmp = vd.time_end.replace(year=tmp.year, month=tmp.month, day=tmp.day) except Exception, e: add_warning(req, str(e)) continue else: if vd.time_end != tmp: ch = vd.changes.get("time_end", dict()) if ch: ch["new"] = tmp else: ch.update({"old" : vd.time_end, "new" : tmp}) vd.time_end = tmp changed.add(date_id) path = join(self.env.path, "htdocs", "rendezvous_graphs") sizex = self.config.getint("rendezvous", "graph_size_x") sizey = self.config.getint("rendezvous", "graph_size_y") for date_id in changed: if date_id in deleted: continue try: d = dates[date_id] check_date_collision(d, dates.values()) except Exception, e: add_warning(req, "RendezVousDate collides with other dates") continue try: d = dates[date_id] self._validate_date(d) if RendezVousDate.exists(self.env, dates[date_id].time_begin, dates[date_id].time_end): raise TracError("RendezVousDate already exists") d.update() real_tz = selected_tz.normalize(selected_tz.astimezone(d.time_begin)).tzinfo update_votes_graph("", d.votes, path, [sizex, sizey], real_tz) except Exception, err: add_warning(req, str(err)) continue req.session["notice"] = "RendezVousDates updated successfully." req.redirect(req.href.date(rendezvous_id)) data["dates"] = RendezVousDate.fetch_by_rendezvous(self.env, rendezvous_id) return 'date.html', data, None def _process_location(self, req): '''process add,change,delete actions''' add_stylesheet (req, 'hw/css/rendezvous.css') data = {"results": []} default_location = self.config.getint("rendezvous", "default_location") try: rendezvous_id = req.args.get("rendezvous_id", None) except Exception: req.redirect(req.href.rendezvouses()) if rendezvous_id: add_ctxtnav(req, "Back to RendezVous #%s" % rendezvous_id, req.href.rendezvous(rendezvous_id)) else: add_ctxtnav(req, "New RendezVous", req.href.newrendezvous()) if req.method == "GET": # edit existing RendezVous req.perm.require("RENDEZVOUS_LOCATION_VIEW") data.update({}) elif req.method == "POST": if req.args.has_key("location_search"): query = unicode(req.args["location_search"]) results = search_location(query) data["location_results"] = results if req.args.has_key("savelocations"): req.perm.require("RENDEZVOUS_LOCATION_MODIFY") deleted = [] locations = {} changed = {} if req.args.has_key("default"): default = int(req.args["default"]) if default_location != default: default_location = default self.config.set("rendezvous", "default_location", default) self.config.save() for key in req.args: kind = location_id = None try: kind, location_id = key.split(":", 1) except ValueError: continue location_id = int(location_id) if location_id in deleted: continue if not locations.has_key(location_id): location = ItemLocation.fetch_one(self.env, location_id) if not location: add_warning(req, "could not find location with location id '%d'" % location_id) continue locations[location_id] = location if kind == "delete": req.perm.require("RENDEZVOUS_LOCATION_DELETE") locations[location_id].delete() deleted.append(location_id) del locations[location_id] elif kind == "name": name = req.args[key] if not name: add_warning(req, "location name must be specified for location '%d'" % location_id) continue if name != locations[location_id].name: locations[location_id].name = name changed[location_id] = True elif kind == "location": coordinates = req.args[key] if coordinates: try: lat, lon = validate_dd_coordinates(coordinates) lat_side, lat_deg, lat_min, lat_sec, lon_side, lon_deg, lon_min, lon_sec = convert_dd_2_dms(lat, lon) except ValueError: try: lat_side, lat_deg, lat_min, lat_sec, lon_side, lon_deg, lon_min, lon_sec = validate_dms_coordinates(coordinates) lat, lon = convert_dms_2_dd(lat_side, lat_deg, lat_min, lat_sec, lon_side, lon_deg, lon_min, lon_sec) except ValueError: add_warning(req, "coordinates have wrong format") continue if lat != location.lat: location.lat = lat changed[location_id] = True if lon != location.lon: location.lon = lon changed[location_id] = True if lat_side != location.lat_side: location.lat_side = lat_side changed[location_id] = True if lat_deg != location.lat_deg: location.lat_deg = lat_deg changed[location_id] = True if lat_min != location.lat_min: location.lat_min = lat_min changed[location_id] = True if lat_sec != location.lat_sec: location.lat_sec= lat_sec changed[location_id] = True if lon_side != location.lon_side: location.lon_side = lon_side changed[location_id] = True if lon_deg != location.lon_deg: location.lon_deg = lon_deg changed[location_id] = True if lon_min != location.lon_min: location.lon_min = lon_min changed[location_id] = True if lon_sec != location.lon_sec: location.lon_sec = lon_sec changed[location_id] = True for dvi in changed: if dvi not in deleted: try: locations[dvi].update() except Exception, err: add_warning(req, str(err)) continue if req.args.has_key("addlocation") and \ req.args.has_key("location_name"): req.perm.require("RENDEZVOUS_LOCATION_ADD") rl = ItemLocation(self.env, name=req.args["location_name"]) is_valid = True if not rl.name: add_warning(req, "Coordinate name is empty") is_valid = False if req.args.has_key("coordinates"): coordinates = unicode(req.args["coordinates"]) if coordinates: try: rl.lat, rl.lon = validate_dd_coordinates(coordinates) rl.lat_side, rl.lat_deg, rl.lat_min, rl.lat_sec, rl.lon_side, rl.lon_deg, rl.lon_min, rl.lon_sec = convert_dd_2_dms(rl.lat, rl.lon) except ValueError: try: rl.lat_side, rl.lat_deg, rl.lat_min, rl.lat_sec, rl.lon_side, rl.lon_deg, rl.lon_min, rl.lon_sec = validate_dms_coordinates(coordinates) rl.lat, rl.lon = convert_dms_2_dd(rl.lat_side, rl.lat_deg, rl.lat_min, rl.lat_sec, rl.lon_side, rl.lon_deg, rl.lon_min, rl.lon_sec) except ValueError: add_warning(req, "coordinates have wrong format") is_valid = False if is_valid: rl.commit() data.update({"results" : None, "default_location" : default_location, "rendezvous_id" : rendezvous_id, "locations" : ItemLocation.fetch_all(self.env)}) return 'location.html', data, None def _process_rendezvous(self, req): '''process add, change and delete rendezvous''' add_ctxtnav(req, 'Back to Overview', req.href.rendezvouses()) data = {"results": None} add_stylesheet (req, 'hw/css/rendezvous.css') add_stylesheet (req, 'hw/css/ui.all.css') add_script (req, 'hw/scripts/jquery-ui-1.6.custom.min.js') rtype = self.config.getint("rendezvous", "default_rendezvous_type") rendezvous = None rendezvous_id = int(req.args.get("rendezvous_id", 0)) if req.session.has_key("notice"): add_notice(req, req.session["notice"]) del req.session["notice"] req.session.save() if rendezvous_id != 0: rendezvous = RendezVous.fetch_one(self.env, rendezvous_id, fetch_dates=True) if not rendezvous: raise TracError("rendezvous not found") add_ctxtnav(req, 'Back to RendezVous # %s' % rendezvous_id, req.href.rendezvous(rendezvous_id)) add_ctxtnav(req, 'Edit dates for RendezVous # %s' % rendezvous_id, req.href.date(rendezvous_id)) add_ctxtnav(req, 'Edit votes for RendezVous # %s' % rendezvous_id, req.href.vote(rendezvous_id)) data["title"] = "RendezVous #%d" % rendezvous.rendezvous_id else: rendezvous = RendezVous(self.env, False, 0, u"", u"", u"", u"", datetime.now(utc), datetime.now(utc) + timedelta(1), 0, rtype, "new", self.config.getint("rendezvous", "default_location"), False, u"") data["title"] = "New RendezVous" actions = api.RendezVousSystem(self.env).get_available_actions( req, rendezvous) new_status = req.args.get("action", None) if req.method == "GET": req.perm.require("RENDEZVOUS_MODIFY") if req.method == "POST": is_valid = True if req.args.has_key("add"): req.perm.require("RENDEZVOUS_ADD") if req.args.has_key("delete") and rendezvous: req.perm.require("RENDEZVOUS_DELETE") RendezVous.delete(self.env, rendezvous_id) req.redirect(req.href.rendezvouses()) elif req.args.has_key("edit"): req.perm.require("RENDEZVOUS_MODIFY") if req.authname != rendezvous.author: req.perm.require("RENDEZVOUS_ADMIN") if new_status: for controller in self._get_action_controllers(req, rendezvous, new_status): controller.change_rendezvous_workflow(req, rendezvous, new_status) new_status = None type_name = req.args.get("type_name", False) location_text = req.args.get("location", False) name = req.args.get("name", False) email = req.args.get("email", False) description = req.args.get("description", False) tags = req.args.get("tags", False) try: rendezvous.schedule_deadline = datetime_parse("%s %s" % (req.args["schedule_deadline_date"], req.args["schedule_deadline_time"]), tzinfo=utc) except Exception, e: is_valid = False add_warning(req, "schedule_deadline has wrong format/type" + str(e)) if name: rendezvous.name = name if email: rendezvous.email = email if description: rendezvous.description = description if tags: rendezvous.tags = sub("[,;.:]", " ", tags) if type_name: rt = RendezVousType.fetch_one(self.env, name=type_name) if rt: rendezvous.type_id = rt.type_id if location_text: try: name,coords = location_text.split(" :", 1) loc = ItemLocation.search_one(self.env, name=name) if loc: rendezvous.location_id = loc.location_id except ValueError: pass try: self._validate_rendezvous(rendezvous) except Exception, err: add_warning(req, str(err)) else: if req.args.has_key("add") and is_valid: rendezvous.author = req.args.get("author", req.authname) rendezvous.commit() req.redirect(req.href.date(rendezvous.rendezvous_id)) add_notice(req, "Added rendezvous!") if req.args.has_key("edit") and is_valid: rendezvous.update() action_controls = [] sorted_actions = api.RendezVousSystem(self.env).get_available_actions(req, rendezvous) if not new_status: new_status = sorted_actions[0] for saction in sorted_actions: first_label = None hints = [] widgets = [] for controller in self._get_action_controllers(req, rendezvous, saction): label, widget, hint = controller.render_rendezvous_action_control( req, rendezvous, saction) if not first_label: first_label = label widgets.append(widget) hints.append(hint) action_controls.append((saction, first_label, tag(widgets), hints)) session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None)) data.update({"action_controls" : action_controls, "cstatus" : new_status, "types" : RendezVousType.fetch_all(self.env), 'settings': {'session': req.session, 'session_id': req.session.sid}, "selected_tz" : selected_tz, "session_tzname" : session_tzname, "default_rendezvous_type" : self.config.getint("rendezvous", "default_rendezvous_type"), "default_location" : self.config.getint("rendezvous", "default_location"), "locations" : ItemLocation.fetch_all(self.env), "rendezvous": rendezvous}) return 'rendezvous_edit.html', data, None def _process_quickvote(self, req, redirecting=True): date_id = int(req.args.get("date_id")) a,b = self._do_quickvote(req, date_id) if a: req.redirect(req.href("rendezvous", b)) def _process_vote(self, req): '''display and process create,view,change and delete actions to vote''' add_stylesheet (req, 'hw/css/rendezvous.css') add_stylesheet (req, 'hw/css/ui.all.css') add_script (req, 'hw/scripts/jquery-ui-1.6.custom.min.js') session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None)) data = {'settings': {'session': req.session, 'session_id': req.session.sid}, "session_tzname" : session_tzname, "selected_tz" : selected_tz} date_id = int(req.args.get("date_id")) validate_id(date_id) rdate = RendezVousDate.fetch_one(self.env, date_id) if not rdate: raise TracError("RendezVousDate not found") if req.session.has_key("notice"): add_notice(req, req.session["notice"]) del req.session["notice"] req.session.save() update_graph=False if req.method == "POST": if req.args.has_key("add"): user = to_unicode(req.args.get("user")) email = to_unicode(req.args.get("email")) req.perm.require("RENDEZVOUS_VOTE_ADD") dv = RendezVousVote(self.env, 1, date_id, user, email, datetime.now(utc), rdate.time_begin, rdate.time_end) try: rd.time_begin = datetime_parse("%s %s" % (req.args["date_begin"], req.args["time_begin"]), selected_tz) except Exception, e: add_warning(req, str(e)) is_valid = False try: rd.time_end = datetime_parse("%s %s" % (req.args["date_end"], req.args["time_end"]) , selected_tz) except Exception, e: add_warning(req, str(e)) is_valid = False if not RendezVousVote.exists(self.env, dv.date_id, dv.user, dv.time_begin, dv.time_end): try: self._validate_vote(dv) check_vote_collision(dv, RendezVousVote.fetch_by_date(self.env, date_id, user)) except Exception, err: add_warning(req, str(err)) else: dv.commit() req.session["notice"] = "Vote added successfully" update_graph = True else: add_warning(req, "vote already exists") elif req.args.has_key("savevotes"): req.perm.require("RENDEZVOUS_VOTE_MODIFY") votes = {} tmp = RendezVousVote.fetch_by_date(self.env, date_id) for i in tmp: votes[i.vote_id] = i del tmp deleted = set() changed = set() deleted_votes = req.args.get("delete", []) if isinstance(deleted_votes, basestring): deleted_votes = [deleted_votes,] for vote_id in deleted_votes: RendezVousVote.delete(self.env, int(vote_id)) try: del votes[vote_id] except Exception: pass del deleted_votes for key in req.args: kind = vote_id = None try: kind, vote_id = key.split(":", 1) except: continue vote_id = int(vote_id) try: validate_id(vote_id) except Exception, e: add_warning(str(e)) continue if vote_id in deleted: continue if not votes.has_key(vote_id): add_warning(req, "could not find RendezVousVote") continue vd = votes[vote_id] if vd.user != req.authname: req.perm.require("RENDEZVOUS_ADMIN") if kind == "delete": vd.delete() deleted.add(vote_id) del votes[vote_id] update_graph = True elif kind == "email": email = req.args[key] if email != vd.email: vd.email = email changed.add(vote_id) elif kind == "time_begin": try: tmp = time_parse(req.args[key]) except ValueError, e: add_warning(req, str(e)) continue else: if vd.time_begin != tmp: vd.time_begin = vd.time_begin.replace(hour=tmp.hour, minute=tmp.minute) changed.add(vote_id) elif kind == "time_end": try: tmp = time_parse(req.args[key]) except ValueError, e: add_warning(req, str(e)) continue else: if vd.time_end != tmp: vd.time_end = vd.time_end.replace(hour=tmp.hour, minute=tmp.minute) changed.add(vote_id) elif kind == "date_begin": try: tmp = date_parse(req.args[key]) except ValueError, e: add_warning(req, str(e)) continue else: if vd.time_begin != tmp: vd.time_begin = vd.time_begin.replace(year=tmp.year, month=tmp.month, day=tmp.day) changed.add(vote_id) elif kind == "date_end": try: tmp = date_parse(req.args[key]) except ValueError, e: add_warning(req, str(e)) continue else: if vd.time_end != tmp: vd.time_end = vd.time_end.replace(year=tmp.year, month=tmp.month, day=tmp.day) changed.add(vote_id) for dvi in changed: if dvi not in deleted: try: vd = votes[dvi] self._validate_vote(vd) check_vote_collision(vd, RendezVousVote.fetch_by_date(self.env, vd.date_id, vd.user)) vd.update() except Exception, err: add_warning(req, str(err)) continue votes = None if "RENDEZVOUS_VOTE_VIEW_OTHERS" not in req.perm: votes = RendezVousVote.fetch_by_date(self.env, date_id, req.authname) else: votes = RendezVousVote.fetch_by_date(self.env, date_id) data["rdate"] = rdate data["votes"] = votes add_ctxtnav(req, 'Back to Overview', req.href.rendezvouses()) add_ctxtnav(req, 'Back to RendezVous # %d' % rdate.rendezvous_id, href=req.href.rendezvous(rdate.rendezvous_id)) if update_graph: path = join(self.env.path, "htdocs", "rendezvous_graphs") size = [self.config.getint("rendezvous", "graph_size_x"), self.config.getint("rendezvous", "graph_size_y")] update_votes_graph("%s - %s" % (rdate.time_begin.strftime('%Y.%m.%d %H:%M'), rdate.time_end.strftime('%Y.%m.%d %H:%M')), votes, path, size, get_timezone(req.session.get('tz'))) if req.session.has_key("notice"): req.redirect(req.href.vote(dv.vote_id)) return 'vote.html', data, None def _display_rendezvous(self, req): '''display an overview of active rendezvous or details about one if requested''' req.perm.require("RENDEZVOUS_VIEW") add_stylesheet(req, 'hw/css/rendezvous.css') uperm = RendezVousTypePermissionSystem(self.env) r_list = RendezVous.fetch_all(self.env, True) api.RendezVousSystem(self.env).workflow_controller.process_expired(r_list) session_tzname, selected_tz = get_tz(req.session.get('tz', self.env.config.get("trac", "default_timezone") or None)) data = {"show_vote_graph" : self.config.getbool("rendezvous", "show_vote_graph"), "show_location_map" : self.config.getbool("rendezvous", "show_location_map"), "voted" : False, 'settings': {'session': req.session, 'session_id': req.session.sid}, "session_tzname" : session_tzname, "selected_tz" : selected_tz} rendezvous_id = int(req.args["rendezvous_id"]) rendezvous = RendezVous.fetch_one(self.env, rendezvous_id, fetch_dates=True) if not rendezvous: add_warning(req, tag.span("RendezVous not found. You can create a new RendezVous ", tag.a("here", href=req.href.newrendezvous()))) return 'rendezvous.html', data, None if not uperm.check_user_type_permissions(req.authname, rendezvous.type_id): raise PermissionError() add_ctxtnav(req, 'Back to Overview', req.href.rendezvouses()) if rendezvous.author == req.authname or 'RENDEZVOUS_ADMIN' in req.perm: add_ctxtnav(req, 'Edit RendezVous #%d'% rendezvous_id, req.href.editrendezvous(rendezvous_id)) if 'RENDEZVOUS_ADD' in req.perm: add_ctxtnav(req, 'New RendezVous', req.href.newrendezvous()) table = self._prepare_rendezvous_table(rendezvous) # Can be None. handle in genshi location = ItemLocation.fetch_one(self.env, rendezvous.location_id) if req.method == "POST" and \ req.args.has_key("comment"): req.perm.require('RENDEZVOUS_COMMENT_ADD') comment = to_unicode(req.args["comment"]) # handle adding totally new comment if not req.args.has_key("comment_id"): # new comment c = RendezVousComment(self.env, 0, rendezvous_id, req.authname, comment, datetime.now(utc)) if not req.args.has_key("preview"): c.commit() req.redirect(req.href.rendezvous(rendezvous_id)) # only previewing new comment data["preview_comment"] = c else: # editing comment_id = int(req.args["comment_id"]) validate_id(comment_id) if req.args.has_key("delete"): RendezVousComment.delete(self.env, comment_id) req.redirect(req.href.rendezvous(rendezvous_id)) c = RendezVousComment.fetch_one(self.env, comment_id) c.comment = comment if not req.args.has_key("preview"): c.update() # saving edited comment req.redirect(req.href.rendezvous(rendezvous_id)) data["preview_comment"] = c else: # preparing editing if req.args.has_key("comment_id"): comment_id = int(req.args["comment_id"]) data["preview_comment"] = RendezVousComment.fetch_one(self.env, comment_id) if "RENDEZVOUS_COMMENT_VIEW" in req.perm: data["comments"] = RendezVousComment.fetch_by_rendezvous(self.env, rendezvous_id) data.update({"location": location, "rendezvous" : rendezvous, "voted" : rendezvous.has_voted(req.authname), "has_votes" : rendezvous.has_votes(), "table" : table}) path = join(self.env.path, "htdocs", "rendezvous_graphs") size = [self.config.getint("rendezvous", "graph_size_x"), self.config.getint("rendezvous", "graph_size_y")] dates = RendezVousDate.fetch_all(self.env) for date in dates: if date.votes: update_votes_graph("", date.votes, path, size, get_timezone(req.session.get('tz'))) #noty = RendezVousSchedulingNotifyEmail(self.env) #noty.notify("hotshelf", rendezvous) return 'rendezvous.html', data, None def _display_overview(self, req): '''displays an overview of active rendezvous or details about one if requested''' req.perm.require("RENDEZVOUS_VIEW") add_stylesheet(req, 'hw/css/rendezvous.css') uperm = RendezVousTypePermissionSystem(self.env) r_list = RendezVous.fetch_all(self.env, True, sort="name") ctl = api.RendezVousSystem(self.env).workflow_controller ctl.process_expired(r_list) if 'RENDEZVOUS_ADD' in req.perm: add_ctxtnav(req, 'New RendezVous', req.href.newrendezvous()) all_rendezvouses = defaultdict(list) if 'RENDEZVOUS_ADMIN' in req.perm: for i in r_list: all_rendezvouses[i.status].append((i.rendezvous_id, i.name)) else: for i in r_list: if uperm.check_user_type_permissions(req.authname, i.type_id): all_rendezvouses[i.status].append((i.rendezvous_id, "%s (%s)" % (i.name, i.status))) data = {"all_rendezvouses" : all_rendezvouses, "icons" : ctl.get_icons(), "show_vote_graph" : self.config.getbool("rendezvous", "show_vote_graph"), "show_location_map" : self.config.getbool("rendezvous", "show_location_map"), "voted" : False} return 'overview.html', data, None def _do_quickvote(self, req, date_id): validate_id(date_id) rdate = RendezVousDate.fetch_one(self.env, date_id) user = req.authname if not rdate: raise TracError("RendezVousDate not found") if not RendezVousVote.exists(self.env, date_id, user, rdate.time_begin, rdate.time_end): dv = RendezVousVote(self.env, 0, date_id, user, "", datetime.now(utc), rdate.time_begin, rdate.time_end) self._validate_vote(dv) check_vote_collision(dv, RendezVousVote.fetch_by_date(self.env, date_id, user)) dv.commit() return True, rdate.rendezvous_id return False, 0 def _get_action_controllers(self, req, rendezvous, action): """Generator yielding the controllers handling the given `action`""" controller = api.RendezVousSystem(self.env).workflow_controller actions = [a for w,a in controller.get_rendezvous_actions(req, rendezvous)] if action in actions: yield controller def _prepare_rendezvous_table(self, rendezvous): users = {} table = [] user_pos=0 def _s(l, r): if l.time_begin < r.time_begin: return -1 elif l.time_begin > r.time_begin: return 1 return 0 dates = sorted(rendezvous.dates, _s) rendezvous.dates = dates for mydate in dates: for vote in mydate.votes: if not users.has_key(vote.user): users[vote.user] = user_pos user_pos += 1 rowCount = len(users) colCount = len(dates)+1 row = [[False, 0, 0] for i in xrange(colCount)] table = [row[:] for i in xrange(rowCount)] # now we want to switch to an row = user, column = date table # count == 0 -> column0 = username dateCount = 1 for i in users: table[users[i]][0] = i for mydate in dates: for i in xrange(user_pos): table[i][dateCount][1] = mydate.date_id votes = mydate.votes if votes: for vote in votes: table[users[vote.user]][dateCount] = [True, mydate.date_id, vote.vote_id] dateCount+=1 return table def _validate_date(self, mydate): if not isinstance(mydate.date_id, int): raise TypeError("validate_date() - date_id has wrong type") if not isinstance(mydate.rendezvous_id, int): raise TypeError("validate_date() - rendezvous_id has wrong type") if not isinstance(mydate.author, unicode): raise TypeError("validate_date() - author has wrong type") if not isinstance(mydate.email, unicode): raise TypeError("validate_date() - email has wrong type") if not isinstance(mydate.time_created, datetime): raise TypeError("validate_date() - time_created has wrong type") if not isinstance(mydate.time_begin, datetime): raise TypeError("validate_date() - time_begin has wrong type") if not isinstance(mydate.time_end, datetime): raise TypeError("validate_date() - time_end has wrong type") validate_id(mydate.date_id) validate_id(mydate.rendezvous_id) def _validate_vote(self, vote): if not isinstance(vote.vote_id, int): raise TypeError("validate_date_vote() vote_id has wrong type") validate_id(vote.vote_id) if not isinstance(vote.date_id, int): raise TypeError("validate_date_vote() date_id has wrong type") validate_id(vote.date_id) if not isinstance(vote.user, unicode): raise TypeError("validate_date_vote() user has wrong type") if not isinstance(vote.email, unicode): raise TypeError("validate_date_vote() email has wrong type") if not isinstance(vote.time_created, datetime): raise TypeError("validate_date_vote() time_created has wrong type") if not isinstance(vote.time_begin, datetime): raise TypeError("validate_date_vote() time_begin has wrong type") if not isinstance(vote.time_end, datetime): raise TypeError("validate_date_vote() time_end has wrong type") if vote.time_begin > vote.time_end: raise ValueError("end datetime before start datetime") def _validate_rendezvous(self, rendezvous): if not isinstance(rendezvous.rendezvous_id, int): raise TypeError("RendezvousValidationError: vote_id has wrong type") if not isinstance(rendezvous.author, unicode): raise TypeError("RendezvousValidationError: user has wrong type") if not isinstance(rendezvous.name, unicode): raise TypeError("RendezvousValidationError: name has wrong type") if not rendezvous.name: raise TypeError("RendezvousValidationError: Title is empty") if not isinstance(rendezvous.email, unicode): raise TypeError("RendezvousValidationError: email has wrong type") if not isinstance(rendezvous.time_created, datetime): raise TypeError("RendezvousValidationError: time_created has wrong type") if not isinstance(rendezvous.description, unicode): raise TypeError("RendezvousValidationError: description has wrong type") if len(rendezvous.description) > self.config.getint("rendezvous", "max_description_length"): raise ValueError("RendezvousValidationError: description is too long") if not isinstance(rendezvous.status, unicode): raise TypeError("RendezvousValidationError: status has wrong type") if not isinstance(rendezvous.type_id, int): raise TypeError("RendezvousValidationError: type_id has wrong type") if not isinstance(rendezvous.location_id, int): raise TypeError("RendezvousValidationError: location_id has wrong type")