Change-Id: I96713d380fedc4b6ff3424785cc10e554ce3fd7b
DURATION={'hostname':[3, 7, 14]} # duration period for motions
#DEBUGUSER={'hostname':'username/create:* vote:*'} # remove # at beginning of line to use local debuguser
DURATION={'hostname':[3, 7, 14]} # duration period for motions
#DEBUGUSER={'hostname':'username/create:* vote:*'} # remove # at beginning of line to use local debuguser
+
+LANGUAGES = {
+ 'en': 'English',
+ 'de': 'Deutsch'
+}
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
def get_db():
db = getattr(g, '_database', None)
def get_db():
db = getattr(g, '_database', None)
app = Flask(__name__)
app.register_blueprint(filters.blueprint)
app = Flask(__name__)
app.register_blueprint(filters.blueprint)
+babel = Babel(app)
+gettext.install('motion')
class EscapeHtml(Extension):
def extendMarkdown(self, md, md_globals):
class EscapeHtml(Extension):
def extendMarkdown(self, md, md_globals):
max_proxy=app.config.get("MAX_PROXY")
max_proxy=app.config.get("MAX_PROXY")
+@babel.localeselector
+def get_locale():
+ return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
+
@app.before_request
def lookup_user():
global prefix
@app.before_request
def lookup_user():
global prefix
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():
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
- 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
@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)
+ "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)
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)
@app.route("/proxy")
def proxy():
if not may_admin("proxyadmin"):
@app.route("/proxy")
def proxy():
if not may_admin("proxyadmin"):
- return "Forbidden", 403
+ return _('Forbidden'), 403
return render_template('proxy.html', voters=get_voters(), proxies=get_all_proxies(), may_proxyadmin=may_admin("proxyadmin"))
@app.route("/proxy/add", methods=['POST'])
def add_proxy():
if not may_admin("proxyadmin"):
return render_template('proxy.html', voters=get_voters(), proxies=get_all_proxies(), may_proxyadmin=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:
if rv[0].get("c") >= max_proxy:
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
+ 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")
@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")
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
<!DOCTYPE html>
<html>
<head>
<!DOCTYPE html>
<html>
<head>
-<title>{% block title %}Motion list{% endblock %}</title>
+<title>{% block title %}{{_('Motion list')}}{% endblock %}</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<style type="text/css">
.form-inline .motion {
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<style type="text/css">
.form-inline .motion {
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
- <a class="navbar-brand" href="../">{{'Motion list'}}</a>
+ <a class="navbar-brand" href="../">{{_('Motion list')}}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item">
- <a class="nav-link" href="/">{{'Home'}}</a>
+ <a class="nav-link" href="/">{{_('Home')}}</a>
</li>
{%- if may_proxyadmin %}
<li class="nav-item">
</li>
{%- if may_proxyadmin %}
<li class="nav-item">
- <a class="nav-link" href="/proxy">{{'Proxy management'}}</a>
+ <a class="nav-link" href="/proxy">{{_('Proxy management')}}</a>
</li>
{%- endif %}
<li class="nav-item">
</li>
{%- endif %}
<li class="nav-item">
- <a class="nav-link">{{'User'}}: {{g.user}}
+ <a class="nav-link">{{_('User')}}: {{g.user}}
{%- if g.proxies_given %}
{%- if g.proxies_given %}
- <br/>proxy granted to: {{g.proxies_given}}
+ <br/>{{_('proxy granted to')}}: {{g.proxies_given}}
{%- endif %}
{%- if g.proxies_received %}
{%- endif %}
{%- if g.proxies_received %}
- <br/>holds proxy of: {{g.proxies_received}}
+ <br/>{{_('holds proxy of')}}: {{g.proxies_received}}
<!-- Footer -->
<footer class="page-footer">
<div class="footer-copyright text-center py-3">
<!-- Footer -->
<footer class="page-footer">
<div class="footer-copyright text-center py-3">
- <p>© {{footer.version_year}} Copyright: <a href="{{footer.copyright_link}}">{{footer.copyright_name}}</a>
- | <a href="{{footer.imprint_link}}">Imprint</a>
- | <a href="{{footer.dataprotection_link}}">Data protection</a></p>
+ <p>© {{footer.version_year}} {{_('Copyright')}}: <a href="{{footer.copyright_link}}">{{footer.copyright_name}}</a>
+ | <a href="{{footer.imprint_link}}">{{_('Imprint')}}</a>
+ | <a href="{{footer.dataprotection_link}}">{{_('Data protection')}}</a></p>
{% block body %}
<div class="container">
{%- if categories|length != 0 %}
{% block body %}
<div class="container">
{%- if categories|length != 0 %}
+<div class="card">
+</div>
<form action="/motion" method="POST" class="form-inline">
<div class="motion card">
<div class="motion-title card-heading alert-light from-group">
<form action="/motion" method="POST" class="form-inline">
<div class="motion card">
<div class="motion-title card-heading alert-light from-group">
- <input class="form-control motion-title-input" placeholder="Motion title" type="text" name="title" id="title" required="yes">
+ <input class="form-control motion-title-input" placeholder="{{_('Motion title')}}" type="text" name="title" id="title" required="yes">
{%- if categories|length == 1 %}
<input type="text" class="float form-control" maxwidth="10" disabled value="{{categories[0]}}">
<input type="hidden" name="category" value="{{categories[0]}}">
{%- if categories|length == 1 %}
<input type="text" class="float form-control" maxwidth="10" disabled value="{{categories[0]}}">
<input type="hidden" name="category" value="{{categories[0]}}">
</select>
</div>
<div class="card-body">
</select>
</div>
<div class="card-body">
- <textarea class="form-control" placeholder="Motion content" name="content" rows="8"></textarea><br>
- Editing note: Markdown is used formatting.<br>
- To add a line break add two lines, to enter a link use [text](https://domain.tld/link)<br>
- <button class="btn btn-primary" type="submit">Submit Motion</button>
+ <textarea class="form-control" placeholder="{{_('Motion content')}}" name="content" rows="8"></textarea><br>
+ {{_('Editing note: Markdown is used formatting.')}}<br>
+ {{_('To add a line break add two lines, to enter a link use [text](https//domain.tld/link)')}}<br>
+ <button class="btn btn-primary" type="submit">{{_('Submit Motion')}}</button>
</div>
</div>
</form>
{%- endif %}
{%- if prev %}
{%- if prev == -1 %}
</div>
</div>
</form>
{%- endif %}
{%- if prev %}
{%- if prev == -1 %}
-<a href="/" class="btn btn-primary">Prev</a>
+<a href="/" class="btn btn-primary">{{_('Prev')}}</a>
-<a href="/?start={{ prev }}" class="btn btn-primary">Prev</a>
+<a href="/?start={{ prev }}" class="btn btn-primary">{{_('Prev')}}</a>
{%- endif %}
{%- endif %}
{%- for motion in motions %}
{% include 'motion.html' %}
{%- endfor %}
{%- if more %}
{%- endif %}
{%- endif %}
{%- for motion in motions %}
{% include 'motion.html' %}
{%- endfor %}
{%- if more %}
-<a href="/?start={{ more }}" class="btn btn-primary">Next</a>
+<a href="/?start={{ more }}" class="btn btn-primary">{{_('Next')}}</a>
{%- endif %}
</div>
{%- endblock %}
{%- endif %}
</div>
{%- endblock %}
{%- elif motion.yes is defined %}{% if motion.yes != None and motion.no != None and motion.yes > motion.no %} alert-success{% else %} alert-danger{% endif %}
{%- else %} bg-light{%- endif -%}
">
{%- elif motion.yes is defined %}{% if motion.yes != None and motion.no != None and motion.yes > motion.no %} alert-success{% else %} alert-danger{% endif %}
{%- else %} bg-light{%- endif -%}
">
- <span class="title-text">{{motion.name}}</span> ({{ 'Running' if motion.running else ('Canceled' if motion.canceled != None else 'Finished') }})
+ <span class="title-text">{{motion.name}}</span> ({{ _('Running') if motion.running else (_('Canceled') if motion.canceled != None else _('Finished')) }})
<span class="motion-type">{{motion.type}}</span>
<div># {{motion.identifier}}
{%- if singlemotion == False %}
<span class="motion-type">{{motion.type}}</span>
<div># {{motion.identifier}}
{%- if singlemotion == False %}
- <a class="btn btn-primary" href="/motion/{{motion.identifier}}" role="button">{{ 'Vote' if motion.running else 'Result' }}</a>
+ <a class="btn btn-primary" href="/motion/{{motion.identifier}}" role="button">{{ _('Vote') if motion.running else _('Result') }}</a>
{%- endif %}
</div>
<div class="date">
{%- endif %}
</div>
<div class="date">
- <div>Proposed: {{motion.posed|timestamp}} (UTC) by {{motion.poser}}</div>
+ <div>{{_('Proposed')}}: {{motion.posed|timestamp}} (UTC) {{_('by')}} {{motion.poser}}</div>
{%- if motion.canceled != None %}
{%- if motion.canceled != None %}
- <div>Canceled: {{motion.canceled|timestamp}} (UTC) by {{motion.canceler}}</div></div>
+ <div>{{_('Canceled')}}: {{motion.canceled|timestamp}} (UTC) {{_('by')}} {{motion.canceler}}</div>
- <div>Votes until: {{motion.deadline|timestamp}} (UTC)</div></div>
+ <div>{{_('Votes until')}}: {{motion.deadline|timestamp}} (UTC)</div>
</div>
<div class="card-body">
<p>{{motion.content|markdown}}</p>
{%- if motion.yes or motion.no or motion.abstain %}
<p>
{%- for vote in ['yes', 'no', 'abstain'] %}
</div>
<div class="card-body">
<p>{{motion.content|markdown}}</p>
{%- if motion.yes or motion.no or motion.abstain %}
<p>
{%- for vote in ['yes', 'no', 'abstain'] %}
-{{vote|capitalize}} <span class="badge badge-pill badge-secondary">{{motion[vote]}}</span><br>
+{{_(vote)|capitalize}} <span class="badge badge-pill badge-secondary">{{motion[vote]}}</span><br>
{%- endfor %}
</p>
{%- endif %}
{%- if motion.canceled != None %}
{%- endfor %}
</p>
{%- endif %}
{%- if motion.canceled != None %}
- <p>Cancelation reason: {{motion.cancelation_reason}}</p>
+ <p>{{_('Cancelation reason')}}: {{motion.cancelation_reason}}</p>
{%- endif %}
</div>
{%- block content %}{% endblock %}
{%- endif %}
</div>
{%- block content %}{% endblock %}
<form action="/proxy/add" method="POST">
<table>
<tr>
<form action="/proxy/add" method="POST">
<table>
<tr>
- <td>Voter</td>
- <td>Proxy</td>
+ <td>{{_('Voter')}}</td>
+ <td>{{_('Proxy')}}</td>
- <button type="submit" class="btn btn-primary">Add</button>
+ <button type="submit" class="btn btn-primary">{{_('Add')}}</button>
<form action="/proxy/revoke" method="POST">
<div class="motion card" id="votes">
<div class="card-heading text-white bg-info">
<form action="/proxy/revoke" method="POST">
<div class="motion card" id="votes">
<div class="card-heading text-white bg-info">
+ {{_('Granted Proxies')}}
</div>
<div class="card-body">
<table>
<thead>
</div>
<div class="card-body">
<table>
<thead>
- <th>Voter</th>
- <th>Proxy</th>
+ <th>{{_('Voter')}}</th>
+ <th>{{_('Proxy')}}</th>
<th></th>
</thead>
{%- for row in proxies %}
<tr>
<td>{{row.voter_email}}</td>
<td>{{row.proxy_email}}</td>
<th></th>
</thead>
{%- for row in proxies %}
<tr>
<td>{{row.voter_email}}</td>
<td>{{row.proxy_email}}</td>
- <td><button type="submit" class="btn btn-danger" name="id" value="{{row.id}}">Revoke</button></td>
+ <td><button type="submit" class="btn btn-danger" name="id" value="{{row.id}}">{{_('Revoke')}}</button></td>
</tr>
{%- endfor %}
</table>
</tr>
{%- endfor %}
</table>
</form>
{%- endif %}
<form action="/proxy/revokeall" method="POST">
</form>
{%- endif %}
<form action="/proxy/revokeall" method="POST">
- <button type="submit" class="btn btn-danger">Revoke ALL</button>
+ <button type="submit" class="btn btn-danger">{{_('Revoke all')}}</button>
</form>
</div>
{%- endblock %}
\ No newline at end of file
</form>
</div>
{%- endblock %}
\ No newline at end of file
{% extends "base.html" %}
{% block title -%}
{% extends "base.html" %}
{% block title -%}
+{{_('Motion')}}: {{motion.name}}
{%- endblock %}
{% block body %}
{%- include 'motion.html' %}
{%- if votes %}
<div class="motion card" id="votes">
<div class="card-heading text-white bg-info">
{%- endblock %}
{% block body %}
{%- include 'motion.html' %}
{%- if votes %}
<div class="motion card" id="votes">
<div class="card-heading text-white bg-info">
</div>
<div class="card-body">
{%- for row in votes %}
</div>
<div class="card-body">
{%- for row in votes %}
- <div>{{row.email}}: {{row.result}}{%- if row.proxyemail %} : given by {{row.proxyemail}}{%- endif %}</div>
+ <div>{{row.email}}: {{row.result}}{%- if row.proxyemail %} : {_('given by')}} {{row.proxyemail}}{%- endif %}</div>
{%- endfor %}
</div>
</div>
{%- endfor %}
</div>
</div>
{%- if may_vote %}
<div class="panel panel-info" id="votes">
<div class="panel-body">
{%- if may_vote %}
<div class="panel panel-info" id="votes">
<div class="panel-body">
+<h3>{{_('My vote')}}</h3>
-Given by {{proxyname[0][0]}}
+{{_('Given by')}} {{proxyname[0][0]}}
{%- endif %}
<form action="/motion/{{motion.identifier}}/vote/{{g.voter}}" method="POST">
{%- endif %}
<form action="/motion/{{motion.identifier}}/vote/{{g.voter}}" method="POST">
-{%- for vote in ['yes','no','abstain'] %}
-<button type="submit" class="btn btn-{{ 'success' if vote == motion.result else 'primary' }}" name="vote" value="{{vote}}" id="vote-{{vote}}">{{vote}}</button>
+{%- for vote in ['yes', 'no', 'abstain'] %}
+<button type="submit" class="btn btn-{{ 'success' if vote == motion.result else 'primary' }}" name="vote" value="{{vote}}" id="vote-{{vote}}">{{_(vote)}}</button>
{%- endfor %}
</form>
{%- for p in proxyvote %}
{%- endfor %}
</form>
{%- for p in proxyvote %}
-<h3>Vote for {{p.email}}</h3>
+<h3>{{_('Vote for')}} {{p.email}}</h3>
{%- if p.owneremail and p.result%}
{%- if p.owneremail and p.result%}
-Voted by {{p.owneremail}}
+{{_('Voted by')}} {{p.owneremail}}
{%- endif %}
<form action="/motion/{{motion.identifier}}/vote/{{p.voter_id}}" method="POST">
{%- for vote in ['yes','no','abstain'] %}
{%- endif %}
<form action="/motion/{{motion.identifier}}/vote/{{p.voter_id}}" method="POST">
{%- for vote in ['yes','no','abstain'] %}
{%- if may_cancel %}
<form action="/motion/{{motion.identifier}}/cancel" method="POST" class="form-inline">
{%- if may_cancel %}
<form action="/motion/{{motion.identifier}}/cancel" method="POST" class="form-inline">
-<input type="text" placeholder="cancelation reason" name="reason" class="form-control" required="yes">
-<button type="submit" class="btn btn-danger" name="cancel" value="cancel" id="cancel">Cancel</button></br>
+<input type="text" placeholder="{{_('Cancelation reason')}}" name="reason" class="form-control" required="yes">
+<button type="submit" class="btn btn-danger" name="cancel" value="cancel" id="cancel">{{_('Cancel')}}</button></br>
</form>
{%- endif %}
{%- if may_finish %}
<form action="/motion/{{motion.identifier}}/finish" method="POST" class="form-inline">
</form>
{%- endif %}
{%- if may_finish %}
<form action="/motion/{{motion.identifier}}/finish" method="POST" class="form-inline">
-<button type="submit" class="btn btn-danger" name="finish" value="finish" id="finish">Finish</button></br>
+<button type="submit" class="btn btn-danger" name="finish" value="finish" id="finish">{{_('Finish')}}</button></br>
-<a href="/?start={{motion.id}}#motion-{{motion.id}}" class="btn btn-primary">Back</a>
+<a href="/?start={{motion.id}}#motion-{{motion.id}}" class="btn btn-primary">{{_('Back')}}</a>
{%- endblock %}
</div>
</div>
{%- endblock %}
</div>
</div>
def test_basic_results_data(self):
result = self.app.get('/', environ_base={'USER_ROLES': user}, follow_redirects=True)
testtext= '<div class="motion card" id="motion-3">\n <div class="motion-title card-heading alert-warning">'\
def test_basic_results_data(self):
result = self.app.get('/', environ_base={'USER_ROLES': user}, follow_redirects=True)
testtext= '<div class="motion card" id="motion-3">\n <div class="motion-title card-heading alert-warning">'\
- + '\n <span class=\"title-text\">Motion C</span> (Canceled)\n <span class=\"motion-type\">group1</span>'\
+ + '\n <span class="title-text">Motion C</span> (Canceled)\n <span class="motion-type">group1</span>'\
+ '\n <div># g1.20200402.003'\
+ '\n <a class="btn btn-primary" href="/motion/g1.20200402.003" role="button">Result</a>'\
+ '\n <div># g1.20200402.003'\
+ '\n <a class="btn btn-primary" href="/motion/g1.20200402.003" role="button">Result</a>'\
- + '\n </div>'\
- + '\n <div class=\"date\">\n <div>Proposed: 2020-04-02 21:47:24 (UTC) by User A</div>'\
- + '\n <div>Canceled: 2020-04-03 21:48:24 (UTC) by User A</div></div>\n </div>'\
- + '\n <div class=\"card-body\">\n <p><p>A third motion</p></p>'\
- + '\n <p>\nYes <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
- + '\nNo <span class=\"badge badge-pill badge-secondary\">0</span><br>'\
- + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n </p>'\
- + '\n <p>Cancelation reason: Entered with wrong text</p>\n </div>\n</div>'
+ + '\n </div>\n <div class="date">'\
+ + '\n <div>Proposed: 2020-04-02 21:47:24 (UTC) by User A</div>'\
+ + '\n <div>Canceled: 2020-04-03 21:48:24 (UTC) by User A</div>\n </div>\n </div>'\
+ + '\n <div class="card-body">\n <p><p>A third motion</p></p>'\
+ + '\n <p>\nYes <span class="badge badge-pill badge-secondary">1</span><br>'\
+ + '\nNo <span class="badge badge-pill badge-secondary">0</span><br>'\
+ + '\nAbstain <span class="badge badge-pill badge-secondary">0</span><br>\n </p>'\
+ + '\n <p>Cancelation reason: Entered with wrong text</p>\n </div>\n</div>\n'
self.assertIn(str.encode(testtext), result.data)
testtext= '<div class="motion card" id="motion-2">\n <div class="motion-title card-heading alert-danger">'\
self.assertIn(str.encode(testtext), result.data)
testtext= '<div class="motion card" id="motion-2">\n <div class="motion-title card-heading alert-danger">'\
- + '\n <span class=\"title-text\">Motion B</span> (Finished)\n <span class=\"motion-type\">group1</span>'\
+ + '\n <span class="title-text">Motion B</span> (Finished)\n <span class="motion-type">group1</span>'\
+ '\n <div># g1.20200402.002'\
+ '\n <a class="btn btn-primary" href="/motion/g1.20200402.002" role="button">Result</a>'\
+ '\n <div># g1.20200402.002'\
+ '\n <a class="btn btn-primary" href="/motion/g1.20200402.002" role="button">Result</a>'\
- + '\n </div>'\
- + '\n <div class=\"date\">\n <div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>'\
- + '\n <div>Votes until: 2020-04-04 21:41:26 (UTC)</div></div>\n </div>'\
- + '\n <div class=\"card-body\">\n <p><p>A second motion</p></p>'\
- + '\n <p>\nYes <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
- + '\nNo <span class=\"badge badge-pill badge-secondary\">2</span><br>'\
- + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n </p>\n </div>\n</div>\n'
- self.assertIn(str.encode(testtext), result.data)
- testtext= '<div class=\"motion card\" id=\"motion-1\">\n <div class=\"motion-title card-heading alert-success\">'\
- + '\n <span class=\"title-text\">Motion A</span> (Finished)\n <span class=\"motion-type\">group1</span>'\
+ + '\n </div>\n <div class="date">\n <div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>'\
+ + '\n <div>Votes until: 2020-04-04 21:41:26 (UTC)</div>\n </div>\n </div>'\
+ + '\n <div class="card-body">\n <p><p>A second motion</p></p>\n <p>'\
+ + '\nYes <span class="badge badge-pill badge-secondary">1</span><br>'\
+ + '\nNo <span class="badge badge-pill badge-secondary">2</span><br>'\
+ + '\nAbstain <span class="badge badge-pill badge-secondary">0</span><br>\n </p>\n </div>\n</div>\n'
+ self.assertIn(str.encode(testtext), result.data)
+ testtext= '<div class="motion card" id="motion-1">\n <div class="motion-title card-heading alert-success">'\
+ + '\n <span class="title-text">Motion A</span> (Finished)\n <span class="motion-type">group1</span>'\
+ '\n <div># g1.20200402.001'\
+ '\n <a class="btn btn-primary" href="/motion/g1.20200402.001" role="button">Result</a>'\
+ '\n <div># g1.20200402.001'\
+ '\n <a class="btn btn-primary" href="/motion/g1.20200402.001" role="button">Result</a>'\
- + '\n </div>'\
- + '\n <div class=\"date">\n <div>Proposed: 2020-04-02 21:40:33 (UTC) by User A</div>'\
- + '\n <div>Votes until: 2020-04-02 21:40:33 (UTC)</div></div>\n </div>'\
- + '\n <div class=\"card-body\">\n <p><p>My special motion</p></p>'\
- + '\n <p>\nYes <span class=\"badge badge-pill badge-secondary\">2</span><br>'\
- + '\nNo <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
- + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n </p>\n </div>\n</div>\n</div>'
+ + '\n </div>\n <div class="date">\n <div>Proposed: 2020-04-02 21:40:33 (UTC) by User A</div>'\
+ + '\n <div>Votes until: 2020-04-02 21:40:33 (UTC)</div>\n </div>\n </div>'\
+ + '\n <div class="card-body">\n <p><p>My special motion</p></p>\n <p>'\
+ + '\nYes <span class="badge badge-pill badge-secondary">2</span><br>'\
+ + '\nNo <span class="badge badge-pill badge-secondary">1</span><br>'\
+ + '\nAbstain <span class="badge badge-pill badge-secondary">0</span><br>\n </p>\n </div>\n</div>\n'
self.assertIn(str.encode(testtext), result.data)
testtext= 'Proxy management'
self.assertNotIn(str.encode(testtext), result.data)
self.assertIn(str.encode(testtext), result.data)
testtext= 'Proxy management'
self.assertNotIn(str.encode(testtext), result.data)
def test_see_old_vote(self):
motion='g1.20200402.002'
result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
def test_see_old_vote(self):
motion='g1.20200402.002'
result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
- testtext= '<div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>\n <div>Votes until: 2020-04-04 21:41:26 (UTC)</div></div>'\
- + '\n </div>\n <div class="card-body">\n <p><p>A second motion</p></p>\n </div>\n</div>'\
+ testtext= '<div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>\n <div>Votes until: 2020-04-04 21:41:26 (UTC)</div>'\
+ + '\n </div>\n </div>\n <div class="card-body">\n <p><p>A second motion</p></p>\n </div>\n</div>'\
+ '\n<a href="/?start=2#motion-2" class="btn btn-primary">Back</a>'
self.assertIn(str.encode(testtext), result.data)
+ '\n<a href="/?start=2#motion-2" class="btn btn-primary">Back</a>'
self.assertIn(str.encode(testtext), result.data)
--- /dev/null
+[python: **.py]
+[jinja2: **/templates/**.html]
+encoding = utf-8
+extensions=jinja2.ext.autoescape,jinja2.ext.with_