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_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 gettext
def get_db():
db = getattr(g, '_database', None)
app = Flask(__name__)
app.register_blueprint(filters.blueprint)
+babel = Babel(app)
+gettext.install('motion')
class EscapeHtml(Extension):
def extendMarkdown(self, md, md_globals):
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
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:
- return "Error, invalid length", 400
+ return _('Error, invalid length'), 400
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 =='':
- return "Error, missing content", 400
+ return _('Error, missing content'), 400
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:
- return "Error, Not found", 404
+ return _('Error, Not found'), 404
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:
- return "Error, motion was canceled", 403
+ return _('Error, motion was canceled'), 403
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
@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)
+ "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)
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)
@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 "Forbidden", 403
+ return _('Forbidden'), 403
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:
- 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:
- 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:
- 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:
- 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"):
- 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")
@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")
click==6.7
Flask==0.12.2
+Flask-Babel==1.0.0
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
<!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 {
</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">
- <a class="nav-link" href="/">{{'Home'}}</a>
+ <a class="nav-link" href="/">{{_('Home')}}</a>
</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">
- <a class="nav-link">{{'User'}}: {{g.user}}
+ <a class="nav-link">{{_('User')}}: {{g.user}}
{%- if g.proxies_given %}
- <br/>proxy granted to: {{g.proxies_given}}
+ <br/>{{_('proxy granted to')}}: {{g.proxies_given}}
{%- endif %}
{%- if g.proxies_received %}
- <br/>holds proxy of: {{g.proxies_received}}
+ <br/>{{_('holds proxy of')}}: {{g.proxies_received}}
{%- endif %}
</a>
</li>
<!-- 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>
</div>
</footer>
</body>
{% 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">
- <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]}}">
</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 %}
-<a href="/" class="btn btn-primary">Prev</a>
+<a href="/" class="btn btn-primary">{{_('Prev')}}</a>
{%- else %}
-<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 %}
-<a href="/?start={{ more }}" class="btn btn-primary">Next</a>
+<a href="/?start={{ more }}" class="btn btn-primary">{{_('Next')}}</a>
{%- 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 -%}
">
- <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 %}
- <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">
- <div>Proposed: {{motion.posed|timestamp}} (UTC) by {{motion.poser}}</div>
+ <div>{{_('Proposed')}}: {{motion.posed|timestamp}} (UTC) {{_('by')}} {{motion.poser}}</div>
{%- 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>
{%- else %}
- <div>Votes until: {{motion.deadline|timestamp}} (UTC)</div></div>
+ <div>{{_('Votes until')}}: {{motion.deadline|timestamp}} (UTC)</div>
{%- endif %}
+ </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'] %}
-{{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 %}
- <p>Cancelation reason: {{motion.cancelation_reason}}</p>
+ <p>{{_('Cancelation reason')}}: {{motion.cancelation_reason}}</p>
{%- endif %}
</div>
{%- block content %}{% endblock %}
<form action="/proxy/add" method="POST">
<table>
<tr>
- <td>Voter</td>
- <td>Proxy</td>
+ <td>{{_('Voter')}}</td>
+ <td>{{_('Proxy')}}</td>
<td></td>
</tr>
<tr>
</select>
</td>
<td>
- <button type="submit" class="btn btn-primary">Add</button>
+ <button type="submit" class="btn btn-primary">{{_('Add')}}</button>
</td>
</tr>
</table>
<form action="/proxy/revoke" method="POST">
<div class="motion card" id="votes">
<div class="card-heading text-white bg-info">
- Granted Proxies
+ {{_('Granted Proxies')}}
</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>
- <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>
</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
{% extends "base.html" %}
{% block title -%}
-Motion: {{motion.name}}
+{{_('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">
- Motion Votes
+ {{_('Motion 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>
{%- if may_vote %}
<div class="panel panel-info" id="votes">
<div class="panel-body">
-<h3>My vote</h3>
+<h3>{{_('My vote')}}</h3>
{%- if proxyname %}
-Given by {{proxyname[0][0]}}
+{{_('Given by')}} {{proxyname[0][0]}}
{%- 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 %}
-<h3>Vote for {{p.email}}</h3>
+<h3>{{_('Vote for')}} {{p.email}}</h3>
{%- 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'] %}
{%- 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">
-<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>
</form>
{%- endif %}
-
{%- endif %}
-<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>
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>'\
- + '\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">'\
- + '\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>'\
- + '\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>'\
- + '\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)
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)
--- /dev/null
+[python: **.py]
+[jinja2: **/templates/**.html]
+encoding = utf-8
+extensions=jinja2.ext.autoescape,jinja2.ext.with_