]> WPIA git - motion.git/blobdiff - motion.py
report 500-error better, remove raw_html postprocessor
[motion.git] / motion.py
index 9e70c991dcd1fbe4354e7b9845749ad419ac964c..23d91f59ca1074e7e361f76dd8ec1dc763e5ca1a 100644 (file)
--- a/motion.py
+++ b/motion.py
@@ -3,11 +3,15 @@ from flask import Flask
 from flask import render_template, redirect
 from flask import request
 from functools import wraps
 from flask import render_template, redirect
 from flask import request
 from functools import wraps
+from flask_babel import Babel, gettext
 import postgresql
 import filters
 from flaskext.markdown import Markdown
 from markdown.extensions import Extension
 from datetime import date, time, datetime
 import postgresql
 import filters
 from flaskext.markdown import Markdown
 from markdown.extensions import Extension
 from datetime import date, time, datetime
+from flask_language import Language, current_language
+import gettext
+import click
 
 def get_db():
     db = getattr(g, '_database', None)
 
 def get_db():
     db = getattr(g, '_database', None)
@@ -18,10 +22,14 @@ def get_db():
 
 app = Flask(__name__)
 app.register_blueprint(filters.blueprint)
 
 app = Flask(__name__)
 app.register_blueprint(filters.blueprint)
+babel = Babel(app)
+lang = Language(app)
+gettext.install('motion')
 
 class EscapeHtml(Extension):
     def extendMarkdown(self, md, md_globals):
         del md.preprocessors['html_block']
 
 class EscapeHtml(Extension):
     def extendMarkdown(self, md, md_globals):
         del md.preprocessors['html_block']
+        del md.postprocessors['raw_html']
         del md.inlinePatterns['html']
 
 md = Markdown(app, extensions=[EscapeHtml()])
         del md.inlinePatterns['html']
 
 md = Markdown(app, extensions=[EscapeHtml()])
@@ -32,6 +40,7 @@ class default_settings(object):
     COPYRIGHTLINK="https://wpia.club"
     IMPRINTLINK="https://documents.wpia.club/imprint.html"
     DATAPROTECTIONLINK="https://documents.wpia.club/data_privacy_policy_html_pages_en.html"
     COPYRIGHTLINK="https://wpia.club"
     IMPRINTLINK="https://documents.wpia.club/imprint.html"
     DATAPROTECTIONLINK="https://documents.wpia.club/data_privacy_policy_html_pages_en.html"
+    MAX_PROXY=2
 
 
 # Load config
 
 
 # Load config
@@ -55,6 +64,24 @@ debuguser = ConfigProxy("DEBUGUSER")
 
 max_proxy=app.config.get("MAX_PROXY")
 
 
 max_proxy=app.config.get("MAX_PROXY")
 
+@babel.localeselector
+def get_locale():
+    return str(current_language)
+
+@lang.allowed_languages
+def get_allowed_languages():
+    return app.config['LANGUAGES'].keys()
+
+@lang.default_language
+def get_default_language():
+    return 'en'
+
+def get_languages():
+    return app.config['LANGUAGES']
+
+# Manually add vote options to the translation strings. They are used as keys in loops.
+TRANSLATION_STRINGS={_('yes'), _('no'), _('abstain')}
+
 @app.before_request
 def lookup_user():
     global prefix
 @app.before_request
 def lookup_user():
     global prefix
@@ -77,11 +104,11 @@ def lookup_user():
         roles = env.get("ROLES")
 
     if user is None:
         roles = env.get("ROLES")
 
     if user is None:
-        return "Server misconfigured", 500
+        return _('Server misconfigured'), 500
     roles = roles.split(" ")
 
     if user == "<invalid>":
     roles = roles.split(" ")
 
     if user == "<invalid>":
-        return "Access denied", 403;
+        return _('Access denied'), 403;
 
     db = get_db()
     with db.xact():
 
     db = get_db()
     with db.xact():
@@ -237,7 +264,7 @@ def main():
         else:
             prev = -1
     return render_template('index.html', motions=rv[:10], more=rv[10]["id"] if len(rv) == 11 else None, times=times.per_host, prev=prev,
         else:
             prev = -1
     return render_template('index.html', motions=rv[:10], more=rv[10]["id"] if len(rv) == 11 else None, times=times.per_host, prev=prev,
-                           categories=get_allowed_cats("create"), singlemotion=False, may_proxyadmin=may_admin("proxyadmin"))
+                           categories=get_allowed_cats("create"), singlemotion=False, may_proxyadmin=may_admin("proxyadmin"), languages=get_languages())
 
 def rel_redirect(loc):
     r = redirect(loc)
 
 def rel_redirect(loc):
     r = redirect(loc)
