Skip to content

Commit

Permalink
Proposition: proper handling fo action permissions and "zero" settings
Browse files Browse the repository at this point in the history
The qualification quorum can be zero (= no supporters needed for
qualification) so the progress bar doesn't make sense in this case. Show
only the number of supporters instead.

Secret voting quorum can also be zero which is interpreted as "requesting secret voting
isn't supported". Don't show the action at all in this case.

If supporting isn't possible only for the current user, just show the data.
  • Loading branch information
dpausp committed Jul 10, 2021
1 parent b5368f7 commit bb5477e
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 78 deletions.
23 changes: 16 additions & 7 deletions src/ekklesia_portal/concepts/proposition/proposition_cells.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ class PropositionCell(LayoutCell):
]

actions = Cell.fragment('proposition_actions')
secret_voting_action = Cell.template_fragment('proposition_secret_voting_action')
support_action = Cell.template_fragment('proposition_support_action')
secret_voting = Cell.template_fragment('proposition_secret_voting')
support = Cell.template_fragment('proposition_support')
tabs = Cell.fragment('proposition_tabs')
small = Cell.fragment('proposition_small')
card = Cell.fragment('proposition_card')
Expand Down Expand Up @@ -232,14 +232,23 @@ def full_title(self):
def ready_to_submit(self):
return self._model.ready_to_submit

def show_support_action(self):
return self._model.status in (PropositionStatus.SUBMITTED, PropositionStatus.QUALIFIED
) and self._request.permitted_for_current_user(self._model, SupportPermission)
def show_support(self):
return self._model.status in (PropositionStatus.SUBMITTED, PropositionStatus.QUALIFIED)

def show_secret_voting_action(self):
def can_support(self):
return self.show_support and self._request.permitted_for_current_user(self._model, SupportPermission)

def supporter_quorum_percent(self):
if self._model.qualification_quorum > 0:
return self._model.active_supporter_count / self._model.qualification_quorum * 100

def show_secret_voting(self):
return self._model.status in (
PropositionStatus.SUBMITTED, PropositionStatus.QUALIFIED, PropositionStatus.SCHEDULED
) and self._request.permitted_for_current_user(self._model, SupportPermission)
) and self._model.secret_voting_quorum > 0 and self._request.permitted_for_current_user(self._model, SupportPermission)

def can_request_secret_voting(self):
return self.show_secret_voting and self._request.permitted_for_current_user(self._model, SupportPermission)

