Validate budget input
This commit is contained in:
parent
0da9bebcae
commit
1975c48273
@ -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) {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user