Validate budget input

This commit is contained in:
Yingtong Li 2020-02-12 09:53:36 +11:00
parent 0da9bebcae
commit 1975c48273
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
2 changed files with 100 additions and 31 deletions

View File

@ -24,6 +24,18 @@
<h1>{% if request.resolver_match.url_name == 'budget_new' %}New{% else %}Edit{% endif %} budget</h1> <h1>{% if request.resolver_match.url_name == 'budget_new' %}New{% else %}Edit{% endif %} budget</h1>
<form class="ui form" method="POST"> <form class="ui form" method="POST">
{% if errors %}
<div class="ui visible error message">
<div class="header">Invalid input</div>
<p>Unable to save the budget. Please correct the issues below and try again:</p>
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<div class="ui disabled inline grid field"> <div class="ui disabled inline grid field">
<label class="three wide column">ID</label> <label class="three wide column">ID</label>
<input class="eleven wide column" type="text" name="id" value="{{ 'BU-{}'.format(revision.budget.id) if revision.budget.id != None else '' }}"> <input class="eleven wide column" type="text" name="id" value="{{ 'BU-{}'.format(revision.budget.id) if revision.budget.id != None else '' }}">
@ -62,7 +74,7 @@
<div class="four wide column">Estimated no. of attendees</div> <div class="four wide column">Estimated no. of attendees</div>
<input class="seven wide column" type="text" name="event_attendees" value="{{ revision.event_attendees or '' }}"> <input class="seven wide column" type="text" name="event_attendees" value="{{ revision.event_attendees or '' }}">
</div> </div>
<div class="ui required inline grid field"> <div class="ui inline grid field">
<label class="three wide column">Contributors <span data-tooltip="To share this budget with other contributors, enter their email addresses, one per line"><i class="grey question circle icon"></i></span></label> <label class="three wide column">Contributors <span data-tooltip="To share this budget with other contributors, enter their email addresses, one per line"><i class="grey question circle icon"></i></span></label>
<textarea class="eleven wide column" rows="2" name="contributors" style="font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;">{{ contributors }}</textarea> <textarea class="eleven wide column" rows="2" name="contributors" style="font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;">{{ contributors }}</textarea>
</div> </div>
@ -178,10 +190,6 @@
$('.ui.form').form({ $('.ui.form').form({
on: 'blur', on: 'blur',
keyboardShortcuts: false, keyboardShortcuts: false,
fields: {
name: 'empty',
contributors: 'empty'
},
onSuccess: function(event, fields) { onSuccess: function(event, fields) {
var revenue_data = []; var revenue_data = [];
$('#revenue_grid .jsgrid-grid-body tr:not(.totalrow):not(.jsgrid-nodata-row)').each(function(i, el) { $('#revenue_grid .jsgrid-grid-body tr:not(.totalrow):not(.jsgrid-nodata-row)').each(function(i, el) {

View File

@ -16,7 +16,7 @@
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied, ValidationError
from django.core.validators import validate_email from django.core.validators import validate_email
from django.conf import settings from django.conf import settings
@ -32,6 +32,7 @@ from . import models
from . import xero from . import xero
from ssmain.email import Emailer from ssmain.email import Emailer
from datetime import datetime
import functools import functools
import io import io
import itertools import itertools
@ -88,14 +89,58 @@ def claim_editable(viewfunc):
# HELPER FUNCTIONS # HELPER FUNCTIONS
class FormValidationError(Exception):
def __init__(self, data, errors):
super().__init__(self)
self.data = data
self.errors = errors
def revision_from_form(budget, revision, form): def revision_from_form(budget, revision, form):
errors = []
revision.budget = budget revision.budget = budget
revision.name = form['name'] if form['name']:
revision.date = form['date'] if form['date'] else None if len(form['name']) > 100:
errors.append('Title must be at most 100 characters')
revision.name = form['name']
else:
errors.append('A title must be specified')
revision.event_dt = form['event_dt'] if form['event_dt'] else None if form['date']:
revision.event_attendees = form['event_attendees'] if form['event_attendees'] else None try:
form_date = datetime.strptime(form['date'], '%Y-%m-%d')
except ValueError:
errors.append('Due date is not a valid date')
revision.date = form['date']
else:
errors.append('A due date must be specified')
if form['event_dt']:
try:
form_event_dt = datetime.strptime(form['event_dt'], '%Y-%m-%d %H:%M')
except ValueError:
errors.append('Event date/time is not a valid date-time')
revision.event_dt = form['event_dt']
else:
revision.event_dt = None
if form['event_attendees']:
if len(form['event_attendees']) > 20:
errors.append('Event attendees must be at most 20 characters')
revision.event_attendees = form['event_attendees']
else:
revision.event_attendees = None
if form['contributors']:
contributors = form['contributors'].split('\n')
try:
for contributor in contributors:
validate_email(contributor.strip())
except ValidationError:
errors.append('Contributors contains invalid data – type only valid email addresses, one per line')
else:
contributors = []
revision.comments = form['comments'] revision.comments = form['comments']
revision.revenue = json.loads(form['revenue']) revision.revenue = json.loads(form['revenue'])
@ -104,11 +149,11 @@ def revision_from_form(budget, revision, form):
revision.expense_comments = form['expense_comments'] revision.expense_comments = form['expense_comments']
revision.expense_no_emergency_fund = True if form.get('expense_no_emergency_fund', False) else False revision.expense_no_emergency_fund = True if form.get('expense_no_emergency_fund', False) else False
if errors:
raise FormValidationError(revision, errors)
revision.save() revision.save()
contributors = form['contributors'].split('\n')
for contributor in contributors:
validate_email(contributor.strip())
for contributor in contributors: for contributor in contributors:
try: try:
user = User.objects.get(email=contributor.strip()) user = User.objects.get(email=contributor.strip())
@ -227,15 +272,22 @@ def budget_print(request, budget, revision):
@login_required @login_required
def budget_new(request): def budget_new(request):
if request.method == 'POST': if request.method == 'POST':
with transaction.atomic(): try:
budget = models.Budget() with transaction.atomic():
budget.save() budget = models.Budget()
revision = models.BudgetRevision() budget.save()
revision.author = request.user revision = models.BudgetRevision()
revision.time = timezone.now() revision.author = request.user
revision.action = models.BudgetAction.CREATE.value revision.time = timezone.now()
revision.state = models.BudgetState.DRAFT.value revision.action = models.BudgetAction.CREATE.value
revision = revision_from_form(budget, revision, request.POST) revision.state = models.BudgetState.DRAFT.value
revision = revision_from_form(budget, revision, request.POST)
except FormValidationError as form_error:
return render(request, 'sstreasury/budget_edit.html', {
'revision': form_error.data,
'contributors': request.POST['contributors'],
'errors': form_error.errors
})
if request.POST['submit'] == 'Save': if request.POST['submit'] == 'Save':
return redirect(reverse('budget_view', kwargs={'id': budget.id})) return redirect(reverse('budget_view', kwargs={'id': budget.id}))
@ -248,7 +300,8 @@ def budget_new(request):
return render(request, 'sstreasury/budget_edit.html', { return render(request, 'sstreasury/budget_edit.html', {
'revision': revision, 'revision': revision,
'contributors': request.user.email 'contributors': request.user.email,
'errors': []
}) })
@login_required @login_required
@ -260,13 +313,20 @@ def budget_edit(request, budget, revision):
budget.delete() budget.delete()
return redirect(reverse('budget_list')) return redirect(reverse('budget_list'))
with transaction.atomic(): try:
new_revision = models.BudgetRevision() with transaction.atomic():
new_revision.author = request.user new_revision = models.BudgetRevision()
new_revision.time = timezone.now() new_revision.author = request.user
new_revision.action = models.BudgetAction.EDIT.value new_revision.time = timezone.now()
new_revision.state = revision.state new_revision.action = models.BudgetAction.EDIT.value
new_revision = revision_from_form(budget, new_revision, request.POST) new_revision.state = revision.state
new_revision = revision_from_form(budget, new_revision, request.POST)
except FormValidationError as form_error:
return render(request, 'sstreasury/budget_edit.html', {
'revision': form_error.data,
'contributors': request.POST['contributors'],
'errors': form_error.errors
})
if request.POST['submit'] == 'Save': if request.POST['submit'] == 'Save':
return redirect(reverse('budget_view', kwargs={'id': budget.id})) return redirect(reverse('budget_view', kwargs={'id': budget.id}))
@ -275,7 +335,8 @@ def budget_edit(request, budget, revision):
else: else:
return render(request, 'sstreasury/budget_edit.html', { return render(request, 'sstreasury/budget_edit.html', {
'revision': revision, 'revision': revision,
'contributors': '\n'.join(revision.contributors.all().values_list('email', flat=True)) 'contributors': '\n'.join(revision.contributors.all().values_list('email', flat=True)),
'errors': []
}) })
@login_required @login_required