Merge master into testing

This commit is contained in:
Tom Price
2015-05-31 22:37:20 +01:00
91 changed files with 17971 additions and 18722 deletions

View File

@@ -1,10 +1,16 @@
import cStringIO as StringIO
from django.core.urlresolvers import reverse_lazy
from django.db import connection
from django.http import Http404, HttpResponseRedirect
from django.views import generic
from django.template import RequestContext
from django.template.loader import get_template
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.contrib import messages
import datetime
from z3c.rml import rml2pdf
from RIGS import models
@@ -33,6 +39,34 @@ class InvoiceIndex(generic.ListView):
class InvoiceDetail(generic.DetailView):
model = models.Invoice
class InvoicePrint(generic.View):
def get(self, request, pk):
invoice = get_object_or_404(models.Invoice, pk=pk)
object = invoice.event
template = get_template('RIGS/event_print.xml')
copies = ('TEC', 'Client')
context = RequestContext(request, {
'object': object,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'invoice':invoice,
})
rml = template.render(context)
buffer = StringIO.StringIO()
buffer = rml2pdf.parseString(rml)
pdfData = buffer.read()
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, object.name)
response.write(pdfData)
return response
class InvoiceVoid(generic.View):
def get(self, *args, **kwargs):
@@ -81,6 +115,7 @@ class InvoiceEvent(generic.View):
class PaymentCreate(generic.CreateView):
model = models.Payment
fields = ['invoice','date','amount','method']
def get_initial(self):
initial = super(generic.CreateView, self).get_initial()

View File

@@ -3,7 +3,9 @@ from django import forms
from django.utils import formats
from django.conf import settings
from django.core import serializers
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm
from registration.forms import RegistrationFormUniqueEmail
from captcha.fields import ReCaptchaField
import simplejson
from RIGS import models
@@ -14,6 +16,11 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
last_name = forms.CharField(required=False, max_length=50)
initials = forms.CharField(required=True, max_length=5)
phone = forms.CharField(required=False, max_length=13)
captcha = ReCaptchaField()
class Meta:
model = models.Profile
fields = ('first_name','last_name','initials','phone')
def clean_initials(self):
"""
@@ -23,6 +30,13 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
raise forms.ValidationError("These initials are already in use. Please supply different initials.")
return self.cleaned_data['initials']
# Login form
class LoginForm(AuthenticationForm):
captcha = ReCaptchaField(label='Captcha')
class PasswordReset(PasswordResetForm):
captcha = ReCaptchaField(label='Captcha')
# Events Shit
class EventForm(forms.ModelForm):
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + settings.DATETIME_INPUT_FORMATS
@@ -58,7 +72,7 @@ class EventForm(forms.ModelForm):
self.fields['end_date'].widget.format = '%Y-%m-%d'
self.fields['access_at'].widget.format = '%Y-%m-%dT%H:%M:%S'
self.fields['access_at'].widget.format = '%Y-%m-%dT%H:%M:%S'
self.fields['meet_at'].widget.format = '%Y-%m-%dT%H:%M:%S'
def init_items(self):
self.items = self.process_items_json()
@@ -116,4 +130,4 @@ class EventForm(forms.ModelForm):
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
'collector',]
'collector','purchase_order']

136
RIGS/ical.py Normal file
View File

@@ -0,0 +1,136 @@
from RIGS import models, forms
from django_ical.views import ICalFeed
from django.db.models import Q
from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch
from django.utils import timezone
from django.conf import settings
import datetime, pytz
class CalendarICS(ICalFeed):
"""
A simple event calender
"""
#Metadata which is passed on to clients
product_id = 'RIGS'
title = 'RIGS Calendar'
timezone = settings.TIME_ZONE
file_name = "rigs.ics"
def get(self, *args, **kwargs):
timezone.activate(timezone.UTC)
return super(CalendarICS, self).get(*args, **kwargs)
def items(self):
#include events from up to 1 year ago
start = datetime.datetime.now() - datetime.timedelta(days=365)
filter = Q(start_date__gte=start) & ~Q(status=models.Event.CANCELLED)
return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
def item_title(self, item):
title = ''
# Prefix title with status (if it's a critical status)
if item.cancelled:
title += 'CANCELLED: '
if not item.is_rig:
title += 'NON-RIG: '
if item.dry_hire:
title += 'DRY HIRE: '
# Add the rig name
title += item.name
# Add the status
title += ' ('+str(item.get_status_display())+')'
return title
def item_start_datetime(self, item):
#set start date to the earliest defined time for the event
if item.meet_at:
startDateTime = item.meet_at
elif item.access_at:
startDateTime = item.access_at
elif item.has_start_time:
startDateTime = datetime.datetime.combine(item.start_date,item.start_time)
tz = pytz.timezone(settings.TIME_ZONE)
startDateTime = tz.normalize(tz.localize(startDateTime)).astimezone(pytz.timezone(self.timezone))
else:
startDateTime = item.start_date
return startDateTime
def item_end_datetime(self, item):
# Assume end is same as start
endDateTime = item.start_date
# If end date defined then use it
if item.end_date:
endDateTime = item.end_date
if item.has_start_time and item.has_end_time: # don't allow an event with specific end but no specific start
endDateTime = datetime.datetime.combine(endDateTime,item.end_time)
tz = pytz.timezone(settings.TIME_ZONE)
endDateTime = tz.normalize(tz.localize(endDateTime)).astimezone(pytz.timezone(self.timezone))
elif item.has_end_time: # if there's a start time specified then an end time should also be specified
endDateTime = datetime.datetime.combine(endDateTime+datetime.timedelta(days=1),datetime.time(00, 00))
#elif item.end_time: # end time but no start time - this is weird - don't think ICS will like it so ignoring
# do nothing
return endDateTime
def item_location(self,item):
return item.venue
def item_description(self, item):
# Create a nice information-rich description
# note: only making use of information available to "non-keyholders"
desc = 'Rig ID = '+str(item.pk)+'\n'
desc += 'Event = ' + item.name + '\n'
desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n'
if item.is_rig and item.person:
desc += 'Client = ' + item.person.name + ( (' for '+item.organisation.name) if item.organisation else '') + '\n'
desc += 'Status = ' + str(item.get_status_display()) + '\n'
desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n'
desc += '\n'
if item.meet_at:
desc += 'Crew Meet = ' + (item.meet_at.strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n'
if item.access_at:
desc += 'Access At = ' + item.access_at.strftime('%Y-%m-%d %H:%M') + '\n'
if item.start_date:
desc += 'Event Start = ' + item.start_date.strftime('%Y-%m-%d') + ((' '+item.start_time.strftime('%H:%M')) if item.has_start_time else '') + '\n'
if item.end_date:
desc += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + ((' '+item.end_time.strftime('%H:%M')) if item.has_end_time else '') + '\n'
desc += '\n'
if item.description:
desc += 'Event Description:\n'+item.description+'\n\n'
if item.notes:
desc += 'Notes:\n'+item.notes+'\n\n'
base_url = "http://rigs.nottinghamtec.co.uk"
desc += 'URL = '+base_url+str(item.get_absolute_url())
return desc
def item_link(self, item):
# Make a link to the event in the web interface
# base_url = "https://pyrigs.nottinghamtec.co.uk"
return item.get_absolute_url()
# def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) - not really necessary though
# return ''
def item_updated(self, item): # some ical clients will display this
return item.last_edited_at
def item_guid(self, item): # use the rig-id as the ical unique event identifier
return item.pk

View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0020_auto_20150303_0243'),
]
operations = [
migrations.AlterModelOptions(
name='invoice',
options={'ordering': ['-invoice_date'], 'permissions': (('view_invoice', 'Can view Invoices'),)},
),
migrations.AddField(
model_name='profile',
name='api_key',
field=models.CharField(max_length=40, null=True, editable=False, blank=True),
preserve_default=True,
),
migrations.AlterField(
model_name='event',
name='collector',
field=models.CharField(max_length=255, null=True, verbose_name=b'Collected By', blank=True),
preserve_default=True,
),
migrations.AlterField(
model_name='profile',
name='initials',
field=models.CharField(max_length=5, unique=True, null=True),
preserve_default=True,
),
]

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0021_auto_20150420_1155'),
]
operations = [
migrations.AlterField(
model_name='event',
name='collector',
field=models.CharField(max_length=255, null=True, verbose_name=b'Collected by', blank=True),
preserve_default=True,
),
migrations.AlterField(
model_name='event',
name='purchase_order',
field=models.CharField(max_length=255, null=True, verbose_name=b'PO', blank=True),
preserve_default=True,
),
]

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.core.validators
import django.contrib.auth.models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0022_auto_20150424_2104'),
]
operations = [
migrations.AlterModelOptions(
name='profile',
options={'permissions': (('view_profile', 'Can view Profile'),)},
),
migrations.AlterModelManagers(
name='profile',
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.AlterField(
model_name='event',
name='collector',
field=models.CharField(max_length=255, null=True, verbose_name=b'collected by', blank=True),
),
migrations.AlterField(
model_name='organisation',
name='email',
field=models.EmailField(max_length=254, null=True, blank=True),
),
migrations.AlterField(
model_name='person',
name='email',
field=models.EmailField(max_length=254, null=True, blank=True),
),
migrations.AlterField(
model_name='profile',
name='email',
field=models.EmailField(max_length=254, verbose_name='email address', blank=True),
),
migrations.AlterField(
model_name='profile',
name='groups',
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'),
),
migrations.AlterField(
model_name='profile',
name='last_login',
field=models.DateTimeField(null=True, verbose_name='last login', blank=True),
),
migrations.AlterField(
model_name='profile',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'),
),
migrations.AlterField(
model_name='venue',
name='email',
field=models.EmailField(max_length=254, null=True, blank=True),
),
]

View File

@@ -7,25 +7,48 @@ from django.conf import settings
from django.utils.functional import cached_property
from django.utils.encoding import python_2_unicode_compatible
import reversion
import string
import random
from django.core.urlresolvers import reverse_lazy
from decimal import Decimal
# Create your models here.
@python_2_unicode_compatible
class Profile(AbstractUser):
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
phone = models.CharField(max_length=13, null=True, blank=True)
api_key = models.CharField(max_length=40,blank=True,editable=False, null=True)
@classmethod
def make_api_key(cls):
size=20
chars=string.ascii_letters + string.digits
new_api_key = ''.join(random.choice(chars) for x in range(size))
return new_api_key;
@property
def profile_picture(self):
url = ""
if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None:
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email).hexdigest() + "?d=identicon&s=500"
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email).hexdigest() + "?d=wavatar&s=500"
return url
@property
def name(self):
return self.get_full_name() + ' "' + self.initials + '"'
@property
def latest_events(self):
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
def __str__(self):
return self.name
class Meta:
permissions = (
('view_profile', 'Can view Profile'),
)
class RevisionMixin(object):
@property
@@ -68,6 +91,9 @@ class Person(models.Model, RevisionMixin):
def latest_events(self):
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
def get_absolute_url(self):
return reverse_lazy('person_detail', kwargs={'pk': self.pk})
class Meta:
permissions = (
('view_person', 'Can view Persons'),
@@ -104,6 +130,9 @@ class Organisation(models.Model, RevisionMixin):
def latest_events(self):
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
def get_absolute_url(self):
return reverse_lazy('organisation_detail', kwargs={'pk': self.pk})
class Meta:
permissions = (
('view_organisation', 'Can view Organisations'),
@@ -166,6 +195,9 @@ class Venue(models.Model, RevisionMixin):
def latest_events(self):
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
def get_absolute_url(self):
return reverse_lazy('venue_detail', kwargs={'pk': self.pk})
class Meta:
permissions = (
('view_venue', 'Can view Venues'),
@@ -245,8 +277,8 @@ class Event(models.Model, RevisionMixin):
# Monies
payment_method = models.CharField(max_length=255, blank=True, null=True)
payment_received = models.CharField(max_length=255, blank=True, null=True)
purchase_order = models.CharField(max_length=255, blank=True, null=True)
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='Collected By')
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')
# Calculated values
"""
@@ -265,7 +297,9 @@ class Event(models.Model, RevisionMixin):
#total = 0.0
#for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
# total += item.sum
total = EventItem.objects.filter(event=self).aggregate(sum_total=models.Sum('cost',field="quantity * cost"))['sum_total']
total = EventItem.objects.filter(event=self).aggregate(
sum_total=models.Sum(models.F('cost')*models.F('quantity'), output_field=models.DecimalField(max_digits=10, decimal_places=2))
)['sum_total']
if total:
return total
return Decimal("0.00")
@@ -293,8 +327,19 @@ class Event(models.Model, RevisionMixin):
def confirmed(self):
return (self.status == self.BOOKED or self.status == self.CONFIRMED)
@property
def has_start_time(self):
return self.start_time is not None
@property
def has_end_time(self):
return self.end_time is not None
objects = EventManager()
def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
def __str__(self):
return str(self.pk) + ": " + self.name

13
RIGS/regbackend.py Normal file
View File

@@ -0,0 +1,13 @@
from RIGS.models import Profile
from RIGS.forms import ProfileRegistrationFormUniqueEmail
def user_created(sender, user, request, **kwargs):
form = ProfileRegistrationFormUniqueEmail(request.POST)
user.first_name = form.data['first_name']
user.last_name = form.data['last_name']
user.initials = form.data['initials']
user.phone = form.data['phone']
user.save()
from registration.signals import user_registered
user_registered.connect(user_created)

View File

@@ -33,6 +33,8 @@ class RigboardIndex(generic.TemplateView):
context['events'] = models.Event.objects.current_events()
return context
class WebCalendar(generic.TemplateView):
template_name = 'RIGS/calendar.html'
class EventDetail(generic.DetailView):
model = models.Event
@@ -88,20 +90,24 @@ class EventPrint(generic.View):
object = get_object_or_404(models.Event, pk=pk)
template = get_template('RIGS/event_print.xml')
copies = ('TEC', 'Client')
context = RequestContext(request, {
'object': object,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
})
merger = PdfFileMerger()
for copy in copies:
context['copy'] = copy
context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this
'object': object,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'copy':copy
})
# context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3
rml = template.render(context)
buffer = StringIO.StringIO()
@@ -124,6 +130,7 @@ class EventPrint(generic.View):
class EventDuplicate(generic.RedirectView):
permanent = False;
def get_redirect_url(self, *args, **kwargs):
new = get_object_or_404(models.Event, pk=kwargs['pk'])
new.pk = None

View File

@@ -0,0 +1,27 @@
/*!
* Ajax Bootstrap Select
*
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
*
* @version 1.3.1
* @author Adam Heim - https://github.com/truckingsim
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
* @copyright 2015 Adam Heim
* @license Released under the MIT license.
*
* Contributors:
* Mark Carver - https://github.com/markcarver
*
* Last build: 2015-01-06 8:43:11 PM EST
*/
.bootstrap-select .status {
background: #f0f0f0;
clear: both;
color: #999;
font-size: 11px;
font-style: italic;
font-weight: 500;
line-height: 1;
margin-bottom: -5px;
padding: 10px 20px;
}

View File

