1077 lines
50 KiB
Python
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.fromutc(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")
|