def show_submit_draft_action(self):
return self._model.ready_to_submit and self._request.permitted_for_current_user(
Expand Down
4 changes: 2 additions & 2 deletions src/ekklesia_portal/concepts/proposition/proposition_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def secret_voting(self, request):
secret_record.last_change = datetime.now(timezone.utc)

if request.headers.get("HX-Request"):
return PropositionCell(self, request).secret_voting_action()
return PropositionCell(self, request).secret_voting()
else:
return redirect(request.link(self))

Expand All @@ -168,7 +168,7 @@ def support(self, request):

if request.headers.get("HX-Request"):
cell = PropositionCell(self, request)
return "\n".join([cell.support_action(), cell.detail_top()])
return "\n".join([cell.support(), cell.detail_top()])
else:
return redirect(request.link(self))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
.proposition_actions

if show_support_action
= render_cell(_model, 'support_action', **options)
if show_support
= render_cell(_model, 'support', **options)

if show_secret_voting_action
= render_cell(_model, 'secret_voting_action', **options)
if show_secret_voting
= render_cell(_model, 'secret_voting', **options)

hr

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#secret_voting_action(hx-swap-oob="true")
#secret_voting(hx-swap-oob="true")
.col-sm-6
form.secret_voting_form(action=secret_voting_url
method="POST"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#support(hx-swap-oob="true")
.col-sm-6
form.support_form(action=support_url
method="POST"
hx-post=support_url)


if current_user_is_supporter
input(type="hidden", name="support", value="retract")
button.btn.btn-secondary.btn-sm(type="submit")
i.far.fa-thumbs-down  
= _('button_retract_support')
else
if can_support
input(type="hidden", name="support", value="support")
button.btn.btn-primary.btn-sm(type="submit")
i.far.fa-thumbs-up  
= _('button_support')
elif supporter_quorum_percent is not none
button.btn.btn-secondary.btn-sm(disabled=true)
= _('supporters')

.col-sm-6
if supporter_quorum_percent is not none
.progress(title=(ngettext("supporter", "supporters", supporter_count) + " / " + _('qualification_quorum')))
.progress-bar(role="progressbar"
style="width: {{ supporter_quorum_percent }}%"
aria-valuenow=supporter_count
aria-valuemin="0"
aria-valuemax=qualification_quorum)
| {{ supporter_count }} / {{ qualification_quorum }}
else
| {{ supporter_count }} {{ _("supporters") }}





//- vim: set filetype=jade sw=2 ts=2 sts=2 expandtab:

This file was deleted.

4 changes: 2 additions & 2 deletions src/ekklesia_portal/sass/portal.sass
Original file line number Diff line number Diff line change
Expand Up @@ -393,13 +393,13 @@ ul.proposition_filter
.progress-bar
color: black

#support_action
#support
@extend .progress_row

.progress-bar
background-color: $light-orange

#secret_voting_action
#secret_voting
@extend .progress_row

.progress-bar
Expand Down
70 changes: 37 additions & 33 deletions tests/concepts/proposition/test_propositions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
import random
import string

Expand Down Expand Up @@ -240,72 +241,75 @@ def test_does_not_create_without_title(db_query, client, proposition_factory, lo


@fixture
def assert_support(db_session, logged_in_user):
def _assert_supporter(proposition, status):
qq = db_session.query(Supporter).filter_by(member_id=logged_in_user.id, proposition_id=proposition.id)
if status is None:
def assert_support(client, db_session, logged_in_user_with_departments, ballot_factory, proposition_factory):

user = logged_in_user_with_departments
area = user.departments[0].areas[0]
ballot = ballot_factory(area=area)
proposition = proposition_factory(title="test", ballot=ballot, status=PropositionStatus.SUBMITTED, submitted_at=datetime.now())
support_url = f'/p/{proposition.id}/test/support'

def _assert_support(data, support_status, http_status=200, headers={}):

if data is None:
res = None
else:
res = client.post(support_url, data, headers=headers, status=http_status)

qq = db_session.query(Supporter).filter_by(member_id=user.id, proposition_id=proposition.id)
if support_status is None:
assert qq.scalar() is None, 'supporter present but should not be present'
else:
assert qq.filter_by(status=status).scalar() is not None, f'no supporter found with status {status}'
return _assert_supporter
assert qq.filter_by(status=support_status).scalar() is not None, f'no supporter found with status {support_status}'

return res

def test_support(client, assert_support, proposition_factory):
return _assert_support

proposition = proposition_factory(title="test")
support_url = f'/p/{proposition.id}/test/support'

def test_support(assert_support):

# Nothing happened yet.
assert_support(proposition, None)
assert_support(data=None, support_status=None)

# Nothing -> active
client.post(support_url, dict(support="support"), status=302)
assert_support(proposition, 'active')
assert_support(dict(support="support"), "active", http_status=302)

# active -> retracted
client.post(support_url, dict(support="retract"), status=302)
assert_support(proposition, 'retracted')
assert_support(dict(support="retract"), "retracted", http_status=302)

# Sending retract twice is still retracted
# retracted -> retracted
client.post(support_url, dict(support="retract"), status=302)
assert_support(proposition, 'retracted')
assert_support(dict(support="retract"), "retracted", http_status=302)

# retracted -> active
client.post(support_url, dict(support="support"), status=302)
assert_support(proposition, 'active')
assert_support(dict(support="support"), "active", http_status=302)

# Sending support twice is still active
# active -> active
client.post(support_url, dict(support="support"), status=302)
assert_support(proposition, 'active')
assert_support(dict(support="support"), "active", http_status=302)

# Invalid requests shouldn't change anything and return bad request.
client.post(support_url, dict(support="invalid"), status=400)
assert_support(proposition, 'active')
assert_support(dict(support="invalid"), "active", http_status=400)

client.post(support_url, dict(), status=400)
assert_support(proposition, 'active')
assert_support(dict(), "active", http_status=400)

# Retracting still works after client has sent invalid things
# active -> retracted
client.post(support_url, dict(support="retract"), status=302)
assert_support(proposition, 'retracted')
assert_support(dict(support="retract"), "retracted", http_status=302)


def test_support_htmx(client, assert_support, proposition_factory):
proposition = proposition_factory(title="test")
support_url = f'/p/{proposition.id}/test/support'
def test_support_htmx(client, assert_support):
# Nothing happened yet.
assert_support(data=None, support_status=None)

# Nothing -> active
res = client.post(support_url, dict(support="support"), headers={"HX-Request": "true"})
assert_support(proposition, 'active')
res = assert_support(dict(support="support"), "active", headers={"HX-Request": "true"})
assert "<html>" not in res, "only snippet expected for HTMX request, this is a full HTML document"
assert 'value="retract"' in res, "retract support button missing"

# active -> retracted
res = client.post(support_url, dict(support="retract"), headers={"HX-Request": "true"})
assert_support(proposition, 'retracted')
res = assert_support(dict(support="retract"), "retracted", headers={"HX-Request": "true"})
assert 'value="support"' in res, "support button missing"


Expand Down

0 comments on commit bb5477e

Please sign in to comment.