mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-23 16:32:15 +00:00
Client facing authorisation procedures.
Add forms, views, templates and URLs. Remove created at in favour of the built in versioning as that's much more accurate. Switch to a OneToOneField with EventAuthorisation -> event as a result of this. Move validation from models to forms where it probably belongs. Provide more descriptive errors. Add success page for authorisation.
This commit is contained in:
@@ -141,3 +141,37 @@ class EventForm(forms.ModelForm):
|
|||||||
'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']
|
'collector', 'purchase_order']
|
||||||
|
|
||||||
|
|
||||||
|
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.')
|
||||||
|
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 ExternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(ExternalClientEventAuthorisationForm, self).__init__(**kwargs)
|
||||||
|
self.fields['po'].required = True
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.EventAuthorisation
|
||||||
|
fields = ('tos', 'name', 'amount', 'po')
|
||||||
|
|||||||
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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -387,7 +387,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def authorised(self):
|
def authorised(self):
|
||||||
return self.authroisations.latest('created_at').amount >= self.total
|
return self.authorisation.amount == self.total
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_start_time(self):
|
def has_start_time(self):
|
||||||
@@ -505,28 +505,15 @@ class EventCrew(models.Model):
|
|||||||
notes = models.TextField(blank=True, null=True)
|
notes = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
@reversion.register
|
||||||
class EventAuthorisation(models.Model):
|
class EventAuthorisation(models.Model):
|
||||||
event = models.ForeignKey('Event', related_name='authroisations')
|
event = models.OneToOneField('Event', related_name='authorisation')
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
|
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)
|
account_code = models.CharField(max_length=50, blank=True, null=True)
|
||||||
po = models.CharField(max_length=255, blank=True, null=True, verbose_name="purchase order")
|
po = models.CharField(max_length=255, blank=True, null=True, verbose_name="purchase order")
|
||||||
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
|
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
if self.amount != self.event.total:
|
|
||||||
raise ValidationError("The amount authorised must equal the total for the event")
|
|
||||||
if self.event.organisation and self.event.organisation.union_account:
|
|
||||||
# Is a union account, requires username and account number
|
|
||||||
if self.uni_id is None or self.uni_id == "" or self.account_code is None or self.account_code == "":
|
|
||||||
raise ValidationError("Internal clients require a University ID number and an account code")
|
|
||||||
else:
|
|
||||||
# Is an external client, only requires PO
|
|
||||||
if self.po is None or self.po == "":
|
|
||||||
raise ValidationError("External clients require a Purchase Order number")
|
|
||||||
return super(EventAuthorisation, self).clean()
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ 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 z3c.rml import rml2pdf
|
from z3c.rml import rml2pdf
|
||||||
@@ -36,15 +38,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 +57,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 +88,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()
|
||||||
@@ -117,15 +119,17 @@ class EventUpdate(generic.UpdateView):
|
|||||||
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)
|
if self.request.method in (
|
||||||
new.pk = None # This means a new event will be created on save, and all items will be re-created
|
'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.')
|
||||||
|
|
||||||
@@ -136,6 +140,7 @@ class EventDuplicate(EventUpdate):
|
|||||||
context["duplicate"] = True
|
context["duplicate"] = True
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class EventPrint(generic.View):
|
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)
|
||||||
@@ -145,8 +150,7 @@ class EventPrint(generic.View):
|
|||||||
merger = PdfFileMerger()
|
merger = PdfFileMerger()
|
||||||
|
|
||||||
for copy in copies:
|
for copy in copies:
|
||||||
|
context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this
|
||||||
context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this
|
|
||||||
'object': object,
|
'object': object,
|
||||||
'fonts': {
|
'fonts': {
|
||||||
'opensans': {
|
'opensans': {
|
||||||
@@ -154,8 +158,8 @@ class EventPrint(generic.View):
|
|||||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'copy':copy,
|
'copy': copy,
|
||||||
'current_user':request.user,
|
'current_user': request.user,
|
||||||
})
|
})
|
||||||
|
|
||||||
# context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3
|
# context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3
|
||||||
@@ -183,6 +187,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"
|
||||||
@@ -219,3 +224,68 @@ 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):
|
||||||
|
# TODO: send email confirmation
|
||||||
|
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.')
|
||||||
|
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 self.event.authorisation
|
||||||
|
|
||||||
|
def get_form_class(self):
|
||||||
|
if self.event.organisation is not None and self.event.organisation.union_account:
|
||||||
|
return forms.InternalClientEventAuthorisationForm
|
||||||
|
else:
|
||||||
|
return forms.ExternalClientEventAuthorisationForm
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(EventAuthorise, self).get_context_data(**kwargs)
|
||||||
|
context['event'] = self.event
|
||||||
|
|
||||||
|
if self.get_form_class() is forms.InternalClientEventAuthorisationForm:
|
||||||
|
context['internal'] = True
|
||||||
|
else:
|
||||||
|
context['internal'] = False
|
||||||
|
|
||||||
|
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. Please confirm you wish to reauthorise")
|
||||||
|
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
|
||||||
|
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']
|
||||||
|
except (signing.BadSignature, AssertionError, KeyError):
|
||||||
|
raise SuspiciousOperation(
|
||||||
|
"The security integrity of that URL is invalid. Please contact your event MIC to obtain a new URL")
|
||||||
|
return super(EventAuthorise, self).dispatch(request, *args, **kwargs)
|
||||||
|
|||||||
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>
|
||||||
118
RIGS/templates/RIGS/eventauthorisation_form.html
Normal file
118
RIGS/templates/RIGS/eventauthorisation_form.html
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
{% 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>
|
||||||
|
|
||||||
|
<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">Event Authorisation</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
<form class="form-horizontal itemised_form" role="form" method="POST">{% csrf_token %}
|
||||||
|
{% include 'form_errors.html' %}
|
||||||
|
<div class="row">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{% if internal %}
|
||||||
|
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||||
|
title="Your University ID 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>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-md-6">
|
||||||
|
{% if internal %}
|
||||||
|
<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>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||||
|
title="Your Purchase Order reference for this event.">
|
||||||
|
<label for="{{ form.po.id_for_label }}"
|
||||||
|
class="col-sm-4 control-label">{{ form.po.label }}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% render_field form.po class+="form-control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<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-6 form-group">
|
||||||
|
<div class="col-sm-offset-4 col-sm-8" 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 agree to the TEC
|
||||||
|
<a href="{{ tos_url }}">Terms of Hire</a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-md-6 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 %}
|
||||||
69
RIGS/templates/RIGS/eventauthorisation_success.html
Normal file
69
RIGS/templates/RIGS/eventauthorisation_success.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{% 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">
|
||||||
|
{% if internal %}
|
||||||
|
<dt>Account code</dt>
|
||||||
|
<dd>{{ object.account_code }}</dd>
|
||||||
|
{% else %}
|
||||||
|
<dt>PO</dt>
|
||||||
|
<dd>{{ object.po }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<dt>Authorised amount</dt>
|
||||||
|
<dd>£ {{ object.amount|floatformat:2 }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
36
RIGS/urls.py
36
RIGS/urls.py
@@ -16,7 +16,8 @@ urlpatterns = patterns('',
|
|||||||
|
|
||||||
url('^user/login/$', 'RIGS.views.login', name='login'),
|
url('^user/login/$', 'RIGS.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/$', 'django.contrib.auth.views.password_reset', {'password_reset_form': forms.PasswordReset}),
|
url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset',
|
||||||
|
{'password_reset_form': forms.PasswordReset}),
|
||||||
|
|
||||||
# People
|
# People
|
||||||
url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()),
|
url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()),
|
||||||
@@ -70,9 +71,12 @@ urlpatterns = patterns('',
|
|||||||
|
|
||||||
# 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()),
|
||||||
@@ -82,10 +86,12 @@ urlpatterns = patterns('',
|
|||||||
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(),
|
||||||
@@ -109,7 +115,8 @@ urlpatterns = patterns('',
|
|||||||
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}),
|
||||||
|
|
||||||
|
url(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
|
||||||
|
name='event_authorise'),
|
||||||
|
|
||||||
# Finance
|
# Finance
|
||||||
url(r'^invoice/$',
|
url(r'^invoice/$',
|
||||||
@@ -152,17 +159,22 @@ urlpatterns = patterns('',
|
|||||||
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')),
|
||||||
)
|
)
|
||||||
|
|||||||
109
templates/base_client.html
Normal file
109
templates/base_client.html
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
{% 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>
|
||||||
|
|
||||||
|
<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>
|
||||||
Reference in New Issue
Block a user