mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-26 16:18:23 +00:00
Merge pull request #283 from nottinghamtec/feature/online-auth
Add online RIGS authorisation
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.template import RequestContext
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
@@ -79,3 +78,17 @@ def api_key_required(function):
|
|||||||
return error_resp
|
return error_resp
|
||||||
return function(request, *args, **kwargs)
|
return function(request, *args, **kwargs)
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
def nottinghamtec_address_required(function):
|
||||||
|
"""
|
||||||
|
Checks that the current user has an email address ending @nottinghamtec.co.uk
|
||||||
|
"""
|
||||||
|
def wrap(request, *args, **kwargs):
|
||||||
|
# Fail if current user's email address isn't @nottinghamtec.co.uk
|
||||||
|
if not request.user.email.endswith('@nottinghamtec.co.uk'):
|
||||||
|
error_resp = render(request, 'RIGS/eventauthorisation_request_error.html')
|
||||||
|
return error_resp
|
||||||
|
|
||||||
|
return function(request, *args, **kwargs)
|
||||||
|
return wrap
|
||||||
|
|||||||
@@ -233,3 +233,4 @@ TEMPLATES = [
|
|||||||
USE_GRAVATAR = True
|
USE_GRAVATAR = True
|
||||||
|
|
||||||
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
||||||
|
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'RIGS.apps.RIGSAppConfig'
|
||||||
|
|||||||
8
RIGS/apps.py
Normal file
8
RIGS/apps.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class RIGSAppConfig(AppConfig):
|
||||||
|
name = 'RIGS'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import RIGS.signals
|
||||||
@@ -144,4 +144,32 @@ class EventForm(forms.ModelForm):
|
|||||||
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
|
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
|
||||||
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
|
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
|
||||||
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
|
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
|
||||||
'collector', 'purchase_order']
|
'purchase_order', 'collector']
|
||||||
|
|
||||||
|
|
||||||
|
class BaseClientEventAuthorisationForm(forms.ModelForm):
|
||||||
|
tos = forms.BooleanField(required=True, label="Terms of hire")
|
||||||
|
name = forms.CharField(label="Your Name")
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.cleaned_data.get('amount') != self.instance.event.total:
|
||||||
|
self.add_error('amount', 'The amount authorised must equal the total for the event (inc VAT).')
|
||||||
|
return super(BaseClientEventAuthorisationForm, self).clean()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(InternalClientEventAuthorisationForm, self).__init__(**kwargs)
|
||||||
|
self.fields['uni_id'].required = True
|
||||||
|
self.fields['account_code'].required = True
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.EventAuthorisation
|
||||||
|
fields = ('tos', 'name', 'amount', 'uni_id', 'account_code')
|
||||||
|
|
||||||
|
|
||||||
|
class EventAuthorisationRequestForm(forms.Form):
|
||||||
|
email = forms.EmailField(required=True, label='Authoriser Email')
|
||||||
|
|||||||
27
RIGS/migrations/0025_eventauthorisation.py
Normal file
27
RIGS/migrations/0025_eventauthorisation.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0024_auto_20160229_2042'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EventAuthorisation',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('email', models.EmailField(max_length=254)),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('uni_id', models.CharField(max_length=10, null=True, verbose_name=b'University ID', blank=True)),
|
||||||
|
('account_code', models.CharField(max_length=50, null=True, blank=True)),
|
||||||
|
('amount', models.DecimalField(verbose_name=b'authorisation amount', max_digits=10, decimal_places=2)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('event', models.ForeignKey(related_name='authroisations', to='RIGS.Event')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
18
RIGS/migrations/0026_remove_eventauthorisation_created_at.py
Normal file
18
RIGS/migrations/0026_remove_eventauthorisation_created_at.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0025_eventauthorisation'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='eventauthorisation',
|
||||||
|
name='created_at',
|
||||||
|
),
|
||||||
|
]
|
||||||
19
RIGS/migrations/0027_eventauthorisation_event_singular.py
Normal file
19
RIGS/migrations/0027_eventauthorisation_event_singular.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0026_remove_eventauthorisation_created_at'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventauthorisation',
|
||||||
|
name='event',
|
||||||
|
field=models.OneToOneField(related_name='authorisation', to='RIGS.Event'),
|
||||||
|
),
|
||||||
|
]
|
||||||
21
RIGS/migrations/0029_eventauthorisation_sent_by.py
Normal file
21
RIGS/migrations/0029_eventauthorisation_sent_by.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0027_eventauthorisation_event_singular'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='eventauthorisation',
|
||||||
|
name='sent_by',
|
||||||
|
field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
30
RIGS/migrations/0030_auth_request_sending.py
Normal file
30
RIGS/migrations/0030_auth_request_sending.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0029_eventauthorisation_sent_by'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='auth_request_at',
|
||||||
|
field=models.DateTimeField(null=True, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='auth_request_by',
|
||||||
|
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='auth_request_to',
|
||||||
|
field=models.EmailField(max_length=254, null=True, blank=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
16
RIGS/migrations/0031_merge_20170512_2102.py
Normal file
16
RIGS/migrations/0031_merge_20170512_2102.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.1 on 2017-05-12 20:02
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0030_auth_request_sending'),
|
||||||
|
('RIGS', '0026_auto_20170510_1846'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
@@ -332,6 +332,11 @@ class Event(models.Model, RevisionMixin):
|
|||||||
purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO')
|
purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO')
|
||||||
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
|
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
|
||||||
|
|
||||||
|
# Authorisation request details
|
||||||
|
auth_request_by = models.ForeignKey('Profile', null=True, blank=True)
|
||||||
|
auth_request_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
auth_request_to = models.EmailField(null=True, blank=True)
|
||||||
|
|
||||||
# Calculated values
|
# Calculated values
|
||||||
"""
|
"""
|
||||||
EX Vat
|
EX Vat
|
||||||
@@ -364,7 +369,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def vat(self):
|
def vat(self):
|
||||||
return self.sum_total * self.vat_rate.rate
|
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01'))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Inc VAT
|
Inc VAT
|
||||||
@@ -372,7 +377,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def total(self):
|
def total(self):
|
||||||
return self.sum_total + self.vat
|
return Decimal(self.sum_total + self.vat).quantize(Decimal('.01'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cancelled(self):
|
def cancelled(self):
|
||||||
@@ -382,6 +387,10 @@ class Event(models.Model, RevisionMixin):
|
|||||||
def confirmed(self):
|
def confirmed(self):
|
||||||
return (self.status == self.BOOKED or self.status == self.CONFIRMED)
|
return (self.status == self.BOOKED or self.status == self.CONFIRMED)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def authorised(self):
|
||||||
|
return not self.internal and self.purchase_order or self.authorisation.amount == self.total
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_start_time(self):
|
def has_start_time(self):
|
||||||
return self.start_time is not None
|
return self.start_time is not None
|
||||||
@@ -442,6 +451,10 @@ class Event(models.Model, RevisionMixin):
|
|||||||
else:
|
else:
|
||||||
return endDate
|
return endDate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def internal(self):
|
||||||
|
return self.organisation and self.organisation.union_account
|
||||||
|
|
||||||
objects = EventManager()
|
objects = EventManager()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
@@ -498,6 +511,17 @@ class EventCrew(models.Model):
|
|||||||
notes = models.TextField(blank=True, null=True)
|
notes = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
@reversion.register
|
||||||
|
class EventAuthorisation(models.Model, RevisionMixin):
|
||||||
|
event = models.OneToOneField('Event', related_name='authorisation')
|
||||||
|
email = models.EmailField()
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
|
||||||
|
account_code = models.CharField(max_length=50, blank=True, null=True)
|
||||||
|
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
|
||||||
|
sent_by = models.ForeignKey('RIGS.Profile')
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Invoice(models.Model):
|
class Invoice(models.Model):
|
||||||
event = models.OneToOneField('Event')
|
event = models.OneToOneField('Event')
|
||||||
|
|||||||
225
RIGS/rigboard.py
225
RIGS/rigboard.py
@@ -1,8 +1,9 @@
|
|||||||
import os
|
|
||||||
import cStringIO as StringIO
|
import cStringIO as StringIO
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import urllib2
|
import urllib2
|
||||||
|
|
||||||
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
@@ -10,14 +11,19 @@ from django.template import RequestContext
|
|||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.core import signing
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.core.exceptions import SuspiciousOperation
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from z3c.rml import rml2pdf
|
from z3c.rml import rml2pdf
|
||||||
from PyPDF2 import PdfFileMerger, PdfFileReader
|
from PyPDF2 import PdfFileMerger, PdfFileReader
|
||||||
import simplejson
|
import simplejson
|
||||||
|
import premailer
|
||||||
|
|
||||||
from RIGS import models, forms
|
from RIGS import models, forms
|
||||||
|
from PyRIGS import decorators
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import copy
|
import copy
|
||||||
@@ -36,15 +42,17 @@ class RigboardIndex(generic.TemplateView):
|
|||||||
context['events'] = models.Event.objects.current_events()
|
context['events'] = models.Event.objects.current_events()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class WebCalendar(generic.TemplateView):
|
class WebCalendar(generic.TemplateView):
|
||||||
template_name = 'RIGS/calendar.html'
|
template_name = 'RIGS/calendar.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(WebCalendar, self).get_context_data(**kwargs)
|
context = super(WebCalendar, self).get_context_data(**kwargs)
|
||||||
context['view'] = kwargs.get('view','')
|
context['view'] = kwargs.get('view', '')
|
||||||
context['date'] = kwargs.get('date','')
|
context['date'] = kwargs.get('date', '')
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class EventDetail(generic.DetailView):
|
class EventDetail(generic.DetailView):
|
||||||
model = models.Event
|
model = models.Event
|
||||||
|
|
||||||
@@ -53,7 +61,6 @@ class EventOembed(generic.View):
|
|||||||
model = models.Event
|
model = models.Event
|
||||||
|
|
||||||
def get(self, request, pk=None):
|
def get(self, request, pk=None):
|
||||||
|
|
||||||
embed_url = reverse('event_embed', args=[pk])
|
embed_url = reverse('event_embed', args=[pk])
|
||||||
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
||||||
|
|
||||||
@@ -85,7 +92,6 @@ class EventCreate(generic.CreateView):
|
|||||||
if re.search('"-\d+"', form['items_json'].value()):
|
if re.search('"-\d+"', form['items_json'].value()):
|
||||||
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
|
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
|
||||||
|
|
||||||
|
|
||||||
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
||||||
for field, model in form.related_models.iteritems():
|
for field, model in form.related_models.iteritems():
|
||||||
value = form[field].value()
|
value = form[field].value()
|
||||||
@@ -112,20 +118,35 @@ class EventUpdate(generic.UpdateView):
|
|||||||
value = form[field].value()
|
value = form[field].value()
|
||||||
if value is not None and value != '':
|
if value is not None and value != '':
|
||||||
context[field] = model.objects.get(pk=value)
|
context[field] = model.objects.get(pk=value)
|
||||||
|
|
||||||
|
# If this event has already been emailed to a client, show a warning
|
||||||
|
if self.object.auth_request_at is not None:
|
||||||
|
messages.info(self.request, 'This event has already been sent to the client for authorisation, any changes you make will be visible to them immediately.')
|
||||||
|
|
||||||
|
if hasattr(self.object, 'authorised'):
|
||||||
|
messages.warning(self.request, 'This event has already been authorised by client, any changes to price will require reauthorisation.')
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse_lazy('event_detail', kwargs={'pk': self.object.pk})
|
return reverse_lazy('event_detail', kwargs={'pk': self.object.pk})
|
||||||
|
|
||||||
|
|
||||||
class EventDuplicate(EventUpdate):
|
class EventDuplicate(EventUpdate):
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating)
|
old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating)
|
||||||
new = copy.copy(old) # Make a copy of the object in memory
|
new = copy.copy(old) # Make a copy of the object in memory
|
||||||
new.based_on = old # Make the new event based on the old event
|
new.based_on = old # Make the new event based on the old event
|
||||||
new.purchase_order = None
|
new.purchase_order = None
|
||||||
|
|
||||||
if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
|
# Remove all the authorisation information from the new event
|
||||||
new.pk = None # This means a new event will be created on save, and all items will be re-created
|
new.auth_request_to = None
|
||||||
|
new.auth_request_by = None
|
||||||
|
new.auth_request_at = None
|
||||||
|
|
||||||
|
if self.request.method in (
|
||||||
|
'POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
|
||||||
|
new.pk = None # This means a new event will be created on save, and all items will be re-created
|
||||||
else:
|
else:
|
||||||
messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.')
|
messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.')
|
||||||
|
|
||||||
@@ -141,34 +162,26 @@ class EventPrint(generic.View):
|
|||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
object = get_object_or_404(models.Event, pk=pk)
|
object = get_object_or_404(models.Event, pk=pk)
|
||||||
template = get_template('RIGS/event_print.xml')
|
template = get_template('RIGS/event_print.xml')
|
||||||
copies = ('TEC', 'Client')
|
|
||||||
|
|
||||||
merger = PdfFileMerger()
|
merger = PdfFileMerger()
|
||||||
|
|
||||||
for thisCopy in copies:
|
context = {
|
||||||
|
'object': object,
|
||||||
|
'fonts': {
|
||||||
|
'opensans': {
|
||||||
|
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
||||||
|
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'quote': True,
|
||||||
|
'current_user': request.user,
|
||||||
|
}
|
||||||
|
|
||||||
context = { # this should be outside the loop, but bug in 1.8.2 prevents this
|
rml = template.render(context)
|
||||||
'object': object,
|
|
||||||
'fonts': {
|
|
||||||
'opensans': {
|
|
||||||
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
|
||||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'copy': thisCopy,
|
|
||||||
'current_user': request.user,
|
|
||||||
}
|
|
||||||
|
|
||||||
# context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3
|
buffer = rml2pdf.parseString(rml)
|
||||||
|
merger.append(PdfFileReader(buffer))
|
||||||
rml = template.render(context)
|
buffer.close()
|
||||||
buffer = StringIO.StringIO()
|
|
||||||
|
|
||||||
buffer = rml2pdf.parseString(rml)
|
|
||||||
|
|
||||||
merger.append(PdfFileReader(buffer))
|
|
||||||
|
|
||||||
buffer.close()
|
|
||||||
|
|
||||||
terms = urllib2.urlopen(settings.TERMS_OF_HIRE_URL)
|
terms = urllib2.urlopen(settings.TERMS_OF_HIRE_URL)
|
||||||
merger.append(StringIO.StringIO(terms.read()))
|
merger.append(StringIO.StringIO(terms.read()))
|
||||||
@@ -184,6 +197,7 @@ class EventPrint(generic.View):
|
|||||||
response.write(merged.getvalue())
|
response.write(merged.getvalue())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class EventArchive(generic.ArchiveIndexView):
|
class EventArchive(generic.ArchiveIndexView):
|
||||||
model = models.Event
|
model = models.Event
|
||||||
date_field = "start_date"
|
date_field = "start_date"
|
||||||
@@ -220,3 +234,148 @@ class EventArchive(generic.ArchiveIndexView):
|
|||||||
messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.")
|
messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.")
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class EventAuthorise(generic.UpdateView):
|
||||||
|
template_name = 'RIGS/eventauthorisation_form.html'
|
||||||
|
success_template = 'RIGS/eventauthorisation_success.html'
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.object = form.save()
|
||||||
|
|
||||||
|
self.template_name = self.success_template
|
||||||
|
messages.add_message(self.request, messages.SUCCESS,
|
||||||
|
'Success! Your event has been authorised. ' +
|
||||||
|
'You will also receive email confirmation to %s.' % (self.object.email))
|
||||||
|
return self.render_to_response(self.get_context_data())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def event(self):
|
||||||
|
return models.Event.objects.select_related('organisation', 'person', 'venue').get(pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
return getattr(self.event, 'authorisation', None)
|
||||||
|
|
||||||
|
def get_form_class(self):
|
||||||
|
return forms.InternalClientEventAuthorisationForm
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(EventAuthorise, self).get_context_data(**kwargs)
|
||||||
|
context['event'] = self.event
|
||||||
|
|
||||||
|
context['tos_url'] = settings.TERMS_OF_HIRE_URL
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if self.get_object() is not None and self.get_object().pk is not None:
|
||||||
|
if self.event.authorised:
|
||||||
|
messages.add_message(self.request, messages.WARNING,
|
||||||
|
"This event has already been authorised. "
|
||||||
|
"Reauthorising is not necessary at this time.")
|
||||||
|
else:
|
||||||
|
messages.add_message(self.request, messages.WARNING,
|
||||||
|
"This event has already been authorised, but the amount has changed. " +
|
||||||
|
"Please check the amount and reauthorise.")
|
||||||
|
return super(EventAuthorise, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_form(self, **kwargs):
|
||||||
|
form = super(EventAuthorise, self).get_form(**kwargs)
|
||||||
|
form.instance.event = self.event
|
||||||
|
form.instance.email = self.request.email
|
||||||
|
form.instance.sent_by = self.request.sent_by
|
||||||
|
return form
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
# Verify our signature matches up and all is well with the integrity of the URL
|
||||||
|
try:
|
||||||
|
data = signing.loads(kwargs.get('hmac'))
|
||||||
|
assert int(kwargs.get('pk')) == int(data.get('pk'))
|
||||||
|
request.email = data['email']
|
||||||
|
request.sent_by = models.Profile.objects.get(pk=data['sent_by'])
|
||||||
|
except (signing.BadSignature, AssertionError, KeyError, models.Profile.DoesNotExist):
|
||||||
|
raise SuspiciousOperation(
|
||||||
|
"This URL is invalid. Please ask your TEC contact for a new URL")
|
||||||
|
return super(EventAuthorise, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin):
|
||||||
|
model = models.Event
|
||||||
|
form_class = forms.EventAuthorisationRequestForm
|
||||||
|
template_name = 'RIGS/eventauthorisation_request.html'
|
||||||
|
|
||||||
|
@method_decorator(decorators.nottinghamtec_address_required)
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
return super(EventAuthorisationRequest, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def object(self):
|
||||||
|
return self.get_object()
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
if self.request.is_ajax():
|
||||||
|
url = reverse_lazy('closemodal')
|
||||||
|
messages.info(self.request, "location.reload()")
|
||||||
|
else:
|
||||||
|
url = reverse_lazy('event_detail', kwargs={
|
||||||
|
'pk': self.object.pk,
|
||||||
|
})
|
||||||
|
messages.add_message(self.request, messages.SUCCESS, "Authorisation request successfully sent.")
|
||||||
|
return url
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
email = form.cleaned_data['email']
|
||||||
|
event = self.object
|
||||||
|
event.auth_request_by = self.request.user
|
||||||
|
event.auth_request_at = datetime.datetime.now()
|
||||||
|
event.auth_request_to = email
|
||||||
|
event.save()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'object': self.object,
|
||||||
|
'request': self.request,
|
||||||
|
'hmac': signing.dumps({
|
||||||
|
'pk': self.object.pk,
|
||||||
|
'email': email,
|
||||||
|
'sent_by': self.request.user.pk,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
if email == event.person.email:
|
||||||
|
context['to_name'] = event.person.name
|
||||||
|
|
||||||
|
msg = EmailMultiAlternatives(
|
||||||
|
"N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name),
|
||||||
|
get_template("RIGS/eventauthorisation_client_request.txt").render(context),
|
||||||
|
to=[email],
|
||||||
|
reply_to=[self.request.user.email],
|
||||||
|
)
|
||||||
|
css = staticfiles_storage.path('css/email.css')
|
||||||
|
html = premailer.Premailer(get_template("RIGS/eventauthorisation_client_request.html").render(context),
|
||||||
|
external_styles=css).transform()
|
||||||
|
msg.attach_alternative(html, 'text/html')
|
||||||
|
|
||||||
|
msg.send()
|
||||||
|
|
||||||
|
return super(EventAuthorisationRequest, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
||||||
|
template_name = "RIGS/eventauthorisation_client_request.html"
|
||||||
|
model = models.Event
|
||||||
|
|
||||||
|
def render_to_response(self, context, **response_kwargs):
|
||||||
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
css = staticfiles_storage.path('css/email.css')
|
||||||
|
response = super(EventAuthoriseRequestEmailPreview, self).render_to_response(context, **response_kwargs)
|
||||||
|
assert isinstance(response, HttpResponse)
|
||||||
|
response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(EventAuthoriseRequestEmailPreview, self).get_context_data(**kwargs)
|
||||||
|
context['hmac'] = signing.dumps({
|
||||||
|
'pk': self.object.pk,
|
||||||
|
'email': self.request.GET.get('email', 'hello@world.test'),
|
||||||
|
'sent_by': self.request.user.pk,
|
||||||
|
})
|
||||||
|
context['to_name'] = self.request.GET.get('to_name', None)
|
||||||
|
return context
|
||||||
|
|||||||
98
RIGS/signals.py
Normal file
98
RIGS/signals.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import cStringIO as StringIO
|
||||||
|
import re
|
||||||
|
import urllib2
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import reversion
|
||||||
|
from PyPDF2 import PdfFileReader, PdfFileMerger
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||||
|
from django.template.loader import get_template
|
||||||
|
from premailer import Premailer
|
||||||
|
from z3c.rml import rml2pdf
|
||||||
|
|
||||||
|
from RIGS import models
|
||||||
|
|
||||||
|
|
||||||
|
def send_eventauthorisation_success_email(instance):
|
||||||
|
# Generate PDF first to prevent context conflicts
|
||||||
|
context = {
|
||||||
|
'object': instance.event,
|
||||||
|
'fonts': {
|
||||||
|
'opensans': {
|
||||||
|
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
||||||
|
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'receipt': True,
|
||||||
|
'current_user': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
template = get_template('RIGS/event_print.xml')
|
||||||
|
merger = PdfFileMerger()
|
||||||
|
|
||||||
|
rml = template.render(context)
|
||||||
|
|
||||||
|
buffer = rml2pdf.parseString(rml)
|
||||||
|
merger.append(PdfFileReader(buffer))
|
||||||
|
buffer.close()
|
||||||
|
|
||||||
|
terms = urllib2.urlopen(settings.TERMS_OF_HIRE_URL)
|
||||||
|
merger.append(StringIO.StringIO(terms.read()))
|
||||||
|
|
||||||
|
merged = BytesIO()
|
||||||
|
merger.write(merged)
|
||||||
|
|
||||||
|
# Produce email content
|
||||||
|
context = {
|
||||||
|
'object': instance,
|
||||||
|
}
|
||||||
|
|
||||||
|
if instance.email == instance.event.person.email:
|
||||||
|
context['to_name'] = instance.event.person.name
|
||||||
|
|
||||||
|
subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name)
|
||||||
|
|
||||||
|
client_email = EmailMultiAlternatives(
|
||||||
|
subject,
|
||||||
|
get_template("RIGS/eventauthorisation_client_success.txt").render(context),
|
||||||
|
to=[instance.email],
|
||||||
|
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
||||||
|
)
|
||||||
|
|
||||||
|
css = staticfiles_storage.path('css/email.css')
|
||||||
|
html = Premailer(get_template("RIGS/eventauthorisation_client_success.html").render(context),
|
||||||
|
external_styles=css).transform()
|
||||||
|
client_email.attach_alternative(html, 'text/html')
|
||||||
|
|
||||||
|
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', instance.event.name)
|
||||||
|
|
||||||
|
client_email.attach('N%05d - %s - CONFIRMATION.pdf' % (instance.event.pk, escapedEventName),
|
||||||
|
merged.getvalue(),
|
||||||
|
'application/pdf'
|
||||||
|
)
|
||||||
|
|
||||||
|
if instance.event.mic:
|
||||||
|
mic_email_address = instance.event.mic.email
|
||||||
|
else:
|
||||||
|
mic_email_address = settings.AUTHORISATION_NOTIFICATION_ADDRESS
|
||||||
|
|
||||||
|
mic_email = EmailMessage(
|
||||||
|
subject,
|
||||||
|
get_template("RIGS/eventauthorisation_mic_success.txt").render(context),
|
||||||
|
to=[mic_email_address]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now we have both emails successfully generated, send them out
|
||||||
|
client_email.send(fail_silently=True)
|
||||||
|
mic_email.send(fail_silently=True)
|
||||||
|
|
||||||
|
|
||||||
|
def on_revision_commit(instances, **kwargs):
|
||||||
|
for instance in instances:
|
||||||
|
if isinstance(instance, models.EventAuthorisation):
|
||||||
|
send_eventauthorisation_success_email(instance)
|
||||||
|
|
||||||
|
|
||||||
|
reversion.revisions.post_revision_commit.connect(on_revision_commit)
|
||||||
1
RIGS/static/css/email.css
Normal file
1
RIGS/static/css/email.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
body{margin:0px}.main-table{width:100%;border-collapse:collapse}.client-header{background-image:url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");background-color:#222;background-repeat:no-repeat;background-position:center;width:100%;margin-bottom:28px}.client-header .logos{width:100%;max-width:640px}.client-header img{height:110px}.content-container{width:100%}.content-container .content{font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;width:100%;max-width:600px;padding:10px;text-align:left}.content-container .content .button-container{width:100%}.content-container .content .button-container .button{padding:6px 12px;background-color:#357ebf;border-radius:4px}.content-container .content .button-container .button a{color:#fff;text-decoration:none}
|
||||||
64
RIGS/static/scss/email.scss
Normal file
64
RIGS/static/scss/email.scss
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
$button_color: #357ebf;
|
||||||
|
|
||||||
|
body{
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-table{
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-header {
|
||||||
|
background-image: url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");
|
||||||
|
background-color: #222;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin-bottom: 28px;
|
||||||
|
|
||||||
|
.logos{
|
||||||
|
width: 100%;
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container{
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.button-container{
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: $button_color;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
78
RIGS/templates/RIGS/client_eventdetails.html
Normal file
78
RIGS/templates/RIGS/client_eventdetails.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-6 col-lg-5">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Contact Details</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Person</dt>
|
||||||
|
<dd>
|
||||||
|
{% if event.person %}
|
||||||
|
{{ event.person.name }}
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Email</dt>
|
||||||
|
<dd>
|
||||||
|
<span class="overflow-ellipsis">{{ event.person.email }}</span>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Phone Number</dt>
|
||||||
|
<dd>{{ event.person.phone }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if event.organisation %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Organisation</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Organisation</dt>
|
||||||
|
<dd>
|
||||||
|
{{ event.organisation.name }}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Phone Number</dt>
|
||||||
|
<dd>{{ object.organisation.phone }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-md-6 col-lg-7">
|
||||||
|
<div class="panel panel-info">
|
||||||
|
<div class="panel-heading">Event Info</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Event Venue</dt>
|
||||||
|
<dd>
|
||||||
|
{% if object.venue %}
|
||||||
|
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
|
||||||
|
{{ object.venue }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Status</dt>
|
||||||
|
<dd>{{ event.get_status_display }}</dd>
|
||||||
|
|
||||||
|
<dd> </dd>
|
||||||
|
|
||||||
|
<dt>Access From</dt>
|
||||||
|
<dd>{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
|
||||||
|
|
||||||
|
<dt>Event Starts</dt>
|
||||||
|
<dd>{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
|
||||||
|
|
||||||
|
<dt>Event Ends</dt>
|
||||||
|
<dd>{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
|
||||||
|
|
||||||
|
<dd> </dd>
|
||||||
|
|
||||||
|
<dt>Event Description</dt>
|
||||||
|
<dd>{{ event.description|linebreaksbr }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -11,34 +11,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 text-right">
|
<div class="col-sm-12 text-right">
|
||||||
<div class="btn-group btn-page">
|
{% include 'RIGS/event_detail_buttons.html' %}
|
||||||
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
|
|
||||||
class="glyphicon glyphicon-edit"></span> <span
|
|
||||||
class="hidden-xs">Edit</span></a>
|
|
||||||
{% if event.is_rig %}
|
|
||||||
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
|
|
||||||
class="glyphicon glyphicon-print"></span> <span
|
|
||||||
class="hidden-xs">Print</span></a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
|
|
||||||
class="glyphicon glyphicon-duplicate"></span> <span
|
|
||||||
class="hidden-xs">Duplicate</span></a>
|
|
||||||
{% if event.is_rig %}
|
|
||||||
{% if perms.RIGS.add_invoice %}
|
|
||||||
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
|
|
||||||
{% if event.invoice and event.invoice.is_closed %}
|
|
||||||
btn-success
|
|
||||||
{% elif event.invoice %}
|
|
||||||
btn-warning
|
|
||||||
{% else %}
|
|
||||||
btn-danger
|
|
||||||
{% endif %}
|
|
||||||
" title="Invoice Rig"><span
|
|
||||||
class="glyphicon glyphicon-gbp"></span>
|
|
||||||
<span class="hidden-xs">Invoice</span></a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -97,6 +70,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if event.is_rig and event.internal %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Client Authorisation</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Authorised</dt>
|
||||||
|
<dd>{{ object.authorised|yesno:"Yes,No" }}</dd>
|
||||||
|
|
||||||
|
<dt>Authorised by</dt>
|
||||||
|
<dd>
|
||||||
|
{% if object.authorisation %}
|
||||||
|
{{ object.authorisation.name }}
|
||||||
|
(<a href="mailto:{{ object.authorisation.email }}">{{ object.authorisation.email }}</a>)
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Authorised at</dt>
|
||||||
|
<dd>{{ object.authorisation.last_edited_at }}</dd>
|
||||||
|
|
||||||
|
<dt>Authorised amount</dt>
|
||||||
|
<dd>
|
||||||
|
{% if object.authorisation %}
|
||||||
|
£ {{ object.authorisation.amount|floatformat:"2" }}
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Requested by</dt>
|
||||||
|
<dd>{{ object.authorisation.sent_by }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="col-sm-12 {% if event.is_rig %}col-md-6 col-lg-7{% endif %}">
|
<div class="col-sm-12 {% if event.is_rig %}col-md-6 col-lg-7{% endif %}">
|
||||||
@@ -175,8 +181,25 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if event.is_rig %}
|
{% if event.is_rig %}
|
||||||
<dt>PO</dt>
|
<dd> </dd>
|
||||||
<dd>{{ object.purchase_order }}</dd>
|
|
||||||
|
{% if object.internal %}
|
||||||
|
<dt>Authorisation Request</dt>
|
||||||
|
<dd>{{ object.auth_request_to|yesno:"Yes,No" }}</dd>
|
||||||
|
|
||||||
|
<dt>By</dt>
|
||||||
|
<dd>{{ object.auth_request_by }}</dd>
|
||||||
|
|
||||||
|
<dt>At</dt>
|
||||||
|
<dd>{{ object.auth_request_at|date:"D d M Y H:i"|default:"" }}</dd>
|
||||||
|
|
||||||
|
<dt>To</dt>
|
||||||
|
<dd>{{ object.auth_request_to }}</dd>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<dt>PO</dt>
|
||||||
|
<dd>{{ object.purchase_order }}</dd>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,34 +207,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if not request.is_ajax %}
|
{% if not request.is_ajax %}
|
||||||
<div class="col-sm-12 text-right">
|
<div class="col-sm-12 text-right">
|
||||||
<div class="btn-group btn-page">
|
{% include 'RIGS/event_detail_buttons.html' %}
|
||||||
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
|
|
||||||
class="glyphicon glyphicon-edit"></span> <span
|
|
||||||
class="hidden-xs">Edit</span></a>
|
|
||||||
{% if event.is_rig %}
|
|
||||||
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
|
|
||||||
class="glyphicon glyphicon-print"></span> <span
|
|
||||||
class="hidden-xs">Print</span></a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
|
|
||||||
class="glyphicon glyphicon-duplicate"></span> <span
|
|
||||||
class="hidden-xs">Duplicate</span></a>
|
|
||||||
{% if event.is_rig %}
|
|
||||||
{% if perms.RIGS.add_invoice %}
|
|
||||||
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
|
|
||||||
{% if event.invoice and event.invoice.is_closed %}
|
|
||||||
btn-success
|
|
||||||
{% elif event.invoice %}
|
|
||||||
btn-warning
|
|
||||||
{% else %}
|
|
||||||
btn-danger
|
|
||||||
{% endif %}
|
|
||||||
" title="Invoice Rig"><span
|
|
||||||
class="glyphicon glyphicon-gbp"></span>
|
|
||||||
<span class="hidden-xs">Invoice</span></a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.is_rig %}
|
{% if event.is_rig %}
|
||||||
@@ -229,34 +225,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if not request.is_ajax %}
|
{% if not request.is_ajax %}
|
||||||
<div class="col-sm-12 text-right">
|
<div class="col-sm-12 text-right">
|
||||||
<div class="btn-group btn-page">
|
{% include 'RIGS/event_detail_buttons.html' %}
|
||||||
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
|
|
||||||
class="glyphicon glyphicon-edit"></span> <span
|
|
||||||
class="hidden-xs">Edit</span></a>
|
|
||||||
{% if event.is_rig %}
|
|
||||||
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
|
|
||||||
class="glyphicon glyphicon-print"></span> <span
|
|
||||||
class="hidden-xs">Print</span></a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
|
|
||||||
class="glyphicon glyphicon-duplicate"></span> <span
|
|
||||||
class="hidden-xs">Duplicate</span></a>
|
|
||||||
{% if event.is_rig %}
|
|
||||||
{% if perms.RIGS.add_invoice %}
|
|
||||||
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
|
|
||||||
{% if event.invoice and event.invoice.is_closed %}
|
|
||||||
btn-success
|
|
||||||
{% elif event.invoice %}
|
|
||||||
btn-warning
|
|
||||||
{% else %}
|
|
||||||
btn-danger
|
|
||||||
{% endif %}
|
|
||||||
" title="Invoice Rig"><span
|
|
||||||
class="glyphicon glyphicon-gbp"></span>
|
|
||||||
<span class="hidden-xs">Invoice</span></a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
54
RIGS/templates/RIGS/event_detail_buttons.html
Normal file
54
RIGS/templates/RIGS/event_detail_buttons.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<div class="btn-group btn-page">
|
||||||
|
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
|
||||||
|
class="glyphicon glyphicon-edit"></span> <span
|
||||||
|
class="hidden-xs">Edit</span></a>
|
||||||
|
{% if event.is_rig %}
|
||||||
|
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
|
||||||
|
class="glyphicon glyphicon-print"></span> <span
|
||||||
|
class="hidden-xs">Print</span></a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
|
||||||
|
class="glyphicon glyphicon-duplicate"></span> <span
|
||||||
|
class="hidden-xs">Duplicate</span></a>
|
||||||
|
{% if event.is_rig %}
|
||||||
|
{% if event.internal %}
|
||||||
|
<a class="btn btn-default item-add modal-href event-authorise-request
|
||||||
|
{% if event.authorised %}
|
||||||
|
btn-success
|
||||||
|
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
|
||||||
|
btn-warning
|
||||||
|
{% elif event.auth_request_to %}
|
||||||
|
btn-info
|
||||||
|
{% endif %}
|
||||||
|
"
|
||||||
|
href="{% url 'event_authorise_request' object.pk %}">
|
||||||
|
<span class="glyphicon glyphicon-send"></span>
|
||||||
|
<span class="hidden-xs">
|
||||||
|
{% if event.authorised %}
|
||||||
|
Authorised
|
||||||
|
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
|
||||||
|
Authorisation Issue
|
||||||
|
{% elif event.auth_request_to %}
|
||||||
|
Awaiting Authorisation
|
||||||
|
{% else %}
|
||||||
|
Request Authorisation
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if perms.RIGS.add_invoice %}
|
||||||
|
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
|
||||||
|
{% if event.invoice and event.invoice.is_closed %}
|
||||||
|
btn-success
|
||||||
|
{% elif event.invoice %}
|
||||||
|
btn-warning
|
||||||
|
{% else %}
|
||||||
|
btn-danger
|
||||||
|
{% endif %}
|
||||||
|
" title="Invoice Rig"><span
|
||||||
|
class="glyphicon glyphicon-gbp"></span>
|
||||||
|
<span class="hidden-xs">Invoice</span></a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
@@ -398,6 +398,7 @@
|
|||||||
{% render_field form.collector class+="form-control" %}
|
{% render_field form.collector class+="form-control" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)">
|
<div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)">
|
||||||
<label for="{{ form.purchase_order.id_for_label }}"
|
<label for="{{ form.purchase_order.id_for_label }}"
|
||||||
class="col-sm-4 control-label">{{ form.purchase_order.label }}</label>
|
class="col-sm-4 control-label">{{ form.purchase_order.label }}</label>
|
||||||
|
|||||||
@@ -54,20 +54,22 @@
|
|||||||
<td><a href="{% url 'event_detail' object.pk %}">N{{ object.pk|stringformat:"05d" }}</a><br>
|
<td><a href="{% url 'event_detail' object.pk %}">N{{ object.pk|stringformat:"05d" }}</a><br>
|
||||||
<span class="text-muted">{{ object.get_status_display }}</span></td>
|
<span class="text-muted">{{ object.get_status_display }}</span></td>
|
||||||
<td>{{ object.start_date }}</td>
|
<td>{{ object.start_date }}</td>
|
||||||
<td>{{ object.name }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
{% if object.organisation %}
|
{{ object.name }}
|
||||||
{{ object.organisation.name }}
|
{% if object.is_rig and perms.RIGS.view_event and object.authorised %}
|
||||||
<br>
|
<span class="glyphicon glyphicon-check"></span>
|
||||||
<span class="text-muted">{{ object.organisation.union_account|yesno:'Internal,External' }}</span>
|
|
||||||
{% else %}
|
|
||||||
{{ object.person.name }}
|
|
||||||
<br>
|
|
||||||
<span class="text-muted">External</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ object.sum_total|floatformat:2 }}</td>
|
<td>
|
||||||
|
{{ object.organisation.name }}
|
||||||
|
<br>
|
||||||
|
<span class="text-muted">{{ object.internal|yesno:'Internal,External' }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ object.sum_total|floatformat:2 }}
|
||||||
|
<br />
|
||||||
|
<span class="text-muted">{% if not object.internal %}{{ object.purchase_order }}{% endif %}</span>
|
||||||
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if object.mic %}
|
{% if object.mic %}
|
||||||
{{ object.mic.initials }}<br>
|
{{ object.mic.initials }}<br>
|
||||||
@@ -77,7 +79,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a href="{% url 'invoice_event' object.pk %}" class="btn btn-default" data-toggle="tooltip" title="'Invoice' this event - click this when paperwork has been sent to treasury">
|
<a href="{% url 'invoice_event' object.pk %}"
|
||||||
|
class="btn btn-default"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="'Invoice' this event - click this when paperwork has been sent to treasury">
|
||||||
<span class="glyphicon glyphicon-gbp"></span>
|
<span class="glyphicon glyphicon-gbp"></span>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -22,21 +22,21 @@
|
|||||||
<paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="10" spaceAfter="2"/>
|
<paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="10" spaceAfter="2"/>
|
||||||
<paraStyle name="style.Heading3" fontName="OpenSans" fontSize="10" spaceAfter="0"/>
|
<paraStyle name="style.Heading3" fontName="OpenSans" fontSize="10" spaceAfter="0"/>
|
||||||
<paraStyle name="center" alignment="center"/>
|
<paraStyle name="center" alignment="center"/>
|
||||||
<paraStyle name="invoice-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/>
|
<paraStyle name="page-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/>
|
||||||
|
|
||||||
<paraStyle name="style.event_description" fontName="OpenSans" textColor="DarkGray" />
|
<paraStyle name="style.event_description" fontName="OpenSans" textColor="DarkGray" />
|
||||||
<paraStyle name="style.item_description" fontName="OpenSans" textColor="DarkGray" leftIndent="10" />
|
<paraStyle name="style.item_description" fontName="OpenSans" textColor="DarkGray" leftIndent="10" />
|
||||||
<paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10" />
|
<paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10" />
|
||||||
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
|
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
|
||||||
<paraStyle name="style.invoice_titles" fontName="OpenSans-Bold" fontSize="10" />
|
<paraStyle name="style.head_titles" fontName="OpenSans-Bold" fontSize="10" />
|
||||||
<paraStyle name="style.invoice_numbers" fontName="OpenSans" fontSize="10" />
|
<paraStyle name="style.head_numbers" fontName="OpenSans" fontSize="10" />
|
||||||
|
|
||||||
<blockTableStyle id="eventSpecifics">
|
<blockTableStyle id="eventSpecifics">
|
||||||
<blockValign value="top"/>
|
<blockValign value="top"/>
|
||||||
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
|
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
|
||||||
</blockTableStyle>
|
</blockTableStyle>
|
||||||
|
|
||||||
<blockTableStyle id="invoiceLayout">
|
<blockTableStyle id="headLayout">
|
||||||
<blockValign value="top"/>
|
<blockValign value="top"/>
|
||||||
|
|
||||||
</blockTableStyle>
|
</blockTableStyle>
|
||||||
@@ -100,10 +100,11 @@
|
|||||||
|
|
||||||
|
|
||||||
<setFont name="OpenSans" size="10" />
|
<setFont name="OpenSans" size="10" />
|
||||||
{% if not invoice %}<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>{% endif %}
|
|
||||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||||
<setFont name="OpenSans" size="7" />
|
<setFont name="OpenSans" size="7" />
|
||||||
<drawCenteredString x="302.5" y="26">[Paperwork generated by {{current_user.name}} | {% now "d/m/Y H:i" %} | {{object.current_version_id}}]</drawCenteredString>
|
<drawCenteredString x="302.5" y="26">
|
||||||
|
[Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
|
||||||
|
</drawCenteredString>
|
||||||
</pageGraphics>
|
</pageGraphics>
|
||||||
|
|
||||||
<frame id="main" x1="50" y1="65" width="495" height="645"/>
|
<frame id="main" x1="50" y1="65" width="495" height="645"/>
|
||||||
@@ -115,10 +116,11 @@
|
|||||||
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
||||||
|
|
||||||
<setFont name="OpenSans" size="10"/>
|
<setFont name="OpenSans" size="10"/>
|
||||||
{% if not invoice %}<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>{% endif %}
|
|
||||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||||
<setFont name="OpenSans" size="7" />
|
<setFont name="OpenSans" size="7" />
|
||||||
<drawCenteredString x="302.5" y="26">[Paperwork generated by {{current_user.name}} | {% now "d/m/Y H:i" %} | {{object.current_version_id}}]</drawCenteredString>
|
<drawCenteredString x="302.5" y="26">
|
||||||
|
[Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
|
||||||
|
</drawCenteredString>
|
||||||
</pageGraphics>
|
</pageGraphics>
|
||||||
<frame id="main" x1="50" y1="65" width="495" height="727"/>
|
<frame id="main" x1="50" y1="65" width="495" height="727"/>
|
||||||
</pageTemplate>
|
</pageTemplate>
|
||||||
|
|||||||
@@ -1,59 +1,70 @@
|
|||||||
<setNextFrame name="main"/>
|
<setNextFrame name="main"/>
|
||||||
<nextFrame/>
|
<nextFrame/>
|
||||||
|
|
||||||
{% if invoice %}
|
|
||||||
|
|
||||||
<blockTable style="invoiceLayout" colWidths="330,165">
|
<blockTable style="headLayout" colWidths="330,165">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% endif %}
|
<h1><b>N{{ object.pk|stringformat:"05d" }}:</b> '{{ object.name }}'<small></small></h1>
|
||||||
|
|
||||||
<h1><b>N{{ object.pk|stringformat:"05d" }}:</b> '{{ object.name }}'<small></small></h1>
|
<para style="style.event_description">
|
||||||
|
<b>{{object.start_date|date:"D jS N Y"}}</b>
|
||||||
<para style="style.event_description">
|
|
||||||
<b>{{object.start_date|date:"D jS N Y"}}</b>
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<keepInFrame>
|
|
||||||
<para style="style.event_description">
|
|
||||||
{{ object.description|default_if_none:""|linebreaksbr }}
|
|
||||||
</para>
|
</para>
|
||||||
</keepInFrame>
|
|
||||||
|
|
||||||
{% if invoice %}
|
|
||||||
|
|
||||||
|
<keepInFrame>
|
||||||
|
<para style="style.event_description">
|
||||||
|
{{ object.description|default_if_none:""|linebreaksbr }}
|
||||||
|
</para>
|
||||||
|
</keepInFrame>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<para style="invoice-head">INVOICE</para>
|
{% if invoice %}
|
||||||
<spacer length="10"/>
|
<para style="page-head">INVOICE</para>
|
||||||
<blockTable style="eventDetails" colWidths="100,175">
|
<spacer length="10"/>
|
||||||
<tr>
|
<blockTable style="eventDetails" colWidths="100,175">
|
||||||
<td><para style="invoice_titles">Invoice Number</para></td>
|
<tr>
|
||||||
<td>
|
<td><para style="head_titles">Invoice Number</para></td>
|
||||||
<para style="invoice_numbers">{{ invoice.pk|stringformat:"05d" }}</para>
|
<td>
|
||||||
</td>
|
<para style="head_numbers">{{ invoice.pk|stringformat:"05d" }}</para>
|
||||||
</tr>
|
</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td><para style="invoice_titles">Invoice Date</para></td>
|
<tr>
|
||||||
<td>
|
<td><para style="head_titles">Invoice Date</para></td>
|
||||||
<para style="invoice_numbers">{{ invoice.invoice_date|date:"d/m/Y" }}</para>
|
<td>
|
||||||
</td>
|
<para style="head_numbers">{{ invoice.invoice_date|date:"d/m/Y" }}</para>
|
||||||
</tr>
|
</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td><para style="invoice_titles">PO Number</para></td>
|
|
||||||
<td>
|
|
||||||
<para style="invoice_numbers">{{ object.purchase_order|default_if_none:"" }}</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</blockTable>
|
{% if not object.internal %}
|
||||||
|
<tr>
|
||||||
|
<td><para style="head_titles">PO</para></td>
|
||||||
|
<td><para style="head_numbers">{{ object.purchase_order }}</para></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</blockTable>
|
||||||
|
|
||||||
|
{% elif quote %}
|
||||||
|
|
||||||
|
<para style="page-head">QUOTE</para>
|
||||||
|
<spacer length="10"/>
|
||||||
|
<blockTable style="eventDetails" colWidths="100,175">
|
||||||
|
<tr>
|
||||||
|
<td><para style="head_titles">Quote Date</para></td>
|
||||||
|
<td>
|
||||||
|
<para style="head_numbers">{% now "d/m/Y" %}</para>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</blockTable>
|
||||||
|
|
||||||
|
{% elif receipt %}
|
||||||
|
|
||||||
|
<para style="page-head">CONFIRMATION</para>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</blockTable>
|
</blockTable>
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<spacer length="15"/>
|
<spacer length="15"/>
|
||||||
<blockTable style="eventSpecifics" colWidths="165,165,165">
|
<blockTable style="eventSpecifics" colWidths="165,165,165">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -189,16 +200,16 @@
|
|||||||
<keepTogether>
|
<keepTogether>
|
||||||
<blockTable style="totalTable" colWidths="300,115,80">
|
<blockTable style="totalTable" colWidths="300,115,80">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% if not invoice %}VAT Registration Number: 170734807{% endif %}</td>
|
<td>{% if quote %}VAT Registration Number: 170734807{% endif %}</td>
|
||||||
<td>Total (ex. VAT)</td>
|
<td>Total (ex. VAT)</td>
|
||||||
<td>£ {{ object.sum_total|floatformat:2 }}</td>
|
<td>£ {{ object.sum_total|floatformat:2 }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if not invoice %}
|
{% if quote %}
|
||||||
<para>
|
<para>
|
||||||
<b>The full hire fee is payable at least 10 days before the event.</b>
|
This quote is valid for 30 days unless otherwise arranged.
|
||||||
</para>
|
</para>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td>
|
<td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td>
|
||||||
@@ -206,113 +217,140 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
{% if quote %}
|
||||||
|
<para>
|
||||||
|
<b>The full hire fee is payable at least 10 days before the event.</b>
|
||||||
|
</para>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% if invoice %}
|
||||||
|
<td>Total</td>
|
||||||
|
<td>£ {{ object.total|floatformat:2 }}</td>
|
||||||
|
{% else %}
|
||||||
|
<td>
|
||||||
|
<para>
|
||||||
|
<b>Total</b>
|
||||||
|
</para>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<para>
|
||||||
|
<b>£ {{ object.total|floatformat:2 }}</b>
|
||||||
|
</para>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</blockTable>
|
||||||
|
</keepTogether>
|
||||||
|
|
||||||
<para>
|
{% if invoice %}
|
||||||
{% if invoice %}
|
<spacer length="15"/>
|
||||||
VAT Registration Number: 170734807
|
<keepTogether>
|
||||||
|
<h2>Payments</h2>
|
||||||
|
<blockTable style="itemTable" colWidths="300,115,80">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<para>
|
||||||
|
<b>Method</b>
|
||||||
|
</para>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<para>
|
||||||
|
<b>Date</b>
|
||||||
|
</para>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<para>
|
||||||
|
<b>Amount</b>
|
||||||
|
</para>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% for payment in object.invoice.payment_set.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ payment.get_method_display }}</td>
|
||||||
|
<td>{{ payment.date }}</td>
|
||||||
|
<td>£ {{ payment.amount|floatformat:2 }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</blockTable>
|
||||||
|
<blockTable style="totalTable" colWidths="300,115,80">
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>Payment Total</td>
|
||||||
|
<td>£ {{ object.invoice.payment_total|floatformat:2 }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>
|
||||||
|
<para>
|
||||||
|
<b>Balance</b> (ex. VAT)
|
||||||
|
</para>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<para>
|
||||||
|
<b>£ {{ object.invoice.balance|floatformat:2 }}</b>
|
||||||
|
</para>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</blockTable>
|
||||||
|
</keepTogether>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<keepTogether>
|
||||||
|
<blockTable style="infoTable">>
|
||||||
|
{% if quote %}
|
||||||
|
<tr><td><spacer length="15" /></td></tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% if object.internal %}
|
||||||
|
<para>Bookings will
|
||||||
|
<b>not</b>
|
||||||
|
be confirmed until the event is authorised online.
|
||||||
|
</para>
|
||||||
{% else %}
|
{% else %}
|
||||||
<b>This contract is not an invoice.</b>
|
<para>Bookings will
|
||||||
|
<b>not</b>
|
||||||
|
be confirmed until we have received written confirmation and a Purchase Order.
|
||||||
|
</para>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</para>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>24 Hour Emergency Contacts: 07825 065681 and 07825 065678</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<para>VAT Registration Number: 170734807</para>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</td>
|
<tr><td><spacer length="15" /></td></tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<para>
|
{% if object.internal and object.authorised %}
|
||||||
<b>Total</b>
|
<para>
|
||||||
</para>
|
Event authorised online by {{ object.authorisation.name }} ({{ object.authorisation.email }}) at
|
||||||
</td>
|
{{ object.authorisation.last_edited_at }}.
|
||||||
<td>
|
</para>
|
||||||
<para>
|
|
||||||
<b>£ {{ object.total|floatformat:2 }}</b>
|
<blockTable colWidths="165,165,165">
|
||||||
</para>
|
<tr>
|
||||||
|
<td><para><b>University ID</b></para></td>
|
||||||
|
<td><para><b>Account Code</b></para></td>
|
||||||
|
<td><para><b>Authorised Amount</b></para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ object.authorisation.uni_id }}</td>
|
||||||
|
<td>{{ object.authorisation.account_code }}</td>
|
||||||
|
<td>£ {{ object.authorisation.amount|floatformat:2 }}</td>
|
||||||
|
</tr>
|
||||||
|
</blockTable>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</blockTable>
|
</blockTable>
|
||||||
</keepTogether>
|
</keepTogether>
|
||||||
{% if not invoice %}
|
|
||||||
<keepTogether>
|
|
||||||
<blockTable style="infoTable">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para>Bookings will
|
|
||||||
<b>not</b>
|
|
||||||
be confirmed until payment is received and the contract is signed.
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>24 Hour Emergency Contacts: 07825 065681 and 07825 065678</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
</keepTogether>
|
|
||||||
<spacer length="15"/>
|
|
||||||
<keepTogether>
|
|
||||||
|
|
||||||
<para style="blockPara">
|
|
||||||
<b>To be signed on booking:</b>
|
|
||||||
</para>
|
|
||||||
{% if object.organisation.union_account %}
|
|
||||||
<para style="blockPara">
|
|
||||||
<i>
|
|
||||||
I agree that am authorised to sign this invoice. I agree that I am the President/Treasurer of the hirer, or
|
|
||||||
that I have provided written permission from either the President or Treasurer of the hirer stating that I can
|
|
||||||
sign for this invoice.
|
|
||||||
</i>
|
|
||||||
</para>
|
|
||||||
<para style="blockPara">
|
|
||||||
<i>
|
|
||||||
I have read, understood and fully accepted the current conditions of hire. I agree to return any dry hire
|
|
||||||
items to TEC PA & Lighting in the same condition at the end of the hire period.
|
|
||||||
</i>
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para style="blockPara">
|
|
||||||
<b>
|
|
||||||
Conditions of hire attached and available on the TEC PA & Lighting website. E&OE
|
|
||||||
</b>
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para style="blockPara">
|
|
||||||
Please return this form directly to TEC PA & Lighting and not the Students' Union Finance Department.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<blockTable style="signatureTable" colWidths="70,100,325">
|
|
||||||
<tr>
|
|
||||||
<td>Account Code</td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<para style="blockPara">
|
|
||||||
<i>
|
|
||||||
I, the hirer, have read, understand and fully accept the current conditions of hire. This document forms a
|
|
||||||
binding contract between TEC PA & Lighting and the hirer, the aforementioned conditions of hire forming
|
|
||||||
an integral part of it.
|
|
||||||
</i>
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para style="blockPara">
|
|
||||||
<b>
|
|
||||||
Conditions of hire attached and available on the TEC PA & Lighting website. E&OE
|
|
||||||
</b>
|
|
||||||
</para>
|
|
||||||
|
|
||||||
{% include "RIGS/event_print_signature.xml" %}
|
|
||||||
<spacer length="10"/>
|
|
||||||
<para style="blockPara">
|
|
||||||
<b>To be signed on the day of the event/hire:</b>
|
|
||||||
</para>
|
|
||||||
<para style="blockPara">
|
|
||||||
<i>
|
|
||||||
I, the hirer, have received the goods/services as requested and in good order. I agree to return any dry hire
|
|
||||||
items to TEC PA & Lighting in a similar condition at the end of the hire period.
|
|
||||||
</i>
|
|
||||||
</para>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% include "RIGS/event_print_signature.xml" %}
|
|
||||||
</keepTogether>
|
|
||||||
{% endif %}
|
|
||||||
<namedString id="lastPage"><pageNumber/></namedString>
|
<namedString id="lastPage"><pageNumber/></namedString>
|
||||||
@@ -33,13 +33,18 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<h4>
|
<h4>
|
||||||
<a {% if perms.RIGS.view_event %}href="{% url 'event_detail' event.pk %}" {% endif %}>{{ event.name }}</a>
|
<a {% if perms.RIGS.view_event %}href="{% url 'event_detail' event.pk %}" {% endif %}>
|
||||||
|
{{ event.name }}
|
||||||
|
</a>
|
||||||
{% if event.venue %}
|
{% if event.venue %}
|
||||||
<small>at {{ event.venue }}</small>
|
<small>at {{ event.venue }}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.dry_hire %}
|
{% if event.dry_hire %}
|
||||||
<span class="label label-default">Dry Hire</span>
|
<span class="label label-default">Dry Hire</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if event.is_rig and perms.RIGS.view_event and event.authorised %}
|
||||||
|
<span class="glyphicon glyphicon-check"></span>
|
||||||
|
{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
{% if event.is_rig and not event.cancelled %}
|
{% if event.is_rig and not event.cancelled %}
|
||||||
<h5>
|
<h5>
|
||||||
|
|||||||
41
RIGS/templates/RIGS/eventauthorisation_client_request.html
Normal file
41
RIGS/templates/RIGS/eventauthorisation_client_request.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{% extends 'base_client_email.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<p>Hi {{ to_name|default:"there" }},</p>
|
||||||
|
|
||||||
|
<p><b>{{ request.user.get_full_name }}</b> has requested that you authorise <b>N{{ object.pk|stringformat:"05d" }}
|
||||||
|
| {{ object.name }}</b>{% if not to_name %} on behalf of <b>{{ object.person.name }}</b>{% endif %}.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Please find the link below to complete the event booking process.
|
||||||
|
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
|
||||||
|
Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward
|
||||||
|
this
|
||||||
|
email on.
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<table class="button-container" width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="button" align="center">
|
||||||
|
<a href="{{ request.scheme }}://{{ request.get_host }}{% url 'event_authorise' object.pk hmac %}">
|
||||||
|
Complete Authorisation Form
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Your event will not be booked until you complete this form.</p>
|
||||||
|
|
||||||
|
<p>TEC PA & Lighting<br/>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
16
RIGS/templates/RIGS/eventauthorisation_client_request.txt
Normal file
16
RIGS/templates/RIGS/eventauthorisation_client_request.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Hi {{ to_name|default:"there" }},
|
||||||
|
|
||||||
|
{{ request.user.get_full_name }} has requested that you authorise N{{ object.pk|stringformat:"05d" }}| {{ object.name }}{% if not to_name %} on behalf of {{ object.person.name }}{% endif %}.
|
||||||
|
|
||||||
|
Please find the link below to complete the event booking process.
|
||||||
|
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
|
||||||
|
Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward
|
||||||
|
this
|
||||||
|
email on.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ request.scheme }}://{{ request.get_host }}{% url 'event_authorise' object.pk hmac %}
|
||||||
|
|
||||||
|
Please note you event will not be booked until you complete this form.
|
||||||
|
|
||||||
|
TEC PA & Lighting
|
||||||
21
RIGS/templates/RIGS/eventauthorisation_client_success.html
Normal file
21
RIGS/templates/RIGS/eventauthorisation_client_success.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base_client_email.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>Hi {{ to_name|default:"there" }},</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Your event <b>N{{ object.event.pk|stringformat:"05d" }}</b> has been successfully authorised
|
||||||
|
for <b>£{{ object.amount }}</b>
|
||||||
|
by <b>{{ object.name }}</b> as of <b>{{ object.last_edited_at }}</b>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
|
||||||
|
Your event is now fully booked and payment will be processed by the finance department automatically.
|
||||||
|
{% else %}{# external #}
|
||||||
|
Your event is now fully booked and our finance department will be contact to arrange payment.
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>TEC PA & Lighting</p>
|
||||||
|
{% endblock %}
|
||||||
11
RIGS/templates/RIGS/eventauthorisation_client_success.txt
Normal file
11
RIGS/templates/RIGS/eventauthorisation_client_success.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Hi {{ to_name|default:"there" }},
|
||||||
|
|
||||||
|
Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.last_edited_at}}.
|
||||||
|
|
||||||
|
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
|
||||||
|
Your event is now fully booked and payment will be processed by the finance department automatically.
|
||||||
|
{% else %}{# external #}
|
||||||
|
Your event is now fully booked and our finance department will be contact to arrange payment.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
TEC PA & Lighting
|
||||||
133
RIGS/templates/RIGS/eventauthorisation_form.html
Normal file
133
RIGS/templates/RIGS/eventauthorisation_form.html
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
{% extends 'base_client.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script src="{% static "js/tooltip.js" %}"></script>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('form').on('submit', function () {
|
||||||
|
$('#loading-modal').modal({
|
||||||
|
backdrop: 'static',
|
||||||
|
show: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1>
|
||||||
|
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %}
|
||||||
|
| {{ event.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% include 'RIGS/client_eventdetails.html' %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{% with object=event %}
|
||||||
|
{% include 'RIGS/item_table.html' %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading" id="eventauth">Event Authorisation</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
<form class="form-horizontal itemised_form" role="form" method="POST" action="#eventauth">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include 'form_errors.html' %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<p>
|
||||||
|
I agree that I am authorised to approve this event. I agree that I am the
|
||||||
|
<strong>President/Treasurer or account holder</strong> of the hirer, or that I
|
||||||
|
have the written permission of the
|
||||||
|
<strong>President/Treasurer or account holder</strong> of the hirer stating that
|
||||||
|
I can authorise this event.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-md-6">
|
||||||
|
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||||
|
title="Your name as the person authorising the event.">
|
||||||
|
<label for="{{ form.name.id_for_label }}"
|
||||||
|
class="col-sm-4 control-label">{{ form.name.label }}</label>
|
||||||
|
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% render_field form.name class+="form-control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||||
|
title="Your Student ID or Staff username as the person authorising the event.">
|
||||||
|
<label for="{{ form.uni_id.id_for_label }}"
|
||||||
|
class="col-sm-4 control-label">{{ form.uni_id.label }}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% render_field form.uni_id class+="form-control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-md-6">
|
||||||
|
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||||
|
title="The Students' Union account code you wish this event to be charged to.">
|
||||||
|
<label for="{{ form.account_code.id_for_label }}"
|
||||||
|
class="col-sm-4 control-label">{{ form.account_code.label }}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% render_field form.account_code class+="form-control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||||
|
title="The full amount chargable for this event as displayed above, including VAT.">
|
||||||
|
<label for="{{ form.amount.id_for_label }}"
|
||||||
|
class="col-sm-4 control-label">{{ form.amount.label }}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% render_field form.amount class+="form-control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="col-sm-12 col-md-8 form-group">
|
||||||
|
<div class="col-sm-offset-4 col-sm-8 col-md-offset-3" data-toggle="tooltip"
|
||||||
|
title="In order to book and event you must agree to the TEC Terms of Hire.">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
{% render_field form.tos %} I have read and agree to the TEC
|
||||||
|
<a href="{{ tos_url }}">Terms of Hire</a>. E&OE.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-md-4 text-right">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-primary" type="submit">Authorise</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
5
RIGS/templates/RIGS/eventauthorisation_mic_success.txt
Normal file
5
RIGS/templates/RIGS/eventauthorisation_mic_success.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}},
|
||||||
|
|
||||||
|
Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.last_edited_at}}.
|
||||||
|
|
||||||
|
The TEC Rig Information Gathering System
|
||||||
59
RIGS/templates/RIGS/eventauthorisation_request.html
Normal file
59
RIGS/templates/RIGS/eventauthorisation_request.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
{% block title %}Request Authorisation{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<h1>Send authorisation request email.</h1>
|
||||||
|
<p>Pressing send will email the address provided. Please triple check everything before continuing.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Person Email</dt>
|
||||||
|
<dd>{{ object.person.email }}</dd>
|
||||||
|
|
||||||
|
<dt>Organisation Email</dt>
|
||||||
|
<dd>{{ object.organisation.email }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-10 col-md-offset-1">
|
||||||
|
<form action="{{ form.action|default:request.path }}" method="post" class="form-horizontal"
|
||||||
|
id="auth-request-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% include 'form_errors.html' %}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-3 control-label"
|
||||||
|
for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
|
||||||
|
|
||||||
|
<div class="col-sm-9">
|
||||||
|
{% render_field form.email type="email" class+="form-control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-right col-sm-3 col-sm-offset-9">
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="form-control btn btn-primary">
|
||||||
|
<span class="glyphicon glyphicon-send"></span>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$('#auth-request-form').on('submit', function () {
|
||||||
|
$('#auth-request-form button').attr('disabled', true);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
15
RIGS/templates/RIGS/eventauthorisation_request_error.html
Normal file
15
RIGS/templates/RIGS/eventauthorisation_request_error.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
{% block title %}NottinghamTEC Email Address Required{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<h1>An error occured.</h1>
|
||||||
|
<p>Your RIGS account must have an @nottinghamtec.co.uk email address before you can send emails to clients.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
64
RIGS/templates/RIGS/eventauthorisation_success.html
Normal file
64
RIGS/templates/RIGS/eventauthorisation_success.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{% extends 'base_client.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1>
|
||||||
|
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %}
|
||||||
|
| {{ event.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'RIGS/client_eventdetails.html' %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{% with object=event %}
|
||||||
|
{% include 'RIGS/item_table.html' %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Event Authorisation</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-6">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Name</dt>
|
||||||
|
<dd>{{ object.name }}</dd>
|
||||||
|
|
||||||
|
<dt>Email</dt>
|
||||||
|
<dd>{{ object.email }}</dd>
|
||||||
|
|
||||||
|
{% if internal %}
|
||||||
|
<dt>University ID</dt>
|
||||||
|
<dd>{{ object.uni_id }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-md-6">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Account code</dt>
|
||||||
|
<dd>{{ object.account_code }}</dd>
|
||||||
|
|
||||||
|
<dt>Authorised amount</dt>
|
||||||
|
<dd>£ {{ object.amount|floatformat:2 }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -76,8 +76,43 @@
|
|||||||
<dd>{{ object.checked_in_by.name }}</dd>
|
<dd>{{ object.checked_in_by.name }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<dt>PO</dt>
|
<dd> </dd>
|
||||||
<dd>{{ object.event.purchase_order }}</dd>
|
|
||||||
|
<dt>Authorised</dt>
|
||||||
|
<dd>{{ object.event.authorised|yesno:"Yes,No" }}</dd>
|
||||||
|
|
||||||
|
<dt>Authorised by</dt>
|
||||||
|
<dd>
|
||||||
|
{% if object.event.authorised %}
|
||||||
|
{{ object.event.authorisation.name }}
|
||||||
|
(<a href="mailto:{{ object.event.authorisation.email }}">{{ object.event.authorisation.email }}</a>)
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
{% if object.event.internal %}
|
||||||
|
{# internal #}
|
||||||
|
<dt>Uni ID</dt>
|
||||||
|
<dd>{{ object.event.authorisation.uni_id }}</dd>
|
||||||
|
|
||||||
|
<dt>Account code</dt>
|
||||||
|
<dd>{{ object.event.authorisation.account_code }}</dd>
|
||||||
|
{% else %}
|
||||||
|
<dt>PO</dt>
|
||||||
|
<dd>{{ object.event.purchase_order }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<dt>Authorised at</dt>
|
||||||
|
<dd>{{ object.event.authorisation.last_edited_at }}</dd>
|
||||||
|
|
||||||
|
<dt>Authorised amount</dt>
|
||||||
|
<dd>
|
||||||
|
{% if object.event.authorised %}
|
||||||
|
£ {{ object.event.authorisation.amount|floatformat:"2" }}
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Authorsation request sent by</dt>
|
||||||
|
<dd>{{ object.authorisation.sent_by }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<td>{% if object.event.organisation %}
|
<td>{% if object.event.organisation %}
|
||||||
{{ object.event.organisation.name }}
|
{{ object.event.organisation.name }}
|
||||||
<br>
|
<br>
|
||||||
<span class="text-muted">{{ object.event.organisation.union_account|yesno:'Internal,External' }}</span>
|
<span class="text-muted">{{ object.event.internal|yesno:'Internal,External' }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ object.event.person.name }}
|
{{ object.event.person.name }}
|
||||||
<br>
|
<br>
|
||||||
@@ -59,7 +59,11 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{{ object.event.start_date }}</td>
|
<td>{{ object.event.start_date }}</td>
|
||||||
<td>{{ object.invoice_date }}</td>
|
<td>{{ object.invoice_date }}</td>
|
||||||
<td>{{ object.balance|floatformat:2 }}</td>
|
<td>
|
||||||
|
{{ object.balance|floatformat:2 }}
|
||||||
|
<br />
|
||||||
|
<span class="text-muted">{{ object.event.purchase_order }}</span>
|
||||||
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default">
|
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default">
|
||||||
<span class="glyphicon glyphicon-pencil"></span>
|
<span class="glyphicon glyphicon-pencil"></span>
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import re
|
|||||||
import pytz
|
import pytz
|
||||||
from datetime import date, time, datetime, timedelta
|
from datetime import date, time, datetime, timedelta
|
||||||
|
|
||||||
|
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.test import LiveServerTestCase
|
from django.http import HttpResponseBadRequest
|
||||||
|
from django.test import LiveServerTestCase, TestCase
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
from selenium.common.exceptions import StaleElementReferenceException, WebDriverException
|
from selenium.common.exceptions import StaleElementReferenceException, WebDriverException
|
||||||
@@ -16,6 +18,9 @@ from selenium.webdriver.support.ui import WebDriverWait
|
|||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.core import mail, signing
|
||||||
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@@ -63,9 +68,9 @@ def create_browser(test_name, desired_capabilities):
|
|||||||
|
|
||||||
@on_platforms(browsers)
|
@on_platforms(browsers)
|
||||||
class UserRegistrationTest(LiveServerTestCase):
|
class UserRegistrationTest(LiveServerTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.browser = create_browser(self.id(), self.desired_capabilities)
|
self.browser = create_browser(self.id(), self.desired_capabilities)
|
||||||
|
|
||||||
self.browser.implicitly_wait(3) # Set implicit wait session wide
|
self.browser.implicitly_wait(3) # Set implicit wait session wide
|
||||||
os.environ['RECAPTCHA_TESTING'] = 'True'
|
os.environ['RECAPTCHA_TESTING'] = 'True'
|
||||||
|
|
||||||
@@ -195,18 +200,19 @@ class UserRegistrationTest(LiveServerTestCase):
|
|||||||
|
|
||||||
@on_platforms(browsers)
|
@on_platforms(browsers)
|
||||||
class EventTest(LiveServerTestCase):
|
class EventTest(LiveServerTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.profile = models.Profile(
|
self.profile = models.Profile(
|
||||||
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
||||||
self.profile.set_password("EventTestPassword")
|
self.profile.set_password("EventTestPassword")
|
||||||
self.profile.save()
|
self.profile.save()
|
||||||
|
|
||||||
|
|
||||||
self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1')
|
self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1')
|
||||||
|
|
||||||
self.browser = create_browser(self.id(), self.desired_capabilities)
|
self.browser = create_browser(self.id(), self.desired_capabilities)
|
||||||
self.browser.implicitly_wait(10) # Set implicit wait session wide
|
self.browser.implicitly_wait(10) # Set implicit wait session wide
|
||||||
# self.browser.maximize_window()
|
# self.browser.maximize_window()
|
||||||
|
|
||||||
os.environ['RECAPTCHA_TESTING'] = 'True'
|
os.environ['RECAPTCHA_TESTING'] = 'True'
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@@ -416,26 +422,28 @@ class EventTest(LiveServerTestCase):
|
|||||||
wait.until(animation_is_finished())
|
wait.until(animation_is_finished())
|
||||||
modal = self.browser.find_element_by_id("itemModal")
|
modal = self.browser.find_element_by_id("itemModal")
|
||||||
modal.find_element_by_id("item_name").send_keys("Test Item 1")
|
modal.find_element_by_id("item_name").send_keys("Test Item 1")
|
||||||
modal.find_element_by_id("item_description").send_keys("This is an item description\nthat for reasons unkown spans two lines")
|
modal.find_element_by_id("item_description").send_keys(
|
||||||
|
"This is an item description\nthat for reasons unkown spans two lines")
|
||||||
e = modal.find_element_by_id("item_quantity")
|
e = modal.find_element_by_id("item_quantity")
|
||||||
e.click()
|
e.click()
|
||||||
e.send_keys(Keys.UP)
|
e.send_keys(Keys.UP)
|
||||||
e.send_keys(Keys.UP)
|
e.send_keys(Keys.UP)
|
||||||
e = modal.find_element_by_id("item_cost")
|
e = modal.find_element_by_id("item_cost")
|
||||||
e.send_keys("23.95")
|
e.send_keys("23.95")
|
||||||
e.send_keys(Keys.ENTER) # enter submit
|
e.send_keys(Keys.ENTER) # enter submit
|
||||||
|
|
||||||
# Confirm item has been saved to json field
|
# Confirm item has been saved to json field
|
||||||
objectitems = self.browser.execute_script("return objectitems;")
|
objectitems = self.browser.execute_script("return objectitems;")
|
||||||
self.assertEqual(1, len(objectitems))
|
self.assertEqual(1, len(objectitems))
|
||||||
testitem = objectitems["-1"]['fields'] # as we are deliberately creating this we know the ID
|
testitem = objectitems["-1"]['fields'] # as we are deliberately creating this we know the ID
|
||||||
self.assertEqual("Test Item 1", testitem['name'])
|
self.assertEqual("Test Item 1", testitem['name'])
|
||||||
self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields
|
self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields
|
||||||
|
|
||||||
# See new item appear in table
|
# See new item appear in table
|
||||||
row = self.browser.find_element_by_id('item--1') # ID number is known, see above
|
row = self.browser.find_element_by_id('item--1') # ID number is known, see above
|
||||||
self.assertIn("Test Item 1", row.find_element_by_xpath('//span[@class="name"]').text)
|
self.assertIn("Test Item 1", row.find_element_by_xpath('//span[@class="name"]').text)
|
||||||
self.assertIn("This is an item description", row.find_element_by_xpath('//div[@class="item-description"]').text)
|
self.assertIn("This is an item description",
|
||||||
|
row.find_element_by_xpath('//div[@class="item-description"]').text)
|
||||||
self.assertEqual(u'£ 23.95', row.find_element_by_xpath('//tr[@id="item--1"]/td[2]').text)
|
self.assertEqual(u'£ 23.95', row.find_element_by_xpath('//tr[@id="item--1"]/td[2]').text)
|
||||||
self.assertEqual("2", row.find_element_by_xpath('//td[@class="quantity"]').text)
|
self.assertEqual("2", row.find_element_by_xpath('//td[@class="quantity"]').text)
|
||||||
self.assertEqual(u'£ 47.90', row.find_element_by_xpath('//tr[@id="item--1"]/td[4]').text)
|
self.assertEqual(u'£ 47.90', row.find_element_by_xpath('//tr[@id="item--1"]/td[4]').text)
|
||||||
@@ -477,6 +485,7 @@ class EventTest(LiveServerTestCase):
|
|||||||
# See redirected to success page
|
# See redirected to success page
|
||||||
successTitle = self.browser.find_element_by_xpath('//h1').text
|
successTitle = self.browser.find_element_by_xpath('//h1').text
|
||||||
event = models.Event.objects.get(name='Test Event Name')
|
event = models.Event.objects.get(name='Test Event Name')
|
||||||
|
|
||||||
self.assertIn("N%05d | Test Event Name"%event.pk, successTitle)
|
self.assertIn("N%05d | Test Event Name"%event.pk, successTitle)
|
||||||
except WebDriverException:
|
except WebDriverException:
|
||||||
# This is a dirty workaround for wercker being a bit funny and not running it correctly.
|
# This is a dirty workaround for wercker being a bit funny and not running it correctly.
|
||||||
@@ -484,91 +493,105 @@ class EventTest(LiveServerTestCase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def testEventDuplicate(self):
|
def testEventDuplicate(self):
|
||||||
testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end", purchase_order="TESTPO")
|
testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
|
||||||
|
start_date=date.today() + timedelta(days=6),
|
||||||
|
description="start future no end",
|
||||||
|
purchase_order='TESTPO',
|
||||||
|
auth_request_by=self.profile,
|
||||||
|
auth_request_at=self.create_datetime(2015, 06, 04, 10, 00),
|
||||||
|
auth_request_to="some@email.address")
|
||||||
|
|
||||||
item1 = models.EventItem(
|
item1 = models.EventItem(
|
||||||
event=testEvent,
|
event=testEvent,
|
||||||
name="Test Item 1",
|
name="Test Item 1",
|
||||||
cost="10.00",
|
cost="10.00",
|
||||||
quantity="1",
|
quantity="1",
|
||||||
order=1
|
order=1
|
||||||
).save()
|
).save()
|
||||||
item2 = models.EventItem(
|
item2 = models.EventItem(
|
||||||
event=testEvent,
|
event=testEvent,
|
||||||
name="Test Item 2",
|
name="Test Item 2",
|
||||||
description="Foo",
|
description="Foo",
|
||||||
cost="9.72",
|
cost="9.72",
|
||||||
quantity="3",
|
quantity="3",
|
||||||
order=2,
|
order=2,
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk) + '/duplicate/')
|
self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk) + '/duplicate/')
|
||||||
self.authenticate('/event/' + str(testEvent.pk) + '/duplicate/')
|
self.authenticate('/event/' + str(testEvent.pk) + '/duplicate/')
|
||||||
|
|
||||||
|
|
||||||
wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations)
|
wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations)
|
||||||
|
|
||||||
save = self.browser.find_element_by_xpath(
|
save = self.browser.find_element_by_xpath(
|
||||||
'(//button[@type="submit"])[3]')
|
'(//button[@type="submit"])[3]')
|
||||||
form = self.browser.find_element_by_tag_name('form')
|
form = self.browser.find_element_by_tag_name('form')
|
||||||
|
|
||||||
|
|
||||||
# Check the items are visible
|
# Check the items are visible
|
||||||
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
||||||
self.assertIn("Test Item 1", table.text)
|
self.assertIn("Test Item 1", table.text)
|
||||||
self.assertIn("Test Item 2", table.text)
|
self.assertIn("Test Item 2", table.text)
|
||||||
|
|
||||||
# Check the info message is visible
|
# Check the info message is visible
|
||||||
self.assertIn("Event data duplicated but not yet saved",self.browser.find_element_by_id('content').text)
|
self.assertIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text)
|
||||||
|
|
||||||
# Add item
|
# Add item
|
||||||
form.find_element_by_xpath('//button[contains(@class, "item-add")]').click()
|
form.find_element_by_xpath('//button[contains(@class, "item-add")]').click()
|
||||||
wait.until(animation_is_finished())
|
wait.until(animation_is_finished())
|
||||||
modal = self.browser.find_element_by_id("itemModal")
|
modal = self.browser.find_element_by_id("itemModal")
|
||||||
modal.find_element_by_id("item_name").send_keys("Test Item 3")
|
modal.find_element_by_id("item_name").send_keys("Test Item 3")
|
||||||
modal.find_element_by_id("item_description").send_keys("This is an item description\nthat for reasons unkown spans two lines")
|
modal.find_element_by_id("item_description").send_keys(
|
||||||
|
"This is an item description\nthat for reasons unkown spans two lines")
|
||||||
e = modal.find_element_by_id("item_quantity")
|
e = modal.find_element_by_id("item_quantity")
|
||||||
e.click()
|
e.click()
|
||||||
e.send_keys(Keys.UP)
|
e.send_keys(Keys.UP)
|
||||||
e.send_keys(Keys.UP)
|
e.send_keys(Keys.UP)
|
||||||
e = modal.find_element_by_id("item_cost")
|
e = modal.find_element_by_id("item_cost")
|
||||||
e.send_keys("23.95")
|
e.send_keys("23.95")
|
||||||
e.send_keys(Keys.ENTER) # enter submit
|
e.send_keys(Keys.ENTER) # enter submit
|
||||||
|
|
||||||
# Attempt to save
|
# Attempt to save
|
||||||
save.click()
|
save.click()
|
||||||
|
|
||||||
|
newEvent = models.Event.objects.latest('pk')
|
||||||
|
|
||||||
|
self.assertEqual(newEvent.auth_request_to, None)
|
||||||
|
self.assertEqual(newEvent.auth_request_by, None)
|
||||||
|
self.assertEqual(newEvent.auth_request_at, None)
|
||||||
|
|
||||||
|
self.assertFalse(hasattr(newEvent, 'authorised'))
|
||||||
|
|
||||||
self.assertNotIn("N%05d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text)
|
self.assertNotIn("N%05d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text)
|
||||||
self.assertNotIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text) # Check info message not visible
|
self.assertNotIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text) # Check info message not visible
|
||||||
|
|
||||||
# Check the new items are visible
|
# Check the new items are visible
|
||||||
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
||||||
self.assertIn("Test Item 1", table.text)
|
self.assertIn("Test Item 1", table.text)
|
||||||
self.assertIn("Test Item 2", table.text)
|
self.assertIn("Test Item 2", table.text)
|
||||||
self.assertIn("Test Item 3", table.text)
|
self.assertIn("Test Item 3", table.text)
|
||||||
|
|
||||||
infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
|
infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
|
||||||
|
self.assertIn("N0000%d" % testEvent.pk,
|
||||||
self.assertIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||||
|
|
||||||
# Check the PO hasn't carried through
|
# Check the PO hasn't carried through
|
||||||
self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text)
|
self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text)
|
||||||
|
|
||||||
|
|
||||||
|
self.assertIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||||
|
|
||||||
self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk)) #Go back to the old event
|
self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk)) #Go back to the old event
|
||||||
|
|
||||||
#Check that based-on hasn't crept into the old event
|
#Check that based-on hasn't crept into the old event
|
||||||
infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
|
infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
|
||||||
|
self.assertNotIn("N0000%d" % testEvent.pk,
|
||||||
self.assertNotIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||||
|
|
||||||
# Check the PO remains on the old event
|
# Check the PO remains on the old event
|
||||||
self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text)
|
self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text)
|
||||||
|
|
||||||
|
self.assertNotIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||||
|
|
||||||
# Check the items are as they were
|
# Check the items are as they were
|
||||||
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
table = self.browser.find_element_by_id('item-table') # ID number is known, see above
|
||||||
self.assertIn("Test Item 1", table.text)
|
self.assertIn("Test Item 1", table.text)
|
||||||
self.assertIn("Test Item 2", table.text)
|
self.assertIn("Test Item 2", table.text)
|
||||||
self.assertNotIn("Test Item 3", table.text)
|
self.assertNotIn("Test Item 3", table.text)
|
||||||
@@ -578,6 +601,7 @@ class EventTest(LiveServerTestCase):
|
|||||||
# Gets redirected to login and back
|
# Gets redirected to login and back
|
||||||
self.authenticate('/event/create/')
|
self.authenticate('/event/create/')
|
||||||
|
|
||||||
|
|
||||||
wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations)
|
wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations)
|
||||||
|
|
||||||
wait.until(animation_is_finished())
|
wait.until(animation_is_finished())
|
||||||
@@ -604,7 +628,6 @@ class EventTest(LiveServerTestCase):
|
|||||||
self.assertTrue(error.is_displayed())
|
self.assertTrue(error.is_displayed())
|
||||||
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
||||||
|
|
||||||
|
|
||||||
# Same date, end time before start time
|
# Same date, end time before start time
|
||||||
form = self.browser.find_element_by_tag_name('form')
|
form = self.browser.find_element_by_tag_name('form')
|
||||||
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
|
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
|
||||||
@@ -624,7 +647,6 @@ class EventTest(LiveServerTestCase):
|
|||||||
self.assertTrue(error.is_displayed())
|
self.assertTrue(error.is_displayed())
|
||||||
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
||||||
|
|
||||||
|
|
||||||
# Same date, end time before start time
|
# Same date, end time before start time
|
||||||
form = self.browser.find_element_by_tag_name('form')
|
form = self.browser.find_element_by_tag_name('form')
|
||||||
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
|
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
|
||||||
@@ -638,7 +660,6 @@ class EventTest(LiveServerTestCase):
|
|||||||
form.find_element_by_id('id_end_time').send_keys(Keys.DELETE)
|
form.find_element_by_id('id_end_time').send_keys(Keys.DELETE)
|
||||||
form.find_element_by_id('id_end_time').send_keys('06:00')
|
form.find_element_by_id('id_end_time').send_keys('06:00')
|
||||||
|
|
||||||
|
|
||||||
# No end date, end time before start time
|
# No end date, end time before start time
|
||||||
form = self.browser.find_element_by_tag_name('form')
|
form = self.browser.find_element_by_tag_name('form')
|
||||||
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
|
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
|
||||||
@@ -658,13 +679,13 @@ class EventTest(LiveServerTestCase):
|
|||||||
self.assertTrue(error.is_displayed())
|
self.assertTrue(error.is_displayed())
|
||||||
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text)
|
||||||
|
|
||||||
|
|
||||||
# 2 dates, end after start
|
# 2 dates, end after start
|
||||||
form = self.browser.find_element_by_tag_name('form')
|
form = self.browser.find_element_by_tag_name('form')
|
||||||
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
|
save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]')
|
||||||
self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'")
|
self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'")
|
||||||
self.browser.execute_script("document.getElementById('id_end_date').value='3015-04-26'")
|
self.browser.execute_script("document.getElementById('id_end_date').value='3015-04-26'")
|
||||||
|
|
||||||
|
|
||||||
self.browser.execute_script("document.getElementById('id_start_time').value=''")
|
self.browser.execute_script("document.getElementById('id_start_time').value=''")
|
||||||
self.browser.execute_script("document.getElementById('id_end_time').value=''")
|
self.browser.execute_script("document.getElementById('id_end_time').value=''")
|
||||||
|
|
||||||
@@ -674,6 +695,7 @@ class EventTest(LiveServerTestCase):
|
|||||||
# See redirected to success page
|
# See redirected to success page
|
||||||
successTitle = self.browser.find_element_by_xpath('//h1').text
|
successTitle = self.browser.find_element_by_xpath('//h1').text
|
||||||
event = models.Event.objects.get(name='Test Event Name')
|
event = models.Event.objects.get(name='Test Event Name')
|
||||||
|
|
||||||
self.assertIn("N%05d | Test Event Name"%event.pk, successTitle)
|
self.assertIn("N%05d | Test Event Name"%event.pk, successTitle)
|
||||||
|
|
||||||
def testRigNonRig(self):
|
def testRigNonRig(self):
|
||||||
@@ -681,6 +703,7 @@ class EventTest(LiveServerTestCase):
|
|||||||
# Gets redirected to login and back
|
# Gets redirected to login and back
|
||||||
self.authenticate('/event/create/')
|
self.authenticate('/event/create/')
|
||||||
|
|
||||||
|
|
||||||
wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations)
|
wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations)
|
||||||
self.browser.implicitly_wait(3) #Set session-long wait (only works for non-existant DOM objects)
|
self.browser.implicitly_wait(3) #Set session-long wait (only works for non-existant DOM objects)
|
||||||
|
|
||||||
@@ -713,7 +736,8 @@ class EventTest(LiveServerTestCase):
|
|||||||
person = models.Person(name="Event Detail Person", email="eventdetail@person.tests.rigs", phone="123 123")
|
person = models.Person(name="Event Detail Person", email="eventdetail@person.tests.rigs", phone="123 123")
|
||||||
person.save()
|
person.save()
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
organisation = models.Organisation(name="Event Detail Organisation", email="eventdetail@organisation.tests.rigs", phone="123 456").save()
|
organisation = models.Organisation(name="Event Detail Organisation",
|
||||||
|
email="eventdetail@organisation.tests.rigs", phone="123 456").save()
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
venue = models.Venue(name="Event Detail Venue").save()
|
venue = models.Venue(name="Event Detail Venue").save()
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
@@ -743,18 +767,21 @@ class EventTest(LiveServerTestCase):
|
|||||||
order=2,
|
order=2,
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
|
self.browser.get(self.live_server_url + '/event/%d' % event.pk)
|
||||||
self.browser.get(self.live_server_url + '/event/%d'%event.pk)
|
self.authenticate('/event/%d/' % event.pk)
|
||||||
self.authenticate('/event/%d/'%event.pk)
|
self.assertIn("N%05d | %s" % (event.pk, event.name), self.browser.find_element_by_xpath('//h1').text)
|
||||||
self.assertIn("N%05d | %s"%(event.pk, event.name), self.browser.find_element_by_xpath('//h1').text)
|
|
||||||
|
|
||||||
personPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..')
|
personPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..')
|
||||||
self.assertEqual(person.name, personPanel.find_element_by_xpath('//dt[text()="Person"]/following-sibling::dd[1]').text)
|
self.assertEqual(person.name,
|
||||||
self.assertEqual(person.email, personPanel.find_element_by_xpath('//dt[text()="Email"]/following-sibling::dd[1]').text)
|
personPanel.find_element_by_xpath('//dt[text()="Person"]/following-sibling::dd[1]').text)
|
||||||
self.assertEqual(person.phone, personPanel.find_element_by_xpath('//dt[text()="Phone Number"]/following-sibling::dd[1]').text)
|
self.assertEqual(person.email,
|
||||||
|
personPanel.find_element_by_xpath('//dt[text()="Email"]/following-sibling::dd[1]').text)
|
||||||
|
self.assertEqual(person.phone,
|
||||||
|
personPanel.find_element_by_xpath('//dt[text()="Phone Number"]/following-sibling::dd[1]').text)
|
||||||
|
|
||||||
organisationPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..')
|
organisationPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..')
|
||||||
|
|
||||||
|
|
||||||
def testEventEdit(self):
|
def testEventEdit(self):
|
||||||
person = models.Person(name="Event Edit Person", email="eventdetail@person.tests.rigs", phone="123 123").save()
|
person = models.Person(name="Event Edit Person", email="eventdetail@person.tests.rigs", phone="123 123").save()
|
||||||
organisation = models.Organisation(name="Event Edit Organisation", email="eventdetail@organisation.tests.rigs", phone="123 456").save()
|
organisation = models.Organisation(name="Event Edit Organisation", email="eventdetail@organisation.tests.rigs", phone="123 456").save()
|
||||||
@@ -816,43 +843,66 @@ class EventTest(LiveServerTestCase):
|
|||||||
|
|
||||||
@on_platforms(browsers)
|
@on_platforms(browsers)
|
||||||
class IcalTest(LiveServerTestCase):
|
class IcalTest(LiveServerTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.all_events = set(range(1, 18))
|
self.all_events = set(range(1, 18))
|
||||||
self.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
|
self.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
|
||||||
self.not_current_events = set(self.all_events) - set(self.current_events)
|
self.not_current_events = set(self.all_events) - set(self.current_events)
|
||||||
|
|
||||||
self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1')
|
self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||||
self.profile = models.Profile(
|
self.profile = models.Profile(
|
||||||
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
||||||
self.profile.set_password("EventTestPassword")
|
self.profile.set_password("EventTestPassword")
|
||||||
self.profile.save()
|
self.profile.save()
|
||||||
|
|
||||||
# produce 7 normal events - 5 current - 1 last week - 1 two years ago - 2 provisional - 2 confirmed - 3 booked
|
# produce 7 normal events - 5 current - 1 last week - 1 two years ago - 2 provisional - 2 confirmed - 3 booked
|
||||||
models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end")
|
models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
|
||||||
models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL, start_date=date.today(), description="start today no end")
|
start_date=date.today() + timedelta(days=6), description="start future no end")
|
||||||
models.Event.objects.create(name="TE E3", status=models.Event.CONFIRMED, start_date=date.today(), end_date=date.today(), description="start today with end today")
|
models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL, start_date=date.today(),
|
||||||
models.Event.objects.create(name="TE E4", status=models.Event.CONFIRMED, start_date=date.today()-timedelta(weeks=104), description="start past 2 years no end")
|
description="start today no end")
|
||||||
models.Event.objects.create(name="TE E5", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=7), end_date=date.today()-timedelta(days=1), description="start past 1 week with end past")
|
models.Event.objects.create(name="TE E3", status=models.Event.CONFIRMED, start_date=date.today(),
|
||||||
models.Event.objects.create(name="TE E6", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start past, end future")
|
end_date=date.today(), description="start today with end today")
|
||||||
models.Event.objects.create(name="TE E7", status=models.Event.BOOKED, start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start + end in future")
|
models.Event.objects.create(name="TE E4", status=models.Event.CONFIRMED,
|
||||||
|
start_date=date.today() - timedelta(weeks=104),
|
||||||
|
description="start past 2 years no end")
|
||||||
|
models.Event.objects.create(name="TE E5", status=models.Event.BOOKED,
|
||||||
|
start_date=date.today() - timedelta(days=7),
|
||||||
|
end_date=date.today() - timedelta(days=1),
|
||||||
|
description="start past 1 week with end past")
|
||||||
|
models.Event.objects.create(name="TE E6", status=models.Event.BOOKED,
|
||||||
|
start_date=date.today() - timedelta(days=2),
|
||||||
|
end_date=date.today() + timedelta(days=2), description="start past, end future")
|
||||||
|
models.Event.objects.create(name="TE E7", status=models.Event.BOOKED,
|
||||||
|
start_date=date.today() + timedelta(days=2),
|
||||||
|
end_date=date.today() + timedelta(days=2), description="start + end in future")
|
||||||
|
|
||||||
# 2 cancelled - 1 current
|
# 2 cancelled - 1 current
|
||||||
models.Event.objects.create(name="TE E8", start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled in future")
|
models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2),
|
||||||
models.Event.objects.create(name="TE E9", start_date=date.today()-timedelta(days=1), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled and started")
|
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
|
||||||
|
description="cancelled in future")
|
||||||
|
models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1),
|
||||||
|
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
|
||||||
|
description="cancelled and started")
|
||||||
|
|
||||||
# 5 dry hire - 3 current - 1 cancelled
|
# 5 dry hire - 3 current - 1 cancelled
|
||||||
models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, description="dryhire today")
|
models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, description="dryhire today")
|
||||||
models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile, description="dryhire today, checked in")
|
models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile,
|
||||||
models.Event.objects.create(name="TE E12", start_date=date.today()-timedelta(days=1), dry_hire=True, status=models.Event.BOOKED, description="dryhire past")
|
description="dryhire today, checked in")
|
||||||
models.Event.objects.create(name="TE E13", start_date=date.today()-timedelta(days=2), dry_hire=True, checked_in_by=self.profile, description="dryhire past checked in")
|
models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True,
|
||||||
models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True, status=models.Event.CANCELLED, description="dryhire today cancelled")
|
status=models.Event.BOOKED, description="dryhire past")
|
||||||
|
models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
|
||||||
|
checked_in_by=self.profile, description="dryhire past checked in")
|
||||||
|
models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True,
|
||||||
|
status=models.Event.CANCELLED, description="dryhire today cancelled")
|
||||||
|
|
||||||
# 4 non rig - 3 current
|
# 4 non rig - 3 current
|
||||||
models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, description="non rig today")
|
models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, description="non rig today")
|
||||||
models.Event.objects.create(name="TE E16", start_date=date.today()+timedelta(days=1), is_rig=False, description="non rig tomorrow")
|
models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False,
|
||||||
models.Event.objects.create(name="TE E17", start_date=date.today()-timedelta(days=1), is_rig=False, description="non rig yesterday")
|
description="non rig tomorrow")
|
||||||
models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED, description="non rig today cancelled")
|
models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False,
|
||||||
|
description="non rig yesterday")
|
||||||
|
models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED,
|
||||||
|
description="non rig today cancelled")
|
||||||
|
|
||||||
|
|
||||||
self.browser = create_browser(self.id(), self.desired_capabilities)
|
self.browser = create_browser(self.id(), self.desired_capabilities)
|
||||||
self.browser.implicitly_wait(3) # Set implicit wait session wide
|
self.browser.implicitly_wait(3) # Set implicit wait session wide
|
||||||
@@ -886,7 +936,8 @@ class IcalTest(LiveServerTestCase):
|
|||||||
|
|
||||||
# Completes and comes back to /user/
|
# Completes and comes back to /user/
|
||||||
# Checks that no api key is displayed
|
# Checks that no api key is displayed
|
||||||
self.assertEqual("No API Key Generated", self.browser.find_element_by_xpath("//div[@id='content']/div/div/div[3]/dl[2]/dd").text)
|
self.assertEqual("No API Key Generated",
|
||||||
|
self.browser.find_element_by_xpath("//div[@id='content']/div/div/div[3]/dl[2]/dd").text)
|
||||||
self.assertEqual("No API Key Generated", self.browser.find_element_by_css_selector("pre").text)
|
self.assertEqual("No API Key Generated", self.browser.find_element_by_css_selector("pre").text)
|
||||||
|
|
||||||
# Now creates an API key, and check a URL is displayed one
|
# Now creates an API key, and check a URL is displayed one
|
||||||
@@ -903,7 +954,9 @@ class IcalTest(LiveServerTestCase):
|
|||||||
self.browser.find_element_by_xpath("//input[@value='confirmed']").click()
|
self.browser.find_element_by_xpath("//input[@value='confirmed']").click()
|
||||||
|
|
||||||
# and then check the url is correct
|
# and then check the url is correct
|
||||||
self.assertIn("rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false", self.browser.find_element_by_id("cal-url").text)
|
self.assertIn(
|
||||||
|
"rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false",
|
||||||
|
self.browser.find_element_by_id("cal-url").text)
|
||||||
|
|
||||||
# Awesome - all seems to work
|
# Awesome - all seems to work
|
||||||
|
|
||||||
@@ -916,8 +969,6 @@ class IcalTest(LiveServerTestCase):
|
|||||||
# Now creates an API key, and check a URL is displayed one
|
# Now creates an API key, and check a URL is displayed one
|
||||||
self.browser.find_element_by_link_text("Generate API Key").click()
|
self.browser.find_element_by_link_text("Generate API Key").click()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
c = Client()
|
c = Client()
|
||||||
|
|
||||||
# Default settings - should have all non-cancelled events
|
# Default settings - should have all non-cancelled events
|
||||||
@@ -926,17 +977,16 @@ class IcalTest(LiveServerTestCase):
|
|||||||
response = c.get(icalUrl)
|
response = c.get(icalUrl)
|
||||||
self.assertEqual(200, response.status_code)
|
self.assertEqual(200, response.status_code)
|
||||||
|
|
||||||
#Check has entire file
|
# Check has entire file
|
||||||
self.assertIn("BEGIN:VCALENDAR", response.content)
|
self.assertIn("BEGIN:VCALENDAR", response.content)
|
||||||
self.assertIn("END:VCALENDAR", response.content)
|
self.assertIn("END:VCALENDAR", response.content)
|
||||||
|
|
||||||
expectedIn= [1,2,3,5,6,7,10,11,12,13,15,16,17]
|
expectedIn = [1, 2, 3, 5, 6, 7, 10, 11, 12, 13, 15, 16, 17]
|
||||||
for test in range(1,18):
|
for test in range(1, 18):
|
||||||
if test in expectedIn:
|
if test in expectedIn:
|
||||||
self.assertIn("TE E"+str(test)+" ", response.content)
|
self.assertIn("TE E" + str(test) + " ", response.content)
|
||||||
else:
|
else:
|
||||||
self.assertNotIn("TE E"+str(test)+" ", response.content)
|
self.assertNotIn("TE E" + str(test) + " ", response.content)
|
||||||
|
|
||||||
|
|
||||||
# Only dry hires
|
# Only dry hires
|
||||||
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
||||||
@@ -946,13 +996,12 @@ class IcalTest(LiveServerTestCase):
|
|||||||
response = c.get(icalUrl)
|
response = c.get(icalUrl)
|
||||||
self.assertEqual(200, response.status_code)
|
self.assertEqual(200, response.status_code)
|
||||||
|
|
||||||
expectedIn= [10,11,12,13]
|
expectedIn = [10, 11, 12, 13]
|
||||||
for test in range(1,18):
|
for test in range(1, 18):
|
||||||
if test in expectedIn:
|
if test in expectedIn:
|
||||||
self.assertIn("TE E"+str(test)+" ", response.content)
|
self.assertIn("TE E" + str(test) + " ", response.content)
|
||||||
else:
|
else:
|
||||||
self.assertNotIn("TE E"+str(test)+" ", response.content)
|
self.assertNotIn("TE E" + str(test) + " ", response.content)
|
||||||
|
|
||||||
|
|
||||||
# Only provisional rigs
|
# Only provisional rigs
|
||||||
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
||||||
@@ -963,12 +1012,12 @@ class IcalTest(LiveServerTestCase):
|
|||||||
response = c.get(icalUrl)
|
response = c.get(icalUrl)
|
||||||
self.assertEqual(200, response.status_code)
|
self.assertEqual(200, response.status_code)
|
||||||
|
|
||||||
expectedIn= [1,2]
|
expectedIn = [1, 2]
|
||||||
for test in range(1,18):
|
for test in range(1, 18):
|
||||||
if test in expectedIn:
|
if test in expectedIn:
|
||||||
self.assertIn("TE E"+str(test)+" ", response.content)
|
self.assertIn("TE E" + str(test) + " ", response.content)
|
||||||
else:
|
else:
|
||||||
self.assertNotIn("TE E"+str(test)+" ", response.content)
|
self.assertNotIn("TE E" + str(test) + " ", response.content)
|
||||||
|
|
||||||
# Only cancelled non-rigs
|
# Only cancelled non-rigs
|
||||||
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
self.browser.find_element_by_xpath("//input[@value='rig']").click()
|
||||||
@@ -980,12 +1029,12 @@ class IcalTest(LiveServerTestCase):
|
|||||||
response = c.get(icalUrl)
|
response = c.get(icalUrl)
|
||||||
self.assertEqual(200, response.status_code)
|
self.assertEqual(200, response.status_code)
|
||||||
|
|
||||||
expectedIn= [18]
|
expectedIn = [18]
|
||||||
for test in range(1,18):
|
for test in range(1, 18):
|
||||||
if test in expectedIn:
|
if test in expectedIn:
|
||||||
self.assertIn("TE E"+str(test)+" ", response.content)
|
self.assertIn("TE E" + str(test) + " ", response.content)
|
||||||
else:
|
else:
|
||||||
self.assertNotIn("TE E"+str(test)+" ", response.content)
|
self.assertNotIn("TE E" + str(test) + " ", response.content)
|
||||||
|
|
||||||
# Nothing selected
|
# Nothing selected
|
||||||
self.browser.find_element_by_xpath("//input[@value='non-rig']").click()
|
self.browser.find_element_by_xpath("//input[@value='non-rig']").click()
|
||||||
@@ -995,17 +1044,19 @@ class IcalTest(LiveServerTestCase):
|
|||||||
response = c.get(icalUrl)
|
response = c.get(icalUrl)
|
||||||
self.assertEqual(200, response.status_code)
|
self.assertEqual(200, response.status_code)
|
||||||
|
|
||||||
expectedIn= []
|
expectedIn = []
|
||||||
for test in range(1,18):
|
for test in range(1, 18):
|
||||||
if test in expectedIn:
|
if test in expectedIn:
|
||||||
self.assertIn("TE E"+str(test)+" ", response.content)
|
self.assertIn("TE E" + str(test) + " ", response.content)
|
||||||
else:
|
else:
|
||||||
self.assertNotIn("TE E"+str(test)+" ", response.content)
|
self.assertNotIn("TE E" + str(test) + " ", response.content)
|
||||||
|
|
||||||
|
# Wow - that was a lot of tests
|
||||||
|
|
||||||
# Wow - that was a lot of tests
|
|
||||||
|
|
||||||
class animation_is_finished(object):
|
class animation_is_finished(object):
|
||||||
""" Checks if animation is done """
|
""" Checks if animation is done """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1016,3 +1067,167 @@ class animation_is_finished(object):
|
|||||||
import time
|
import time
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
return finished
|
return finished
|
||||||
|
|
||||||
|
|
||||||
|
class ClientEventAuthorisationTest(TestCase):
|
||||||
|
auth_data = {
|
||||||
|
'name': 'Test ABC',
|
||||||
|
'po': '1234ABCZXY',
|
||||||
|
'account_code': 'ABC TEST 12345',
|
||||||
|
'uni_id': 1234567890,
|
||||||
|
'tos': True
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.profile = models.Profile.objects.get_or_create(
|
||||||
|
first_name='Test',
|
||||||
|
last_name='TEC User',
|
||||||
|
username='eventauthtest',
|
||||||
|
email='teccie@functional.test',
|
||||||
|
is_superuser=True # lazily grant all permissions
|
||||||
|
)[0]
|
||||||
|
self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||||
|
venue = models.Venue.objects.create(name='Authorisation Test Venue')
|
||||||
|
client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test')
|
||||||
|
organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=False)
|
||||||
|
self.event = models.Event.objects.create(
|
||||||
|
name='Authorisation Test',
|
||||||
|
start_date=date.today(),
|
||||||
|
venue=venue,
|
||||||
|
person=client,
|
||||||
|
organisation=organisation,
|
||||||
|
)
|
||||||
|
self.hmac = signing.dumps({'pk': self.event.pk, 'email': 'authemail@function.test',
|
||||||
|
'sent_by': self.profile.pk})
|
||||||
|
self.url = reverse('event_authorise', kwargs={'pk': self.event.pk, 'hmac': self.hmac})
|
||||||
|
|
||||||
|
def test_requires_valid_hmac(self):
|
||||||
|
bad_hmac = self.hmac[:-1]
|
||||||
|
url = reverse('event_authorise', kwargs={'pk': self.event.pk, 'hmac': bad_hmac})
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertIsInstance(response, HttpResponseBadRequest)
|
||||||
|
# TODO: Add some form of sensbile user facing error
|
||||||
|
# self.assertIn(response.content, "new URL") # check there is some level of sane instruction
|
||||||
|
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
self.assertContains(response, self.event.organisation.name)
|
||||||
|
|
||||||
|
def test_generic_validation(self):
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
self.assertContains(response, "Terms of Hire")
|
||||||
|
|
||||||
|
response = self.client.post(self.url)
|
||||||
|
self.assertContains(response, "This field is required.", 5)
|
||||||
|
|
||||||
|
data = self.auth_data
|
||||||
|
data['amount'] = self.event.total + 1
|
||||||
|
|
||||||
|
response = self.client.post(self.url, data)
|
||||||
|
self.assertContains(response, "The amount authorised must equal the total for the event")
|
||||||
|
self.assertNotContains(response, "This field is required.")
|
||||||
|
|
||||||
|
data['amount'] = self.event.total
|
||||||
|
response = self.client.post(self.url, data)
|
||||||
|
self.assertContains(response, "Your event has been authorised")
|
||||||
|
|
||||||
|
self.event.refresh_from_db()
|
||||||
|
self.assertTrue(self.event.authorised)
|
||||||
|
self.assertEqual(self.event.authorisation.email, "authemail@function.test")
|
||||||
|
|
||||||
|
def test_internal_validation(self):
|
||||||
|
self.event.organisation.union_account = True
|
||||||
|
self.event.organisation.save()
|
||||||
|
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
self.assertContains(response, "Account code")
|
||||||
|
self.assertContains(response, "University ID")
|
||||||
|
|
||||||
|
response = self.client.post(self.url)
|
||||||
|
self.assertContains(response, "This field is required.", 5)
|
||||||
|
|
||||||
|
data = self.auth_data
|
||||||
|
response = self.client.post(self.url, data)
|
||||||
|
self.assertContains(response, "Your event has been authorised.")
|
||||||
|
|
||||||
|
def test_duplicate_warning(self):
|
||||||
|
auth = models.EventAuthorisation.objects.create(event=self.event, name='Test ABC', email='dupe@functional.test',
|
||||||
|
amount=self.event.total, sent_by=self.profile)
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
self.assertContains(response, 'This event has already been authorised.')
|
||||||
|
|
||||||
|
auth.amount += 1
|
||||||
|
auth.save()
|
||||||
|
|
||||||
|
response = self.client.get(self.url)
|
||||||
|
self.assertContains(response, 'amount has changed')
|
||||||
|
|
||||||
|
def test_email_sent(self):
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
|
data = self.auth_data
|
||||||
|
data['amount'] = self.event.total
|
||||||
|
|
||||||
|
response = self.client.post(self.url, data)
|
||||||
|
self.assertContains(response, "Your event has been authorised.")
|
||||||
|
self.assertEqual(len(mail.outbox), 2)
|
||||||
|
|
||||||
|
self.assertEqual(mail.outbox[0].to, ['authemail@function.test'])
|
||||||
|
self.assertEqual(mail.outbox[1].to, [settings.AUTHORISATION_NOTIFICATION_ADDRESS])
|
||||||
|
|
||||||
|
|
||||||
|
class TECEventAuthorisationTest(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.profile = models.Profile.objects.get_or_create(
|
||||||
|
first_name='Test',
|
||||||
|
last_name='TEC User',
|
||||||
|
username='eventauthtest',
|
||||||
|
email='teccie@nottinghamtec.co.uk',
|
||||||
|
is_superuser=True # lazily grant all permissions
|
||||||
|
)[0]
|
||||||
|
cls.profile.set_password('eventauthtest123')
|
||||||
|
cls.profile.save()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||||
|
venue = models.Venue.objects.create(name='Authorisation Test Venue')
|
||||||
|
client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test')
|
||||||
|
organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=False)
|
||||||
|
self.event = models.Event.objects.create(
|
||||||
|
name='Authorisation Test',
|
||||||
|
start_date=date.today(),
|
||||||
|
venue=venue,
|
||||||
|
person=client,
|
||||||
|
organisation=organisation,
|
||||||
|
)
|
||||||
|
self.url = reverse('event_authorise_request', kwargs={'pk': self.event.pk})
|
||||||
|
|
||||||
|
def test_email_check(self):
|
||||||
|
self.profile.email = 'teccie@someotherdomain.com'
|
||||||
|
self.profile.save()
|
||||||
|
|
||||||
|
self.assertTrue(self.client.login(username=self.profile.username, password='eventauthtest123'))
|
||||||
|
|
||||||
|
response = self.client.post(self.url)
|
||||||
|
|
||||||
|
self.assertContains(response, 'must have an @nottinghamtec.co.uk email address')
|
||||||
|
|
||||||
|
def test_request_send(self):
|
||||||
|
self.assertTrue(self.client.login(username=self.profile.username, password='eventauthtest123'))
|
||||||
|
response = self.client.post(self.url)
|
||||||
|
self.assertContains(response, 'This field is required.')
|
||||||
|
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
|
response = self.client.post(self.url, {'email': 'client@functional.test'})
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
email = mail.outbox[0]
|
||||||
|
self.assertIn('client@functional.test', email.to)
|
||||||
|
self.assertIn('/event/%d/' % (self.event.pk), email.body)
|
||||||
|
|
||||||
|
# Check sent by details are populated
|
||||||
|
self.event.refresh_from_db()
|
||||||
|
self.assertEqual(self.event.auth_request_by, self.profile)
|
||||||
|
self.assertEqual(self.event.auth_request_to, 'client@functional.test')
|
||||||
|
self.assertIsNotNone(self.event.auth_request_at)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import pytz
|
import pytz
|
||||||
|
import reversion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
from datetime import date, timedelta, datetime, time
|
from datetime import date, timedelta, datetime, time
|
||||||
@@ -354,3 +356,37 @@ class EventPricingTestCase(TestCase):
|
|||||||
def test_grand_total(self):
|
def test_grand_total(self):
|
||||||
self.assertEqual(self.e1.total, Decimal('84.48'))
|
self.assertEqual(self.e1.total, Decimal('84.48'))
|
||||||
self.assertEqual(self.e2.total, Decimal('419.32'))
|
self.assertEqual(self.e2.total, Decimal('419.32'))
|
||||||
|
|
||||||
|
|
||||||
|
class EventAuthorisationTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01')
|
||||||
|
self.profile = models.Profile.objects.get_or_create(
|
||||||
|
first_name='Test',
|
||||||
|
last_name='TEC User',
|
||||||
|
username='eventauthtest',
|
||||||
|
email='teccie@functional.test',
|
||||||
|
is_superuser=True # lazily grant all permissions
|
||||||
|
)[0]
|
||||||
|
self.person = models.Person.objects.create(name='Authorisation Test Person')
|
||||||
|
self.organisation = models.Organisation.objects.create(name='Authorisation Test Organisation')
|
||||||
|
self.event = models.Event.objects.create(name="AuthorisationTestCase", person=self.person,
|
||||||
|
start_date=date.today())
|
||||||
|
# Add some items
|
||||||
|
models.EventItem.objects.create(event=self.event, name="Authorisation test item", quantity=2, cost=123.45,
|
||||||
|
order=1)
|
||||||
|
|
||||||
|
def test_event_property(self):
|
||||||
|
auth1 = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case",
|
||||||
|
name="Test Auth 1", amount=self.event.total - 1,
|
||||||
|
sent_by=self.profile)
|
||||||
|
self.assertFalse(self.event.authorised)
|
||||||
|
auth1.amount = self.event.total
|
||||||
|
auth1.save()
|
||||||
|
self.assertTrue(self.event.authorised)
|
||||||
|
|
||||||
|
def test_last_edited(self):
|
||||||
|
with reversion.revisions.create_revision():
|
||||||
|
auth = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case",
|
||||||
|
name="Test Auth", amount=self.event.total, sent_by=self.profile)
|
||||||
|
self.assertIsNotNone(auth.last_edited_at)
|
||||||
|
|||||||
47
RIGS/urls.py
47
RIGS/urls.py
@@ -18,6 +18,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
url('^user/login/$', views.login, name='login'),
|
url('^user/login/$', views.login, name='login'),
|
||||||
url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'),
|
url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'),
|
||||||
|
|
||||||
url(r'^user/password_reset/$', password_reset, {'password_reset_form': forms.PasswordReset}),
|
url(r'^user/password_reset/$', password_reset, {'password_reset_form': forms.PasswordReset}),
|
||||||
|
|
||||||
# People
|
# People
|
||||||
@@ -72,9 +73,12 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Rigboard
|
# Rigboard
|
||||||
url(r'^rigboard/$', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'),
|
url(r'^rigboard/$', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'),
|
||||||
url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()),
|
||||||
url(r'^rigboard/calendar/(?P<view>(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
name='web_calendar'),
|
||||||
url(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
url(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
|
||||||
|
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||||
|
url(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
|
||||||
|
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||||
url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
||||||
url(r'^rigboard/activity/$',
|
url(r'^rigboard/activity/$',
|
||||||
permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()),
|
permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()),
|
||||||
@@ -84,10 +88,12 @@ urlpatterns = [
|
|||||||
name='activity_feed'),
|
name='activity_feed'),
|
||||||
|
|
||||||
url(r'^event/(?P<pk>\d+)/$',
|
url(r'^event/(?P<pk>\d+)/$',
|
||||||
permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()),
|
permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(
|
||||||
|
rigboard.EventDetail.as_view()),
|
||||||
name='event_detail'),
|
name='event_detail'),
|
||||||
url(r'^event/(?P<pk>\d+)/embed/$',
|
url(r'^event/(?P<pk>\d+)/embed/$',
|
||||||
xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())),
|
xframe_options_exempt(
|
||||||
|
login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())),
|
||||||
name='event_embed'),
|
name='event_embed'),
|
||||||
url(r'^event/(?P<pk>\d+)/oembed_json/$',
|
url(r'^event/(?P<pk>\d+)/oembed_json/$',
|
||||||
rigboard.EventOembed.as_view(),
|
rigboard.EventOembed.as_view(),
|
||||||
@@ -111,8 +117,6 @@ urlpatterns = [
|
|||||||
permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()),
|
permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()),
|
||||||
name='event_history', kwargs={'model': models.Event}),
|
name='event_history', kwargs={'model': models.Event}),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Finance
|
# Finance
|
||||||
url(r'^invoice/$',
|
url(r'^invoice/$',
|
||||||
permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()),
|
permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()),
|
||||||
@@ -147,6 +151,20 @@ urlpatterns = [
|
|||||||
permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()),
|
permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()),
|
||||||
name='payment_delete'),
|
name='payment_delete'),
|
||||||
|
|
||||||
|
# Client event authorisation
|
||||||
|
url(r'^event/(?P<pk>\d+)/auth/$',
|
||||||
|
permission_required_with_403('RIGS.change_event')(
|
||||||
|
rigboard.EventAuthorisationRequest.as_view()
|
||||||
|
),
|
||||||
|
name='event_authorise_request'),
|
||||||
|
url(r'^event/(?P<pk>\d+)/auth/preview/$',
|
||||||
|
permission_required_with_403('RIGS.change_event')(
|
||||||
|
rigboard.EventAuthoriseRequestEmailPreview.as_view()
|
||||||
|
),
|
||||||
|
name='event_authorise_preview'),
|
||||||
|
url(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
|
||||||
|
name='event_authorise'),
|
||||||
|
|
||||||
# User editing
|
# User editing
|
||||||
url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'),
|
url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'),
|
||||||
url(r'^user/(?P<pk>\d+)/$',
|
url(r'^user/(?P<pk>\d+)/$',
|
||||||
@@ -154,17 +172,22 @@ urlpatterns = [
|
|||||||
name='profile_detail'),
|
name='profile_detail'),
|
||||||
url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()),
|
url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()),
|
||||||
name='profile_update_self'),
|
name='profile_update_self'),
|
||||||
url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'),
|
url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)),
|
||||||
|
name='reset_api_key'),
|
||||||
|
|
||||||
# ICS Calendar - API key authentication
|
# ICS Calendar - API key authentication
|
||||||
url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"),
|
url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),
|
||||||
|
name="ics_calendar"),
|
||||||
|
|
||||||
# API
|
# API
|
||||||
url(r'^api/(?P<model>\w+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"),
|
url(r'^api/(?P<model>\w+)/$', login_required(views.SecureAPIRequest.as_view()),
|
||||||
url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"),
|
name="api_secure"),
|
||||||
|
url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$', login_required(views.SecureAPIRequest.as_view()),
|
||||||
|
name="api_secure"),
|
||||||
|
|
||||||
# Legacy URL's
|
# Legacy URL's
|
||||||
url(r'^rig/show/(?P<pk>\d+)/$', RedirectView.as_view(permanent=True, pattern_name='event_detail')),
|
url(r'^rig/show/(?P<pk>\d+)/$',
|
||||||
|
RedirectView.as_view(permanent=True, pattern_name='event_detail')),
|
||||||
url(r'^bookings/$', RedirectView.as_view(permanent=True, pattern_name='rigboard')),
|
url(r'^bookings/$', RedirectView.as_view(permanent=True, pattern_name='rigboard')),
|
||||||
url(r'^bookings/past/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
url(r'^bookings/past/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ django-recaptcha==1.3.0
|
|||||||
django-registration-redux==1.6
|
django-registration-redux==1.6
|
||||||
django-reversion==1.10.2
|
django-reversion==1.10.2
|
||||||
django-toolbelt==0.0.1
|
django-toolbelt==0.0.1
|
||||||
|
premailer==3.0.1
|
||||||
django-widget-tweaks==1.4.1
|
django-widget-tweaks==1.4.1
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
icalendar==3.11.4
|
icalendar==3.11.4
|
||||||
|
|||||||
125
templates/base_client.html
Normal file
125
templates/base_client.html
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
{% load static from staticfiles %}
|
||||||
|
{% load raven %}
|
||||||
|
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html
|
||||||
|
dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}"
|
||||||
|
xml:lang="{% firstof LANGUAGE_CODE 'en' %}"
|
||||||
|
lang="{% firstof LANGUAGE_CODE 'en' %}">
|
||||||
|
<head>
|
||||||
|
<title>{% block title %}{% endblock %} | Rig Information Gathering System</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="{% static "imgs/pyrigs-avatar.png" %}">
|
||||||
|
<link rel="apple-touch-icon" href="{% static "imgs/pyrigs-avatar.png" %}">
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet'
|
||||||
|
type='text/css'>
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "css/screen.css" %}">
|
||||||
|
{% block css %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-1.8.3.min.js"
|
||||||
|
integrity="sha256-YcbK69I5IXQftf/mYD8WY0/KmEDCv1asggHpJk1trM8=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.ravenjs.com/1.3.0/jquery,native/raven.min.js"></script>
|
||||||
|
<script>Raven.config('{% sentry_public_dsn %}').install()</script>
|
||||||
|
{% block preload_js %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra-head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% include "analytics.html" %}
|
||||||
|
<div class="navbar navbar-fixed-top navbar-inverse hidden-print" role="navigation">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<span class="navbar-brand">RIGS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div id="content" class="row">
|
||||||
|
{% block content-header %}
|
||||||
|
{% if error %}
|
||||||
|
<div class="error">{{ error }}</div>{% endif %}
|
||||||
|
{% if info %}
|
||||||
|
<div class="info">{{ info }}</div>{% endif %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.level_tag }} alert-dismissible" role="alert">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||||
|
aria-hidden="true">×</span></button>
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="sidebar" class="column">
|
||||||
|
{% block sidebar %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="modal" role="dialog" tabindex=-1></div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="loading-modal" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Please wait</h2>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="loading-animation">
|
||||||
|
<div class="circle"></div>
|
||||||
|
<div class="circle1"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Date.prototype.getISOString = function () {
|
||||||
|
var yyyy = this.getFullYear().toString();
|
||||||
|
var mm = (this.getMonth() + 1).toString(); // getMonth() is zero-based
|
||||||
|
var dd = this.getDate().toString();
|
||||||
|
return yyyy + '-' + (mm[1] ? mm : "0" + mm[0]) + '-' + (dd[1] ? dd : "0" + dd[0]); // padding
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="{% static "js/jquery.cookie.js" %}"></script>
|
||||||
|
<script src="{% static "js/alert.js" %}"></script>
|
||||||
|
<script src="{% static "js/collapse.js" %}"></script>
|
||||||
|
<script>
|
||||||
|
$('.navbar-collapse').addClass('collapse')
|
||||||
|
</script>
|
||||||
|
<script src="{% static "js/dropdown.js" %}"></script>
|
||||||
|
<script src="{% static "js/modal.js" %}"></script>
|
||||||
|
<script>
|
||||||
|
jQuery(document).ready(function () {
|
||||||
|
jQuery(document).on('click', '.modal-href', function (e) {
|
||||||
|
$link = jQuery(this);
|
||||||
|
// Anti modal inception
|
||||||
|
if ($link.parents('#modal').length == 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
modaltarget = $link.data('target');
|
||||||
|
modalobject = "";
|
||||||
|
jQuery('#modal').load($link.attr('href'), function (e) {
|
||||||
|
jQuery('#modal').modal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% block js %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
58
templates/base_client_email.html
Normal file
58
templates/base_client_email.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{% load static from staticfiles %}
|
||||||
|
{% load raven %}
|
||||||
|
|
||||||
|
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<table class="main-table">
|
||||||
|
<tr class="client-header">
|
||||||
|
<td align="center">
|
||||||
|
<!--[if mso]>
|
||||||
|
<center>
|
||||||
|
<table><tr><td width="640">
|
||||||
|
<![endif]-->
|
||||||
|
<table class="logos" cellpadding=20>
|
||||||
|
<tr>
|
||||||
|
<td align="left">
|
||||||
|
<img src="https://www.nottinghamtec.co.uk/imgs/tec_logo_white.png"/>
|
||||||
|
</td>
|
||||||
|
<td align="right">
|
||||||
|
<img src="https://www.nottinghamtec.co.uk/imgs/UoNSU.png"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!--[if mso]>
|
||||||
|
</td></tr></table>
|
||||||
|
</center>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" class="content-container">
|
||||||
|
<!--[if mso]>
|
||||||
|
<center>
|
||||||
|
<table><tr><td width="600">
|
||||||
|
<![endif]-->
|
||||||
|
<div class="content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<!--[if mso]>
|
||||||
|
</td></tr></table>
|
||||||
|
</center>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user