Implement sending urgent reminder emails to all members of the applicable committee for an endorsed budget
This commit is contained in:
parent
490faa8147
commit
8a3a09d8ab
@ -259,73 +259,80 @@
|
|||||||
{% if revision.state == import('sstreasury.models').BudgetState.ENDORSED.value %}
|
{% if revision.state == import('sstreasury.models').BudgetState.ENDORSED.value %}
|
||||||
<h2>Committee voting</h2>
|
<h2>Committee voting</h2>
|
||||||
|
|
||||||
<p>{{ dict(settings.AVAILABLE_APPROVERS)[revision.approver][1] }} votes in favour are required for approval.</p>
|
<form action="{{ url('budget_action', kwargs={'id': revision.budget.id}) }}" method="POST">
|
||||||
|
<p>
|
||||||
|
{{ dict(settings.AVAILABLE_APPROVERS)[revision.approver][1] }} votes in favour are required for approval.
|
||||||
|
{% if is_latest and request.user.groups.filter(name='Executive').exists() %}
|
||||||
|
<button style="margin-left: 1em;" class="ui small primary labeled icon button" type="submit" name="action" value="SendVotingReminders"><i class="envelope icon"></i>Send urgent reminder emails</button>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
<form class="ui three column grid" action="{{ url('budget_action', kwargs={'id': revision.budget.id}) }}" method="POST">
|
<div class="ui three column grid">
|
||||||
|
<div class="column">
|
||||||
|
<div class="ui fluid card">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">In favour ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value).count() }})</div>
|
||||||
|
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value).exists() %}
|
||||||
|
<div class="description">
|
||||||
|
<ul style="margin-bottom:0">
|
||||||
|
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value) %}
|
||||||
|
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if is_latest and revision.can_vote(request.user) %}
|
||||||
|
<button class="ui bottom attached positive button" type="submit" name="action" value="VoteInFavour">
|
||||||
|
Vote in favour
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="ui fluid card">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">Against ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value).count() }})</div>
|
||||||
|
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value).exists() %}
|
||||||
|
<div class="description">
|
||||||
|
<ul style="margin-bottom:0">
|
||||||
|
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value) %}
|
||||||
|
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if is_latest and revision.can_vote(request.user) %}
|
||||||
|
<button class="ui bottom attached negative button" type="submit" name="action" value="VoteAgainst">
|
||||||
|
Vote against
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="ui fluid card">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">Abstentions ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value).count() }})</div>
|
||||||
|
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value).exists() %}
|
||||||
|
<div class="description">
|
||||||
|
<ul style="margin-bottom:0">
|
||||||
|
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value) %}
|
||||||
|
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if is_latest and revision.can_vote(request.user) %}
|
||||||
|
<button class="ui bottom attached secondary button" type="submit" name="action" value="VoteAbstain">
|
||||||
|
Abstain
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
||||||
<div class="column">
|
|
||||||
<div class="ui fluid card">
|
|
||||||
<div class="content">
|
|
||||||
<div class="header">In favour ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value).count() }})</div>
|
|
||||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value).exists() %}
|
|
||||||
<div class="description">
|
|
||||||
<ul style="margin-bottom:0">
|
|
||||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.IN_FAVOUR.value) %}
|
|
||||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if is_latest and revision.can_vote(request.user) %}
|
|
||||||
<button class="ui bottom attached positive button" type="submit" name="action" value="VoteInFavour">
|
|
||||||
Vote in favour
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<div class="ui fluid card">
|
|
||||||
<div class="content">
|
|
||||||
<div class="header">Against ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value).count() }})</div>
|
|
||||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value).exists() %}
|
|
||||||
<div class="description">
|
|
||||||
<ul style="margin-bottom:0">
|
|
||||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.AGAINST.value) %}
|
|
||||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if is_latest and revision.can_vote(request.user) %}
|
|
||||||
<button class="ui bottom attached negative button" type="submit" name="action" value="VoteAgainst">
|
|
||||||
Vote against
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<div class="ui fluid card">
|
|
||||||
<div class="content">
|
|
||||||
<div class="header">Abstentions ({{ revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value).count() }})</div>
|
|
||||||
{% if revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value).exists() %}
|
|
||||||
<div class="description">
|
|
||||||
<ul style="margin-bottom:0">
|
|
||||||
{% for vote in revision.budgetvote_set.filter(is_current=True, vote_type=import('sstreasury.models').BudgetVoteType.ABSTAIN.value) %}
|
|
||||||
<li>{{ vote.voter.first_name }} {{ vote.voter.last_name }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if is_latest and revision.can_vote(request.user) %}
|
|
||||||
<button class="ui bottom attached secondary button" type="submit" name="action" value="VoteAbstain">
|
|
||||||
Abstain
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
This is an urgent reminder from {{ requester.first_name }} {{ requester.last_name }} to vote on a budget titled *{{ revision.name }}* (BU-{{ revision.budget.id }}), which has been endorsed by Treasury and is awaiting committee review.
|
||||||
|
|
||||||
|
{{ baseurl }}{{ url('budget_view', kwargs={'id': revision.budget.id}) }}
|
@ -477,6 +477,20 @@ def budget_action(request, budget, revision):
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
revision.update_state(request.user, models.BudgetState.CANCELLED)
|
revision.update_state(request.user, models.BudgetState.CANCELLED)
|
||||||
|
|
||||||
|
if 'SendVotingReminders' in actions:
|
||||||
|
if revision.state != models.BudgetState.ENDORSED.value:
|
||||||
|
raise PermissionDenied
|
||||||
|
if not request.user.groups.filter(name='Executive').exists():
|
||||||
|
# TODO: Make this group configurable
|
||||||
|
raise PermissionDenied
|
||||||
|
|
||||||
|
# Send emails
|
||||||
|
emailer = Emailer()
|
||||||
|
for user in User.objects.filter(groups__name=revision.approver):
|
||||||
|
# Email only if not voted yet
|
||||||
|
if not revision.budgetvote_set.filter(voter=user).exists():
|
||||||
|
emailer.send_mail([user.email], 'URGENT action required: {} (BU-{})'.format(revision.name, budget.id), 'sstreasury/email/budget_vote_reminder.md', {'revision': revision, 'requester': request.user})
|
||||||
|
|
||||||
if 'VoteInFavour' in actions or 'VoteAgainst' in actions or 'VoteAbstain' in actions:
|
if 'VoteInFavour' in actions or 'VoteAgainst' in actions or 'VoteAbstain' in actions:
|
||||||
if not revision.can_vote(request.user):
|
if not revision.can_vote(request.user):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
@ -489,7 +503,7 @@ def budget_action(request, budget, revision):
|
|||||||
vote_type = models.BudgetVoteType.ABSTAIN
|
vote_type = models.BudgetVoteType.ABSTAIN
|
||||||
|
|
||||||
# Already exists?
|
# Already exists?
|
||||||
if revision.budgetvote_set.filter(is_current=True, voter=request.user, vote_type=vote_type.value):
|
if revision.budgetvote_set.filter(is_current=True, voter=request.user, vote_type=vote_type.value).exists():
|
||||||
# No need to create new vote
|
# No need to create new vote
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user