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

1077 lines
50 KiB
Python

# -*- 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")