diff --git a/sstreasury/jinja2/sstreasury/budget_list.html b/sstreasury/jinja2/sstreasury/budget_list.html
index 54cef10..b46e00f 100644
--- a/sstreasury/jinja2/sstreasury/budget_list.html
+++ b/sstreasury/jinja2/sstreasury/budget_list.html
@@ -59,17 +59,13 @@
{% if budgets_open %}
Open budgets
- {% for budget in budgets_open %}
- {{ budget.name }}
- {% endfor %}
+ {{ listbudgets(budgets_open) }}
{% endif %}
{% if budgets_closed %}
Closed budgets
- {% for budget in budgets_closed %}
- {{ budget.name }}
- {% endfor %}
+ {{ listbudgets(budgets_closed) }}
{% endif %}
{% endblock %}
diff --git a/sstreasury/jinja2/sstreasury/budget_view.html b/sstreasury/jinja2/sstreasury/budget_view.html
index a09d1c6..deb9106 100644
--- a/sstreasury/jinja2/sstreasury/budget_view.html
+++ b/sstreasury/jinja2/sstreasury/budget_view.html
@@ -29,9 +29,24 @@
{% if revision.state == import('sstreasury.models').BudgetState.DRAFT.value or revision.state == import('sstreasury.models').BudgetState.RESUBMIT.value %}
Submit
+ {% elif revision.state == import('sstreasury.models').BudgetState.AWAIT_REVIEW.value and request.user.groups.filter(name='Treasury').exists() %}
+ Endorse
+
+ Return for re-drafting
+ {% elif revision.state == import('sstreasury.models').BudgetState.ENDORSED.value and request.user.groups.filter(name='Secretary').exists() %}
+ {# TODO #}
+ {% elif revision.state == import('sstreasury.models').BudgetState.APPROVED.value %}
+ {# Blank #}
+ {% else %}
+ Withdraw
+ {% endif %}
+
+ {% if revision.state == import('sstreasury.models').BudgetState.DRAFT.value or revision.state == import('sstreasury.models').BudgetState.RESUBMIT.value or (revision.state != import('sstreasury.models').BudgetState.APPROVED.value and (request.user.groups.filter(name='Treasury').exists() or request.user.groups.filter(name='Secretary').exists())) %}
Edit
- {% elif revision.state != import('sstreasury.models').BudgetState.APPROVED.value %}
- Withdraw
+ {% else %}
+
+
This budget has been submitted and is now awaiting approval. If you wish to edit this budget, you must first withdraw it. This will revert the budget to a draft.
+
{% endif %}
@@ -126,6 +141,12 @@
+
+ {% if revision.state == import('sstreasury.models').BudgetState.AWAIT_REVIEW.value and request.user.groups.filter(name='Treasury').exists() %}
+ Comment and endorse
+
+ Comment and return for re-drafting
+ {% endif %}
diff --git a/sstreasury/jinja2/sstreasury/email/endorsed.md b/sstreasury/jinja2/sstreasury/email/endorsed.md
new file mode 100644
index 0000000..1003ce0
--- /dev/null
+++ b/sstreasury/jinja2/sstreasury/email/endorsed.md
@@ -0,0 +1,3 @@
+Your budget titled *{{ revision.name }}* has been reviewed and endorsed by Treasury, and referred to the committee. The committee will determine whether or not to approve the budget at its next meeting.
+
+{{ baseurl }}{{ url('budget_view', kwargs={'id': revision.budget.id}) }}
diff --git a/sstreasury/jinja2/sstreasury/email/returned.md b/sstreasury/jinja2/sstreasury/email/returned.md
new file mode 100644
index 0000000..7126511
--- /dev/null
+++ b/sstreasury/jinja2/sstreasury/email/returned.md
@@ -0,0 +1,3 @@
+Your budget titled *{{ revision.name }}* has been reviewed by Treasury and returned to you for re-drafting. You should make any requested changes and resubmit the budget.
+
+{{ baseurl }}{{ url('budget_view', kwargs={'id': revision.budget.id}) }}
diff --git a/sstreasury/views.py b/sstreasury/views.py
index 2e9a54e..0b06ed4 100644
--- a/sstreasury/views.py
+++ b/sstreasury/views.py
@@ -28,6 +28,7 @@ from django.views import generic
from . import models
from ssmain.email import Emailer
+import functools
import itertools
import json
@@ -44,12 +45,24 @@ def budget_list(request):
for budget in models.Budget.objects.all():
revision = budget.budgetrevision_set.reverse()[0]
state = models.BudgetState(revision.state)
- if state in [models.BudgetState.DRAFT, models.BudgetState.RESUBMIT]:
- budgets_action.append(revision)
- elif state in [models.BudgetState.AWAIT_REVIEW, models.BudgetState.ENDORSED]:
- budgets_open.append(revision)
+
+ if request.user.groups.filter(name='Treasury').exists():
+ if state == models.BudgetState.AWAIT_REVIEW:
+ budgets_action.append(revision)
+ elif state != models.BudgetState.APPROVED:
+ budgets_open.append(revision)
+ else:
+ budgets_closed.append(revision)
else:
- budgets_closed.append(revision)
+ if request.user not in revision.contributors.all():
+ continue
+
+ if state in [models.BudgetState.DRAFT, models.BudgetState.RESUBMIT]:
+ budgets_action.append(revision)
+ elif state in [models.BudgetState.AWAIT_REVIEW, models.BudgetState.ENDORSED]:
+ budgets_open.append(revision)
+ else:
+ budgets_closed.append(revision)
return render(request, 'sstreasury/budget_list.html', {
'budgets_action': budgets_action,
@@ -131,15 +144,46 @@ def budget_new(request):
'contributors': request.user.email
})
-@login_required
-def budget_edit(request, id):
- if request.method == 'POST':
+def uses_budget(viewfunc):
+ @functools.wraps(viewfunc)
+ def func(request, id):
budget = models.Budget.objects.get(id=id)
revision = budget.budgetrevision_set.reverse()[0]
-
+ return viewfunc(request, budget, revision)
+ return func
+
+def budget_viewable(viewfunc):
+ @functools.wraps(viewfunc)
+ def func(request, budget, revision):
if request.user not in revision.contributors.all():
+ if not request.user.groups.filter(name='Treasury').exists():
+ raise PermissionDenied
+
+ return viewfunc(request, budget, revision)
+ return func
+
+def budget_editable(viewfunc):
+ @functools.wraps(viewfunc)
+ def func(request, budget, revision):
+ if revision.state == models.BudgetState.APPROVED.value:
raise PermissionDenied
+ if revision.state != models.BudgetState.DRAFT.value and revision.state != models.BudgetState.RESUBMIT.value:
+ if not request.user.groups.filter(name='Treasury').exists():
+ raise PermissionDenied
+
+ if request.user not in revision.contributors.all():
+ if not request.user.groups.filter(name='Treasury').exists():
+ raise PermissionDenied
+
+ return viewfunc(request, budget, revision)
+ return func
+
+@login_required
+@uses_budget
+@budget_editable
+def budget_edit(request, budget, revision):
+ if request.method == 'POST':
if request.POST['submit'] == 'Delete':
budget.delete()
return redirect(reverse('budget_list'))
@@ -156,23 +200,18 @@ def budget_edit(request, id):
else:
return redirect(reverse('budget_edit', kwargs={'id': budget.id}))
else:
- budget = models.Budget.objects.get(id=id)
- revision = budget.budgetrevision_set.reverse()[0]
-
return render(request, 'sstreasury/budget_edit.html', {
'revision': revision,
'contributors': '\n'.join(revision.contributors.all().values_list('email', flat=True))
})
@login_required
-def budget_action(request, id):
- budget = models.Budget.objects.get(id=id)
- revision = budget.budgetrevision_set.reverse()[0]
+@uses_budget
+@budget_editable
+def budget_action(request, budget, revision):
+ actions = request.POST['action'].split(',')
- if request.user not in revision.contributors.all():
- raise PermissionDenied
-
- if request.POST['action'] == 'Comment':
+ if 'Comment' in actions and request.POST.get('comment', None):
with transaction.atomic():
comment = models.BudgetComment()
comment.budget = budget
@@ -189,12 +228,11 @@ def budget_action(request, id):
if user != request.user:
emailer.send_mail([user.email], 'New comment on budget: {}'.format(revision.name), 'sstreasury/email/commented.md', {'revision': revision, 'comment': comment})
- if request.POST['action'] == 'Submit':
+ if 'Submit' in actions:
if revision.state != models.BudgetState.DRAFT.value and revision.state != models.BudgetState.RESUBMIT.value:
raise PermissionDenied
with transaction.atomic():
- # Copy revision
revision.copy()
revision.author = request.user
revision.time = timezone.now()
@@ -204,15 +242,14 @@ def budget_action(request, id):
emailer = Emailer()
for user in User.objects.filter(groups__name='Treasury'):
- emailer.send_mail([user.email], 'Budget submitted: {}'.format(revision.name), 'sstreasury/email/submitted_treasurer.md', {'revision': revision})
+ emailer.send_mail([user.email], 'Action required: Budget submitted: {}'.format(revision.name), 'sstreasury/email/submitted_treasurer.md', {'revision': revision})
for user in revision.contributors.all():
emailer.send_mail([user.email], 'Budget submitted: {}'.format(revision.name), 'sstreasury/email/submitted_drafter.md', {'revision': revision})
- if request.POST['action'] == 'Withdraw':
+ if 'Withdraw' in actions:
if revision.state == models.BudgetState.DRAFT.value or revision.state == models.BudgetState.RESUBMIT.value or revision.state == models.BudgetState.APPROVED.value:
raise PermissionDenied
- # Copy revision
revision.copy()
revision.author = request.user
revision.time = timezone.now()
@@ -220,4 +257,40 @@ def budget_action(request, id):
revision.action = models.BudgetAction.UPDATE_STATE.value
revision.save()
+ if 'Endorse' in actions:
+ if revision.state != models.BudgetState.AWAIT_REVIEW.value:
+ raise PermissionDenied
+ if not request.user.groups.filter(name='Treasury').exists():
+ raise PermissionDenied
+
+ with transaction.atomic():
+ revision.copy()
+ revision.author = request.user
+ revision.time = timezone.now()
+ revision.state = models.BudgetState.ENDORSED.value
+ revision.action = models.BudgetAction.UPDATE_STATE.value
+ revision.save()
+
+ emailer = Emailer()
+ for user in revision.contributors.all():
+ emailer.send_mail([user.email], 'Budget endorsed, awaiting committee approval: {}'.format(revision.name), 'sstreasury/email/endorsed.md', {'revision': revision})
+
+ if 'Return' in actions:
+ if revision.state != models.BudgetState.AWAIT_REVIEW.value:
+ raise PermissionDenied
+ if not request.user.groups.filter(name='Treasury').exists():
+ raise PermissionDenied
+
+ with transaction.atomic():
+ revision.copy()
+ revision.author = request.user
+ revision.time = timezone.now()
+ revision.state = models.BudgetState.RESUBMIT.value
+ revision.action = models.BudgetAction.UPDATE_STATE.value
+ revision.save()
+
+ emailer = Emailer()
+ for user in revision.contributors.all():
+ emailer.send_mail([user.email], 'Action required: Budget returned for re-drafting: {}'.format(revision.name), 'sstreasury/email/returned.md', {'revision': revision})
+
return redirect(reverse('budget_view', kwargs={'id': budget.id}))