+ if time not in times.per_host:
+ return _('Error, invalid length'), 400
+ title=request.form.get("title", "")
+ title=title.strip()
+ if title =='':
+ return _('Error, missing title'), 400
+ content=request.form.get("content", "")
+ content=content.strip()
+ if content =='':
+ return _('Error, missing content'), 400
+ ratelimit = is_in_ratelimit(cat)
+ if ratelimit is not True:
+ return ratelimit, 400
+
+ db = get_db()
+ with db.xact():
+ t = db.prepare("SELECT CURRENT_TIMESTAMP")()[0][0];
+ s = db.prepare("SELECT MAX(\"identifier\") FROM \"motion\" WHERE \"type\"=$1 AND \"host\"=$2 AND DATE(\"posed\")=DATE(CURRENT_TIMESTAMP)")
+ sr = s(cat, request.host)
+ ident=""
+ if len(sr) == 0 or sr[0][0] is None:
+ ident=prefix.per_host[cat]+"."+t.strftime("%Y%m%d")+".001"
+ else:
+ nextId = int(sr[0][0].split(".")[2])+1
+ if nextId >= 1000:
+ return _('Too many motions for this day'), 500
+ ident=prefix.per_host[cat]+"."+t.strftime("%Y%m%d")+"."+("%03d" % nextId)
+ p = db.prepare("INSERT INTO motion(\"name\", \"content\", \"deadline\", \"posed_by\", \"type\", \"identifier\", \"host\") VALUES($1, $2, CURRENT_TIMESTAMP + $3 * interval '1 days', $4, $5, $6, $7)")
+ p(title, content, time, g.voter, cat, ident, request.host)
+ return rel_redirect("/")
+
+def motion_edited(motion):
+ return rel_redirect("/motion/" + motion)
+
+def validate_motion_access(privilege):
+ def decorator(f):
+ def decorated_function(motion):
+ db = get_db()
+ with db.xact():
+ rv = db.prepare("SELECT id, type, deadline < CURRENT_TIMESTAMP AS expired, canceled FROM motion WHERE identifier=$1 AND host=$2")(motion, request.host);
+ if len(rv) == 0:
+ return _('Error, Not found'), 404
+ id = rv[0].get("id")
+ if not may(privilege, rv[0].get("type")):
+ return _('Forbidden'), 403
+ if rv[0].get("canceled") is not None:
+ return _('Error, motion was canceled'), 403
+ if rv[0].get("expired"):
+ return _('Error, out of time'), 403
+ return f(motion, id)
+ decorated_function.__name__ = f.__name__
+ return decorated_function
+ return decorator
+
+def validate_motion_access_vote(privilege):
+ simple_decorator = validate_motion_access(privilege)
+ def decorator(f):
+ def decorated_function(motion, voter):
+ return simple_decorator(lambda motion, id : f(motion, voter, id))(motion)
+ decorated_function.__name__ = f.__name__
+ return decorated_function
+ return decorator
+
+@app.route("/motion/<string:motion>/cancel", methods=['POST'])
+@validate_motion_access('cancel')
+def cancel_motion(motion, id):
+ if request.form.get("reason", "none") == "none":
+ return _('Error, form requires reason'), 500
+ rv = get_db().prepare("UPDATE motion SET canceled=CURRENT_TIMESTAMP, cancelation_reason=$1, canceled_by=$2 WHERE identifier=$3 AND host=$4 AND canceled is NULL")(request.form.get("reason", ""), g.voter, motion, request.host)
+ return motion_edited(motion)
+
+@app.route("/motion/<string:motion>/finish", methods=['POST'])
+@validate_motion_access('finish')
+def finish_motion(motion, id):
+ rv = get_db().prepare("UPDATE motion SET deadline=CURRENT_TIMESTAMP WHERE identifier=$1 AND host=$2 AND canceled is NULL")(motion, request.host)
+ return motion_edited(motion)
+
+@app.route("/motion/<string:motion>")
+def show_motion(motion):
+ p = get_db().prepare("SELECT motion.*, poser.email AS poser, canceler.email AS canceler, (motion.deadline > CURRENT_TIMESTAMP AND canceled is NULL) AS running, vote.result FROM motion "\
+ + "LEFT JOIN vote on vote.motion_id=motion.id AND vote.voter_id=$2 "\
+ + "LEFT JOIN voter poser ON poser.id = motion.posed_by "\
+ + "LEFT JOIN voter canceler ON canceler.id = motion.canceled_by "
+ + "WHERE motion.identifier=$1 AND motion.host=$3")
+ resultmotion = p(motion, g.voter, request.host)
+ if len(resultmotion) == 0:
+ return _('Error, Not found'), 404
+
+ p = get_db().prepare("SELECT voter.email FROM vote INNER JOIN voter ON vote.proxy_id = voter.id WHERE vote.motion_id=$1 AND vote.voter_id=$2 AND vote.proxy_id <> vote.voter_id")
+ resultproxyname = p(resultmotion[0][0], g.voter)
+
+ p = get_db().prepare("SELECT v.result, proxy.voter_id, voter.email, CASE WHEN proxy.proxy_id = v.proxy_id THEN NULL ELSE voter.email END AS owneremail FROM proxy LEFT JOIN "\
+ + "(SELECT vote.voter_id, vote.result, vote.proxy_id FROM vote "\
+ + "WHERE vote.motion_id=$1) AS v ON proxy.voter_id = v.voter_id "\
+ + "LEFT JOIN voter ON proxy.voter_id = voter.id "\
+ + "WHERE proxy.proxy_id=$2 AND proxy.revoked IS NULL")
+ resultproxyvote = p(resultmotion[0][0], g.voter)
+
+ votes = None
+ if may("audit", resultmotion[0].get("type")) and not resultmotion[0].get("running") and not resultmotion[0].get("canceled"):
+ votes = get_db().prepare("SELECT vote.result, voter.email FROM vote INNER JOIN voter ON voter.id = vote.voter_id WHERE vote.motion_id=$1")(resultmotion[0].get("id"));
+ votes = get_db().prepare("SELECT vote.result, voter.email, CASE voter.email WHEN proxy.email THEN NULL ELSE proxy.email END as proxyemail FROM vote INNER JOIN voter ON voter.id = vote.voter_id INNER JOIN voter as proxy ON proxy.id = vote.proxy_id WHERE vote.motion_id=$1")(resultmotion[0].get("id"));
+ return render_template('single_motion.html', motion=resultmotion[0], may_vote=may("vote", resultmotion[0].get("type")), may_cancel=may("cancel", resultmotion[0].get("type")), may_finish=may("finish", resultmotion[0].get("type")), votes=votes, proxyvote=resultproxyvote, proxyname=resultproxyname, languages=get_languages())
+
+@app.route("/motion/<string:motion>/vote/<string:voter>", methods=['POST'])
+@validate_motion_access_vote('vote')
+def vote(motion, voter, id):