@@ -0,0 +1,366 @@
/*!
* Datetimepicker for Bootstrap 3
* ! version : 4.7.14
* https://github.com/Eonasdan/bootstrap-datetimepicker/
*/
.bootstrap-datetimepicker-widget {
list-style: none;
}
.bootstrap-datetimepicker-widget.dropdown-menu {
margin: 2px 0;
padding: 4px;
width: 19em;
}
@media (min-width: 768px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
@media (min-width: 992px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
@media (min-width: 1200px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
.bootstrap-datetimepicker-widget.dropdown-menu:before,
.bootstrap-datetimepicker-widget.dropdown-menu:after {
content: '';
display: inline-block;
position: absolute;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #cccccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
top: -7px;
left: 7px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid white;
top: -6px;
left: 8px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.top:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #cccccc;
border-top-color: rgba(0, 0, 0, 0.2);
bottom: -7px;
left: 6px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.top:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid white;
bottom: -6px;
left: 7px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before {
left: auto;
right: 6px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after {
left: auto;
right: 7px;
}
.bootstrap-datetimepicker-widget .list-unstyled {
margin: 0;
}
.bootstrap-datetimepicker-widget a[data-action] {
padding: 6px 0;
}
.bootstrap-datetimepicker-widget a[data-action]:active {
box-shadow: none;
}
.bootstrap-datetimepicker-widget .timepicker-hour,
.bootstrap-datetimepicker-widget .timepicker-minute,
.bootstrap-datetimepicker-widget .timepicker-second {
width: 54px;
font-weight: bold;
font-size: 1.2em;
margin: 0;
}
.bootstrap-datetimepicker-widget button[data-action] {
padding: 6px;
}
.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Increment Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Increment Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Decrement Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Decrement Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Show Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Show Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Toggle AM/PM";
}
.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Clear the picker";
}
.bootstrap-datetimepicker-widget .btn[data-action="today"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Set the date to today";
}
.bootstrap-datetimepicker-widget .picker-switch {
text-align: center;
}
.bootstrap-datetimepicker-widget .picker-switch::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Toggle Date and Time Screens";
}
.bootstrap-datetimepicker-widget .picker-switch td {
padding: 0;
margin: 0;
height: auto;
width: auto;
line-height: inherit;
}
.bootstrap-datetimepicker-widget .picker-switch td span {
line-height: 2.5;
height: 2.5em;
width: 100%;
}
.bootstrap-datetimepicker-widget table {
width: 100%;
margin: 0;
}
.bootstrap-datetimepicker-widget table td,
.bootstrap-datetimepicker-widget table th {
text-align: center;
border-radius: 4px;
}
.bootstrap-datetimepicker-widget table th {
height: 20px;
line-height: 20px;
width: 20px;
}
.bootstrap-datetimepicker-widget table th.picker-switch {
width: 145px;
}
.bootstrap-datetimepicker-widget table th.disabled,
.bootstrap-datetimepicker-widget table th.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget table th.prev::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Previous Month";
}
.bootstrap-datetimepicker-widget table th.next::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Next Month";
}
.bootstrap-datetimepicker-widget table thead tr:first-child th {
cursor: pointer;
}
.bootstrap-datetimepicker-widget table thead tr:first-child th:hover {
background: #eeeeee;
}
.bootstrap-datetimepicker-widget table td {
height: 54px;
line-height: 54px;
width: 54px;
}
.bootstrap-datetimepicker-widget table td.cw {
font-size: .8em;
height: 20px;
line-height: 20px;
color: #777777;
}
.bootstrap-datetimepicker-widget table td.day {
height: 20px;
line-height: 20px;
width: 20px;
}
.bootstrap-datetimepicker-widget table td.day:hover,
.bootstrap-datetimepicker-widget table td.hour:hover,
.bootstrap-datetimepicker-widget table td.minute:hover,
.bootstrap-datetimepicker-widget table td.second:hover {
background: #eeeeee;
cursor: pointer;
}
.bootstrap-datetimepicker-widget table td.old,
.bootstrap-datetimepicker-widget table td.new {
color: #777777;
}
.bootstrap-datetimepicker-widget table td.today {
position: relative;
}
.bootstrap-datetimepicker-widget table td.today:before {
content: '';
display: inline-block;
border: 0 0 7px 7px solid transparent;
border-bottom-color: #337ab7;
border-top-color: rgba(0, 0, 0, 0.2);
position: absolute;
bottom: 4px;
right: 4px;
}
.bootstrap-datetimepicker-widget table td.active,
.bootstrap-datetimepicker-widget table td.active:hover {
background-color: #337ab7;
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.bootstrap-datetimepicker-widget table td.active.today:before {
border-bottom-color: #fff;
}
.bootstrap-datetimepicker-widget table td.disabled,
.bootstrap-datetimepicker-widget table td.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget table td span {
display: inline-block;
width: 54px;
height: 54px;
line-height: 54px;
margin: 2px 1.5px;
cursor: pointer;
border-radius: 4px;
}
.bootstrap-datetimepicker-widget table td span:hover {
background: #eeeeee;
}
.bootstrap-datetimepicker-widget table td span.active {
background-color: #337ab7;
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.bootstrap-datetimepicker-widget table td span.old {
color: #777777;
}
.bootstrap-datetimepicker-widget table td span.disabled,
.bootstrap-datetimepicker-widget table td span.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget.usetwentyfour td.hour {
height: 27px;
line-height: 27px;
}
.input-group.date .input-group-addon {
cursor: pointer;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}

File diff suppressed because one or more lines are too long

1061
RIGS/static/css/fullcalendar.css Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,202 @@
/*!
* FullCalendar v2.3.1 Print Stylesheet
* Docs & License: http://fullcalendar.io/
* (c) 2015 Adam Shaw
*/
/*
* Include this stylesheet on your page to get a more printer-friendly calendar.
* When including this stylesheet, use the media='print' attribute of the <link> tag.
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
*/
.fc {
max-width: 100% !important;
}
/* Global Event Restyling
--------------------------------------------------------------------------------------------------*/
.fc-event {
background: #fff !important;
color: #000 !important;
page-break-inside: avoid;
}
.fc-event .fc-resizer {
display: none;
}
/* Table & Day-Row Restyling
--------------------------------------------------------------------------------------------------*/
th,
td,
hr,
thead,
tbody,
.fc-row {
border-color: #ccc !important;
background: #fff !important;
}
/* kill the overlaid, absolutely-positioned common components */
.fc-bg,
.fc-bgevent-skeleton,
.fc-highlight-skeleton,
.fc-helper-skeleton {
display: none;
}
/* don't force a min-height on rows (for DayGrid) */
.fc tbody .fc-row {
height: auto !important; /* undo height that JS set in distributeHeight */
min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */
}
.fc tbody .fc-row .fc-content-skeleton {
position: static; /* undo .fc-rigid */
padding-bottom: 0 !important; /* use a more border-friendly method for this... */
}
.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */
padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */
}
.fc tbody .fc-row .fc-content-skeleton table {
/* provides a min-height for the row, but only effective for IE, which exaggerates this value,
making it look more like 3em. for other browers, it will already be this tall */
height: 1em;
}
/* Undo month-view event limiting. Display all events and hide the "more" links
--------------------------------------------------------------------------------------------------*/
.fc-more-cell,
.fc-more {
display: none !important;
}
.fc tr.fc-limited {
display: table-row !important;
}
.fc td.fc-limited {
display: table-cell !important;
}
.fc-popover {
display: none; /* never display the "more.." popover in print mode */
}
/* TimeGrid Restyling
--------------------------------------------------------------------------------------------------*/
/* undo the min-height 100% trick used to fill the container's height */
.fc-time-grid {
min-height: 0 !important;
}
/* don't display the side axis at all ("all-day" and time cells) */
.fc-agenda-view .fc-axis {
display: none;
}
/* don't display the horizontal lines */
.fc-slats,
.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */
display: none !important; /* important overrides inline declaration */
}
/* let the container that holds the events be naturally positioned and create real height */
.fc-time-grid .fc-content-skeleton {
position: static;
}
/* in case there are no events, we still want some height */
.fc-time-grid .fc-content-skeleton table {
height: 4em;
}
/* kill the horizontal spacing made by the event container. event margins will be done below */
.fc-time-grid .fc-event-container {
margin: 0 !important;
}
/* TimeGrid *Event* Restyling
--------------------------------------------------------------------------------------------------*/
/* naturally position events, vertically stacking them */
.fc-time-grid .fc-event {
position: static !important;
margin: 3px 2px !important;
}
/* for events that continue to a future day, give the bottom border back */
.fc-time-grid .fc-event.fc-not-end {
border-bottom-width: 1px !important;
}
/* indicate the event continues via "..." text */
.fc-time-grid .fc-event.fc-not-end:after {
content: "...";
}
/* for events that are continuations from previous days, give the top border back */
.fc-time-grid .fc-event.fc-not-start {
border-top-width: 1px !important;
}
/* indicate the event is a continuation via "..." text */
.fc-time-grid .fc-event.fc-not-start:before {
content: "...";
}
/* time */
/* undo a previous declaration and let the time text span to a second line */
.fc-time-grid .fc-event .fc-time {
white-space: normal !important;
}
/* hide the the time that is normally displayed... */
.fc-time-grid .fc-event .fc-time span {
display: none;
}
/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */
.fc-time-grid .fc-event .fc-time:after {
content: attr(data-full);
}
/* Vertical Scroller & Containers
--------------------------------------------------------------------------------------------------*/
/* kill the scrollbars and allow natural height */
.fc-scroller,
.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */
.fc-time-grid-container { /* */
overflow: visible !important;
height: auto !important;
}
/* kill the horizontal border/padding used to compensate for scrollbars */
.fc-row {
border: 0 !important;
margin: 0 !important;
}
/* Button Controls
--------------------------------------------------------------------------------------------------*/
.fc-button-group,
.fc button {
display: none; /* don't display any button-related controls */
}

View File

@@ -1,5 +0,0 @@
/* Welcome to Compass. Use this file to write IE specific override styles.
* Import this file using the following HTML or equivalent:
* <!--[if IE]>
* <link href="/stylesheets/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
* <![endif]--> */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because it is too large Load Diff

73
RIGS/static/js/asteroids.min.js vendored Normal file
View File

