db.prepare("INSERT INTO voter(\"email\") VALUES($1)")(user)
rv = db.prepare("SELECT id FROM voter WHERE email=$1")(user)
g.voter = rv[0].get("id");
- rv = db.prepare("SELECT email FROM voter, proxy WHERE proxy.proxy_id = voter.id AND proxy.revoked IS NULL AND proxy.voter_id = $1 ")(g.voter)
+ g.proxies_given = ""
+ rv = db.prepare("SELECT email, voter_id FROM voter, proxy WHERE proxy.proxy_id = voter.id AND proxy.revoked IS NULL AND proxy.voter_id = $1 ")(g.voter)
if len(rv) != 0:
g.proxies_given = rv[0].get("email")
- rv = db.prepare("SELECT email FROM voter, proxy WHERE proxy.voter_id = voter.id AND proxy.revoked IS NULL AND proxy.proxy_id = $1 ")(g.voter)
+ rv = db.prepare("SELECT email, voter_id FROM voter, proxy WHERE proxy.voter_id = voter.id AND proxy.revoked IS NULL AND proxy.proxy_id = $1 ")(g.voter)
if len(rv) != 0:
sep = ""
g.proxies_received = ""
db.execute(f.read())
db.prepare("UPDATE \"schema_version\" SET \"version\"=4")()
+ if ver < 5:
+ with app.open_resource('sql/from_4.sql', mode='r') as f:
+ db.execute(f.read())
+ db.prepare("UPDATE \"schema_version\" SET \"version\"=5")()
+
init_db()
return rel_redirect("/")
def motion_edited(motion):
- return rel_redirect("/?start=" + str(motion) + "#motion-" + str(motion))
+ return rel_redirect("/motion/" + motion)
def validate_motion_access(privilege):
def decorator(f):
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(id)
+ 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(id)
+ return motion_edited(motion)
@app.route("/motion/<string:motion>")
def show_motion(motion):
+ "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")
- rv = p(motion, g.voter, request.host)
- if len(rv) == 0:
+ resultmotion = p(motion, g.voter, request.host)
+ if len(resultmotion) == 0:
return "Error, Not found", 404
- votes = None
- if may("audit", rv[0].get("type")) and not rv[0].get("running") and not rv[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")(rv[0].get("id"));
- return render_template('single_motion.html', motion=rv[0], may_vote=may("vote", rv[0].get("type")), may_cancel=may("cancel", rv[0].get("type")), may_finish=may("finish", rv[0].get("type")), votes=votes, singlemotion=True, may_proxyadmin=may_admin("proxyadmin"))
-@app.route("/motion/<string:motion>/vote", methods=['POST'])
-@validate_motion_access('vote')
-def vote(motion, id):
+ 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")), votes=votes, proxyvote=resultproxyvote, proxyname=resultproxyname)
+
+@app.route("/motion/<string:motion>/vote/<string:voter>", methods=['POST'])
+@validate_motion_access_vote('vote')
+def vote(motion, voter, id):
v = request.form.get("vote", "abstain")
+ voterid=int(voter)
db = get_db()
+
+ # test if voter is proxy
+ 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
+
p = db.prepare("SELECT * FROM vote WHERE motion_id = $1 AND voter_id = $2")
- rv = p(id, g.voter)
+ rv = p(id, voterid)
if len(rv) == 0:
- db.prepare("INSERT INTO vote(motion_id, voter_id, result) VALUES($1,$2,$3)")(id, g.voter, v)
+ db.prepare("INSERT INTO vote(motion_id, voter_id, result, proxy_id) VALUES($1,$2,$3,$4)")(id, voterid, v, g.voter)
else:
- db.prepare("UPDATE vote SET result=$3, entered=CURRENT_TIMESTAMP WHERE motion_id=$1 AND voter_id = $2")(id, g.voter, v)
- return motion_edited(id)
+ db.prepare("UPDATE vote SET result=$3, entered=CURRENT_TIMESTAMP, proxy_id=$4 WHERE motion_id=$1 AND voter_id = $2")(id, voterid, v, g.voter)
+ return motion_edited(motion)
@app.route("/proxy")
def proxy():
--- /dev/null
+ALTER TABLE "vote" ADD COLUMN "proxy_id" INTEGER;
+UPDATE "vote" SET "proxy_id" = "voter_id";
+ALTER TABLE "vote" ALTER COLUMN "proxy_id" SET NOT NULL;
(4,'g1.20200402.004','Motion D','group1','127.0.0.1:5000','A fourth motion', current_timestamp ,1,current_timestamp + interval '1' day,Null,Null,Null);
ALTER SEQUENCE motion_id_seq RESTART WITH 5;
-INSERT INTO vote (motion_id,voter_id,result,entered) VALUES (1,1,'yes','2020-04-02 21:54:34.469784');
-INSERT INTO vote (motion_id,voter_id,result,entered) VALUES (1,2,'yes','2020-04-02 21:54:34.469784');
-INSERT INTO vote (motion_id,voter_id,result,entered) VALUES (1,3,'no','2020-04-02 21:54:34.469784');
-INSERT INTO vote (motion_id,voter_id,result,entered) VALUES (2,1,'yes','2020-04-02 21:54:34.469784');
-INSERT INTO vote (motion_id,voter_id,result,entered) VALUES (2,2,'no','2020-04-02 21:54:34.469784');
-INSERT INTO vote (motion_id,voter_id,result,entered) VALUES (2,3,'no','2020-04-02 21:54:34.469784');
-INSERT INTO vote (motion_id,voter_id,result,entered) VALUES (3,3,'yes','2020-04-02 21:48:34.469784');
+INSERT INTO vote (motion_id,voter_id,proxy_id,result,entered) VALUES (1,1,1,'yes','2020-04-02 21:54:34.469784');
+INSERT INTO vote (motion_id,voter_id,proxy_id,result,entered) VALUES (1,2,2,'yes','2020-04-02 21:54:34.469784');
+INSERT INTO vote (motion_id,voter_id,proxy_id,result,entered) VALUES (1,3,2,'no','2020-04-02 21:54:34.469784');
+INSERT INTO vote (motion_id,voter_id,proxy_id,result,entered) VALUES (2,1,1,'yes','2020-04-02 21:54:34.469784');
+INSERT INTO vote (motion_id,voter_id,proxy_id,result,entered) VALUES (2,2,2,'no','2020-04-02 21:54:34.469784');
+INSERT INTO vote (motion_id,voter_id,proxy_id,result,entered) VALUES (2,3,3,'no','2020-04-02 21:54:34.469784');
+INSERT INTO vote (motion_id,voter_id,proxy_id,result,entered) VALUES (3,3,3,'yes','2020-04-02 21:48:34.469784');
voter_id INTEGER NOT NULL,
result vote_type NOT NULL,
entered timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ proxy_id INTEGER NOT NULL,
PRIMARY KEY(motion_id, voter_id));
DROP TABLE IF EXISTS proxy;
DROP TABLE IF EXISTS schema_version;
CREATE TABLE schema_version (version INTEGER NOT NULL);
-INSERT INTO schema_version(version) VALUES(4);
+INSERT INTO schema_version(version) VALUES(5);
.motion p {
padding: 10px;
}
+td {
+ padding: 4px;
+}
form {
- margin-top:5px;
- margin-bottom:5px;
+ padding-top: 4px;
+ padding-bottom: 4px;
}
</style>
</head>
</ul>
</div>
</nav>
-
{%- block body %}
{% endblock %}
<!-- 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>
+ <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>
</div>
<div class="card-body">
{%- for row in votes %}
- <div>{{row.email}}: {{row.result}}</div>
+ <div>{{row.email}}: {{row.result}}{%- if row.proxyemail %} : given by {{row.proxyemail}}{%- endif %}</div>
{%- endfor %}
</div>
</div>
{%- endif %}
+
{%- if motion.running %}
+
{%- if may_vote %}
-<form action="/motion/{{motion.identifier}}/vote" method="POST">
+<div class="panel panel-info" id="votes">
+ <div class="panel-body">
+<h3>My vote</h3>
+{%- if proxyname %}
+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>
{%- endfor %}
</form>
+
+{%- for p in proxyvote %}
+<h3>Vote for {{p.email}}</h3>
+{%- if p.owneremail and p.result%}
+Voted by {{p.owneremail}}
+{%- endif %}
+<form action="/motion/{{motion.identifier}}/vote/{{p.voter_id}}" method="POST">
+{%- for vote in ['yes','no','abstain'] %}
+<button type="submit" class="btn btn-{{ 'success' if vote == p.result else 'primary' }}" name="vote" value="{{vote}}" id="vote-{{vote}}">{{vote}}</button>
+{%- endfor %}
+</form>
+{%- endfor %}
{%- endif %}
+
{%- 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="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>
{%- endblock %}
+ </div>
+</div>
self.db_clear()
# functions to manipulate motions
- def createVote(self, user, motion, vote):
+ def createVote(self, user, motion, vote, voter):
return self.app.post(
- '/motion/' + motion +'/vote',
+ '/motion/' + motion + '/vote/' + str(voter),
environ_base={'USER_ROLES': user},
data=dict(vote=vote)
)
self.init_test()
global user
user = 'testuser/'
+ global userid
+ userid = 4
self.db_sampledata()
def tearDown(self):
def test_vote(self):
motion='g1.20200402.004'
- response = self.createVote(user, motion, 'yes')
+ response = self.createVote(user, motion, 'yes', userid)
self.assertEqual(response.status_code, 403)
self.assertIn(str.encode('Forbidden'), response.data)
self.init_test()
global user
user='testuser/vote:*'
+ global userid
+ userid = 4
self.db_sampledata()
def tearDown(self):
def test_vote_yes(self):
motion='g1.20200402.004'
- response = self.createVote(user, motion, 'yes')
+ response = self.createVote(user, motion, 'yes', userid)
self.assertEqual(response.status_code, 302)
result = self.app.get('/', environ_base={'USER_ROLES': user})
resulttext=self.buildResultText('A fourth motion', 1, 0, 0)
def test_vote_no(self):
motion='g1.20200402.004'
- response = self.createVote(user, motion, 'no')
+ response = self.createVote(user, motion, 'no', userid)
self.assertEqual(response.status_code, 302)
result = self.app.get('/', environ_base={'USER_ROLES': user})
resulttext=self.buildResultText('A fourth motion', 0, 1, 0)
def test_vote_abstain(self):
motion='g1.20200402.004'
- response = self.createVote(user, motion, 'abstain')
+ response = self.createVote(user, motion, 'abstain', userid)
self.assertEqual(response.status_code, 302)
result = self.app.get('/', environ_base={'USER_ROLES': user})
resulttext=self.buildResultText('A fourth motion', 0, 0, 1)
def test_vote_change(self):
motion='g1.20200402.004'
- response = self.createVote(user, motion, 'yes')
+ response = self.createVote(user, motion, 'yes', userid)
self.assertEqual(response.status_code, 302)
result = self.app.get('/', environ_base={'USER_ROLES': user})
resulttext=self.buildResultText('A fourth motion', 1, 0, 0)
self.assertIn(str.encode(resulttext), result.data)
- response = self.createVote(user, motion, 'no')
+ response = self.createVote(user, motion, 'no', userid)
self.assertEqual(response.status_code, 302)
result = self.app.get('/', environ_base={'USER_ROLES': user})
resulttext=self.buildResultText('A fourth motion', 0, 1, 0)
self.assertIn(str.encode(resulttext), result.data)
- response = self.createVote(user, motion, 'abstain')
+ response = self.createVote(user, motion, 'abstain', userid)
self.assertEqual(response.status_code, 302)
result = self.app.get('/', environ_base={'USER_ROLES': user})
resulttext=self.buildResultText('A fourth motion', 0, 0, 1)
def test_vote_group(self):
motion='g1.20200402.004'
- response = self.createVote(user, motion, 'yes')
+ response = self.createVote(user, motion, 'yes', userid)
self.assertEqual(response.status_code, 302)
motion='g1.20200402.004'
user1='testuser/vote:group1'
- response = self.createVote(user1, motion, 'yes')
+ response = self.createVote(user1, motion, 'yes', userid)
self.assertEqual(response.status_code, 302)
motion='g1.20200402.004'
user1='testuser/vote:group1 vote:group2'
- response = self.createVote(user1, motion, 'yes')
+ response = self.createVote(user1, motion, 'yes', userid)
self.assertEqual(response.status_code, 302)
def test_vote_wrong_group(self):
motion='g1.20200402.004'
user1='testuser/vote:group2'
- response = self.createVote(user1, motion, 'yes')
+ response = self.createVote(user1, motion, 'yes', userid)
self.assertEqual(response.status_code, 403)
self.assertIn(str.encode('Forbidden'), response.data)
def test_vote_closed(self):
motion='g1.20200402.002'
- response = self.createVote(user, motion, 'abstain')
+ response = self.createVote(user, motion, 'abstain', userid)
self.assertEqual(response.status_code, 403)
self.assertIn(str.encode('Error, out of time'), response.data)
def test_vote_canceled(self):
motion='g1.20200402.003'
- response = self.createVote(user, motion, 'abstain')
+ response = self.createVote(user, motion, 'abstain', userid)
self.assertEqual(response.status_code, 403)
self.assertIn(str.encode('Error, motion was canceled'), response.data)
def test_vote_not_given(self):
motion='g1.30190402.001'
- response = self.createVote(user, motion, 'abstain')
+ response = self.createVote(user, motion, 'abstain', userid)
self.assertEqual(response.status_code, 404)
self.assertIn(str.encode('Error, Not found'), response.data)
testtext= 'holds proxy of: User B\n'
self.assertIn(str.encode(testtext), result.data)
- response = self.revokeProxy(user, 4)
+ response = self.revokeProxy(user, userid)
self.assertEqual(response.status_code, 302)
result = self.app.get('proxy', environ_base={'USER_ROLES': user}, follow_redirects=True)
testtext= '<table>\n '\
+ '</table>\n'
self.assertIn(str.encode(testtext), result.data)
+class ProxyVoteTests(BasicTest):
+
+ def setUp(self):
+ self.init_test()
+ global user
+ user='testuser/vote:* proxyadmin:*'
+ self.db_sampledata()
+
+ def tearDown(self):
+ pass
+
+ def test_proxy_vote(self):
+ voter='testuser'
+ proxy='User B'
+ proxyid=2
+ proxyuser='User B/vote:*'
+
+ response = self.addProxy(user, proxy, voter)
+ self.assertEqual(response.status_code, 302)
+
+ motion='g1.20200402.004'
+ response = self.createVote(user, motion, 'yes', proxyid)
+ self.assertEqual(response.status_code, 302)
+
+ # testuser view
+ result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
+ # own vote without change
+ testtext= '<form action="/motion/g1.20200402.004/vote/4" method="POST">\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="yes" id="vote-yes">yes</button>\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">no</button>\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">abstain</button>\n</form>'
+ self.assertIn(str.encode(testtext), result.data)
+ # proxy vote with change
+ testtext= '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
+ + '<button type="submit" class="btn btn-success" name="vote" value="yes" id="vote-yes">yes</button>\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">no</button>\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">abstain</button>\n</form>\n'
+ self.assertIn(str.encode(testtext), result.data)
+
+ # User B view
+ result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': proxyuser}, follow_redirects=True)
+ # own vote without change
+ testtext= '<h3>My vote</h3>\nGiven by testuser\n'\
+ + '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
+ + '<button type="submit" class="btn btn-success" name="vote" value="yes" id="vote-yes">yes</button>\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">no</button>\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">abstain</button>\n</form>'
+ self.assertIn(str.encode(testtext), result.data)
+
+ # change vote
+ response = self.createVote(user, motion, 'no', proxyid)
+ self.assertEqual(response.status_code, 302)
+
+ result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
+ testtext= '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="yes" id="vote-yes">yes</button>\n'\
+ + '<button type="submit" class="btn btn-success" name="vote" value="no" id="vote-no">no</button>\n'\
+ + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">abstain</button>\n</form>\n'
+ self.assertIn(str.encode(testtext), result.data)
+
+ def test_proxy_vote_no_proxy(self):
+ voter='testuser'
+ proxy='User B'
+ # wrong proxy id
+ proxyid=3
+
+ response = self.addProxy(user, proxy, voter)
+ self.assertEqual(response.status_code, 302)
+
+ motion='g1.20200402.004'
+ response = self.createVote(user, motion, 'yes', proxyid)
+ self.assertEqual(response.status_code, 400)
+ self.assertIn(str.encode('Error, proxy not found'), response.data)
+
+ # non existing id
+ proxyid=10000
+
+ motion='g1.20200402.004'
+ response = self.createVote(user, motion, 'yes', proxyid)
+ self.assertEqual(response.status_code, 400)
+ self.assertIn(str.encode('Error, proxy not found'), response.data)
+
+ def test_proxy_vote_no_voter(self):
+ voter='User A'
+ proxy='User B'
+ proxyid=2
+
+ response = self.addProxy(user, proxy, voter)
+ self.assertEqual(response.status_code, 302)
+
+ user1='testuser1/'
+ motion='g1.20200402.004'
+ response = self.createVote(user1, motion, 'yes', proxyid)
+ self.assertEqual(response.status_code, 403)
+ self.assertIn(str.encode('Forbidden'), response.data)
+
+
if __name__ == "__main__":
unittest.main()