@@ -248,18 +275,18 @@ def rel_redirect(loc):
 def put_motion():
     cat=request.form.get("category", "")
     if cat not in get_allowed_cats("create"):
 def put_motion():
     cat=request.form.get("category", "")
     if cat not in get_allowed_cats("create"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     time = int(request.form.get("days", "3"));
     if time not in times.per_host:
     time = int(request.form.get("days", "3"));
     if time not in times.per_host:
-        return "Error, invalid length", 400
+        return _('Error, invalid length'), 400
     title=request.form.get("title", "")
     title=title.strip()
     if title =='':
     title=request.form.get("title", "")
     title=title.strip()
     if title =='':
-        return "Error, missing title", 400
+        return _('Error, missing title'), 400
     content=request.form.get("content", "")
     content=content.strip()
     if content =='':
     content=request.form.get("content", "")
     content=content.strip()
     if content =='':
-        return "Error, missing content", 400
+        return _('Error, missing content'), 400
 
     db = get_db()
     with db.xact():
 
     db = get_db()
     with db.xact():
@@ -270,7 +297,10 @@ def put_motion():
         if len(sr) == 0 or sr[0][0] is None:
             ident=prefix.per_host[cat]+"."+t.strftime("%Y%m%d")+".001"
         else:
         if len(sr) == 0 or sr[0][0] is None:
             ident=prefix.per_host[cat]+"."+t.strftime("%Y%m%d")+".001"
         else:
-            ident=prefix.per_host[cat]+"."+t.strftime("%Y%m%d")+"."+("%03d" % (int(sr[0][0].split(".")[2])+1))
+            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("/")
         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("/")
@@ -285,14 +315,14 @@ def validate_motion_access(privilege):
             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:
             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
+                    return _('Error, Not found'), 404
                 id = rv[0].get("id")
                 if not may(privilege, rv[0].get("type")):
                 id = rv[0].get("id")
                 if not may(privilege, rv[0].get("type")):
-                    return "Forbidden", 403
+                    return _('Forbidden'), 403
                 if rv[0].get("canceled") is not None:
                 if rv[0].get("canceled") is not None:
-                    return "Error, motion was canceled", 403
+                    return _('Error, motion was canceled'), 403
                 if rv[0].get("expired"):
                 if rv[0].get("expired"):
-                    return "Error, out of time", 403
+                    return _('Error, out of time'), 403
             return f(motion, id)
         decorated_function.__name__ = f.__name__
         return decorated_function
             return f(motion, id)
         decorated_function.__name__ = f.__name__
         return decorated_function
@@ -311,7 +341,7 @@ def validate_motion_access_vote(privilege):
 @validate_motion_access('cancel')
 def cancel_motion(motion, id):
     if request.form.get("reason", "none") == "none":
 @validate_motion_access('cancel')
 def cancel_motion(motion, id):
     if request.form.get("reason", "none") == "none":
-        return "Error, form requires reason", 500
+        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)
 
     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)
 
@@ -330,7 +360,7 @@ def show_motion(motion):
                          + "WHERE motion.identifier=$1 AND motion.host=$3")
     resultmotion = p(motion, g.voter, request.host)
     if len(resultmotion) == 0:
                          + "WHERE motion.identifier=$1 AND motion.host=$3")
     resultmotion = p(motion, g.voter, request.host)
     if len(resultmotion) == 0:
-        return "Error, Not found", 404
+        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 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)
@@ -346,7 +376,7 @@ def show_motion(motion):
     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"));
     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")), votes=votes, proxyvote=resultproxyvote, proxyname=resultproxyname)
+    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")), votes=votes, proxyvote=resultproxyvote, proxyname=resultproxyname, languages=get_languages())
 
 @app.route("/motion/<string:motion>/vote/<string:voter>", methods=['POST'])
 @validate_motion_access_vote('vote')
 
 @app.route("/motion/<string:motion>/vote/<string:voter>", methods=['POST'])
 @validate_motion_access_vote('vote')
@@ -359,7 +389,7 @@ def vote(motion, voter, id):
     if (voterid != g.voter):
         rv = db.prepare("SELECT voter_id FROM proxy WHERE proxy.revoked IS NULL AND proxy.proxy_id = $1 AND proxy.voter_id = $2")(g.voter, voterid);
         if len(rv) == 0:
     if (voterid != g.voter):
         rv = db.prepare("SELECT voter_id FROM proxy WHERE proxy.revoked IS NULL AND proxy.proxy_id = $1 AND proxy.voter_id = $2")(g.voter, voterid);
         if len(rv) == 0:
-            return "Error, proxy not found.", 400
+            return _('Error, proxy not found.'), 400
 
     p = db.prepare("SELECT * FROM vote WHERE motion_id = $1 AND voter_id = $2")
     rv = p(id, voterid)
 
     p = db.prepare("SELECT * FROM vote WHERE motion_id = $1 AND voter_id = $2")
     rv = p(id, voterid)
@@ -372,39 +402,39 @@ def vote(motion, voter, id):
 @app.route("/proxy")
 def proxy():
     if not may_admin("proxyadmin"):
 @app.route("/proxy")
 def proxy():
     if not may_admin("proxyadmin"):