@@ -0,0 +1,73 @@
(function(){function Asteroids(){if(!window.ASTEROIDS)
window.ASTEROIDS={enemiesKilled:0};function Vector(x,y){if(typeof x=='Object'){this.x=x.x;this.y=x.y;}else{this.x=x;this.y=y;}};Vector.prototype={cp:function(){return new Vector(this.x,this.y);},mul:function(factor){this.x*=factor;this.y*=factor;return this;},mulNew:function(factor){return new Vector(this.x*factor,this.y*factor);},add:function(vec){this.x+=vec.x;this.y+=vec.y;return this;},addNew:function(vec){return new Vector(this.x+vec.x,this.y+vec.y);},sub:function(vec){this.x-=vec.x;this.y-=vec.y;return this;},subNew:function(vec){return new Vector(this.x-vec.x,this.y-vec.y);},rotate:function(angle){var x=this.x,y=this.y;this.x=x*Math.cos(angle)-Math.sin(angle)*y;this.y=x*Math.sin(angle)+Math.cos(angle)*y;return this;},rotateNew:function(angle){return this.cp().rotate(angle);},setAngle:function(angle){var l=this.len();this.x=Math.cos(angle)*l;this.y=Math.sin(angle)*l;return this;},setAngleNew:function(angle){return this.cp().setAngle(angle);},setLength:function(length){var l=this.len();if(l)this.mul(length/l);else this.x=this.y=length;return this;},setLengthNew:function(length){return this.cp().setLength(length);},normalize:function(){var l=this.len();this.x/=l;this.y/=l;return this;},normalizeNew:function(){return this.cp().normalize();},angle:function(){return Math.atan2(this.y,this.x);},collidesWith:function(rect){return this.x>rect.x&&this.y>rect.y&&this.x<rect.x+rect.width&&this.y<rect.y+rect.height;},len:function(){var l=Math.sqrt(this.x*this.x+this.y*this.y);if(l<0.005&&l>-0.005)return 0;return l;},is:function(test){return typeof test=='object'&&this.x==test.x&&this.y==test.y;},toString:function(){return'[Vector('+this.x+', '+this.y+') angle: '+this.angle()+', length: '+this.len()+']';}};function Line(p1,p2){this.p1=p1;this.p2=p2;};Line.prototype={shift:function(pos){this.p1.add(pos);this.p2.add(pos);},intersectsWithRect:function(rect){var LL=new Vector(rect.x,rect.y+rect.height);var UL=new Vector(rect.x,rect.y);var LR=new Vector(rect.x+rect.width,rect.y+rect.height);var UR=new Vector(rect.x+rect.width,rect.y);if(this.p1.x>LL.x&&this.p1.x<UR.x&&this.p1.y<LL.y&&this.p1.y>UR.y&&this.p2.x>LL.x&&this.p2.x<UR.x&&this.p2.y<LL.y&&this.p2.y>UR.y)return true;if(this.intersectsLine(new Line(UL,LL)))return true;if(this.intersectsLine(new Line(LL,LR)))return true;if(this.intersectsLine(new Line(UL,UR)))return true;if(this.intersectsLine(new Line(UR,LR)))return true;return false;},intersectsLine:function(line2){var v1=this.p1,v2=this.p2;var v3=line2.p1,v4=line2.p2;var denom=((v4.y-v3.y)*(v2.x-v1.x))-((v4.x-v3.x)*(v2.y-v1.y));var numerator=((v4.x-v3.x)*(v1.y-v3.y))-((v4.y-v3.y)*(v1.x-v3.x));var numerator2=((v2.x-v1.x)*(v1.y-v3.y))-((v2.y-v1.y)*(v1.x-v3.x));if(denom==0.0){return false;}
var ua=numerator/denom;var ub=numerator2/denom;return(ua>=0.0&&ua<=1.0&&ub>=0.0&&ub<=1.0);}};var that=this;var isIE=!!window.ActiveXObject;var isIEQuirks=isIE&&document.compatMode=="BackCompat";var w=document.documentElement.clientWidth,h=document.documentElement.clientHeight;if(isIEQuirks){w=document.body.clientWidth;h=document.body.clientHeight;}
var playerWidth=20,playerHeight=30;var playerVerts=[[-1*playerHeight/2,-1*playerWidth/2],[-1*playerHeight/2,playerWidth/2],[playerHeight/2,0]];var ignoredTypes=['HTML','HEAD','BODY','SCRIPT','TITLE','META','STYLE','LINK'];if(window.ActiveXObject)
ignoredTypes=['HTML','HEAD','BODY','SCRIPT','TITLE','META','STYLE','LINK','SHAPE','LINE','GROUP','IMAGE','STROKE','FILL','SKEW','PATH','TEXTPATH','INS'];var hiddenTypes=['BR','HR'];var FPS=50;var acc=300;var maxSpeed=600;var rotSpeed=360;var bulletSpeed=700;var particleSpeed=400;var timeBetweenFire=150;var timeBetweenBlink=250;var timeBetweenEnemyUpdate=isIE?10000:2000;var bulletRadius=2;var maxParticles=isIE?20:40;var maxBullets=isIE?10:20;this.flame={r:[],y:[]};this.toggleBlinkStyle=function(){if(this.updated.blink.isActive){removeClass(document.body,'ASTEROIDSBLINK');}else{addClass(document.body,'ASTEROIDSBLINK');}
this.updated.blink.isActive=!this.updated.blink.isActive;};addStylesheet(".ASTEROIDSBLINK .ASTEROIDSYEAHENEMY","outline: 2px dotted red;");this.pos=new Vector(100,100);this.lastPos=false;this.vel=new Vector(0,0);this.dir=new Vector(0,1);this.keysPressed={};this.firedAt=false;this.updated={enemies:false,flame:new Date().getTime(),blink:{time:0,isActive:false}};this.scrollPos=new Vector(0,0);this.bullets=[];this.enemies=[];this.dying=[];this.totalEnemies=0;this.particles=[];function updateEnemyIndex(){for(var i=0,enemy;enemy=that.enemies[i];i++)
removeClass(enemy,"ASTEROIDSYEAHENEMY");var all=document.body.getElementsByTagName('*');that.enemies=[];for(var i=0,el;el=all[i];i++){if(indexOf(ignoredTypes,el.tagName.toUpperCase())==-1&&el.prefix!='g_vml_'&&hasOnlyTextualChildren(el)&&el.className!="ASTEROIDSYEAH"&&el.offsetHeight>0){el.aSize=size(el);that.enemies.push(el);addClass(el,"ASTEROIDSYEAHENEMY");if(!el.aAdded){el.aAdded=true;that.totalEnemies++;}}}};updateEnemyIndex();var createFlames;(function(){var rWidth=playerWidth,rIncrease=playerWidth*0.1,yWidth=playerWidth*0.6,yIncrease=yWidth*0.2,halfR=rWidth/2,halfY=yWidth/2,halfPlayerHeight=playerHeight/2;createFlames=function(){that.flame.r=[[-1*halfPlayerHeight,-1*halfR]];that.flame.y=[[-1*halfPlayerHeight,-1*halfY]];for(var x=0;x<rWidth;x+=rIncrease){that.flame.r.push([-random(2,7)-halfPlayerHeight,x-halfR]);}
that.flame.r.push([-1*halfPlayerHeight,halfR]);for(var x=0;x<yWidth;x+=yIncrease){that.flame.y.push([-random(2,7)-halfPlayerHeight,x-halfY]);}
that.flame.y.push([-1*halfPlayerHeight,halfY]);};})();createFlames();function radians(deg){return deg*0.0174532925;};function degrees(rad){return rad*57.2957795;};function random(from,to){return Math.floor(Math.random()*(to+1)+from);};function code(name){var table={'up':38,'down':40,'left':37,'right':39,'esc':27};if(table[name])return table[name];return name.charCodeAt(0);};function boundsCheck(vec){if(vec.x>w)
vec.x=0;else if(vec.x<0)
vec.x=w;if(vec.y>h)
vec.y=0;else if(vec.y<0)
vec.y=h;};function size(element){var el=element,left=0,top=0;do{left+=el.offsetLeft||0;top+=el.offsetTop||0;el=el.offsetParent;}while(el);return{x:left,y:top,width:element.offsetWidth||10,height:element.offsetHeight||10};};function addEvent(obj,type,fn){if(obj.addEventListener)
obj.addEventListener(type,fn,false);else if(obj.attachEvent){obj["e"+type+fn]=fn;obj[type+fn]=function(){obj["e"+type+fn](window.event);}
obj.attachEvent("on"+type,obj[type+fn]);}}
function removeEvent(obj,type,fn){if(obj.removeEventListener)
obj.removeEventListener(type,fn,false);else if(obj.detachEvent){obj.detachEvent("on"+type,obj[type+fn]);obj[type+fn]=null;obj["e"+type+fn]=null;}}
function arrayRemove(array,from,to){var rest=array.slice((to||from)+1||array.length);array.length=from<0?array.length+from:from;return array.push.apply(array,rest);};function applyVisibility(vis){for(var i=0,p;p=window.ASTEROIDSPLAYERS[i];i++){p.gameContainer.style.visibility=vis;}}
function getElementFromPoint(x,y){applyVisibility('hidden');var element=document.elementFromPoint(x,y);if(!element){applyVisibility('visible');return false;}
if(element.nodeType==3)
element=element.parentNode;applyVisibility('visible');return element;};function addParticles(startPos){var time=new Date().getTime();var amount=maxParticles;for(var i=0;i<amount;i++){that.particles.push({dir:(new Vector(Math.random()*20-10,Math.random()*20-10)).normalize(),pos:startPos.cp(),cameAlive:time});}};function setScore(){that.points.innerHTML=window.ASTEROIDS.enemiesKilled*10;};function hasOnlyTextualChildren(element){if(element.offsetLeft<-100&&element.offsetWidth>0&&element.offsetHeight>0)return false;if(indexOf(hiddenTypes,element.tagName)!=-1)return true;if(element.offsetWidth==0&&element.offsetHeight==0)return false;for(var i=0;i<element.childNodes.length;i++){if(indexOf(hiddenTypes,element.childNodes[i].tagName)==-1&&element.childNodes[i].childNodes.length!=0)return false;}
return true;};function indexOf(arr,item,from){if(arr.indexOf)return arr.indexOf(item,from);var len=arr.length;for(var i=(from<0)?Math.max(0,len+from):from||0;i<len;i++){if(arr[i]===item)return i;}
return-1;};function addClass(element,className){if(element.className.indexOf(className)==-1)
element.className=(element.className+' '+className).replace(/\s+/g,' ').replace(/^\s+|\s+$/g,'');};function removeClass(element,className){element.className=element.className.replace(new RegExp('(^|\\s)'+className+'(?:\\s|$)'),'$1');};function addStylesheet(selector,rules){var stylesheet=document.createElement('style');stylesheet.type='text/css';stylesheet.rel='stylesheet';stylesheet.id='ASTEROIDSYEAHSTYLES';try{stylesheet.innerHTML=selector+"{"+rules+"}";}catch(e){stylesheet.styleSheet.addRule(selector,rules);}
document.getElementsByTagName("head")[0].appendChild(stylesheet);};function removeStylesheet(name){var stylesheet=document.getElementById(name);if(stylesheet){stylesheet.parentNode.removeChild(stylesheet);}};this.gameContainer=document.createElement('div');this.gameContainer.className='ASTEROIDSYEAH';document.body.appendChild(this.gameContainer);this.canvas=document.createElement('canvas');this.canvas.setAttribute('width',w);this.canvas.setAttribute('height',h);this.canvas.className='ASTEROIDSYEAH';with(this.canvas.style){width=w+"px";height=h+"px";position="fixed";top="0px";left="0px";bottom="0px";right="0px";zIndex="10000";}
if(typeof G_vmlCanvasManager!='undefined'){this.canvas=G_vmlCanvasManager.initElement(this.canvas);if(!this.canvas.getContext){alert("So... you're using IE? Please join me at http://github.com/erkie/erkie.github.com if you think you can help");}}else{if(!this.canvas.getContext){alert('This program does not yet support your browser. Please join me at http://github.com/erkie/erkie.github.com if you think you can help');}}
addEvent(this.canvas,'mousedown',function(e){e=e||window.event;var message=document.createElement('span');message.style.position='absolute';message.style.border='1px solid #999';message.style.background='white';message.style.color="black";message.innerHTML='Press Esc to quit';document.body.appendChild(message);var x=e.pageX||(e.clientX+document.documentElement.scrollLeft);var y=e.pageY||(e.clientY+document.documentElement.scrollTop);message.style.left=x-message.offsetWidth/2+'px';message.style.top=y-message.offsetHeight/2+'px';setTimeout(function(){try{message.parentNode.removeChild(message);}catch(e){}},1000);});var eventResize=function(){if(!isIE){that.canvas.style.display="none";w=document.documentElement.clientWidth;h=document.documentElement.clientHeight;that.canvas.setAttribute('width',w);that.canvas.setAttribute('height',h);with(that.canvas.style){display="block";width=w+"px";height=h+"px";}}else{w=document.documentElement.clientWidth;h=document.documentElement.clientHeight;if(isIEQuirks){w=document.body.clientWidth;h=document.body.clientHeight;}
that.canvas.setAttribute('width',w);that.canvas.setAttribute('height',h);}};addEvent(window,'resize',eventResize);this.gameContainer.appendChild(this.canvas);this.ctx=this.canvas.getContext("2d");this.ctx.fillStyle="black";this.ctx.strokeStyle="black";if(!document.getElementById('ASTEROIDS-NAVIGATION')){this.navigation=document.createElement('div');this.navigation.id="ASTEROIDS-NAVIGATION";this.navigation.className="ASTEROIDSYEAH";with(this.navigation.style){fontFamily="Arial,sans-serif";position="fixed";zIndex="10001";bottom="10px";right="10px";textAlign="right";}
this.navigation.innerHTML="(press esc to quit) ";this.gameContainer.appendChild(this.navigation);this.points=document.createElement('span');this.points.id='ASTEROIDS-POINTS';this.points.style.font="28pt Arial, sans-serif";this.points.style.fontWeight="bold";this.points.className="ASTEROIDSYEAH";this.navigation.appendChild(this.points);}else{this.navigation=document.getElementById('ASTEROIDS-NAVIGATION');this.points=document.getElementById('ASTEROIDS-POINTS');}
if(isIEQuirks){this.gameContainer.style.position=this.canvas.style.position=this.navigation.style.position="absolute";}
setScore();if(typeof G_vmlCanvasManager!='undefined'){var children=this.canvas.getElementsByTagName('*');for(var i=0,c;c=children[i];i++)
addClass(c,'ASTEROIDSYEAH');}
var eventKeydown=function(event){event=event||window.event;that.keysPressed[event.keyCode]=true;switch(event.keyCode){case code(' '):that.firedAt=1;break;}
if(indexOf([code('up'),code('down'),code('right'),code('left'),code(' '),code('B'),code('W'),code('A'),code('S'),code('D')],event.keyCode)!=-1){if(event.preventDefault)
event.preventDefault();if(event.stopPropagation)
event.stopPropagation();event.returnValue=false;event.cancelBubble=true;return false;}};addEvent(document,'keydown',eventKeydown);var eventKeypress=function(event){event=event||window.event;if(indexOf([code('up'),code('down'),code('right'),code('left'),code(' '),code('W'),code('A'),code('S'),code('D')],event.keyCode||event.which)!=-1){if(event.preventDefault)
event.preventDefault();if(event.stopPropagation)
event.stopPropagation();event.returnValue=false;event.cancelBubble=true;return false;}};addEvent(document,'keypress',eventKeypress);var eventKeyup=function(event){event=event||window.event;that.keysPressed[event.keyCode]=false;if(indexOf([code('up'),code('down'),code('right'),code('left'),code(' '),code('B'),code('W'),code('A'),code('S'),code('D')],event.keyCode)!=-1){if(event.preventDefault)
event.preventDefault();if(event.stopPropagation)
event.stopPropagation();event.returnValue=false;event.cancelBubble=true;return false;}};addEvent(document,'keyup',eventKeyup);this.ctx.clear=function(){this.clearRect(0,0,w,h);};this.ctx.clear();this.ctx.drawLine=function(xFrom,yFrom,xTo,yTo){this.beginPath();this.moveTo(xFrom,yFrom);this.lineTo(xTo,yTo);this.lineTo(xTo+1,yTo+1);this.closePath();this.fill();};this.ctx.tracePoly=function(verts){this.beginPath();this.moveTo(verts[0][0],verts[0][1]);for(var i=1;i<verts.length;i++)
this.lineTo(verts[i][0],verts[i][1]);this.closePath();};this.ctx.drawPlayer=function(){this.save();this.translate(that.pos.x,that.pos.y);this.rotate(that.dir.angle());this.tracePoly(playerVerts);this.fillStyle="white";this.fill();this.tracePoly(playerVerts);this.stroke();this.restore();};var PI_SQ=Math.PI*2;this.ctx.drawBullets=function(bullets){for(var i=0;i<bullets.length;i++){this.beginPath();this.arc(bullets[i].pos.x,bullets[i].pos.y,bulletRadius,0,PI_SQ,true);this.closePath();this.fill();}};var randomParticleColor=function(){return(['red','yellow'])[random(0,1)];};this.ctx.drawParticles=function(particles){var oldColor=this.fillStyle;for(var i=0;i<particles.length;i++){this.fillStyle=randomParticleColor();this.drawLine(particles[i].pos.x,particles[i].pos.y,particles[i].pos.x-particles[i].dir.x*10,particles[i].pos.y-particles[i].dir.y*10);}
this.fillStyle=oldColor;};this.ctx.drawFlames=function(flame){this.save();this.translate(that.pos.x,that.pos.y);this.rotate(that.dir.angle());var oldColor=this.strokeStyle;this.strokeStyle="red";this.tracePoly(flame.r);this.stroke();this.strokeStyle="yellow";this.tracePoly(flame.y);this.stroke();this.strokeStyle=oldColor;this.restore();}
try{window.focus();}catch(e){}
addParticles(this.pos);addClass(document.body,'ASTEROIDSYEAH');var isRunning=true;var lastUpdate=new Date().getTime();this.update=function(){var forceChange=false;var nowTime=new Date().getTime();var tDelta=(nowTime-lastUpdate)/1000;lastUpdate=nowTime;var drawFlame=false;if(nowTime-this.updated.flame>50){createFlames();this.updated.flame=nowTime;}
this.scrollPos.x=window.pageXOffset||document.documentElement.scrollLeft;this.scrollPos.y=window.pageYOffset||document.documentElement.scrollTop;if((this.keysPressed[code('up')])||(this.keysPressed[code('W')])){this.vel.add(this.dir.mulNew(acc*tDelta));drawFlame=true;}else{this.vel.mul(0.96);}
if((this.keysPressed[code('left')])||(this.keysPressed[code('A')])){forceChange=true;this.dir.rotate(radians(rotSpeed*tDelta*-1));}
if((this.keysPressed[code('right')])||(this.keysPressed[code('D')])){forceChange=true;this.dir.rotate(radians(rotSpeed*tDelta));}
if(this.keysPressed[code(' ')]&&nowTime-this.firedAt>timeBetweenFire){this.bullets.unshift({'dir':this.dir.cp(),'pos':this.pos.cp(),'startVel':this.vel.cp(),'cameAlive':nowTime});this.firedAt=nowTime;if(this.bullets.length>maxBullets){this.bullets.pop();}}
if(this.keysPressed[code('B')]){if(!this.updated.enemies){updateEnemyIndex();this.updated.enemies=true;}
forceChange=true;this.updated.blink.time+=tDelta*1000;if(this.updated.blink.time>timeBetweenBlink){this.toggleBlinkStyle();this.updated.blink.time=0;}}else{this.updated.enemies=false;}
if(this.keysPressed[code('esc')]){destroy.apply(this);return;}
if(this.vel.len()>maxSpeed){this.vel.setLength(maxSpeed);}
this.pos.add(this.vel.mulNew(tDelta));if(this.pos.x>w){window.scrollTo(this.scrollPos.x+50,this.scrollPos.y);this.pos.x=0;}else if(this.pos.x<0){window.scrollTo(this.scrollPos.x-50,this.scrollPos.y);this.pos.x=w;}
if(this.pos.y>h){window.scrollTo(this.scrollPos.x,this.scrollPos.y+h*0.75);this.pos.y=0;}else if(this.pos.y<0){window.scrollTo(this.scrollPos.x,this.scrollPos.y-h*0.75);this.pos.y=h;}
for(var i=this.bullets.length-1;i>=0;i--){if(nowTime-this.bullets[i].cameAlive>2000){this.bullets.splice(i,1);forceChange=true;continue;}
var bulletVel=this.bullets[i].dir.setLengthNew(bulletSpeed*tDelta).add(this.bullets[i].startVel.mulNew(tDelta));this.bullets[i].pos.add(bulletVel);boundsCheck(this.bullets[i].pos);var murdered=getElementFromPoint(this.bullets[i].pos.x,this.bullets[i].pos.y);if(murdered&&murdered.tagName&&indexOf(ignoredTypes,murdered.tagName.toUpperCase())==-1&&hasOnlyTextualChildren(murdered)&&murdered.className!="ASTEROIDSYEAH"){didKill=true;addParticles(this.bullets[i].pos);this.dying.push(murdered);this.bullets.splice(i,1);continue;}}
if(this.dying.length){for(var i=this.dying.length-1;i>=0;i--){try{if(this.dying[i].parentNode)
window.ASTEROIDS.enemiesKilled++;this.dying[i].parentNode.removeChild(this.dying[i]);}catch(e){}}
setScore();this.dying=[];}
for(var i=this.particles.length-1;i>=0;i--){this.particles[i].pos.add(this.particles[i].dir.mulNew(particleSpeed*tDelta*Math.random()));if(nowTime-this.particles[i].cameAlive>1000){this.particles.splice(i,1);forceChange=true;continue;}}
if(isIEQuirks){this.gameContainer.style.left=this.canvas.style.left=document.documentElement.scrollLeft+"px";this.gameContainer.style.top=this.canvas.style.top=document.documentElement.scrollTop+"px";this.navigation.style.right="10px";this.navigation.style.top=document.documentElement.scrollTop+document.body.clientHeight-this.navigation.clientHeight-10+"px";}
if(forceChange||this.bullets.length!=0||this.particles.length!=0||!this.pos.is(this.lastPos)||this.vel.len()>0){this.ctx.clear();this.ctx.drawPlayer();if(drawFlame)
this.ctx.drawFlames(that.flame);if(this.bullets.length){this.ctx.drawBullets(this.bullets);}
if(this.particles.length){this.ctx.drawParticles(this.particles);}}
this.lastPos=this.pos;}
var updateFunc=function(){try{that.update.call(that);}
catch(e){clearInterval(interval);throw e;}};var interval=setInterval(updateFunc,1000/FPS);function destroy(){removeEvent(document,'keydown',eventKeydown);removeEvent(document,'keypress',eventKeypress);removeEvent(document,'keyup',eventKeyup);removeEvent(window,'resize',eventResize);isRunning=false;removeStylesheet("ASTEROIDSYEAHSTYLES");removeClass(document.body,'ASTEROIDSYEAH');this.gameContainer.parentNode.removeChild(this.gameContainer);};}
if(!window.ASTEROIDSPLAYERS)
window.ASTEROIDSPLAYERS=[];if(window.ActiveXObject&&!document.createElement('canvas').getContext){try{var xamlScript=document.createElement('script');xamlScript.setAttribute('type','text/xaml');xamlScript.textContent='<?xml version="1.0"?><Canvas xmlns="http://schemas.microsoft.com/client/2007"></Canvas>';document.getElementsByTagName('head')[0].appendChild(xamlScript);}catch(e){}
var script=document.createElement("script");script.setAttribute('type','text/javascript');script.onreadystatechange=function(){if(script.readyState=='loaded'||script.readyState=='complete'){if(typeof G_vmlCanvasManager!="undefined")
window.ASTEROIDSPLAYERS[window.ASTEROIDSPLAYERS.length]=new Asteroids();}};script.src="http://erkie.github.com/excanvas.js";document.getElementsByTagName('head')[0].appendChild(script);}
else window.ASTEROIDSPLAYERS[window.ASTEROIDSPLAYERS.length]=new Asteroids();})();

View File

@@ -1,29 +1,104 @@
$(document).ready(function() {
$(".autocomplete-json").each(function() {
var field = $(this)
$.getJSON($(this).data('valueurl'), function(json) {
field.val(json[0]['fields']['name']);
});
var source = $(this).data('sourceurl');
$(this).autocomplete({
source: source,
minLength: 3,
delay: 500,
focus: function(e, ui) {
e.preventDefault();
$(this).val(ui.item.label);
},
select: function(e, ui) {
e.preventDefault();
$(this).val(ui.item.label);
$("#"+$(this).data('target')).val(ui.item.value)
clearSelectionLabel = '(no selection)';
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
//console.log('Changing selected value');
obj.find('option').remove(); //Remove all the available options
obj.append( //Add the new option
$("<option></option>")
.attr("value",pk)
.text(text)
.data('update_url',update_url)
);
obj.selectpicker('render'); //Re-render the UI
obj.selectpicker('refresh'); //Re-render the UI
obj.selectpicker('val', pk); //Set the new value to be selected
obj.change(); //Trigger the change function manually
}
function refreshUpdateHref(obj) {
//console.log('Refreshing Update URL');
targetObject = $('#'+obj.attr('id')+'-update');
update_url = $('option:selected', obj).data('update_url');
if (update_url=="") { //Probably "clear selection" has been chosen
// console.log('Trying to disable');
targetObject.attr('disabled', true);
} else {
targetObject.attr('href', update_url);
targetObject.attr('disabled', false);
}
}
$(".selectpicker").each(function() {
var options = {
ajax: {
url: $(this).data('sourceurl'),
type: 'GET',
dataType: 'json',
// Use "{{{q}}}" as a placeholder and Ajax Bootstrap Select will
// automatically replace it with the value of the search query.
data: {
term: '{{{q}}}'
}
});
$(this).on('blur', function () {
if ($(this).val() == "") {
$("#" + $(this).data('target')).val('');
},
locale: {
emptyTitle: ''
},
clearOnEmpty:false,
//log: 3,
preprocessData: function (data) {
var i, l = data.length, array = [];
array.push({
text: clearSelectionLabel,
value: '',
data:{
update_url: '',
subtext:''
}
});
if (l) {
for(i = 0; i < l; i++){
array.push($.extend(true, data[i], {
text: data[i]['label'],
value: data[i]['pk'],
data:{
update_url: data[i]['update'],
subtext:''
}
}));
}
}
})
return array;
}
};
$(this).prepend($("<option></option>")
.attr("value",'')
.text(clearSelectionLabel)
.data('update_url','')); //Add "clear selection" option
$(this).selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
$(this).change(function(){ //on change, update the edit button href
// console.log('Selectbox Changed');
refreshUpdateHref($(this));
});
refreshUpdateHref($(this)); //Ensure href is correct at the beginning
});
//When update/edit modal box submitted
$('#modal').on('hide.bs.modal', function (e) {
if (modaltarget != undefined && modalobject != "") {
//Update the selector with new values
changeSelectedValue($(modaltarget),modalobject[0]['pk'],modalobject[0]['fields']['name'],modalobject[0]['update_url']);
}
});
});

File diff suppressed because one or more lines are too long

1209
RIGS/static/js/bootstrap-select.js vendored Executable file

File diff suppressed because it is too large Load Diff

10789
RIGS/static/js/fullcalendar.js Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -132,12 +132,4 @@ $("#item-table tbody").sortable({
});
}
});
$('.autocomplete-update').on("autocompleteselect", function(event, ui) {
update_url = ui['item']['update'];
target = $('#' + event['target'].dataset.target + "-update");
console.log(update_url);
console.log(target);
target.attr('href', update_url);
});

