diff --git a/.gitignore b/.gitignore index 064616b6..9a6f7595 100644 --- a/.gitignore +++ b/.gitignore @@ -99,4 +99,5 @@ atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties -crashlytics-build.properties \ No newline at end of file +crashlytics-build.properties +.vagrant diff --git a/RIGS/finance.py b/RIGS/finance.py index e0f7ec7a..2ae0fc47 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -14,6 +14,7 @@ from z3c.rml import rml2pdf from RIGS import models +import re class InvoiceIndex(generic.ListView): model = models.Invoice @@ -63,8 +64,10 @@ class InvoicePrint(generic.View): pdfData = buffer.read() + escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name) + response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, object.name) + response['Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, escapedEventName) response.write(pdfData) return response diff --git a/RIGS/models.py b/RIGS/models.py index 95d50138..49963a6f 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -9,7 +9,9 @@ from django.utils.encoding import python_2_unicode_compatible import reversion import string import random +from collections import Counter from django.core.urlresolvers import reverse_lazy +from django.core.exceptions import ValidationError from decimal import Decimal @@ -91,9 +93,13 @@ class Person(models.Model, RevisionMixin): def organisations(self): o = [] for e in Event.objects.filter(person=self).select_related('organisation'): - if e.organisation and e.organisation not in o: + if e.organisation: o.append(e.organisation) - return o + + #Count up occurances and put them in descending order + c = Counter(o) + stats = c.most_common() + return stats @property def latest_events(self): @@ -131,9 +137,13 @@ class Organisation(models.Model, RevisionMixin): def persons(self): p = [] for e in Event.objects.filter(organisation=self).select_related('person'): - if e.person and e.person not in p: + if e.person: p.append(e.person) - return p + + #Count up occurances and put them in descending order + c = Counter(p) + stats = c.most_common() + return stats @property def latest_events(self): @@ -415,7 +425,21 @@ class Event(models.Model, RevisionMixin): return reverse_lazy('event_detail', kwargs={'pk': self.pk}) def __str__(self): - return str(self.pk) + ": " + self.name + return unicode(self.pk) + ": " + self.name + + def clean(self): + if self.end_date and self.start_date > self.end_date: + raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.') + + startEndSameDay = not self.end_date or self.end_date == self.start_date + hasStartAndEnd = self.has_start_time and self.has_end_time + if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time: + raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.') + + def save(self, *args, **kwargs): + """Call :meth:`full_clean` before saving.""" + self.full_clean() + super(Event, self).save(*args, **kwargs) class Meta: permissions = ( diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 790a46f4..27f00307 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -47,6 +47,7 @@ class EventCreate(generic.CreateView): def get_context_data(self, **kwargs): context = super(EventCreate, self).get_context_data(**kwargs) context['edit'] = True + context['currentVAT'] = models.VatRate.objects.current_rate() form = context['form'] if re.search('"-\d+"', form['items_json'].value()): @@ -124,7 +125,10 @@ class EventPrint(generic.View): merger.write(merged) response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = "filename=N%05d | %s.pdf" % (object.pk, object.name) + + escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name) + + response['Content-Disposition'] = "filename=N%05d | %s.pdf" % (object.pk, escapedEventName) response.write(merged.getvalue()) return response diff --git a/RIGS/static/imgs/paperwork/corner-bl.jpg b/RIGS/static/imgs/paperwork/corner-bl.jpg new file mode 100644 index 00000000..d31070f3 Binary files /dev/null and b/RIGS/static/imgs/paperwork/corner-bl.jpg differ diff --git a/RIGS/static/imgs/paperwork/corner.jpg b/RIGS/static/imgs/paperwork/corner-tr-su.jpg similarity index 100% rename from RIGS/static/imgs/paperwork/corner.jpg rename to RIGS/static/imgs/paperwork/corner-tr-su.jpg diff --git a/RIGS/static/imgs/paperwork/corner-tr.jpg b/RIGS/static/imgs/paperwork/corner-tr.jpg new file mode 100644 index 00000000..02a6fddd Binary files /dev/null and b/RIGS/static/imgs/paperwork/corner-tr.jpg differ diff --git a/RIGS/static/js/interaction.js b/RIGS/static/js/interaction.js index 8d2fae16..e7d023e1 100644 --- a/RIGS/static/js/interaction.js +++ b/RIGS/static/js/interaction.js @@ -11,6 +11,10 @@ function nl2br (str, is_xhtml) { return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2'); } +function escapeHtml (str) { + return $('
').text(str).html(); +} + function updatePrices() { // individual rows var sum = 0; @@ -101,8 +105,8 @@ $('body').on('submit', '#item-form', function (e) { } // update the table $row = $('#item-' + pk); - $row.find('.name').html(fields.name); - $row.find('.description').html(nl2br(fields.description)); + $row.find('.name').html(escapeHtml(fields.name)); + $row.find('.description').html(nl2br(escapeHtml(fields.description))); $row.find('.cost').html(parseFloat(fields.cost).toFixed(2)); $row.find('.quantity').html(fields.quantity); diff --git a/RIGS/static/js/modal.js b/RIGS/static/js/modal.js index e7704b8f..d50a8560 100644 --- a/RIGS/static/js/modal.js +++ b/RIGS/static/js/modal.js @@ -1,5 +1,5 @@ /* ======================================================================== - * Bootstrap: modal.js v3.3.2 + * Bootstrap: modal.js v3.3.5 * http://getbootstrap.com/javascript/#modals * ======================================================================== * Copyright 2011-2015 Twitter, Inc. @@ -14,12 +14,15 @@ // ====================== var Modal = function (element, options) { - this.options = options - this.$body = $(document.body) - this.$element = $(element) - this.$backdrop = - this.isShown = null - this.scrollbarWidth = 0 + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false if (this.options.remote) { this.$element @@ -30,7 +33,7 @@ } } - Modal.VERSION = '3.3.2' + Modal.VERSION = '3.3.5' Modal.TRANSITION_DURATION = 300 Modal.BACKDROP_TRANSITION_DURATION = 150 @@ -64,6 +67,12 @@ this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + this.backdrop(function () { var transition = $.support.transition && that.$element.hasClass('fade') @@ -75,23 +84,20 @@ .show() .scrollTop(0) - if (that.options.backdrop) that.adjustBackdrop() that.adjustDialog() if (transition) { that.$element[0].offsetWidth // force reflow } - that.$element - .addClass('in') - .attr('aria-hidden', false) + that.$element.addClass('in') that.enforceFocus() var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) transition ? - that.$element.find('.modal-dialog') // wait for modal to slide in + that.$dialog // wait for modal to slide in .one('bsTransitionEnd', function () { that.$element.trigger('focus').trigger(e) }) @@ -118,8 +124,10 @@ this.$element .removeClass('in') - .attr('aria-hidden', true) .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') $.support.transition && this.$element.hasClass('fade') ? this.$element @@ -179,14 +187,20 @@ if (this.isShown && this.options.backdrop) { var doAnimate = $.support.transition && animate - this.$backdrop = $('