-        return "Forbidden", 403
-    return render_template('proxy.html', voters=get_voters(), proxies=get_all_proxies(), may_proxyadmin=may_admin("proxyadmin"))
+        return _('Forbidden'), 403
+    return render_template('proxy.html', voters=get_voters(), proxies=get_all_proxies(), may_proxyadmin=may_admin("proxyadmin"), languages=get_languages())
 
 @app.route("/proxy/add", methods=['POST'])
 def add_proxy():
     if not may_admin("proxyadmin"):
 
 @app.route("/proxy/add", methods=['POST'])
 def add_proxy():
     if not may_admin("proxyadmin"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     voter=request.form.get("voter", "")
     proxy=request.form.get("proxy", "")
     if voter == proxy :
     voter=request.form.get("voter", "")
     proxy=request.form.get("proxy", "")
     if voter == proxy :
-        return "Error, voter equals proxy.", 400
+        return _('Error, voter equals proxy.'), 400
     rv = get_db().prepare("SELECT id FROM voter WHERE email=$1")(voter);
     if len(rv) == 0:
     rv = get_db().prepare("SELECT id FROM voter WHERE email=$1")(voter);
     if len(rv) == 0:
-        return "Error, voter not found.", 400
+        return _('Error, voter not found.'), 400
     voterid = rv[0].get("id")
     rv = get_db().prepare("SELECT id FROM voter WHERE email=$1")(proxy);
     if len(rv) == 0:
     voterid = rv[0].get("id")
     rv = get_db().prepare("SELECT id FROM voter WHERE email=$1")(proxy);
     if len(rv) == 0:
-        return "Error, proxy not found.", 400
+        return _('Error, proxy not found.'), 400
     proxyid = rv[0].get("id")
     rv = get_db().prepare("SELECT id FROM proxy WHERE voter_id=$1 AND revoked is NULL")(voterid);
     if len(rv) != 0:
     proxyid = rv[0].get("id")
     rv = get_db().prepare("SELECT id FROM proxy WHERE voter_id=$1 AND revoked is NULL")(voterid);
     if len(rv) != 0:
-        return "Error, proxy allready given.", 400
+        return _('Error, proxy allready given.'), 400
     rv = get_db().prepare("SELECT COUNT(id) as c FROM proxy WHERE proxy_id=$1 AND revoked is NULL GROUP BY proxy_id")(proxyid);
     if len(rv) != 0:
     rv = get_db().prepare("SELECT COUNT(id) as c FROM proxy WHERE proxy_id=$1 AND revoked is NULL GROUP BY proxy_id")(proxyid);
     if len(rv) != 0:
-        if rv[0].get("c") >= max_proxy:
-            return "Error, Max proxy for '" + proxy + "' reached.", 400
+        if rv[0].get("c") is None or rv[0].get("c") >= max_proxy:
+            return _("Error, Max proxy for '%s' reached.") % (proxy), 400
     rv = get_db().prepare("INSERT INTO proxy(voter_id, proxy_id, granted_by) VALUES ($1,$2,$3)")(voterid, proxyid, g.voter)
     return rel_redirect("/proxy")
 
 @app.route("/proxy/revoke", methods=['POST'])
 def revoke_proxy():
     if not may_admin("proxyadmin"):
     rv = get_db().prepare("INSERT INTO proxy(voter_id, proxy_id, granted_by) VALUES ($1,$2,$3)")(voterid, proxyid, g.voter)
     return rel_redirect("/proxy")
 
 @app.route("/proxy/revoke", methods=['POST'])
 def revoke_proxy():
     if not may_admin("proxyadmin"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     id=request.form.get("id", "")
     rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE id=$2")(g.voter, int(id))
     return rel_redirect("/proxy")
     id=request.form.get("id", "")
     rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE id=$2")(g.voter, int(id))
     return rel_redirect("/proxy")
@@ -412,7 +442,23 @@ def revoke_proxy():
 @app.route("/proxy/revokeall", methods=['POST'])
 def revoke_proxy_all():
     if not may_admin("proxyadmin"):
 @app.route("/proxy/revokeall", methods=['POST'])
 def revoke_proxy_all():
     if not may_admin("proxyadmin"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE revoked IS NULL")(g.voter)
     return rel_redirect("/proxy")
 
     rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE revoked IS NULL")(g.voter)
     return rel_redirect("/proxy")
 
+@app.route("/language/<string:language>")
+def set_language(language):
+    lang.change_language(language)
+    return rel_redirect("/")
+
+@app.cli.command("create-user")
+@click.argument("email")
+def create_user(email):
+    db = get_db()
+    with db.xact():
+        rv = db.prepare("SELECT id FROM voter WHERE lower(email)=lower($1)")(email)
+        messagetext=_("User '%s' already exists.") % (email)
+        if len(rv) == 0:
+            db.prepare("INSERT INTO voter(\"email\") VALUES($1)")(email)
+            messagetext=_("User '%s' inserted.") % (email)
+    click.echo(messagetext)