105
RIGS/static/js/konami.js Executable file
View File

@@ -0,0 +1,105 @@
/*
* Konami-JS ~
* :: Now with support for touch events and multiple instances for
* :: those situations that call for multiple easter eggs!
* Code: http://konami-js.googlecode.com/
* Examples: http://www.snaptortoise.com/konami-js
* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
* Version: 1.4.2 (9/2/2013)
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1 and Dolphin Browser
*/
var Konami = function (callback) {
var konami = {
addEvent: function (obj, type, fn, ref_obj) {
if (obj.addEventListener)
obj.addEventListener(type, fn, false);
else if (obj.attachEvent) {
// IE
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event, ref_obj);
}
obj.attachEvent("on" + type, obj[type + fn]);
}
},
input: "",
pattern: "38384040373937396665",
load: function (link) {
this.addEvent(document, "keydown", function (e, ref_obj) {
if (ref_obj) konami = ref_obj; // IE
konami.input += e ? e.keyCode : event.keyCode;
if (konami.input.length > konami.pattern.length)
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
if (konami.input == konami.pattern) {
konami.code(link);
konami.input = "";
e.preventDefault();
return false;
}
}, this);
this.iphone.load(link);
},
code: function (link) {
window.location = link
},
iphone: {
start_x: 0,
start_y: 0,
stop_x: 0,
stop_y: 0,
tap: false,
capture: false,
orig_keys: "",
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
code: function (link) {
konami.code(link);
},
load: function (link) {
this.orig_keys = this.keys;
konami.addEvent(document, "touchmove", function (e) {
if (e.touches.length == 1 && konami.iphone.capture == true) {
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX;
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false;
konami.iphone.capture = false;
konami.iphone.check_direction();
}
});
konami.addEvent(document, "touchend", function (evt) {
if (konami.iphone.tap == true) konami.iphone.check_direction(link);
}, false);
konami.addEvent(document, "touchstart", function (evt) {
konami.iphone.start_x = evt.changedTouches[0].pageX;
konami.iphone.start_y = evt.changedTouches[0].pageY;
konami.iphone.tap = true;
konami.iphone.capture = true;
});
},
check_direction: function (link) {
x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y);
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
result = (x_magnitude > y_magnitude) ? x : y;
result = (this.tap == true) ? "TAP" : result;
if (result == this.keys[0]) this.keys = this.keys.slice(1, this.keys.length);
if (this.keys.length == 0) {
this.keys = this.orig_keys;
this.code(link);
}
}
}
}
typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") {
konami.code = callback;
konami.load();
}
return konami;
};

View File

@@ -0,0 +1,86 @@
(function() {
var day, formats, hour, initialize, minute, second, week;
second = 1e3;
minute = 6e4;
hour = 36e5;
day = 864e5;
week = 6048e5;
formats = {
seconds: {
short: 's',
long: ' sec'
},
minutes: {
short: 'm',
long: ' min'
},
hours: {
short: 'h',
long: ' hr'
},
days: {
short: 'd',
long: ' day'
}
};
initialize = function(moment) {
var twitterFormat;
twitterFormat = function(format) {
var diff, num, unit, unitStr;
diff = Math.abs(this.diff(moment()));
unit = null;
num = null;
if (diff <= second) {
unit = 'seconds';
num = 1;
} else if (diff < minute) {
unit = 'seconds';
} else if (diff < hour) {
unit = 'minutes';
} else if (diff < day) {
unit = 'hours';
} else if (format === 'short') {
if (diff < week) {
unit = 'days';
} else {
return this.format('M/D/YY');
}
} else {
return this.format('MMM D');
}
if (!(num && unit)) {
num = moment.duration(diff)[unit]();
}
unitStr = unit = formats[unit][format];
if (format === 'long' && num > 1) {
unitStr += 's';
}
return num + unitStr;
};
moment.fn.twitterLong = function() {
return twitterFormat.call(this, 'long');
};
moment.fn.twitter = moment.fn.twitterShort = function() {
return twitterFormat.call(this, 'short');
};
return moment;
};
if (typeof define === 'function' && define.amd) {
define('moment-twitter', ['moment'], function(moment) {
return this.moment = initialize(moment);
});
} else if (typeof module !== 'undefined') {
module.exports = initialize(require('moment'));
} else if (typeof window !== "undefined" && window.moment) {
this.moment = initialize(this.moment);
}
}).call(this);

7
RIGS/static/js/moment.min.js vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -43,7 +43,7 @@ $link-hover-color: darken($link-color, 15%) !default;
//
//## Font, line-height, and color for body text, headings, and more.
$font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif !default;
$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif !default;
$font-family-serif: Georgia, "Times New Roman", Times, serif !default;
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default;
@@ -377,7 +377,7 @@ $navbar-inverse-bg: #222 !default;
$navbar-inverse-border: darken($navbar-inverse-bg, 10%) !default;
// Inverted navbar links
$navbar-inverse-link-color: $gray-light !default;
$navbar-inverse-link-color: lighten($gray-light, 20%) !default;
$navbar-inverse-link-hover-color: #fff !default;
$navbar-inverse-link-hover-bg: transparent !default;
$navbar-inverse-link-active-color: $navbar-inverse-link-hover-color !default;

View File

@@ -7,6 +7,9 @@
@import "jq-ui-bootstrap/_menu";
@import "jq-ui-bootstrap/_tooltip";
@import "compass/css3/animation";
@import "compass/css3/transform";
body, .pad-top {
padding-top: 50px;
}
@@ -17,7 +20,9 @@ body, .pad-top {
#userdropdown > li {
padding: 0 0.3em;
}
#userdropdown > li, #activity {
.media-object {
max-width: 3em;
}
@@ -47,4 +52,88 @@ textarea {
.event-mic-photo {
max-width: 2em;
}
}
.item-description {
margin-left: 1em;
}
.overflow-ellipsis {
text-overflow: ellipsis;
display: inline-block;
max-width: 100%;
overflow: hidden;
}
.modal-dialog {
z-index: inherit; // bug fix introduced in 52682ce
}
.panel-default {
.default {
background-color: $panel-default-heading-bg;
}
}
.loading-animation {
position: relative;
margin: 30px auto 0;
.circle {
background-color: rgba(0,0,0,0);
border: 5px solid rgba(0,183,229,0.9);
opacity: .9;
border-right: 5px solid rgba(0,0,0,0);
border-left: 5px solid rgba(0,0,0,0);
border-radius: 50px;
box-shadow: 0 0 35px #2187e7;
width: 50px;
height: 50px;
margin: 0 auto;
@include animation(spinPulse 1s infinite ease-in-out);
}
.circle1 {
background-color: rgba(0,0,0,0);
border: 5px solid rgba(0,183,229,0.9);
opacity: .9;
border-left: 5px solid rgba(0,0,0,0);
border-right: 5px solid rgba(0,0,0,0);
border-radius: 50px;
box-shadow: 0 0 15px #2187e7;
width: 30px;
height: 30px;
margin: 0 auto;
position: relative;
top: -40px;
@include animation(spinoffPulse 1s infinite linear);
}
@include keyframes(spinPulse) {
0% {
@include rotate(160deg);
opacity: 0;
box-shadow: 0 0 1px #2187e7;
}
50% {
@include rotate(145deg);
opacity: 1;
}
100% {
@include rotate(-320deg);
opacity: 0;
};
}
@include keyframes(spinoffPulse) {
0% {
@include rotate(0deg);
}
100% {
@include rotate(360deg);
};
}
}

View File

@@ -0,0 +1,66 @@
{% load static %}
{% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script src="{% static "js/popover.js" %}"></script>
<script src="{% static "js/moment.min.js" %}"></script>
<script src="{% static "js/moment-twitter.js" %}"></script>
<script>
$(function () {
$('[data-toggle="popover"]').popover().click(function(){
if($(this).attr('href')){
window.location.href = $(this).attr('href');
}
});
// This keeps timeago values correct, but uses an insane amount of resources
// $(function () {
// setInterval(function() {
// $('.date').each(function (index, dateElem) {
// var $dateElem = $(dateElem);
// var formatted = moment($dateElem.attr('data-date')).fromNow();
// $dateElem.text(formatted);
// })
// });
// }, 10000);
moment().twitter();
})
$(document).ready(function() {
$(function () {
$( "#activity" ).hide();
$( "#activity" ).load( "{% url 'activity_feed' %}", function() {
$('#activity_loading').slideUp('slow',function(){
$('#activity').slideDown('slow');
});
$('#activity [data-toggle="popover"]').popover();
$('.date').each(function (index, dateElem) {
var $dateElem = $(dateElem);
var formatted = moment($dateElem.attr('data-date'),"DD/MM/YYYY HH:mm").twitterLong();
$dateElem.text(formatted);
});
});
});
});
</script>
{% endblock %}
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Recent Changes</h4>
</div>
<div class="list-group">
<div id="activity_loading" class="list-group-item loading-animation">
<div class="circle"></div>
<div class="circle1"></div>
</div>
<div id="activity">
</div>
</div>
</div>

View File

@@ -0,0 +1,51 @@
{% extends request.is_ajax|yesno:"base_ajax_nomodal.html,base.html" %}
{% load static %}
{% load paginator from filters %}
{% load to_class_name from filters %}
{% block content %}
<div class="list-group-item">
<div class="media">
{% for version in object_list %}
{% if not version.withPrevious %}
{% if not forloop.first %}
</div> {#/.media-body#}
</div> {#/.media#}
</div>
<div class="list-group-item">
<div class="media">
{% endif %}
<div class="media-left">
{% if version.revision.user %}
<a href="{% url 'profile_detail' pk=version.revision.user.pk %}" class="modal-href">
<img class="media-object img-rounded" src="{{ version.revision.user.profile_picture}}" />
</a>
{% endif %}
</div>
<div class="media-body">
<h5>{{ version.revision.user.name }}
<span class="pull-right"><small><span class="date" data-date="{{version.revision.date_created}}"></span></small></span>
</h5>
{% endif %}
<p>
<small>
{% if version.old == None %}
Created
{% else %}
Changed {% include 'RIGS/version_changes.html' %} in
{% endif %}
{% include 'RIGS/object_button.html' with object=version.new %}
</small>
</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,88 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
{% load static %}
{% load paginator from filters %}
{% load to_class_name from filters %}
{% block title %}Rigboard Activity Stream{% endblock %}
{% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script src="{% static "js/popover.js" %}"></script>
<script src="{% static "js/moment.min.js" %}"></script>
<script>
$(function () {
$('[data-toggle="popover"]').popover().click(function(){
if($(this).attr('href')){
window.location.href = $(this).attr('href');
}
});
// This keeps timeago values correct, but uses an insane amount of resources
// $(function () {
// setInterval(function() {
// $('.date').each(function (index, dateElem) {
// var $dateElem = $(dateElem);
// var formatted = moment($dateElem.attr('data-date')).fromNow();
// $dateElem.text(formatted);
// })
// });
// }, 10000);
$('.date').each(function (index, dateElem) {
var $dateElem = $(dateElem);
var formatted = moment($dateElem.attr('data-date')).fromNow();
$dateElem.text(formatted);
});
})
</script>
{% endblock %}
{% block content %}
<div class="col-sm-12">
<div class="row">
<div class="col-sm-12">
<h3>Rigboard Activity Stream</h3>
</div>
<div class="text-right col-sm-12">{% paginator %}</div>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<td>Date</td>
<td>Object</td>
<td>Version ID</td>
<td>User</td>
<td>Changes</td>
</tr>
</thead>
<tbody>
{% for version in object_list %}
<tr>
<td>{{ version.revision.date_created }}</td>
<td><a href="{{ version.new.get_absolute_url }}">{{version.new|to_class_name}} {{ version.new.pk|stringformat:"05d" }}</a></td>
<td>{{ version.version.pk }}|{{ version.revision.pk }}</td>
<td>{{ version.revision.user.name }}</td>
<td>
{% if version.old == None %}
Object Created
{% else %}
{% include 'RIGS/version_changes.html' %}
{% endif %} </td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="align-right">{% paginator %}</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,111 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Calendar{% endblock %}
{% block css %}
<link href="{% static "css/fullcalendar.css" %}" rel='stylesheet' />
<link href="{% static "css/fullcalendar.print.css" %}" rel='stylesheet' media='print' />
{% endblock %}
{% block js %}
{# <script src="//code.jquery.com/jquery-latest.min.js"></script> #}
<script src="{% static "js/moment.min.js" %}"></script>
<script src="{% static "js/fullcalendar.js" %}"></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
//defaultDate: '2015-02-12',
editable: false,
eventLimit: true, // allow "more" link when too many events
firstDay: 1,
aspectRatio: 1.5,
timeFormat: 'HH:mm',
views: {
basic: {
// options apply to basicWeek and basicDay views
},
agenda: {
// options apply to agendaWeek and agendaDay views
},
week: {
columnFormat:'ddd D/M'
},
day: {
// options apply to basicDay and agendaDay views
}
},
buttonText:{
today: 'Today',
month: 'Month',
week: 'Week',
day: 'Day'
},
header:{
left: 'title',
center: '',
right: 'today prev,next month,agendaWeek,agendaDay'
},
events: function(start_moment, end_moment, timezone, callback) {
$.ajax({
url: '/api/event',
dataType: 'json',
data: {
start: moment(start_moment).format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
end: moment(end_moment).format("YYYY-MM-DD[T]HH:mm:ss[Z]")
},
success: function(doc) {
var events = [];
var colours =
$(doc).each(function() {
thisEvent = [];
colours = {'Provisional': 'orange',
'Confirmed': 'green' ,
'Booked': 'green' ,
'Cancelled': 'grey'
};
thisEvent['start'] = $(this).attr('start_date');
if ($(this).attr('start_time')) {
thisEvent['start'] += 'T' + $(this).attr('start_time');
}
if ($(this).attr('end_date')) {
thisEvent['end'] = $(this).attr('end_date');
if ($(this).attr('end_time')) {
thisEvent['end'] += 'T' + $(this).attr('end_time');
}
}
thisEvent['className'] = 'modal-href';
thisEvent['title'] = $(this).attr('title');
thisEvent['color'] = colours[$(this).attr('status')];
thisEvent['url'] = $(this).attr('url');
events.push(thisEvent);
});
callback(events);
}
});
}
});
});
</script>
{% endblock %}
{% block content %}
<div class="row">
<div style="col-sm-12">
<div id='calendar'>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,32 +1,41 @@
{% extends 'base.html' %}
{% block title %}Event {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}
{% endif %}{% endblock %}
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
{% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %}
{% block content %}
<div class="row">
{% if not request.is_ajax %}
<div class="col-sm-12">
<div class="col-sm-8">
<h1>Event {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}</h1>
<h1>
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
| {{ object.name }}
</h1>
</div>
<div class="col-sm-4 text-right">
</div>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span></a>
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
<a href="{% url 'event_print' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span></a>
<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></a>
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<a href="{% url 'invoice_event' event.pk %}" class="btn btn-default" title="Invoice Rig"><span
class="glyphicon glyphicon-gbp"></span></a>
class="glyphicon glyphicon-gbp"></span> <span
class="hidden-xs">Invoice</span></a>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endif %}
{% if object.is_rig %}
{# only need contact details for a rig #}
<div class="col-sm-12 col-md-6 col-lg-5">
@@ -46,7 +55,7 @@
<dt>Email</dt>
<dd>
<a href="mailto:{{object.person.email}}" target="_blank">
{{ object.person.email }}
<span class="overflow-ellipsis">{{ object.person.email }}</span>
</a>
</dd>
@@ -86,7 +95,7 @@
{% endif %}
<div class="col-sm-12 {% if event.is_rig %}col-md-6 col-lg-7{% endif %}">
<div class="panel panel-info">
<div class="panel-heading">{{ object.name }}</div>
<div class="panel-heading">Event Info</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Event Venue</dt>
@@ -100,7 +109,15 @@
{% if event.is_rig %}
<dt>Event MIC</dt>
<dd>{{ event.mic.name }}</dd>
<dd>
{% if event.mic and perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{{ event.mic.name }}
</a>
{% else %}
{{ event.mic.name }}
{% endif %}
</dd>
{% endif %}
<dt>Status</dt>
@@ -110,18 +127,18 @@
{% if event.is_rig %}
<dt>Crew Meet</dt>
<dd>{{ event.meet_at|date:"d M Y H:i"|default:"" }}</dd>
<dd>{{ event.meet_at|date:"D d M Y H:i"|default:"" }}</dd>
<dd>{{ event.meet_info|default:"" }}</dd>
<dt>Access From</dt>
<dd>{{ event.access_at|date:"d M Y H:i"|default:"" }}</dd>
<dd>{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
{% endif %}
<dt>Event Starts</dt>
<dd>{{ event.start_date|date:"d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
<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 M Y" }} {{ event.end_time|date:"H:i" }}</dd>
<dd>{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
<dd>&nbsp;</dd>
@@ -150,28 +167,39 @@
<dt>Collected By</dt>
<dd>{{ object.collector }}</dd>
{% endif %}
{% if event.is_rig %}
<dt>PO</dt>
<dd>{{ object.purchase_order }}</dd>
{% endif %}
</dl>
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span></a>
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
<a href="{% url 'event_print' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span></a>
<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></a>
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<a href="{% url 'invoice_event' event.pk %}" class="btn btn-default" title="Invoice Rig"><span
class="glyphicon glyphicon-gbp"></span></a>
class="glyphicon glyphicon-gbp"></span> <span
class="hidden-xs">Invoice</span></a>
{% endif %}
{% endif %}
</div>
</div>
{% endif %}
{% if event.is_rig %}
<div class="col-sm-12">
<div class="panel panel-default">
@@ -185,22 +213,53 @@
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span></a>
<a href="{% url 'event_print' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span></a>
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></a>
{% if perms.RIGS.add_invoice %}
<a href="{% url 'invoice_event' event.pk %}" class="btn btn-default" title="Invoice Rig"><span
class="glyphicon glyphicon-gbp"></span></a>
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<a href="{% url 'invoice_event' event.pk %}" class="btn btn-default" title="Invoice Rig"><span
class="glyphicon glyphicon-gbp"></span> <span
class="hidden-xs">Invoice</span></a>
{% endif %}
{% endif %}
</div>
<div>Last edited at {{ object.last_edited_at|date:"SHORT_DATETIME_FORMAT" }}
by {{ object.last_edited_by.name }}.
<div>
<a href="{% url 'event_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}
{% if request.is_ajax %}
{% block footer %}
<div class="row">
<div class="col-sm-10 align-left">
<a href="{% url 'event_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
<div class="col-sm-2">
<div class="pull-right">
<a href="{% url 'event_detail' object.pk %}" class="btn btn-primary">Open Event Page <span
class="glyphicon glyphicon-eye"></span></a>
</div>
</div>
</div>
{% endblock %}
{% endif %}

View File

@@ -6,7 +6,13 @@
{{ object.pk }}{% endif %}{% else %}New Event{% endif %}{% endblock %}
{% block css %}
<link href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"/>
<link rel="stylesheet" href="{% static "css/bootstrap-select.min.css" %}"/>
<link rel="stylesheet" href="{% static "css/ajax-bootstrap-select.css" %}"/>
{% endblock %}
{% block preload_js %}
<script src="{% static "js/bootstrap-select.js" %}"></script>
<script src="{% static "js/ajax-bootstrap-select.js" %}"></script>
{% endblock %}
{% block js %}
@@ -62,6 +68,43 @@
}
})
{% endif %}
function supportsDate() {
//return false; //for development
var input = document.createElement('input');
input.setAttribute('type','date');
var notADateValue = 'not-a-date';
input.setAttribute('value', notADateValue);
return !(input.value === notADateValue);
}
if(supportsDate()){
//Good, we'll use the browser implementation
}else{
//Rubbish browser - do JQuery backup
$('<link>')
.appendTo('head')
.attr({type : 'text/css', rel : 'stylesheet'})
.attr('href', '{% static "css/bootstrap-datetimepicker.min.css" %}');
$.when(
$.getScript( "{% static "js/moment.min.js" %}" ),
$.getScript( "{% static "js/bootstrap-datetimepicker.min.js" %}" ),
$.Deferred(function( deferred ){
$( deferred.resolve );
})
).done(function(){
$('input[type=date]').attr('type','text').datetimepicker({
format: 'YYYY-MM-DD',
});
$('input[type=time]').attr('type','text').datetimepicker({
format: 'HH:mm',
});
$('input[type=datetime-local]').attr('type','text').datetimepicker({
format: 'YYYY-MM-DD[T]HH:mm',
sideBySide: true,
});
});
}
})
$(document).ready(function () {
@@ -123,15 +166,12 @@
<div class="col-sm-8">
<div class="row">
<input type="hidden" id="{{ form.person.id_for_label }}" name="{{ form.person.name }}"
value="{{ form.person.value|default_if_none:"" }}"/>
<div class="col-sm-9 col-md-7 col-lg-8">
<input type="text" id="{{ form.person.id_for_label }}-input"
class="form-control autocomplete-json autocomplete-update"
value="{{ person|default_if_none:"" }}"
data-sourceurl="{% url 'api_secure' model='person' %}"
data-target="{{ form.person.id_for_label }}"/>
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='person' %}">
{% if person %}
<option value="{{form.person.value}}" selected="selected" data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
@@ -139,7 +179,7 @@
data-target="#{{ form.person.id_for_label }}">
<span class="glyphicon glyphicon-plus"></span>
</a>
<a href="{% if form.person.value %}{% url 'person_update' form.person.value %}{% endif %}" class="btn btn-default modal-href" id="{{ form.person.id_for_label }}-update">
<a href="{% if form.person.value %}{% url 'person_update' form.person.value %}{% endif %}" class="btn btn-default modal-href" id="{{ form.person.id_for_label }}-update" data-target="#{{ form.person.id_for_label }}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</div>
@@ -153,16 +193,12 @@
<div class="col-sm-8">
<div class="row">
<input type="hidden" id="{{ form.organisation.id_for_label }}"
name="{{ form.organisation.name }}"
value="{{ form.organisation.value|default_if_none:"" }}"/>
<div class="col-sm-9 col-md-7 col-lg-8">
<input type="text" id="{{ form.organisation.id_for_label }}-input"
class="form-control autocomplete-json autocomplete-update"
value="{{ organisation|default_if_none:"" }}"
data-sourceurl="{% url 'api_secure' model='organisation' %}"
data-target="{{ form.organisation.id_for_label }}"/>
<select id="{{ form.organisation.id_for_label }}" name="{{ form.organisation.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='organisation' %}">
{% if organisation %}
<option value="{{form.organisation.value}}" selected="selected" data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
@@ -170,7 +206,7 @@
data-target="#{{ form.organisation.id_for_label }}">
<span class="glyphicon glyphicon-plus"></span>
</a>
<a href="{% if form.organisation.value %}{% url 'organisation_update' form.organisation.value %}{% endif %}" class="btn btn-default modal-href" id="{{ form.organisation.id_for_label }}-update">
<a href="{% if form.organisation.value %}{% url 'organisation_update' form.organisation.value %}{% endif %}" class="btn btn-default modal-href" id="{{ form.organisation.id_for_label }}-update" data-target="#{{ form.organisation.id_for_label }}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</div>
@@ -216,15 +252,12 @@
<div class="col-sm-8">
<div class="row">
<input type="hidden" id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}"
value="{{ form.venue.value|default_if_none:"" }}"/>
<div class="col-sm-9 col-md-7 col-lg-8">
<input type="text" id="{{ form.venue.id_for_label }}-input"
class="form-control autocomplete-json autocomplete-update"
value="{{ venue|default_if_none:"" }}"
data-sourceurl="{% url 'api_secure' model='venue' %}"
data-target="{{ form.venue.id_for_label }}"/>
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
{% if venue %}
<option value="{{form.venue.value}}" selected="selected" data-update_url="{% url 'venue_update' form.venue.value %}">{{ venue }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
@@ -232,7 +265,7 @@
data-target="#{{ form.venue.id_for_label }}">
<span class="glyphicon glyphicon-plus"></span>
</a>
<a href="{% if object.venue %}{% url 'venue_update' object.venue.pk %}{% endif %}" class="btn btn-default modal-href" id="{{ form.venue.id_for_label }}-update">
<a href="{% if object.venue %}{% url 'venue_update' object.venue.pk %}{% endif %}" class="btn btn-default modal-href" id="{{ form.venue.id_for_label }}-update" data-target="#{{ form.venue.id_for_label }}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</div>
@@ -325,14 +358,11 @@
class="col-sm-4 control-label">{{ form.mic.label }}</label>
<div class="col-sm-8">
<input type="hidden" id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}"
value="{{ form.mic.value|default_if_none:"" }}"/>
<input type="text" id="{{ form.mic.id_for_label }}-input"
class="form-control autocomplete-json"
data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials"
data-target="{{ form.mic.id_for_label }}"
value="{{ mic.name|default_if_none:"" }}"/>
<select id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if mic %}
<option value="{{form.mic.value}}" selected="selected" >{{ mic.name }}</option>
{% endif %}
</select>
</div>
</div>
@@ -342,15 +372,11 @@
class="col-sm-4 control-label">{{ form.checked_in_by.label }}</label>
<div class="col-sm-8">
<input type="hidden" id="{{ form.checked_in_by.id_for_label }}"
name="{{ form.checked_in_by.name }}"
value="{{ form.checked_in_by.value|default_if_none:"" }}"/>
<input type="text" id="{{ form.checked_in_by.id_for_label }}-input"
class="form-control autocomplete-json"
data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials"
data-target="{{ form.checked_in_by.id_for_label }}"
value="{{ checked_in_by.name|default_if_none:"" }}"/>
<select id="{{ form.checked_in_by.id_for_label }}" name="{{ form.checked_in_by.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if checked_in_by %}
<option value="{{form.checked_in_by.value}}" selected="selected" >{{ checked_in_by.name }}</option>
{% endif %}
</select>
</div>
</div>
{% endif %}
@@ -363,19 +389,28 @@
{% render_field form.collector class+="form-control" %}
</div>
</div>
<div class="form-group">
<label for="{{ form.purchase_order.id_for_label }}"
class="col-sm-4 control-label">{{ form.purchase_order.label }}</label>
<div class="col-sm-8">
{% render_field form.purchase_order class+="form-control" %}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span>
</button>
</div>
</div>
</div>
<!-- /.col-sm-12 .col-md-6 -->
<div class="col-sm-12 text-right">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span>
</button>
</div>
</div>
{# Notes and item shit #}
<div class="col-sm-12">
@@ -391,6 +426,13 @@
</div>
</div>
</div>
<div class="col-sm-12 text-right form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="btn-group btn-page">
<button type="submit" class="btn btn-default" title="Save"><span
class="glyphicon glyphicon-floppy-disk"></span>
</button>
</div>
</div>
</form>
{% include 'RIGS/item_modal.html' %}

View File

@@ -37,7 +37,7 @@
danger
{% endif %}
">
<td class="hidden-xs">N{{ object.pk|stringformat:"05d" }}</td>
<td class="hidden-xs"><a href="{% url 'event_detail' object.pk %}" target="_blank">N{{ object.pk|stringformat:"05d" }}</a></td>
<td>{{ object.end_date }}</td>
<td>{{ object.name }}</td>
<td>
@@ -53,8 +53,8 @@
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo"/>
</td>
<td class="text-right">
<a href="{% url 'invoice_event' object.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-pencil"></span>
<a href="{% url 'invoice_event' object.pk %}" target="_blank" class="btn btn-default">
<span class="glyphicon glyphicon-gbp"></span>
</a>
</td>
</tr>

View File

@@ -1,48 +1,62 @@
<?xml version="1.0" encoding="UTF-8" ?>
{% load multiply from filters %}
{% load static %}
<!DOCTYPE document SYSTEM "rml.dtd">
<document filename="Event {{ object.id }} - {{ object.name }} - {{ object.start_date }}.pdf">
<docinit>
<registerTTFont faceName="OpenSans" fileName="{{ fonts.opensans.regular }}"/>
<registerTTFont faceName="OpenSans-Bold" fileName="{{ fonts.opensans.bold }}"/>
<registerFontFamily name="OpenSans" bold="OpenSans-Bold" boldItalic="OpenSans-BoldItalic"/>
<registerFontFamily name="OpenSans" bold="OpenSans-Bold" boldItalic="OpenSans-Bold"/>
</docinit>
<stylesheet>
<initialize>
<color id="LightGray" RGB="#D3D3D3"/>
<color id="DarkGray" RGB="#707070"/>
</initialize>
<paraStyle name="style.para" fontName="OpenSans"/>
<paraStyle name="style.para" fontName="OpenSans" />
<paraStyle name="blockPara" spaceAfter="5" spaceBefore="5"/>
<paraStyle name="style.Heading1" fontName="OpenSans" fontSize="14" leading="10" spaceAfter="10"/>
<paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="12" spaceAfter="5"/>
<paraStyle name="style.Heading3" fontName="OpenSans" fontSize="11" spaceAfter="5"/>
<paraStyle name="style.Heading1" fontName="OpenSans" fontSize="16" leading="18" spaceAfter="0"/>
<paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="10" spaceAfter="2"/>
<paraStyle name="style.Heading3" fontName="OpenSans" fontSize="10" spaceAfter="0"/>
<paraStyle name="center" alignment="center"/>
<paraStyle name="invoice-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/>
<blockTableStyle id="headerTable">
<blockFont name="OpenSans-Bold"/>
<blockAlignment value="left"/>
<blockLeftPadding start="0,0" stop="0,0" length="0"/>
<blockBackground start="1,0" stop="1,0" colorName="LightGray"/>
<blockBackground start="3,0" stop="3,0" colorName="LightGray"/>
<lineStyle kind="box" start="1,0" stop="1,0" colorName="black"/>
<lineStyle kind="box" start="3,0" stop="3,0" colorName="black"/>
<paraStyle name="style.event_description" fontName="OpenSans" textColor="DarkGray" />
<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.times" fontName="OpenSans" fontSize="10" />
<paraStyle name="style.invoice_titles" fontName="OpenSans-Bold" fontSize="10" />
<paraStyle name="style.invoice_numbers" fontName="OpenSans" fontSize="10" />
<blockTableStyle id="eventSpecifics">
<blockValign value="top"/>
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
</blockTableStyle>
<blockTableStyle id="invoiceLayout">
<blockValign value="top"/>
</blockTableStyle>
<blockTableStyle id="eventDetails">
<blockValign value="top"/>
<blockTopPadding start="0,0" stop="-1,0" length="0"/>
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
</blockTableStyle>
<blockTableStyle id="itemTable">
<blockValign value="top"/>
<lineStyle kind="grid" colorName="black" thickness="1" start="0,0" stop="-1,-1"/>
<lineStyle kind="LINEBELOW" colorName="LightGrey" start="0,0" stop="-1,-1" thickness="1"/>
{#<lineStyle kind="box" colorName="black" thickness="1" start="0,0" stop="-1,-1"/>#}
</blockTableStyle>
<blockTableStyle id="totalTable">
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
<lineStyle cap="default" kind="grid" colorName="black" thickness="1" start="1,0" stop="-1,-1"/>
<lineStyle kind="LINEBELOW" colorName="LightGrey" start="-2,0" stop="-1,-1" thickness="1"/>
{# <lineStyle cap="default" kind="grid" colorName="black" thickness="1" start="1,0" stop="-1,-1"/> #}
</blockTableStyle>
<blockTableStyle id="infoTable" keepWithNext="true">
@@ -65,39 +79,38 @@
</blockTableStyle>
</stylesheet>
<template>
<pageTemplate id="Headed">
<template > {# Note: page is 595x842 points (1 point=1/72in) #}
<pageTemplate id="Headed" >
<pageGraphics>
<image file="http://images.nottinghamtec.co.uk/rigs_logo.jpg" x="50" y="702"/>
<setFont name="OpenSans-Bold" size="14" leading="10"/>
<drawString x="140" y="775">TEC PA &amp; Lighting</drawString>
<image file="RIGS/static/imgs/paperwork/corner.jpg" x="395" y="642" height="200" width="200"/>
<setFont name="OpenSans" size="10"/>
<drawString x="140" y="762">Portland Building</drawString>
<drawString x="140" y="751">University Park</drawString>
<drawString x="140" y="740">Nottingham</drawString>
<drawString x="140" y="729">NG7 2RD</drawString>
<drawString x="140" y="718">0115 846 8720</drawString>
<drawString x="140" y="707">info@nottinghamtec.co.uk</drawString>
{# logo positioned 42 from left, 33 from top #}
<image file="RIGS/static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
<drawString x="137" y="780">TEC PA &amp; Lighting</drawString>
<image file="http://images.nottinghamtec.co.uk/rigs_union_logo.jpg" x="365" y="746"/>
<setFont name="OpenSans" size="9"/>
<drawString x="137" y="760">Portland Building, University Park, Nottingham, NG7 2RD</drawString>
<drawString x="137" y="746">www.nottinghamtec.co.uk</drawString>
<drawString x="265" y="746">info@nottinghamtec.co.uk</drawString>
<drawString x="137" y="732">Phone: (0115) 846 8720</drawString>
<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>
<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>
</pageGraphics>
<frame id="jobDetails" x1="50" y1="682" width="495" height="20"/>
<frame id="client" x1="50" y1="560" width="150" height="120" topPadding="5"
rightPadding="5" bottomPadding="5" leftPadding="5"/>
<frame id="venue" x1="221" y1="560" width="150" height="120" topPadding="5"
rightPadding="5" bottomPadding="5" leftPadding="5"/>
<frame id="event" x1="395" y1="560" width="150" height="120" topPadding="5"
rightPadding="5" bottomPadding="5" leftPadding="5"/>
<frame id="main" x1="50" y1="65" width="495" height="502"/>
<frame id="main" x1="50" y1="65" width="495" height="645"/>
</pageTemplate>
<pageTemplate id="Main">
<pageGraphics>
<setFont name="OpenSans" size="10"/>
<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>
{% 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>
</pageGraphics>
<frame id="main" x1="50" y1="65" width="495" height="727"/>
</pageTemplate>

View File

@@ -1,85 +1,154 @@
<setNextFrame name="jobDetails"/>
<nextFrame/>
<blockTable style="headerTable" colWidths="123,124,123,123">
<tr>
<td>Event Date:</td>
<td>{{ object.start_date }}</td>
<td>JOB NUMBER:</td>
<td>N{{ object.pk|stringformat:"05d" }}</td>
</tr>
</blockTable>
<setNextFrame name="client"/>
<nextFrame/>
<keepInFrame>
<h2>Hirer</h2>
<h3>{{ object.person.name }}</h3>
<h3>{{ object.organisation.name|default_if_none:"" }}</h3>
{% if object.person.phone %}
<para>Tel: {{ object.person.phone }}</para>
{% elif object.organisation.phone %}
<para>Tel: {{ object.organisation.phone }}</para>
{% endif %}
{% if object.person.email %}
<para>Email: {{ object.person.email }}</para>
{% elif object.organisation.email %}
<para>Email: {{ object.organisation.email }}</para>
{% endif %}
</keepInFrame>
<setNextFrame name="venue"/>
<nextFrame/>
<keepInFrame>
<h2>Venue</h2>
<h3>{{ object.venue.name }}</h3>
<para>{{ object.venue.address|default_if_none:""|linebreaks }}</para>
</keepInFrame>
<setNextFrame name="event"/>
<nextFrame/>
<h2>Event Details</h2>
<blockTable style="eventDetails" colWidths="75,75">
<tr>
<td>Start Time</td>
<td>
<para>{{ object.start_time|time:"H:i" }}
<br/>
{{ object.start_date|date:"(d/m/Y)" }}
</para>
</td>
</tr>
<tr>
<td>End Time</td>
<td>
<para>{{ object.end_time|time:"H:i" }}
<br/>
{{ object.end_date|date:"(d/m/Y)" }}
</para>
</td>
</tr>
<tr>
<td>Earliest Access</td>
<td>
<para>{{ object.access_at|time:"H:i" }}
<br/>
{{ object.access_at|date:"d/m/Y" }}
</para>
</td>
</tr>
</blockTable>
<setNextTemplate name="Main"/>
<setNextFrame name="main"/>
<nextFrame/>
{% if invoice %}
<blockTable style="invoiceLayout" colWidths="330,165">
<tr>
<td>
{% endif %}
<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>
<keepInFrame maxHeight="30">
<para style="style.event_description">
{{ object.description|default_if_none:""|linebreaksbr }}
</para>
</keepInFrame>
{% if invoice %}
</td>
<td>
<para style="invoice-head">INVOICE</para>
<spacer length="10"/>
<blockTable style="eventDetails" colWidths="100,175">
<tr>
<td><para style="invoice_titles">Invoice Number</para></td>
<td>
<para style="invoice_numbers">{{ invoice.pk|stringformat:"05d" }}</para>
</td>
</tr>
<tr>
<td><para style="invoice_titles">Invoice Date</para></td>
<td>
<para style="invoice_numbers">{{ invoice.invoice_date|date:"d/m/Y" }}</para>
</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>
</td>
</tr>
</blockTable>
{% endif %}
<spacer length="15"/>
<blockTable style="eventSpecifics" colWidths="165,165,165">
<tr>
<td leftPadding="0">
<h2>Hirer</h2>
<h3>{{ object.person.name }}</h3>
<h3>{{ object.organisation.name|default_if_none:"" }}</h3>
{% if invoice %}
<keepInFrame>
{% if object.organisation.address %}
<para style="specific_description">{{ object.organisation.address|default_if_none:""|linebreaksbr }}</para>
{% elif object.person.address %}
<para style="specific_description">{{ object.person.address|default_if_none:""|linebreaksbr }}</para>
{% endif %}
</keepInFrame>
{% endif %}
<keepInFrame>
{% if object.person.phone %}
<para style="specific_description">{{ object.person.phone }}</para>
{% elif object.organisation.phone %}
<para style="specific_description">{{ object.organisation.phone }}</para>
{% endif %}
</keepInFrame>
<keepInFrame>
{% if invoice %}
{% if object.organisation.email %}
<para style="specific_description">{{ object.organisation.email }}</para>
{% elif object.person.email %}
<para style="specific_description">{{ object.person.email }}</para>
{% endif %}
{% else %}
{% if object.person.email %}
<para style="specific_description">{{ object.person.email }}</para>
{% elif object.organisation.email %}
<para style="specific_description">{{ object.organisation.email }}</para>
{% endif %}
{% endif %}
</keepInFrame>
</td>
<td>
<h2>Venue</h2>
<h3>{{ object.venue.name }}</h3>
{% if not invoice %}
<keepInFrame>
<para style="specific_description">{{ object.venue.address|default_if_none:""|linebreaksbr }}</para>
</keepInFrame>
{% endif %}
</td>
<td rightPadding="0">
<h2>Timings</h2>
<blockTable style="eventDetails" colWidths="55,75">
<tr>
<td leftPadding="0" topPadding="0"><h3>Start</h3></td>
<td>
<para style="times">{{ object.start_time|time:"H:i" }}
{{ object.start_date|date:"d/m/Y" }}
</para>
</td>
</tr>
<tr>
<td leftPadding="0"><h3>End</h3></td>
<td>
<para style="times">{{ object.end_time|default_if_none:""|time:"H:i" }}
{{ object.end_date|date:"d/m/Y" }}
</para>
</td>
</tr>
{% if object.access_at and not invoice%}
<tr>
<td leftPadding="0"><h3>Access</h3></td>
<td>
<para style="times">{{ object.access_at|time:"H:i" }}
{{ object.access_at|date:"d/m/Y" }}
</para>
</td>
</tr>
{% endif %}
</blockTable>
</td>
</tr>
</blockTable>
<spacer length="15"/>
<setNextTemplate name="Main"/>
<blockTable style="itemTable" colWidths="300,80,35,80">
<tr>
{# Bold tags need to be in a para in order to render in reportlab #}
<td>
<para>
<b>Equipment Details</b>
<b>Item</b>
</para>
</td>
<td>
@@ -103,8 +172,11 @@
<td>
<para>{{ item.name }}
{% if item.description %}
<br/>
<em>{{ item.description|linebreaks }}</em>
</para>
<para style="item_description">
<em>{{ item.description|linebreaksbr }}</em>
</para>
<para>
{% endif %}
</para>
</td>
@@ -114,123 +186,133 @@
</tr>
{% endfor %}
</blockTable>
<blockTable style="totalTable" colWidths="300,115,80">
<tr>
<td>VAT may be charged at the current rate date of event ({{ object.vat_rate.as_percent|floatformat:2 }}%)</td>
<td>Sum-total</td>
<td>£ {{ object.sum_total|floatformat:2 }}</td>
</tr>
<tr>
<td>VAT Registration Number: 116252989</td>
<td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td>
<td>£ {{ object.vat|floatformat:2 }}</td>
</tr>
<tr>
<td>
<para>
<b>The full hire fee is payable at least 10 days before the event.</b>
</para>
</td>
<td>
<para>
<b>Total</b>
</para>
</td>
<td>
<para>
<b>£ {{ object.total|floatformat:2 }}</b>
</para>
</td>
</tr>
</blockTable>
<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 or 07825 065678</td>
</tr>
</blockTable>
<blockTable style="paymentTable" colWidths="100,146,93,154">
<tr></tr>
<tr>
<td>Payment Received:</td>
<td>{{ object.payment_received }}</td>
<td>Payment Method:</td>
<td>{{ object.payment_method }}</td>
</tr>
<tr></tr>
</blockTable>
<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 &amp; Lighting in the same condition at the end of the hire period.
</i>
</para>
<para style="blockPara">
<b>
Conditions of hire available on request or on the TEC PA &amp; Lighting website. E&amp;OE
</b>
</para>
<para style="blockPara">
Please return this form directly to TEC PA &amp; Lighting and not the Students' Union Finance Department.
</para>
<blockTable style="signatureTable" colWidths="70,100,325">
<keepTogether>
<blockTable style="totalTable" colWidths="300,115,80">
<tr>
<td>Account Code</td>
<td></td>
<td></td>
<td>{% if not invoice %}VAT Registration Number: 116252989{% endif %}</td>
<td>Total (ex. VAT)</td>
<td>£ {{ object.sum_total|floatformat:2 }}</td>
</tr>
<tr>
<td>
{% if not invoice %}
<para>
<b>The full hire fee is payable at least 10 days before the event.</b>
</para>
{% endif %}
</td>
<td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td>
<td>£ {{ object.vat|floatformat:2 }}</td>
</tr>
<tr>
<td>
<para>
{% if invoice %}
VAT Registration Number: 116252989
{% else %}
<b>This contract is not an invoice.</b>
{% endif %}
</para>
</td>
<td>
<para>
<b>Total</b>
</para>
</td>
<td>
<para>
<b>£ {{ object.total|floatformat:2 }}</b>
</para>
</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 &amp; Lighting and the hirer, the aforementioned conditions of hire forming
an integral part of it.
</i>
</para>
</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 or 07825 065678</td>
</tr>
</blockTable>
</keepTogether>
<spacer length="15"/>
<keepTogether>
<para style="blockPara">
<b>
Conditions of hire available on request or on the TEC PA &amp; Lighting website. E&amp;OE
</b>
<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 &amp; Lighting in the same condition at the end of the hire period.
</i>
</para>
<para style="blockPara">
<b>
Conditions of hire available on request or on the TEC PA &amp; Lighting website. E&amp;OE
</b>
</para>
<para style="blockPara">
Please return this form directly to TEC PA &amp; 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 &amp; Lighting and the hirer, the aforementioned conditions of hire forming
an integral part of it.
</i>
</para>
<para style="blockPara">
<b>
Conditions of hire available on request or on the TEC PA &amp; Lighting website. E&amp;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 &amp; Lighting in a similar condition at the end of the hire period.
</i>
</para>
{% endif %}
{% include "RIGS/event_print_signature.xml" %}
<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 &amp; 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>

View File

@@ -25,15 +25,15 @@
">
<td class="hidden-xs">{{ event.pk }}</td>
<td>
<div><strong>{{ event.start_date|date:"SHORT_DATE_FORMAT" }}</strong></div>
<div><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></div>
{% if event.end_date and event.end_date != event.start_date %}
<div><strong>{{ event.end_date|date:"SHORT_DATE_FORMAT" }}</strong></div>
<div><strong>{{ event.end_date|date:"D d/m/Y" }}</strong></div>
{% endif %}
<span class="text-muted">{{ event.get_status_display }}</span>
</td>
<td>
<h4>
<a href="{% url 'event_detail' event.pk %}">{{ event.name }}</a>
<a {% if perms.RIGS.view_event %}href="{% url 'event_detail' event.pk %}" {% endif %}>{{ event.name }}</a>
{% if event.venue %}
<small>at {{ event.venue }}</small>
{% endif %}
@@ -62,30 +62,36 @@
<dt>Crew meet</dt>
<dd>{{ event.meet_at|date:"H:i" }}<br/>{{ event.meet_at|date:"(Y-m-d)" }}</dd>
{% endif %}
{% if event.start_time %}
{% if event.has_start_time %}
<dt>Event starts</dt>
<dd>
{{ event.start_time|date:"H:i" }}<br/>
{{ event.start_date|date:"(Y-m-d)" }}<br/>
</dd>
{% endif %}
{% if event.end_time and event.start_time != event.end_time %}
{% if event.has_end_time%}{% if event.start_date != event.end_date or event.start_time != event.end_time %}
<dt>Event ends</dt>
<dd>
{{ event.end_time|date:"H:i" }}<br/>
{{ event.end_date|date:"(Y-m-d)" }}
</dd>
{% endif %}
{% endif %}{% endif %}
</dl>
{% endif %}
</td>
<td class="text-right">
{% if event.mic or not event.is_rig %}
{% if event.mic %}
{{ event.mic.initials }}
<div>
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo"/>
{% if perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo"/>
</a>
{% else %}
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo"/>
{% endif %}
</div>
{% else %}
{% elif event.is_rig %}
<span class="glyphicon glyphicon-exclamation-sign"></span>
{% endif %}
</td>

View File

@@ -3,41 +3,76 @@
{% block content %}
<div class="col-sm-12">
<h2>Rig Information Gathering System</h2>
<h1>R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></h1>
</div>
<div class="col-sm-6">
<div class="well">
{% if user.is_authenticated %}
<h3>Welcome back {{ user.get_full_name }}.<br />
<small>Your rigboard initials are {{ user.initials }}</small></h3>
{% endif %}
<h3>There are currently {{ rig_count }} rigs coming up.</h3>
<a class="btn btn-default" href="{% url 'rigboard' %}">View Rigboard</a>
<div class="col-sm-12">
<p><h4 class="list-group-item-heading" style="margin:0;">Welcome back {{ user.get_full_name }}, there are {{ rig_count }} rigs coming up.</h4>
</p>
</div>
<div class="row">
<div class="col-sm-{% if perms.RIGS.view_event %}6{% else %}12{% endif %}">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="list-group-item-heading">Quick Links</h4>
</div>
<div class="list-group">
<a class="list-group-item" href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span> Rigboard</a>
<a class="list-group-item" href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span> Calendar</a>
{% if perms.RIGS.add_event %}<a class="list-group-item" href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span> New Event</a>{% endif %}
<div class="list-group-item default"></div>
<a class="list-group-item" href="//members.nottinghamtec.co.uk/forum" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Forum</a>
<a class="list-group-item" href="//members.nottinghamtec.co.uk/wiki" target="_blank"><span class="glyphicon glyphicon-link"></span> TEC Wiki</a>
<a class="list-group-item" href="//members.nottinghamtec.co.uk/price" target="_blank"><span class="glyphicon glyphicon-link"></span> Price List</a>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="well">
<div>
<h4><a href="{% url 'person_list' %}">People</a></h4>
<form class="form-inline" role="form" action="{% url 'person_list' %}" method="GET">
<input type="search" name="q" class="form-control" placeholder="Search People" />
<button type="submit" class="form-control"><span class="glyphicon glyphicon-search"></span></button>
</form>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Search Rigboard</h4>
</div>
<div>
<h4><a href="{% url 'organisation_list' %}">Organisations</a></h4>
<form class="form-inline" role="form" action="{% url 'organisation_list' %}" method="GET">
<input type="search" name="q" class="form-control" placeholder="Search Organisations" />
<button type="submit" class="form-control"><span class="glyphicon glyphicon-search"></span></button>
</form>
</div>
<div>
<h4><a href="{% url 'venue_list' %}">Venues</a></h4>
<form class="form-inline" role="form" action="{% url 'venue_list' %}" method="GET">
<input type="search" name="q" class="form-control" placeholder="Search Venues" />
<button type="submit" class="form-control"><span class="glyphicon glyphicon-search"></span></button>
</form>
<div class="list-group">
<div class="list-group-item">
<form class="form" role="form" action="{% url 'person_list' %}" method="GET">
<div class="input-group">
<input type="search" name="q" class="form-control" placeholder="Search People" />
<span class="input-group-btn">
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span></button>
</span>
</div>
</form>
</div>
<div class="list-group-item">
<form class="form" role="form" action="{% url 'organisation_list' %}" method="GET">
<div class="input-group">
<input type="search" name="q" class="form-control" placeholder="Search Organisations" />
<span class="input-group-btn">
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span></button>
</span>
</div>
</form>
</div>
<div class="list-group-item">
<form class="form" role="form" action="{% url 'venue_list' %}" method="GET">
<div class="input-group">
<input type="search" name="q" class="form-control" placeholder="Search Venues" />
<span class="input-group-btn">
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span></button>
</span>
</div>
</form>
</div>
</div>
</div>
</div>
{% if perms.RIGS.view_event %}
<div class="col-sm-6" >
{% include 'RIGS/activity_feed.html' %}
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -6,13 +6,19 @@
<div class="col-sm-12">
<div class="row">
<div class="col-sm-8">
<h2>Invoice {{ object.pk }}</h2>
<h2>Invoice {{ object.pk }} ({{ object.invoice_date|date:"d/m/Y"}})</h2>
</div>
<div class="col-sm-4 text-right">
<div class="btn-group btn-page">
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-default" title="Void Invoice">
<span class="glyphicon glyphicon-text-background"></span>
<span class="glyphicon glyphicon-ban-circle"></span> <span
class="hidden-xs">Void</span>
</a>
<a href="{% url 'invoice_print' object.pk %}" target="_blank" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span> <span
class="hidden-xs">Print</span></a>
</div>
</div>
</div>
@@ -40,7 +46,7 @@
<dd>N{{ object.event.pk|stringformat:"05d" }}</dd>
<dt>Event</dt>
<dd>{{ objet.event.pk }}</dd>
<dd>{{ object.event.name }}</dd>
<dt>Event Venue</dt>
<dd>{{ object.event.venue }}</dd>
@@ -48,6 +54,12 @@
<dt>Event MIC</dt>
<dd>{{ object.event.mic.name }}</dd>
<dt>Event Starts</dt>
<dd>{{ object.event.start_date|date:"d M Y" }} {{ object.event.start_time|date:"H:i" }}</dd>
<dt>Event Ends</dt>
<dd>{{ object.event.end_date|date:"d M Y" }} {{ object.event.end_time|date:"H:i" }}</dd>
<dt>Status</dt>
<dd>{{ object.event.get_status_display }}</dd>

View File

@@ -24,8 +24,8 @@
<tbody>
{% for object in object_list %}
<tr class="{% if object.void %}danger{% elif object.balance == 0 %}success{% endif %}">
<td>{{ object.pk }}</td>
<td>{{ object.event }}</td>
<td>{{ object.pk }}</td>
<td><a href="{% url 'event_detail' object.event.pk %}" target="_blank">N{{ object.event.pk|stringformat:"05d" }}</a>: {{ object.event.name }}</td>
<td>{{ object.invoice_date }}</td>
<td>{{ object.balance|floatformat:2 }}</td>
<td class="text-right">

View File

@@ -2,7 +2,7 @@
<tr id="item-{{item.pk}}" data-pk="{{item.pk}}" class="item_row">
<td>
<span class="name">{{ item.name }}</span>
<div>
<div class="item-description">
<em class="description">{{item.description|linebreaksbr}}</em>
</div>
</td>

View File

@@ -1,4 +1,4 @@
<div class="panel">
<div class="panel table-responsive">
<table class="table table-hover" id="item-table" {% if edit %}data-orderurl="{#% url 'item_order' %#}"{% endif %}>
<thead>
<tr>
@@ -25,7 +25,7 @@
<tfoot>
<tr>
<td rowspan="3" colspan="2"></td>
<td>Sum Total</td>
<td>Total (ex. VAT)</td>
<td colspan="2">£ <span id="sumtotal">{{object.sum_total|default:0|floatformat:2}}</span></td>
</tr>
<tr>
@@ -44,8 +44,8 @@
<tr id="new-item-row" class="item_row">
<td>
<span class="name"></span>
<div class="description">
<em></em>
<div class="item-description">
<em class="description"></em>
</div>
</td>
<td>£&nbsp;<span class="cost"></span></td>

View File

@@ -0,0 +1,4 @@
{% load to_class_name from filters %}
{# pass in variable "object" to this template #}
<a title="{% if object.is_rig == False %}Non-rig{% elif object.dry_hire %}Dry Hire{% elif object.is_rig %}Rig{%else%}{{object|to_class_name}}{% endif %} | '{{object.name}}'" href="{{ object.get_absolute_url }}">{% if object.is_rig == False %}Non-rig{% elif object.dry_hire %}Dry Hire{% elif object.is_rig %}Rig{%else%}{{object|to_class_name}}{% endif %} | '{{object.name}}'</a>

View File

@@ -8,7 +8,9 @@
<div class="row">
<div class="col-sm-8">
<h3>{{ object.name }}<br/>
<span class="small">Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}</span>
<span class="small"><a href="{% url 'organisation_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
</a></span>
</h3>
</div>
<div class="pull-right">
@@ -67,8 +69,9 @@
{% block footer %}
<div class="row">
<div class="col-sm-10 align-left">
Lasted edited at {{ object.last_edited_at|date:"SHORT_DATE_FORMAT" }}
by {{ object.last_edited_by.name }}
<a href="{% url 'organisation_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at }} by {{ object.last_edited_by.name }}
</a>
</div>
<div class="col-sm-2">
<div class="pull-right">

View File

@@ -24,9 +24,9 @@
</div>
</form>
</div>
<div class="text-right col-sm-12">{% paginator %}</div>
</div>
<div class="pull-right">{% paginator %}</div>
<div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>

View File

@@ -9,7 +9,9 @@
<h4>Details</h4>
{% if not request.is_ajax %}
<h3>{{ object.name }}<br/>
<span class="small">Last edited {{ object.last_edited_at }} by {{ object.last_edited_by.name }}</span>
<span class="small"><a href="{% url 'person_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
</a></span>
</h3>
<div class="pull-right">
<a href="{% url 'person_update' object.pk %}" class="btn btn-primary">Edit <span
@@ -59,7 +61,9 @@
{% block footer %}
<div class="row">
<div class="col-sm-10 align-left">
Lasted edited at {{ object.last_edited_at|date:"SHORT_DATE_FORMAT" }} by {{ object.last_edited_by.name }}
<a href="{% url 'person_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
</a>
</div>
<div class="col-sm-2">
<div class="pull-right">

View File

@@ -24,9 +24,9 @@
</div>
</form>
</div>
<div class="text-right col-sm-12">{% paginator %}</div>
</div>
<div class="pull-right">{% paginator %}</div>
<div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>

View File

@@ -0,0 +1,12 @@
{# pass in variable "profile" to this template #}
<button title="{{profile.name}}" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover focus' data-toggle="popover" data-content='
<img src="{{profile.profile_picture}}" class="img-responsive img-rounded center-block" style="max-width:4em" />
<dl class="dl-vertical">
<dt>Email</dt>
<dd>{{profile.email}}</dd>
<dt>Phone</dt>
<dd>{{profile.phone}}</dd>
</dl>
'>{{profile.first_name}}</button>

View File

@@ -1,48 +1,104 @@
{% extends 'base.html' %}
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
{% block title %}RIGS Profile {{object.pk}}{% endblock %}
{% block content %}
<div class="col-sm-10 col-sm-offset-1">
<div class="col-sm-10">
<h3>{{object.name}}</h3>
</div>
{% if object.pk == user.pk %}
<div class="col-sm-2">
<div class="pull-right">
<a href="{% url 'profile_update_self' %}" class="btn btn-primary">
Edit <span class="glyphicon glyphicon-pencil"></span>
</a>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="col-sm-6">
<h3>{{object.name}}</h3>
</div>
{% if not request.is_ajax %}
{% if object.pk == user.pk %}
<div class="col-sm-6 text-right">
<div class="btn-group btn-page">
<a href="{% url 'profile_update_self' %}" class="btn btn-default">
Edit Profile <span class="glyphicon glyphicon-pencil"></span>
</a>
<a href="{% url 'password_change' %}" class="btn btn-default">
Change Password <span class="glyphicon glyphicon-lock"></span>
</a>
</div>
</div>
{% endif %}
{% endif %}
<div class="col-sm-8 ">
<dl class="dl-horizontal">
<dt>First Name</dt>
<dd>{{object.first_name}}</dd>
<dt>Last Name</dt>
<dd>{{object.last_name}}</dd>
<dt>Email</dt>
<dd>{{object.email}}</dd>
<dt>Last Login</dt>
<dd>{{object.last_login|date:"d/m/Y H:i"}}</dd>
<dt>Date Joined</dt>
<dd>{{object.date_joined|date:"d/m/Y H:i"}}</dd>
<dt>Initials</dt>
<dd>{{object.initials}}</dd>
<dt>Phone</dt>
<dd>{{object.phone}}</dd>
</dl>
{% if not request.is_ajax %}
{% if object.pk == user.pk %}
<div class="pull-right">
<a href="{% url 'reset_api_key' %}" class="btn btn-default">
{% if user.api_key %}Reset API Key{% else %}Generate API Key{% endif %}
<span class="glyphicon glyphicon-repeat"></span>
</a>
</div>
<h4>Personal iCal Details</h4>
<dl class="dl-horizontal">
<dt>API Key</dt>
<dd>
{% if user.api_key %}
{{user.api_key}}
{% else %}
No API Key Generated
{% endif %}
</dd>
<dt>Calendar URL</dt>
<dd>
{% if user.api_key %}
<pre>http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}</pre>
<small><a href="http://www.google.com/calendar/render?cid=http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}">Click here</a> to add to google calendar.<br/>
To sync from google calendar to mobile device, visit <a href="https://www.google.com/calendar/syncselect" target="_blank">this page</a> on your device and tick "RIGS Calendar".</small>
{% else %}
<pre>No API Key Generated</pre>
{% endif %}
</dd>
</dl>
{% endif %}
{% endif %}
</div>
<div class="col-sm-3">
<div class="center-block">
<img src="{{object.profile_picture}}" class="img-responsive img-rounded" />
</div>
</div>
{% endif %}
<div class="col-sm-6">
<dl class="dl-horizontal">
<dt>First Name</dt>
<dd>{{object.first_name}}</dd>
<dt>Last Name</dt>
<dd>{{object.last_name}}</dd>
<dt>Email</dt>
<dd>{{object.email}}</dd>
<dt>Last Login</dt>
<dd>{{object.last_login}}</dd>
<dt>Date Joined</dt>
<dd>{{object.date_joined}}</dd>
<dt>Initials</dt>
<dd>{{object.initials}}</dd>
<dt>Phone</dt>
<dd>{{object.phone}}</dd>
</dl>
<div class="row">
<div class="col-sm-12">
<h4>Events</h4>
{% with object.latest_events as events %}
{% include 'RIGS/event_table.html' %}
{% endwith %}
</div>
</div>
<div class="col-sm-3 col-sm-offset-2">
<div class="center-block">
<img src="{{object.profile_picture}}" class="img-responsive img-rounded" />
</div>
</div>
{% endblock %}

View File

@@ -8,11 +8,14 @@
<div class="row">
<div class="col-sm-10">
<h3>Rigboard</h3>
</div>
</div>
{% if perms.RIGS.add_event %}
<div class="col-sm-2">
<a href="{% url 'event_create' %}" class="btn btn-default pull-right">New <span
class="glyphicon glyphicon-plus"></span></a>
</div>
{% endif %}
{% comment %}
{# Bring search back at a later date #}
<div class="col-sm-3 col-sm-offset-9">

View File

@@ -8,7 +8,9 @@
<div class="col-sm-10 col-sm-offset-1">
{% if not request.is_ajax %}
<h3>{{ object.name }}<br/>
<span class="small">Last edited {{ object.last_edited_at }} by {{ object.last_edited_by }}</span>
<span class="small"><a href="{% url 'venue_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
</a></span>
</h3>
<div class="pull-right">
<a href="{% url 'venue_update' object.pk %}" class="btn btn-primary">Edit <span
@@ -49,7 +51,9 @@
{% block footer %}
<div class="row">
<div class="col-sm-10 align-left">
Lasted edited at {{ object.last_edited_at|date:"SHORT_DATE_FORMAT" }} by {{ object.last_edited_by }}
<a href="{% url 'venue_history' object.pk %}" title="View Revision History">
Last edited at {{ object.last_edited_at|date:"d/m/Y H:i" }} by {{ object.last_edited_by.name }}
</a>
</div>
<div class="col-sm-2">
<div class="pull-right">

View File

@@ -24,9 +24,10 @@
</div>
</form>
</div>
<div class="text-right col-sm-12">{% paginator %}</div>
</div>
<div class="pull-right">{% paginator %}</div>
<div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>

View File

@@ -0,0 +1,23 @@
{% for change in version.field_changes %}
<button title="Changes to {{ change.field.verbose_name }}" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='
{% if change.new %}<div class="alert alert-success {% if change.long %}overflow-ellipsis{% endif %}">{{change.new|linebreaksbr}}</div>{% endif %}
{% if change.old %}<div class="alert alert-danger {% if change.long %}overflow-ellipsis{% endif %}">{{change.old|linebreaksbr}}</div>{% endif %}
'>{{ change.field.verbose_name }}</button>
{% endfor %}
{% for itemChange in version.item_changes %}
<button title="Changes to item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'" type="button" class="btn btn-default btn-xs" data-container="body" data-html="true" data-trigger='hover' data-toggle="popover" data-content='
{% for change in itemChange.changes %}
<h4>{{ change.field.verbose_name }}</h4>
{% if change.new %}<div class="alert alert-success">{{change.new|linebreaksbr}}</div>{% endif %}
{% if change.old %}<div class="alert alert-danger">{{change.old|linebreaksbr}}</div>{% endif %}
{% endfor %}
'>item '{% if itemChange.new %}{{ itemChange.new.name }}{% else %}{{ itemChange.old.name }}{% endif %}'</button>
{% endfor %}

View File

@@ -0,0 +1,63 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
{% load to_class_name from filters %}
{% load paginator from filters %}
{% load static %}
{% block title %}{{object|to_class_name}} {{ object.pk|stringformat:"05d" }} - Revision History{% endblock %}
{% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script src="{% static "js/popover.js" %}"></script>
<script>
$(function () {
$('[data-toggle="popover"]').popover().click(function(){
if($(this).attr('href')){
window.location.href = $(this).attr('href');
}
});
})
</script>
{% endblock %}
{% block content %}
<div class="col-sm-12">
<div class="row">
<div class="col-sm-12">
<h3><a href="{{ object.get_absolute_url }}">{{object|to_class_name}} {{ object.pk|stringformat:"05d" }}</a> - Revision History</h3>
</div>
<div class="text-right col-sm-12">{% paginator %}</div>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<td>Date</td>
<td>Version ID</td>
<td>User</td>
<td>Changes</td>
</tr>
</thead>
<tbody>
{% for version in object_list %}
{% if version.item_changes or version.field_changes or version.old == None %}
<tr>
<td>{{ version.revision.date_created }}</td>
<td>{{ version.version.pk }}|{{ version.revision.pk }}</td>
<td>{{ version.revision.user.name }}</td>
<td>
{% if version.old == None %}
Object Created
{% else %}
{% include 'RIGS/version_changes.html' %}
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
<div class="align-right">{% paginator %}</div>
</div>
{% endblock %}

View File

@@ -1,7 +1,7 @@
from django import template
from django import forms
from django.forms.forms import NON_FIELD_ERRORS
from django.forms.util import ErrorDict
from django.forms.utils import ErrorDict
register = template.Library()
@@ -9,6 +9,10 @@ register = template.Library()
def multiply(value, arg):
return value*arg
@register.filter
def to_class_name(value):
return value.__class__.__name__
@register.filter
def nice_errors(form, non_field_msg='General form errors'):
nice_errors = ErrorDict()

View File

@@ -1,18 +1,20 @@
from django.conf.urls import patterns, include, url
from django.contrib.auth.decorators import login_required
from RIGS import views, rigboard, finance
from RIGS import models, views, rigboard, finance, ical, versioning, forms
from django.views.generic import RedirectView
from PyRIGS.decorators import permission_required_with_403
from PyRIGS.decorators import api_key_required
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'PyRIGS.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
url('^$', views.Index.as_view(), name='index'),
url('^$', login_required(views.Index.as_view()), name='index'),
url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'),
url('^user/login/$', 'RIGS.views.login', name='login'),
url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}),
# People
url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()),
@@ -23,6 +25,9 @@ urlpatterns = patterns('',
url(r'^people/(?P<pk>\d+)/$',
permission_required_with_403('RIGS.view_person')(views.PersonDetail.as_view()),
name='person_detail'),
url(r'^people/(?P<pk>\d+)/history/$',
permission_required_with_403('RIGS.view_person')(versioning.VersionHistory.as_view()),
name='person_history', kwargs={'model': models.Person}),
url(r'^people/(?P<pk>\d+)/edit/$',
permission_required_with_403('RIGS.change_person')(views.PersonUpdate.as_view()),
name='person_update'),
@@ -37,6 +42,9 @@ urlpatterns = patterns('',
url(r'^organisations/(?P<pk>\d+)/$',
permission_required_with_403('RIGS.view_organisation')(views.OrganisationDetail.as_view()),
name='organisation_detail'),
url(r'^organisations/(?P<pk>\d+)/history/$',
permission_required_with_403('RIGS.view_organisation')(versioning.VersionHistory.as_view()),
name='organisation_history', kwargs={'model': models.Organisation}),
url(r'^organisations/(?P<pk>\d+)/edit/$',
permission_required_with_403('RIGS.change_organisation')(views.OrganisationUpdate.as_view()),
name='organisation_update'),
@@ -51,13 +59,23 @@ urlpatterns = patterns('',
url(r'^venues/(?P<pk>\d+)/$',
permission_required_with_403('RIGS.view_venue')(views.VenueDetail.as_view()),
name='venue_detail'),
url(r'^venues/(?P<pk>\d+)/history/$',
permission_required_with_403('RIGS.view_venue')(versioning.VersionHistory.as_view()),
name='venue_history', kwargs={'model': models.Venue}),
url(r'^venues/(?P<pk>\d+)/edit/$',
permission_required_with_403('RIGS.change_venue')(views.VenueUpdate.as_view()),
name='venue_update'),
# Rigboard
url(r'^rigboard/$', rigboard.RigboardIndex.as_view(), name='rigboard'),
url(r'^rigboard/archive/$', RedirectView.as_view(pattern_name='event_archive')),
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/archive/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')),
url(r'^rigboard/activity/$',
permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()),
name='activity_table'),
url(r'^rigboard/activity/feed/$',
permission_required_with_403('RIGS.view_event')(versioning.ActivityFeed.as_view()),
name='activity_feed'),
url(r'^event/(?P<pk>\d+)/$',
permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()),
@@ -77,6 +95,11 @@ urlpatterns = patterns('',
url(r'^event/archive/$', login_required()(rigboard.EventArchive.as_view()),
name='event_archive'),
url(r'^event/(?P<pk>\d+)/history/$',
permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()),
name='event_history', kwargs={'model': models.Event}),
# Finance
url(r'^invoice/$',
@@ -96,6 +119,9 @@ urlpatterns = patterns('',
url(r'^invoice/(?P<pk>\d+)/$',
permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()),
name='invoice_detail'),
url(r'^invoice/(?P<pk>\d+)/print/$',
permission_required_with_403('RIGS.view_invoice')(finance.InvoicePrint.as_view()),
name='invoice_print'),
url(r'^invoice/(?P<pk>\d+)/void/$',
permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()),
name='invoice_void'),
@@ -113,17 +139,18 @@ urlpatterns = patterns('',
name='profile_detail'),
url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()),
name='profile_update_self'),
url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'),
# 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"),
# API
url(r'^api/(?P<model>\w+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"),
url(r'^api/(?P<model>\w+)/(?P<pk>\d+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"),
# Legacy URL's
url(r'^rig/show/(?P<pk>\d+)/$', RedirectView.as_view(pattern_name='event_detail')),
url(r'^bookings/$', RedirectView.as_view(pattern_name='rigboard')),
url(r'^bookings/past/$', RedirectView.as_view(pattern_name='event_archive')),
# Calendar may have gone away, redirect to the archive for now
url(r'^rigboard/calendar/$',
RedirectView.as_view(pattern_name='event_archive', permanent=False)),
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/past/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')),
)

271
RIGS/versioning.py Normal file
View File

@@ -0,0 +1,271 @@
import logging
from django.views import generic
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import get_object_or_404
from django.template import RequestContext
from django.template.loader import get_template
from django.conf import settings
from django.http import HttpResponse
from django.db.models import Q
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist
# Versioning
import reversion
import simplejson
from reversion.models import Version
from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import ForeignKey, IntegerField, EmailField
from RIGS import models, forms
import datetime
import re
logger = logging.getLogger('tec.pyrigs')
def model_compare(oldObj, newObj, excluded_keys=[]):
# recieves two objects of the same model, and compares them. Returns an array of FieldCompare objects
try:
theFields = oldObj._meta.fields #This becomes deprecated in Django 1.8!!!!!!!!!!!!! (but an alternative becomes available)
except AttributeError:
theFields = newObj._meta.fields
class FieldCompare(object):
def __init__(self, field=None, old=None, new=None):
self.field = field
self._old = old
self._new = new
def display_value(self, value):
if isinstance(self.field, IntegerField) and len(self.field.choices) > 0:
return [x[1] for x in self.field.choices if x[0] == value][0]
return value
@property
def old(self):
return self.display_value(self._old)
@property
def new(self):
return self.display_value(self._new)
@property
def long(self):
if isinstance(self.field, EmailField):
return True
return False
changes = []
for thisField in theFields:
name = thisField.name
if name in excluded_keys:
continue # if we're excluding this field, skip over it
oldValue = getattr(oldObj, name, None)
newValue = getattr(newObj, name, None)
try:
bothBlank = (not oldValue) and (not newValue)
if oldValue != newValue and not bothBlank:
compare = FieldCompare(thisField,oldValue,newValue)
changes.append(compare)
except TypeError: # logs issues with naive vs tz-aware datetimes
logger.error('TypeError when comparing models')
return changes
def compare_event_items(old, new):
# Recieves two event version objects and compares their items, returns an array of ItemCompare objects
item_type = ContentType.objects.get_for_model(models.EventItem)
old_item_versions = old.revision.version_set.filter(content_type=item_type)
new_item_versions = new.revision.version_set.filter(content_type=item_type)
class ItemCompare(object):
def __init__(self, old=None, new=None, changes=None):
self.old = old
self.new = new
self.changes = changes
# Build some dicts of what we have
item_dict = {} # build a list of items, key is the item_pk
for version in old_item_versions: # put all the old versions in a list
compare = ItemCompare(old=version.object_version.object)
item_dict[version.object_id] = compare
for version in new_item_versions: # go through the new versions
try:
compare = item_dict[version.object_id] # see if there's a matching old version
compare.new = version.object_version.object # then add the new version to the dictionary
except KeyError: # there's no matching old version, so add this item to the dictionary by itself
compare = ItemCompare(new=version.object_version.object)
item_dict[version.object_id] = compare # update the dictionary with the changes
changes = []
for (_, compare) in item_dict.items():
compare.changes = model_compare(compare.old, compare.new, ['id','event','order']) # see what's changed
if len(compare.changes) >= 1:
changes.append(compare) # transfer into a sequential array to make it easier to deal with later
return changes
def get_versions_for_model(models):
content_types = []
for model in models:
content_types.append(ContentType.objects.get_for_model(model))
versions = reversion.models.Version.objects.filter(
content_type__in = content_types,
).select_related("revision").order_by("-pk")
return versions
def get_previous_version(version):
thisId = version.object_id
thisVersionId = version.pk
versions = reversion.get_for_object_reference(version.content_type.model_class(), thisId)
try:
previousVersions = versions.filter(revision_id__lt=version.revision_id).latest(field_name='revision__date_created')
except ObjectDoesNotExist:
return False
return previousVersions
def get_changes_for_version(newVersion, oldVersion=None):
#Pass in a previous version if you already know it (for efficiancy)
#if not provided then it will be looked up in the database
if oldVersion == None:
oldVersion = get_previous_version(newVersion)
modelClass = newVersion.content_type.model_class()
compare = {
'revision': newVersion.revision,
'new': newVersion.object_version.object,
'current': modelClass.objects.filter(pk=newVersion.pk).first(),
'version': newVersion,
# Old things that may not be used
'old': None,
'field_changes': None,
'item_changes': None,
}
if oldVersion:
compare['old'] = oldVersion.object_version.object
compare['field_changes'] = model_compare(compare['old'], compare['new'])
compare['item_changes'] = compare_event_items(oldVersion, newVersion)
return compare
class VersionHistory(generic.ListView):
model = reversion.revisions.Version
template_name = "RIGS/version_history.html"
paginate_by = 25
def get_queryset(self, **kwargs):
thisModel = self.kwargs['model']
# thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk'])
versions = reversion.get_for_object_reference(thisModel, self.kwargs['pk'])
return versions
def get_context_data(self, **kwargs):
thisModel = self.kwargs['model']
context = super(VersionHistory, self).get_context_data(**kwargs)
versions = context['object_list']
thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk'])
items = []
for versionNo, thisVersion in enumerate(versions):
if versionNo >= len(versions)-1:
thisItem = get_changes_for_version(thisVersion, None)
else:
thisItem = get_changes_for_version(thisVersion, versions[versionNo+1])
items.append(thisItem)
context['object_list'] = items
context['object'] = thisObject
return context
class ActivityTable(generic.ListView):
model = reversion.revisions.Version
template_name = "RIGS/activity_table.html"
paginate_by = 25
def get_queryset(self):
versions = get_versions_for_model([models.Event,models.Venue,models.Person,models.Organisation])
return versions
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(ActivityTable, self).get_context_data(**kwargs)
items = []
for thisVersion in context['object_list']:
thisItem = get_changes_for_version(thisVersion, None)
items.append(thisItem)
context ['object_list'] = items
return context
class ActivityFeed(generic.ListView):
model = reversion.revisions.Version
template_name = "RIGS/activity_feed_data.html"
paginate_by = 25
def get_queryset(self):
versions = get_versions_for_model([models.Event,models.Venue,models.Person,models.Organisation])
return versions
def get_context_data(self, **kwargs):
maxTimeDelta = []
maxTimeDelta.append({ 'maxAge':datetime.timedelta(days=1), 'group':datetime.timedelta(hours=1)})
maxTimeDelta.append({ 'maxAge':None, 'group':datetime.timedelta(days=1)})
# Call the base implementation first to get a context
context = super(ActivityFeed, self).get_context_data(**kwargs)
items = []
for thisVersion in context['object_list']:
thisItem = get_changes_for_version(thisVersion, None)
if thisItem['item_changes'] or thisItem['field_changes'] or thisItem['old'] == None:
thisItem['withPrevious'] = False
if len(items)>=1:
timeAgo = datetime.datetime.now(thisItem['revision'].date_created.tzinfo) - thisItem['revision'].date_created
timeDiff = items[-1]['revision'].date_created - thisItem['revision'].date_created
timeTogether = False
for params in maxTimeDelta:
if params['maxAge'] is None or timeAgo <= params['maxAge']:
timeTogether = timeDiff < params['group']
break
sameUser = thisItem['revision'].user == items[-1]['revision'].user
thisItem['withPrevious'] = timeTogether & sameUser
items.append(thisItem)
context ['object_list'] = items
return context

View File

@@ -8,6 +8,9 @@ from django.shortcuts import get_object_or_404
from django.core import serializers
import simplejson
from django.contrib import messages
import datetime
import operator
from registration.views import RegistrationView
from RIGS import models, forms
@@ -29,8 +32,7 @@ def login(request, **kwargs):
else:
from django.contrib.auth.views import login
return login(request)
return login(request, authentication_form=forms.LoginForm)
"""
Called from a modal window (e.g. when an item is submitted to an event/invoice).
@@ -66,11 +68,14 @@ class PersonDetail(generic.DetailView):
class PersonCreate(generic.CreateView):
model = models.Person
fields = ['name','phone','email','address','notes']
def get_success_url(self):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy('person_update',kwargs={'pk':self.object.pk}))
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
else:
url = reverse_lazy('person_detail', kwargs={
'pk': self.object.pk,
@@ -80,11 +85,14 @@ class PersonCreate(generic.CreateView):
class PersonUpdate(generic.UpdateView):
model = models.Person
fields = ['name','phone','email','address','notes']
def get_success_url(self):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy('person_update',kwargs={'pk':self.object.pk}))
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
else:
url = reverse_lazy('person_detail', kwargs={
'pk': self.object.pk,
@@ -114,11 +122,14 @@ class OrganisationDetail(generic.DetailView):
class OrganisationCreate(generic.CreateView):
model = models.Organisation
fields = ['name','phone','email','address','notes','union_account']
def get_success_url(self):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy('organisation_update',kwargs={'pk':self.object.pk}))
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
else:
url = reverse_lazy('organisation_detail', kwargs={
'pk': self.object.pk,
@@ -128,11 +139,14 @@ class OrganisationCreate(generic.CreateView):
class OrganisationUpdate(generic.UpdateView):
model = models.Organisation
fields = ['name','phone','email','address','notes','union_account']
def get_success_url(self):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy('organisation_update',kwargs={'pk':self.object.pk}))
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
else:
url = reverse_lazy('organisation_detail', kwargs={
'pk': self.object.pk,
@@ -162,11 +176,14 @@ class VenueDetail(generic.DetailView):
class VenueCreate(generic.CreateView):
model = models.Venue
fields = ['name','phone','email','address','notes']
def get_success_url(self):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy('venue_update',kwargs={'pk':self.object.pk}))
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
else:
url = reverse_lazy('venue_detail', kwargs={
'pk': self.object.pk,
@@ -176,11 +193,14 @@ class VenueCreate(generic.CreateView):
class VenueUpdate(generic.UpdateView):
model = models.Venue
fields = ['name','phone','email','address','notes']
def get_success_url(self):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy('venue_update',kwargs={'pk':self.object.pk}))
messages.info(self.request, "modalobject="+serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='"+update_url+"'")
else:
url = reverse_lazy('venue_detail', kwargs={
'pk': self.object.pk,
@@ -194,6 +214,7 @@ class SecureAPIRequest(generic.View):
'person': models.Person,
'organisation': models.Organisation,
'profile': models.Profile,
'event': models.Event,
}
perms = {
@@ -201,6 +222,7 @@ class SecureAPIRequest(generic.View):
'person': 'RIGS.view_person',
'organisation': 'RIGS.view_organisation',
'profile': None,
'event': 'RIGS.view_event',
}
'''
@@ -238,27 +260,102 @@ class SecureAPIRequest(generic.View):
# Supply data for autocomplete ajax request in json form
term = request.GET.get('term', None)
if term:
if fields is None:
if fields is None: # Default to just name
fields = ['name']
# Build a list of Q objects for use later
queries = []
for part in term.split(" "):
qs = []
for field in fields:
q = Q(**{field + "__icontains": part})
qs.append(q)
queries.append(reduce(operator.or_, qs))
# Build the data response list
results = []
query = reduce(operator.and_, queries)
objects = self.models[model].objects.filter(query)
for o in objects:
data = {
'pk': o.pk,
'value': o.pk,
'label': o.name,
}
try: # See if there is a valid update URL
data['update'] = reverse("%s_update" % model, kwargs={'pk': o.pk})
except NoReverseMatch:
pass
results.append(data)
# return a data response
json = simplejson.dumps(results)
return HttpResponse(json, content_type="application/json") # Always json
start = request.GET.get('start', None)
end = request.GET.get('end', None)
if model == "event" and start and end:
# Probably a calendar request
start_datetime = datetime.datetime.strptime( start, "%Y-%m-%dT%H:%M:%SZ" )
end_datetime = datetime.datetime.strptime( end, "%Y-%m-%dT%H:%M:%SZ" )
all_objects = self.models[model].objects
results = []
for field in fields:
filter = field + "__icontains"
objects = all_objects.filter(**{filter: term})
for o in objects:
data = {
'pk': o.pk,
'value': o.pk,
'label': o.name,
filter = Q(start_date__lte=end_datetime) & Q(start_date__gte=start_datetime)
objects = all_objects.filter(filter).select_related('person', 'organisation', 'venue', 'mic').order_by('-start_date')
for item in objects:
data = {
'pk': item.pk,
'title': item.name
}
data['is_rig'] = item.is_rig
data['status'] = str(item.get_status_display())
if item.start_date:
data['start_date'] = item.start_date.strftime('%Y-%m-%d')
if item.has_start_time:
data['start_time'] = item.start_time.strftime('%H:%M:%SZ')
if item.end_date:
data['end_date'] = item.end_date.strftime('%Y-%m-%d')
if item.has_end_time:
data['end_time'] = item.end_time.strftime('%H:%M:%SZ')
if item.meet_at:
data['meet_at'] = item.meet_at.strftime('%Y-%m-%dT%H:%M:%SZ')
if item.access_at:
data['access_at'] = item.access_at.strftime('%Y-%m-%dT%H:%M:%SZ')
if item.venue:
data['venue'] = item.venue.name
if item.person:
data['person'] = item.person.name
if item.organisation:
data['organisation'] = item.organisation.name
if item.mic:
data['mic'] = {
'name':item.mic.get_full_name(),
'initials':item.mic.initials
}
try: # See if there is an update url or don't bother with it otherwise
data['update'] = reverse("%s_update" % model, kwargs={'pk': o.pk})
except NoReverseMatch:
pass
if item.description:
data['description'] = item.description
results.append(data)
json = simplejson.dumps(results[:20])
if item.notes:
data['notes'] = item.notes
data['url'] = str(reverse_lazy('event_detail',kwargs={'pk':item.pk}))
results.append(data)
json = simplejson.dumps(results)
return HttpResponse(json, content_type="application/json") # Always json
return HttpResponse(model)
@@ -287,4 +384,12 @@ class ProfileUpdateSelf(generic.UpdateView):
def get_success_url(self):
url = reverse_lazy('profile_detail')
return url
return url
class ResetApiKey(generic.RedirectView):
def get_redirect_url(self, *args, **kwargs):
self.request.user.api_key = self.request.user.make_api_key()
self.request.user.save()
return reverse_lazy('profile_detail')