mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-12 17:49:41 +00:00
Merge master into testing
This commit is contained in:
4
.slugignore
Normal file
4
.slugignore
Normal file
@@ -0,0 +1,4 @@
|
||||
*.sqlite3
|
||||
*.scss
|
||||
*.md
|
||||
*.rb
|
||||
@@ -34,3 +34,34 @@ def permission_required_with_403(perm, login_url=None):
|
||||
enabled, redirecting to the log-in page or rendering a 403 as necessary.
|
||||
"""
|
||||
return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url)
|
||||
|
||||
from RIGS import models
|
||||
|
||||
def api_key_required(function):
|
||||
"""
|
||||
Decorator for views that checks api_pk and api_key.
|
||||
Failed users will be given a 403 error.
|
||||
Should only be used for urls which include <api_pk> and <api_key> kwargs
|
||||
"""
|
||||
def wrap(request, *args, **kwargs):
|
||||
|
||||
userid = kwargs.get('api_pk')
|
||||
key = kwargs.get('api_key')
|
||||
|
||||
error_resp = render_to_response('403.html', context_instance=RequestContext(request))
|
||||
error_resp.status_code = 403
|
||||
|
||||
if key is None:
|
||||
return error_resp
|
||||
if userid is None:
|
||||
return error_resp
|
||||
|
||||
try:
|
||||
user_object = models.Profile.objects.get(pk=userid)
|
||||
except Profile.DoesNotExist:
|
||||
return error_resp
|
||||
|
||||
if user_object.api_key != key:
|
||||
return error_resp
|
||||
return function(request, *args, **kwargs)
|
||||
return wrap
|
||||
0
PyRIGS/formats/__init__.py
Normal file
0
PyRIGS/formats/__init__.py
Normal file
0
PyRIGS/formats/en/__init__.py
Normal file
0
PyRIGS/formats/en/__init__.py
Normal file
5
PyRIGS/formats/en/formats.py
Normal file
5
PyRIGS/formats/en/formats.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
DATETIME_FORMAT = ('d/m/Y H:i')
|
||||
DATE_FORMAT = ('d/m/Y')
|
||||
TIME_FORMAT = ('H:i')
|
||||
@@ -12,20 +12,26 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||
import os
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e'
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get('SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True
|
||||
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
||||
|
||||
INTERNAL_IPS = ['127.0.0.1', '10.20.30.20']
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
|
||||
ADMINS = (
|
||||
('Tom Price', 'tomtom5152@gmail.com')
|
||||
)
|
||||
|
||||
|
||||
# Application definition
|
||||
@@ -42,6 +48,7 @@ INSTALLED_APPS = (
|
||||
'debug_toolbar',
|
||||
'registration',
|
||||
'reversion',
|
||||
'captcha',
|
||||
'widget_tweaks',
|
||||
)
|
||||
|
||||
@@ -63,23 +70,63 @@ WSGI_APPLICATION = 'PyRIGS.wsgi.application'
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
try:
|
||||
import pymysql
|
||||
pymysql.install_as_MySQLdb()
|
||||
except ImportError:
|
||||
pass
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
if not DEBUG:
|
||||
import dj_database_url
|
||||
DATABASES['default'] = dj_database_url.config()
|
||||
|
||||
# Logging
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': ('%(asctime)s [%(process)d] [%(levelname)s] ' +
|
||||
'pathname=%(pathname)s lineno=%(lineno)s ' +
|
||||
'funcname=%(funcName)s %(message)s'),
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S'
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(message)s'
|
||||
}
|
||||
},
|
||||
# 'legacy': {
|
||||
# 'ENGINE': 'django.db.backends.mysql',
|
||||
# 'HOST': 'alfie.codedinternet.com',
|
||||
# 'NAME': 'tec_rigs',
|
||||
# 'USER': 'tec_rigs',
|
||||
# 'PASSWORD': 'xMNb(b+Giu]&',
|
||||
# }
|
||||
'handlers': {
|
||||
'null': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.NullHandler',
|
||||
},
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose'
|
||||
},
|
||||
'mail_admins': {
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
'level': 'ERROR',
|
||||
# But the emails are plain text by default - HTML is nicer
|
||||
'include_html': True,
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
# Again, default Django configuration to email unhandled exceptions
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
# Might as well log any errors anywhere else in Django
|
||||
'django': {
|
||||
'handlers': ['console'],
|
||||
'level': 'ERROR',
|
||||
'propagate': False,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# User system
|
||||
@@ -91,15 +138,22 @@ LOGOUT_URL = '/user/logout'
|
||||
|
||||
ACCOUNT_ACTIVATION_DAYS = 7
|
||||
|
||||
# reCAPTCHA settings
|
||||
RECAPTCHA_PUBLIC_KEY = '6Le16gUTAAAAAO5f-6te_x0NjWmF65_h7saBI6Cg'
|
||||
RECAPTCHA_PRIVATE_KEY = '***REMOVED***'
|
||||
NOCAPTCHA = True
|
||||
|
||||
# Email
|
||||
EMAILER_TEST = False
|
||||
if not DEBUG or EMAILER_TEST:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = 'server.techost.co.uk'
|
||||
EMAIL_HOST = 'mail.nottinghamtec.co.uk'
|
||||
EMAIL_PORT = 465
|
||||
EMAIL_HOST_USER = 'tec'
|
||||
EMAIL_HOST_PASSWORD = '***REMOVED***'
|
||||
DEFAULT_FROM_EMAIL = 'rigs@nottinghamtec.co.uk'
|
||||
EMAIL_HOST_USER = 'pyrigs@nottinghamtec.co.uk'
|
||||
EMAIL_HOST_PASSWORD = 'N_dF9T&dD(Th'
|
||||
EMAIL_USE_TLS = False
|
||||
EMAIL_USE_SSL = True
|
||||
DEFAULT_FROM_EMAIL = 'pyrigs@nottinghamtec.co.uk'
|
||||
else:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
@@ -108,7 +162,9 @@ else:
|
||||
|
||||
LANGUAGE_CODE = 'en-gb'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
TIME_ZONE = 'Europe/London'
|
||||
|
||||
FORMAT_MODULE_PATH = 'PyRIGS.formats'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
@@ -132,7 +188,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
||||
|
||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
|
||||
STATIC_DIRS = (
|
||||
@@ -145,4 +201,4 @@ TEMPLATE_DIRS = (
|
||||
|
||||
USE_GRAVATAR=True
|
||||
|
||||
TERMS_OF_HIRE_URL = "http://dev.nottinghamtec.co.uk/wp-content/uploads/2014/11/terms.pdf"
|
||||
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
||||
|
||||
@@ -4,6 +4,7 @@ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.conf import settings
|
||||
from registration.backends.default.views import RegistrationView
|
||||
import RIGS
|
||||
from RIGS import regbackend
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# Examples:
|
||||
@@ -12,7 +13,7 @@ urlpatterns = patterns('',
|
||||
|
||||
url(r'^', include('RIGS.urls')),
|
||||
url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail),
|
||||
name="registration_register"),
|
||||
name="registration_register"),
|
||||
url('^user/', include('django.contrib.auth.urls')),
|
||||
url('^user/', include('registration.backends.default.urls')),
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "PyRIGS.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
application = get_wsgi_application()
|
||||
from dj_static import Cling
|
||||
|
||||
application = Cling(get_wsgi_application())
|
||||
|
||||
19
README.md
19
README.md
@@ -1,5 +1,5 @@
|
||||
# TEC PA & Lighting - PyRIGS #
|
||||
Welcome to TEC PA & Lightings PyRIGS program. This is a reiplmentation of the exisiting Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.
|
||||
Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.
|
||||
|
||||
The purpose of this project is to make the system more compatible and easier to understand such that should future changes be needed they can be made without having to understand the intricacies of Rails.
|
||||
|
||||
@@ -9,19 +9,21 @@ This document is intended to get you up and running, but if don't care about wha
|
||||
|
||||
### What is this repository for? ###
|
||||
For the rapid development of the application for medium term deployment, the main branch is being used.
|
||||
Once the application is deployed in a production environment, other branches should be used to properly stage edits and pushes of new features.
|
||||
Once the application is deployed in a production environment, other branches should be used to properly stage edits and pushes of new features. When a significant feature is developed on a branch, raise a pull request and it can be reviewed before being put into production.
|
||||
|
||||
Most of the documents here assume a basic knowledge of how Python and Django work (hint, if I don't say something, Google it, you will find 10000's of answers). The documentation is purely to be specific to TEC's application of the framework.
|
||||
|
||||
### Editing ###
|
||||
It is recommended that you use the PyCharm IDE by JetBrains. Whilst other editors are available, this is the best for integration with Django as it can automatically manage all the pesky admin commands that frequently need running, as well as nice integration with git.
|
||||
|
||||
Please contact TJP for details on how to acquire this.
|
||||
For the more experienced developer/somebody who doesn't want a full IDE and wants it to open in less than the age of the universe, I can strongly recommend [Sublime Text](http://www.sublimetext.com/). It has a bit of a steeper learning curve, and won't manage anything Django/git related out of the box, but once you get the hang of it is by far the fastest and most powerful editor I have used (for any type of project).
|
||||
|
||||
Please contact TJP for details on how to acquire these.
|
||||
|
||||
### Python Environment ###
|
||||
Whilst it is not critical to the success of the application, it has be written with **Python 3** in mind. It might have been tested with Python 2, but it is not guaranteed to work with it.
|
||||
Whilst the Python version used is not critical to the running of the application, using the same version usually helps avoid a lot of issues. Mainly the C implementation of Python 2 (CPython 2) has been used (specifically the Python 2.7 standard). Most of the application has been written with Python 3 in mind however, and should run without issue. Some level of testing on Python 3 has been done, but there is no guarantee it will work (for more information on this please see [[Python Version]] on the wiki)
|
||||
|
||||
Once you have Python 3 installed, please follow steps to set up a virtualenv, which will isolate the project from your system environment.
|
||||
Once you have your Python distribution installed, go ahead an follow the steps to set up a virtualenv, which will isolate the project from the system environment.
|
||||
|
||||
#### PyCharm ####
|
||||
If you are using the prefered PyCharm IDE, then this should be quite easy.
|
||||
@@ -43,11 +45,12 @@ To run the Django application follow these steps
|
||||
5. Run the application
|
||||
|
||||
#### Console Based ####
|
||||
If you aren't using PyCharm, or want to use a console for some reason, this is really easy. Simply run
|
||||
If you aren't using PyCharm, or want to use a console for some reason, this is really easy, there is even [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) to help things along. Simply run
|
||||
```
|
||||
virtualenv <dir>
|
||||
```
|
||||
Where dir is the directory you wish to create the virtualenv in. Watch this doesn't do something dumb like call the Python 2 version of it, else you will have that ballache to deal with later.
|
||||
Where dir is the directory you wish to create the virtualenv in.
|
||||
|
||||
Next activate the virtualenv.
|
||||
```
|
||||
Windows
|
||||
@@ -70,4 +73,4 @@ python manage.py runserver
|
||||
Please refer to Django documentation for a full list of options available here.
|
||||
|
||||
### Committing, pushing and testing ###
|
||||
Feel free to commit as you wish, on your own branch. On my branch (master for development) do not commit code that you either know doesn't work or don't know works. If you must commit this code, please make sure you say in the commit message that it isn't work, and if you can why it isn't working. If and only if you absolutely must push, then please don't leave it as the HEAD for too long, it's not much to ask but when you are done just make sure you haven't broken the HEAD for the next person.
|
||||
Feel free to commit as you wish, on your own branch. On my branch (master for development) do not commit code that you either know doesn't work or don't know works. If you must commit this code, please make sure you say in the commit message that it isn't working, and if you can why it isn't working. If and only if you absolutely must push, then please don't leave it as the HEAD for too long, it's not much to ask but when you are done just make sure you haven't broken the HEAD for the next person.
|
||||
@@ -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()
|
||||
|
||||
@@ -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
136
RIGS/ical.py
Normal 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
|
||||
36
RIGS/migrations/0021_auto_20150420_1155.py
Normal file
36
RIGS/migrations/0021_auto_20150420_1155.py
Normal 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,
|
||||
),
|
||||
]
|
||||
26
RIGS/migrations/0022_auto_20150424_2104.py
Normal file
26
RIGS/migrations/0022_auto_20150424_2104.py
Normal 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,
|
||||
),
|
||||
]
|
||||
66
RIGS/migrations/0023_auto_20150529_0048.py
Normal file
66
RIGS/migrations/0023_auto_20150529_0048.py
Normal 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),
|
||||
),
|
||||
]
|
||||
@@ -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
13
RIGS/regbackend.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
27
RIGS/static/css/ajax-bootstrap-select.css
Executable file
27
RIGS/static/css/ajax-bootstrap-select.css
Executable 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;
|
||||
}
|
||||
366
RIGS/static/css/bootstrap-datetimepicker.min.css
vendored
Normal file
366
RIGS/static/css/bootstrap-datetimepicker.min.css
vendored
Normal 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;
|
||||
}
|
||||
6
RIGS/static/css/bootstrap-select.min.css
vendored
Normal file
6
RIGS/static/css/bootstrap-select.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1061
RIGS/static/css/fullcalendar.css
Executable file
1061
RIGS/static/css/fullcalendar.css
Executable file
File diff suppressed because it is too large
Load Diff
202
RIGS/static/css/fullcalendar.print.css
Executable file
202
RIGS/static/css/fullcalendar.print.css
Executable 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 */
|
||||
}
|
||||
@@ -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
BIN
RIGS/static/imgs/paperwork/corner.jpg
Normal file
BIN
RIGS/static/imgs/paperwork/corner.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
RIGS/static/imgs/paperwork/tec-logo.jpg
Normal file
BIN
RIGS/static/imgs/paperwork/tec-logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
1554
RIGS/static/js/ajax-bootstrap-select.js
Executable file
1554
RIGS/static/js/ajax-bootstrap-select.js
Executable file
File diff suppressed because it is too large
Load Diff
73
RIGS/static/js/asteroids.min.js
vendored
Normal file
73
RIGS/static/js/asteroids.min.js
vendored
Normal 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();})();
|
||||
@@ -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);
|
||||
clearSelectionLabel = '(no selection)';
|
||||
|
||||
},
|
||||
select: function(e, ui) {
|
||||
e.preventDefault();
|
||||
$(this).val(ui.item.label);
|
||||
$("#"+$(this).data('target')).val(ui.item.value)
|
||||
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']);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
8
RIGS/static/js/bootstrap-datetimepicker.min.js
vendored
Normal file
8
RIGS/static/js/bootstrap-datetimepicker.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1209
RIGS/static/js/bootstrap-select.js
vendored
Executable file
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
10789
RIGS/static/js/fullcalendar.js
Executable file
File diff suppressed because it is too large
Load Diff
@@ -133,11 +133,3 @@ $("#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
105
RIGS/static/js/konami.js
Executable 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;
|
||||
};
|
||||
86
RIGS/static/js/moment-twitter.js
Normal file
86
RIGS/static/js/moment-twitter.js
Normal 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
7
RIGS/static/js/moment.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -48,3 +53,87 @@ 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
66
RIGS/templates/RIGS/activity_feed.html
Normal file
66
RIGS/templates/RIGS/activity_feed.html
Normal 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>
|
||||
|
||||
51
RIGS/templates/RIGS/activity_feed_data.html
Normal file
51
RIGS/templates/RIGS/activity_feed_data.html
Normal 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 %}
|
||||
88
RIGS/templates/RIGS/activity_table.html
Normal file
88
RIGS/templates/RIGS/activity_table.html
Normal 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 %}
|
||||
111
RIGS/templates/RIGS/calendar.html
Normal file
111
RIGS/templates/RIGS/calendar.html
Normal 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 %}
|
||||
@@ -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> </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 %}
|
||||
|
||||
@@ -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' %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
<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 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"/>
|
||||
</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 & 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"/>
|
||||
|
||||
<image file="http://images.nottinghamtec.co.uk/rigs_union_logo.jpg" x="365" y="746"/>
|
||||
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
|
||||
<drawString x="137" y="780">TEC PA & Lighting</drawString>
|
||||
|
||||
<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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 & 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 & Lighting website. E&OE
|
||||
</b>
|
||||
</para>
|
||||
|
||||
<para style="blockPara">
|
||||
Please return this form directly to TEC PA & Lighting and not the Students' Union Finance Department.
|
||||
</para>
|
||||
|
||||
<blockTable style="signatureTable" colWidths="70,100,325">
|
||||
<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 & 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 & Lighting website. E&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 & 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 & Lighting website. E&OE
|
||||
</b>
|
||||
</para>
|
||||
|
||||
<para style="blockPara">
|
||||
Please return this form directly to TEC PA & Lighting and not the Students' Union Finance Department.
|
||||
</para>
|
||||
|
||||
<blockTable style="signatureTable" colWidths="70,100,325">
|
||||
<tr>
|
||||
<td>Account Code</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
||||
{% else %}
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I, the hirer, have read, understand and fully accept the current conditions of hire. This document forms a
|
||||
binding contract between TEC PA & Lighting and the hirer, the aforementioned conditions of hire forming
|
||||
an integral part of it.
|
||||
</i>
|
||||
</para>
|
||||
|
||||
<para style="blockPara">
|
||||
<b>
|
||||
Conditions of hire available on request or on the TEC PA & Lighting website. E&OE
|
||||
</b>
|
||||
</para>
|
||||
|
||||
{% include "RIGS/event_print_signature.xml" %}
|
||||
<spacer length="10"/>
|
||||
<para style="blockPara">
|
||||
<b>To be signed on the day of the event/hire:</b>
|
||||
</para>
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I, the hirer, have received the goods/services as requested and in good order. I agree to return any dry hire
|
||||
items to TEC PA & Lighting in a similar condition at the end of the hire period.
|
||||
</i>
|
||||
</para>
|
||||
{% endif %}
|
||||
|
||||
{% include "RIGS/event_print_signature.xml" %}
|
||||
|
||||
<para style="blockPara">
|
||||
<b>To be signed on the day of the event/hire:</b>
|
||||
</para>
|
||||
<para style="blockPara">
|
||||
<i>
|
||||
I, the hirer, have received the goods/services as requested and in good order. I agree to return any dry hire
|
||||
items to TEC PA & Lighting in a similar condition at the end of the hire period.
|
||||
</i>
|
||||
</para>
|
||||
{% endif %}
|
||||
|
||||
{% include "RIGS/event_print_signature.xml" %}
|
||||
</keepTogether>
|
||||
{% endif %}
|
||||
<namedString id="lastPage"><pageNumber/></namedString>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>£ <span class="cost"></span></td>
|
||||
|
||||
4
RIGS/templates/RIGS/object_button.html
Normal file
4
RIGS/templates/RIGS/object_button.html
Normal 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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
12
RIGS/templates/RIGS/profile_button.html
Normal file
12
RIGS/templates/RIGS/profile_button.html
Normal 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>
|
||||
@@ -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 %}
|
||||
@@ -9,10 +9,13 @@
|
||||
<div class="col-sm-10">
|
||||
<h3>Rigboard</h3>
|
||||
</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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
23
RIGS/templates/RIGS/version_changes.html
Normal file
23
RIGS/templates/RIGS/version_changes.html
Normal 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 %}
|
||||
63
RIGS/templates/RIGS/version_history.html
Normal file
63
RIGS/templates/RIGS/version_history.html
Normal 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 %}
|
||||
@@ -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()
|
||||
|
||||
47
RIGS/urls.py
47
RIGS/urls.py
@@ -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
271
RIGS/versioning.py
Normal 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
|
||||
139
RIGS/views.py
139
RIGS/views.py
@@ -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)
|
||||
@@ -288,3 +385,11 @@ class ProfileUpdateSelf(generic.UpdateView):
|
||||
def get_success_url(self):
|
||||
url = reverse_lazy('profile_detail')
|
||||
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')
|
||||
|
||||
11
importer.py
11
importer.py
@@ -345,6 +345,7 @@ def main():
|
||||
# [x.start() for x in processs]
|
||||
# # Wait for all processs to finish
|
||||
# [x.join() for x in processs]
|
||||
|
||||
import_users()
|
||||
import_people(True)
|
||||
import_organisations(True)
|
||||
@@ -354,8 +355,18 @@ def main():
|
||||
import_rigs(True)
|
||||
import_eventitem(True)
|
||||
import_invoices(True)
|
||||
|
||||
# Do this before doing non rigs else it gets ugly
|
||||
sql = "SELECT setval(\'\"RIGS_%s_id_seq\"\', (SELECT MAX(id) FROM \"RIGS_%s\"));" % ('event', 'event')
|
||||
cursor = connections['default'].cursor()
|
||||
cursor.execute(sql)
|
||||
import_nonrigs(False)
|
||||
|
||||
sequences = ['profile', 'person', 'organisation', 'vatrate', 'venue', 'event', 'eventitem', 'invoice', 'payment']
|
||||
for seq in sequences:
|
||||
sql = "SELECT setval(\'\"RIGS_%s_id_seq\"\', (SELECT MAX(id) FROM \"RIGS_%s\"));" % (seq, seq)
|
||||
cursor = connections['default'].cursor()
|
||||
cursor.execute(sql)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
Django==1.7.1
|
||||
PyMySQL==0.6.2
|
||||
django-debug-toolbar==1.2.2
|
||||
django-registration-redux==1.1
|
||||
django-reversion==1.8.5
|
||||
sqlparse==0.1.13
|
||||
simplejson==3.6.5
|
||||
lxml==3.4.1
|
||||
pillow==2.7.0
|
||||
reportlab==2.7
|
||||
z3c.rml==2.7.2
|
||||
pyPDF2==1.23
|
||||
dj-database-url==0.3.0
|
||||
dj-static==0.0.6
|
||||
Django==1.8.2
|
||||
django-debug-toolbar==1.3.0
|
||||
django-ical==1.3
|
||||
django-recaptcha==1.0.4
|
||||
django-registration-redux==1.2
|
||||
django-reversion==1.8.7
|
||||
django-toolbelt==0.0.1
|
||||
django-widget-tweaks==1.3
|
||||
gunicorn==19.3.0
|
||||
icalendar==3.9.0
|
||||
lxml==3.4.4
|
||||
Pillow==2.8.1
|
||||
psycopg2==2.6
|
||||
Pygments==2.0.2
|
||||
PyPDF2==1.24
|
||||
python-dateutil==2.4.2
|
||||
pytz==2015.4
|
||||
reportlab==3.1.44
|
||||
selenium==2.45.0
|
||||
simplejson==3.7.2
|
||||
six==1.9.0
|
||||
sqlparse==0.1.15
|
||||
static3==0.6.1
|
||||
svg2rlg==0.3
|
||||
yolk==0.4.3
|
||||
z3c.rml==2.8.1
|
||||
zope.event==4.0.3
|
||||
zope.interface==4.1.2
|
||||
zope.schema==4.4.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% load static from staticfiles %}
|
||||
{% load url from future %}
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
@@ -11,10 +11,16 @@
|
||||
|
||||
<meta name="viewport" content="initial-scale=1">
|
||||
|
||||
<link rel="icon" type="image/png" href="{% static "imgs/pyrigs-avatar.png" %}">
|
||||
<link rel="apple-touch-icon" href="{% static "imgs/pyrigs-avatar.png" %}">
|
||||
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet' type='text/css'>
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static "css/screen.css" %}">
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
|
||||
<script src="//code.jquery.com/jquery-latest.min.js"></script>
|
||||
{% block preload_js %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -36,23 +42,33 @@
|
||||
</div>
|
||||
<div class="navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="/">Home</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Rigboard<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'rigboard' %}">Rigboard</a></li>
|
||||
<li><a href="{% url 'event_archive' %}">Archive</a></li>
|
||||
<li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span> Rigboard</a></li>
|
||||
<li><a href="{% url 'event_archive' %}"><span class="glyphicon glyphicon-book"></span> Archive</a></li>
|
||||
<li><a href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span> Calendar</a></li>
|
||||
{% if perms.RIGS.view_event %}
|
||||
<li><a href="{% url 'activity_table' %}"><span class="glyphicon glyphicon-random"></span> Recent Changes</a></li>
|
||||
{% endif %}
|
||||
{% if perms.RIGS.add_event %}
|
||||
<li><a href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span> New Event</a></li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.RIGS.view_invoice %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'invoice_list' %}">Active</a></li>
|
||||
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Active</a></li>
|
||||
{% if perms.RIGS.add_invoice %}
|
||||
<li><a href="{% url 'invoice_waiting' %}">Waiting</a></li>
|
||||
<li><a href="{% url 'invoice_waiting' %}"><span class="glyphicon glyphicon-briefcase"></span> Waiting</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'invoice_archive' %}">Archive</a></li>
|
||||
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span> Archive</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -65,6 +81,7 @@
|
||||
{% if perms.RIGS.view_venue %}
|
||||
<li><a href="{% url 'venue_list' %}">Venues</a></li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown">
|
||||
@@ -97,7 +114,7 @@
|
||||
{% else %}
|
||||
<a href="{% url "login" %}?next={{ request.path }}">
|
||||
<span class="icon-user"></span>
|
||||
Hi guest
|
||||
Login
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
@@ -133,9 +150,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal"></div>
|
||||
<div class="modal fade" id="modal" role="dialog" tabindex=-1></div>
|
||||
|
||||
<script src="//code.jquery.com/jquery-latest.min.js"></script>
|
||||
<script>
|
||||
Date.prototype.getISOString = function () {
|
||||
var yyyy = this.getFullYear().toString();
|
||||
@@ -152,22 +168,32 @@
|
||||
</script>
|
||||
<script src="{% static "js/dropdown.js" %}"></script>
|
||||
<script src="{% static "js/modal.js" %}"></script>
|
||||
<script src="{% static "js/konami.js" %}"></script>
|
||||
<script>
|
||||
jQuery(document).ready(function () {
|
||||
jQuery(document).on('click', '.modal-href', function (e) {
|
||||
e.preventDefault()
|
||||
modaltarget = jQuery(this).data('target')
|
||||
modalobject = "";
|
||||
jQuery('#modal').load(jQuery(this).attr('href'), function (e) {
|
||||
jQuery('#modal').modal();
|
||||
});
|
||||
jQuery('#modal').on('hide.bs.modal', function (e) {
|
||||
if (modaltarget != "" && modalobject != "") {
|
||||
jQuery(modaltarget).val(modalobject[0]['pk']);
|
||||
jQuery(modaltarget + '-input').val(modalobject[0]['fields']['name']);
|
||||
}
|
||||
})
|
||||
$link = jQuery(this);
|
||||
// Anti modal inception
|
||||
if($link.parents('#modal').length == 0) {
|
||||
e.preventDefault();
|
||||
modaltarget = $link.data('target');
|
||||
modalobject = "";
|
||||
jQuery('#modal').load($link.attr('href'), function (e) {
|
||||
jQuery('#modal').modal();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var easter_egg = new Konami();
|
||||
easter_egg.code = function() {
|
||||
var s = document.createElement('script');
|
||||
s.type='text/javascript';
|
||||
document.body.appendChild(s);
|
||||
s.src='{% static "js/asteroids.min.js"%}';
|
||||
ga('send', 'event', 'easter_egg', 'activated');
|
||||
}
|
||||
easter_egg.load();
|
||||
});
|
||||
</script>
|
||||
{% block js %}
|
||||
|
||||
2
templates/base_ajax_nomodal.html
Normal file
2
templates/base_ajax_nomodal.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{% block js %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% load url from future %}
|
||||
|
||||
Welcome {{ user }},
|
||||
|
||||
Thank you for registering on {{ site }}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% load url from future %}
|
||||
|
||||
{% load widget_tweaks %}
|
||||
{% include 'form_errors.html' %}
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="col-sm-6 col-sm-offset-3 col-lg-4 col-lg-offset-4">
|
||||
|
||||
<form action="{% url 'login' %}" method="post" role="form">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
@@ -12,6 +12,12 @@
|
||||
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
|
||||
{% render_field form.password class+="form-control" placeholder=form.password.label %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.captcha.id_for_label }}">{{ form.captcha.label }}</label>
|
||||
<div class="text-center">
|
||||
{{ form.captcha }}
|
||||
</div>
|
||||
</div>
|
||||
<a href="{% url 'registration_register' %}" class="btn">Register</a>
|
||||
<a href="{% url 'password_reset' %}" class="btn">Forgotten Password</a>
|
||||
<input type="submit" value="Login" class="btn btn-primary"/>
|
||||
|
||||
20
templates/registration/password_change_done.html
Normal file
20
templates/registration/password_change_done.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
||||
› {% trans 'Password reset' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{% trans 'Password change successful' %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h1>{% trans 'Password change successful' %}</h1>
|
||||
|
||||
<p>{% trans "Your password has been changed" %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,56 +1,33 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n static %}
|
||||
{% load url from future %}
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
|
||||
{% block userlinks %}{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %} {% trans 'Change password' %} / <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>{% endblock %}
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
||||
› {% trans 'Password change' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{% trans 'Password change' %}{% endblock %}
|
||||
|
||||
{% block content %}<div id="content-main">
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
<div>
|
||||
{% if form.errors %}
|
||||
<p class="errornote">
|
||||
{% blocktrans count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<h1>{% trans 'Password change' %}</h1>
|
||||
|
||||
<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
|
||||
<fieldset class="module aligned wide">
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.old_password.errors }}
|
||||
<label for="id_old_password" class="required">{% trans 'Old password' %}:</label>{{ form.old_password }}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.new_password1.errors }}
|
||||
<label for="id_new_password1" class="required">{% trans 'New password' %}:</label>{{ form.new_password1 }}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.new_password2.errors }}
|
||||
<label for="id_new_password2" class="required">{% trans 'Password (again)' %}:</label>{{ form.new_password2 }}
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<div class="submit-row">
|
||||
<input type="submit" value="{% trans 'Change my password' %}" class="default" />
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">document.getElementById("id_old_password").focus();</script>
|
||||
</div>
|
||||
</form></div>
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
|
||||
{% block title %}Change Password{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
<h3>Change Password</h3>
|
||||
{% if form.errors or supplement_form.errors %}
|
||||
<div class="alert alert-danger">
|
||||
Please correct the error(s) below.
|
||||
{{form.errors}}
|
||||
{{supplement_form.errors}}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p>Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly.</p>
|
||||
|
||||
<div class="col-sm-8 col-sm-offset-2">
|
||||
<form action="" method="post" class="form-horizontal" role="form">{% csrf_token %}
|
||||
{% for field in form %}
|
||||
<div class="form-group">
|
||||
<label for="{{ field.id_for_label }}" class="control-label col-sm-4">{{ field.label }}</label>
|
||||
<div class="controls col-sm-8">
|
||||
{% render_field field class+="form-control" placeholder=field.label %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<p><input type="submit" value="Change Password" class="btn btn-primary pull-right"></p>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
{% load url from future %}
|
||||
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% load i18n %}{% load url from future %}{% autoescape off %}
|
||||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}You're receiving this e-mail because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
{% render_field form.email type="email" class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="pull-right">
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Submit" class="btn btn-primary" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-10 col-md-8 col-md-offset-2">
|
||||
{{ form.captcha }}
|
||||
</div>
|
||||
<div class="col-sm-2 text-right">
|
||||
<input type="submit" value="Submit" class="btn btn-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import re
|
||||
from django.template import Library, Node, Variable, TemplateSyntaxError
|
||||
register = Library()
|
||||
|
||||
|
||||
def silence_without_field(fn):
|
||||
def wrapped(field, attr):
|
||||
if not field:
|
||||
return ""
|
||||
return fn(field, attr)
|
||||
return wrapped
|
||||
|
||||
|
||||
def _process_field_attributes(field, attr, process):
|
||||
|
||||
# split attribute name and value from 'attr:value' string
|
||||
params = attr.split(':', 1)
|
||||
attribute = params[0]
|
||||
value = params[1] if len(params) == 2 else ''
|
||||
|
||||
# decorate field.as_widget method with updated attributes
|
||||
old_as_widget = field.as_widget
|
||||
|
||||
def as_widget(self, widget=None, attrs=None, only_initial=False):
|
||||
attrs = attrs or {}
|
||||
process(widget or self.field.widget, attrs, attribute, value)
|
||||
return old_as_widget(widget, attrs, only_initial)
|
||||
|
||||
bound_method = type(old_as_widget)
|
||||
try:
|
||||
field.as_widget = bound_method(as_widget, field, field.__class__)
|
||||
except TypeError: # python 3
|
||||
field.as_widget = bound_method(as_widget, field)
|
||||
return field
|
||||
|
||||
|
||||
@register.filter("attr")
|
||||
@silence_without_field
|
||||
def set_attr(field, attr):
|
||||
|
||||
def process(widget, attrs, attribute, value):
|
||||
attrs[attribute] = value
|
||||
|
||||
return _process_field_attributes(field, attr, process)
|
||||
|
||||
|
||||
@register.filter("add_error_attr")
|
||||
@silence_without_field
|
||||
def add_error_attr(field, attr):
|
||||
if hasattr(field, 'errors') and field.errors:
|
||||
return set_attr(field, attr)
|
||||
return field
|
||||
|
||||
|
||||
@register.filter("append_attr")
|
||||
@silence_without_field
|
||||
def append_attr(field, attr):
|
||||
def process(widget, attrs, attribute, value):
|
||||
if attrs.get(attribute):
|
||||
attrs[attribute] += ' ' + value
|
||||
elif widget.attrs.get(attribute):
|
||||
attrs[attribute] = widget.attrs[attribute] + ' ' + value
|
||||
else:
|
||||
attrs[attribute] = value
|
||||
return _process_field_attributes(field, attr, process)
|
||||
|
||||
|
||||
@register.filter("add_class")
|
||||
@silence_without_field
|
||||
def add_class(field, css_class):
|
||||
return append_attr(field, 'class:' + css_class)
|
||||
|
||||
|
||||
@register.filter("add_error_class")
|
||||
@silence_without_field
|
||||
def add_error_class(field, css_class):
|
||||
if hasattr(field, 'errors') and field.errors:
|
||||
return add_class(field, css_class)
|
||||
return field
|
||||
|
||||
|
||||
@register.filter("set_data")
|
||||
@silence_without_field
|
||||
def set_data(field, data):
|
||||
return set_attr(field, 'data-' + data)
|
||||
|
||||
|
||||
@register.filter(name='field_type')
|
||||
def field_type(field):
|
||||
"""
|
||||
Template filter that returns field class name (in lower case).
|
||||
E.g. if field is CharField then {{ field|field_type }} will
|
||||
return 'charfield'.
|
||||
"""
|
||||
if hasattr(field, 'field') and field.field:
|
||||
return field.field.__class__.__name__.lower()
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter(name='widget_type')
|
||||
def widget_type(field):
|
||||
"""
|
||||
Template filter that returns field widget class name (in lower case).
|
||||
E.g. if field's widget is TextInput then {{ field|widget_type }} will
|
||||
return 'textinput'.
|
||||
"""
|
||||
if hasattr(field, 'field') and hasattr(field.field, 'widget') and field.field.widget:
|
||||
return field.field.widget.__class__.__name__.lower()
|
||||
return ''
|
||||
|
||||
|
||||
# ======================== render_field tag ==============================
|
||||
|
||||
ATTRIBUTE_RE = re.compile(r"""
|
||||
(?P<attr>
|
||||
[\w_-]+
|
||||
)
|
||||
(?P<sign>
|
||||
\+?=
|
||||
)
|
||||
(?P<value>
|
||||
['"]? # start quote
|
||||
[^"']*
|
||||
['"]? # end quote
|
||||
)
|
||||
""", re.VERBOSE | re.UNICODE)
|
||||
|
||||
@register.tag
|
||||
def render_field(parser, token):
|
||||
"""
|
||||
Render a form field using given attribute-value pairs
|
||||
|
||||
Takes form field as first argument and list of attribute-value pairs for
|
||||
all other arguments. Attribute-value pairs should be in the form of
|
||||
attribute=value or attribute="a value" for assignment and attribute+=value
|
||||
or attribute+="value" for appending.
|
||||
"""
|
||||
error_msg = '%r tag requires a form field followed by a list of attributes and values in the form attr="value"' % token.split_contents()[0]
|
||||
try:
|
||||
bits = token.split_contents()
|
||||
tag_name = bits[0]
|
||||
form_field = bits[1]
|
||||
attr_list = bits[2:]
|
||||
except ValueError:
|
||||
raise TemplateSyntaxError(error_msg)
|
||||
|
||||
form_field = parser.compile_filter(form_field)
|
||||
|
||||
set_attrs = []
|
||||
append_attrs = []
|
||||
for pair in attr_list:
|
||||
match = ATTRIBUTE_RE.match(pair)
|
||||
if not match:
|
||||
raise TemplateSyntaxError(error_msg + ": %s" % pair)
|
||||
dct = match.groupdict()
|
||||
attr, sign, value = dct['attr'], dct['sign'], parser.compile_filter(dct['value'])
|
||||
if sign == "=":
|
||||
set_attrs.append((attr, value))
|
||||
else:
|
||||
append_attrs.append((attr, value))
|
||||
|
||||
return FieldAttributeNode(form_field, set_attrs, append_attrs)
|
||||
|
||||
|
||||
class FieldAttributeNode(Node):
|
||||
def __init__(self, field, set_attrs, append_attrs):
|
||||
self.field = field
|
||||
self.set_attrs = set_attrs
|
||||
self.append_attrs = append_attrs
|
||||
|
||||
def render(self, context):
|
||||
bounded_field = self.field.resolve(context)
|
||||
field = getattr(bounded_field, 'field', None)
|
||||
if (getattr(bounded_field, 'errors', None) and
|
||||
'WIDGET_ERROR_CLASS' in context):
|
||||
bounded_field = append_attr(bounded_field, 'class:%s' %
|
||||
context['WIDGET_ERROR_CLASS'])
|
||||
if field and field.required and 'WIDGET_REQUIRED_CLASS' in context:
|
||||
bounded_field = append_attr(bounded_field, 'class:%s' %
|
||||
context['WIDGET_REQUIRED_CLASS'])
|
||||
for k, v in self.set_attrs:
|
||||
bounded_field = set_attr(bounded_field, '%s:%s' % (k,v.resolve(context)))
|
||||
for k, v in self.append_attrs:
|
||||
bounded_field = append_attr(bounded_field, '%s:%s' % (k,v.resolve(context)))
|
||||
return bounded_field
|
||||
@@ -1,333 +0,0 @@
|
||||
import string
|
||||
try:
|
||||
from django.utils.unittest import expectedFailure
|
||||
except ImportError:
|
||||
def expectedFailure(func):
|
||||
return lambda *args, **kwargs: None
|
||||
|
||||
from django.test import TestCase
|
||||
from django.forms import Form, CharField, TextInput
|
||||
from django import forms
|
||||
from django.template import Template, Context
|
||||
from django.forms.extras.widgets import SelectDateWidget
|
||||
|
||||
# ==============================
|
||||
# Testing helpers
|
||||
# ==============================
|
||||
|
||||
class MyForm(Form):
|
||||
"""
|
||||
Test form. If you want to test rendering of a field,
|
||||
add it to this form and use one of 'render_...' functions
|
||||
from this module.
|
||||
"""
|
||||
simple = CharField()
|
||||
with_attrs = CharField(widget=TextInput(attrs={
|
||||
'foo': 'baz',
|
||||
'egg': 'spam'
|
||||
}))
|
||||
with_cls = CharField(widget=TextInput(attrs={'class':'class0'}))
|
||||
date = forms.DateField(widget=SelectDateWidget(attrs={'egg': 'spam'}))
|
||||
|
||||
|
||||
def render_form(text, form=None, **context_args):
|
||||
"""
|
||||
Renders template ``text`` with widget_tweaks library loaded
|
||||
and MyForm instance available in context as ``form``.
|
||||
"""
|
||||
tpl = Template("{% load widget_tweaks %}" + text)
|
||||
context_args.update({'form': MyForm() if form is None else form})
|
||||
context = Context(context_args)
|
||||
return tpl.render(context)
|
||||
|
||||
|
||||
def render_field(field, template_filter, params, *args, **kwargs):
|
||||
"""
|
||||
Renders ``field`` of MyForm with filter ``template_filter`` applied.
|
||||
``params`` are filter arguments.
|
||||
|
||||
If you want to apply several filters (in a chain),
|
||||
pass extra ``template_filter`` and ``params`` as positional arguments.
|
||||
|
||||
In order to use custom form, pass form instance as ``form``
|
||||
keyword argument.
|
||||
"""
|
||||
filters = [(template_filter, params)]
|
||||
filters.extend(zip(args[::2], args[1::2]))
|
||||
filter_strings = ['|%s:"%s"' % (f[0], f[1],) for f in filters]
|
||||
render_field_str = '{{ form.%s%s }}' % (field, ''.join(filter_strings))
|
||||
return render_form(render_field_str, **kwargs)
|
||||
|
||||
|
||||
def render_field_from_tag(field, *attributes):
|
||||
"""
|
||||
Renders MyForm's field ``field`` with attributes passed
|
||||
as positional arguments.
|
||||
"""
|
||||
attr_strings = [' %s' % f for f in attributes]
|
||||
tpl = string.Template('{% render_field form.$field$attrs %}')
|
||||
render_field_str = tpl.substitute(field=field, attrs=''.join(attr_strings))
|
||||
return render_form(render_field_str)
|
||||
|
||||
|
||||
def assertIn(value, obj):
|
||||
assert value in obj, "%s not in %s" % (value, obj,)
|
||||
|
||||
|
||||
def assertNotIn(value, obj):
|
||||
assert value not in obj, "%s in %s" % (value, obj,)
|
||||
|
||||
|
||||
# ===============================
|
||||
# Test cases
|
||||
# ===============================
|
||||
|
||||
class SimpleAttrTest(TestCase):
|
||||
def test_attr(self):
|
||||
res = render_field('simple', 'attr', 'foo:bar')
|
||||
assertIn('type="text"', res)
|
||||
assertIn('name="simple"', res)
|
||||
assertIn('id="id_simple"', res)
|
||||
assertIn('foo="bar"', res)
|
||||
|
||||
def test_attr_chaining(self):
|
||||
res = render_field('simple', 'attr', 'foo:bar', 'attr', 'bar:baz')
|
||||
assertIn('type="text"', res)
|
||||
assertIn('name="simple"', res)
|
||||
assertIn('id="id_simple"', res)
|
||||
assertIn('foo="bar"', res)
|
||||
assertIn('bar="baz"', res)
|
||||
|
||||
def test_add_class(self):
|
||||
res = render_field('simple', 'add_class', 'foo')
|
||||
assertIn('class="foo"', res)
|
||||
|
||||
def test_add_multiple_classes(self):
|
||||
res = render_field('simple', 'add_class', 'foo bar')
|
||||
assertIn('class="foo bar"', res)
|
||||
|
||||
def test_add_class_chaining(self):
|
||||
res = render_field('simple', 'add_class', 'foo', 'add_class', 'bar')
|
||||
assertIn('class="bar foo"', res)
|
||||
|
||||
def test_set_data(self):
|
||||
res = render_field('simple', 'set_data', 'key:value')
|
||||
assertIn('data-key="value"', res)
|
||||
|
||||
|
||||
class ErrorsTest(TestCase):
|
||||
|
||||
def _err_form(self):
|
||||
form = MyForm({'foo': 'bar'}) # some random data
|
||||
form.is_valid() # trigger form validation
|
||||
return form
|
||||
|
||||
def test_error_class_no_error(self):
|
||||
res = render_field('simple', 'add_error_class', 'err')
|
||||
assertNotIn('class="err"', res)
|
||||
|
||||
def test_error_class_error(self):
|
||||
form = self._err_form()
|
||||
res = render_field('simple', 'add_error_class', 'err', form=form)
|
||||
assertIn('class="err"', res)
|
||||
|
||||
def test_error_attr_no_error(self):
|
||||
res = render_field('simple', 'add_error_attr', 'aria-invalid:true')
|
||||
assertNotIn('aria-invalid="true"', res)
|
||||
|
||||
def test_error_attr_error(self):
|
||||
form = self._err_form()
|
||||
res = render_field('simple', 'add_error_attr', 'aria-invalid:true', form=form)
|
||||
assertIn('aria-invalid="true"', res)
|
||||
|
||||
|
||||
class SilenceTest(TestCase):
|
||||
def test_silence_without_field(self):
|
||||
res = render_field("nothing", 'attr', 'foo:bar')
|
||||
self.assertEqual(res, "")
|
||||
res = render_field("nothing", 'add_class', 'some')
|
||||
self.assertEqual(res, "")
|
||||
|
||||
|
||||
class CustomizedWidgetTest(TestCase):
|
||||
def test_attr(self):
|
||||
res = render_field('with_attrs', 'attr', 'foo:bar')
|
||||
assertIn('foo="bar"', res)
|
||||
assertNotIn('foo="baz"', res)
|
||||
assertIn('egg="spam"', res)
|
||||
|
||||
# see https://code.djangoproject.com/ticket/16754
|
||||
@expectedFailure
|
||||
def test_selectdatewidget(self):
|
||||
res = render_field('date', 'attr', 'foo:bar')
|
||||
assertIn('egg="spam"', res)
|
||||
assertIn('foo="bar"', res)
|
||||
|
||||
def test_attr_chaining(self):
|
||||
res = render_field('with_attrs', 'attr', 'foo:bar', 'attr', 'bar:baz')
|
||||
assertIn('foo="bar"', res)
|
||||
assertNotIn('foo="baz"', res)
|
||||
assertIn('egg="spam"', res)
|
||||
assertIn('bar="baz"', res)
|
||||
|
||||
def test_attr_class(self):
|
||||
res = render_field('with_cls', 'attr', 'foo:bar')
|
||||
assertIn('foo="bar"', res)
|
||||
assertIn('class="class0"', res)
|
||||
|
||||
def test_default_attr(self):
|
||||
res = render_field('with_cls', 'attr', 'type:search')
|
||||
assertIn('class="class0"', res)
|
||||
assertIn('type="search"', res)
|
||||
|
||||
def test_add_class(self):
|
||||
res = render_field('with_cls', 'add_class', 'class1')
|
||||
assertIn('class0', res)
|
||||
assertIn('class1', res)
|
||||
|
||||
def test_add_class_chaining(self):
|
||||
res = render_field('with_cls', 'add_class', 'class1', 'add_class', 'class2')
|
||||
assertIn('class0', res)
|
||||
assertIn('class1', res)
|
||||
assertIn('class2', res)
|
||||
|
||||
|
||||
class FieldReuseTest(TestCase):
|
||||
|
||||
def test_field_double_rendering_simple(self):
|
||||
res = render_form('{{ form.simple }}{{ form.simple|attr:"foo:bar" }}{{ form.simple }}')
|
||||
self.assertEqual(res.count("bar"), 1)
|
||||
|
||||
def test_field_double_rendering_simple_css(self):
|
||||
res = render_form('{{ form.simple }}{{ form.simple|add_class:"bar" }}{{ form.simple|add_class:"baz" }}')
|
||||
self.assertEqual(res.count("baz"), 1)
|
||||
self.assertEqual(res.count("bar"), 1)
|
||||
|
||||
def test_field_double_rendering_attrs(self):
|
||||
res = render_form('{{ form.with_cls }}{{ form.with_cls|add_class:"bar" }}{{ form.with_cls }}')
|
||||
self.assertEqual(res.count("class0"), 3)
|
||||
self.assertEqual(res.count("bar"), 1)
|
||||
|
||||
|
||||
class SimpleRenderFieldTagTest(TestCase):
|
||||
def test_attr(self):
|
||||
res = render_field_from_tag('simple', 'foo="bar"')
|
||||
assertIn('type="text"', res)
|
||||
assertIn('name="simple"', res)
|
||||
assertIn('id="id_simple"', res)
|
||||
assertIn('foo="bar"', res)
|
||||
|
||||
def test_multiple_attrs(self):
|
||||
res = render_field_from_tag('simple', 'foo="bar"', 'bar="baz"')
|
||||
assertIn('type="text"', res)
|
||||
assertIn('name="simple"', res)
|
||||
assertIn('id="id_simple"', res)
|
||||
assertIn('foo="bar"', res)
|
||||
assertIn('bar="baz"', res)
|
||||
|
||||
|
||||
class RenderFieldTagSilenceTest(TestCase):
|
||||
def test_silence_without_field(self):
|
||||
res = render_field_from_tag("nothing", 'foo="bar"')
|
||||
self.assertEqual(res, "")
|
||||
res = render_field_from_tag("nothing", 'class+="some"')
|
||||
self.assertEqual(res, "")
|
||||
|
||||
|
||||
class RenderFieldTagCustomizedWidgetTest(TestCase):
|
||||
def test_attr(self):
|
||||
res = render_field_from_tag('with_attrs', 'foo="bar"')
|
||||
assertIn('foo="bar"', res)
|
||||
assertNotIn('foo="baz"', res)
|
||||
assertIn('egg="spam"', res)
|
||||
|
||||
# see https://code.djangoproject.com/ticket/16754
|
||||
@expectedFailure
|
||||
def test_selectdatewidget(self):
|
||||
res = render_field_from_tag('date', 'foo="bar"')
|
||||
assertIn('egg="spam"', res)
|
||||
assertIn('foo="bar"', res)
|
||||
|
||||
def test_multiple_attrs(self):
|
||||
res = render_field_from_tag('with_attrs', 'foo="bar"', 'bar="baz"')
|
||||
assertIn('foo="bar"', res)
|
||||
assertNotIn('foo="baz"', res)
|
||||
assertIn('egg="spam"', res)
|
||||
assertIn('bar="baz"', res)
|
||||
|
||||
def test_attr_class(self):
|
||||
res = render_field_from_tag('with_cls', 'foo="bar"')
|
||||
assertIn('foo="bar"', res)
|
||||
assertIn('class="class0"', res)
|
||||
|
||||
def test_default_attr(self):
|
||||
res = render_field_from_tag('with_cls', 'type="search"')
|
||||
assertIn('class="class0"', res)
|
||||
assertIn('type="search"', res)
|
||||
|
||||
def test_append_attr(self):
|
||||
res = render_field_from_tag('with_cls', 'class+="class1"')
|
||||
assertIn('class0', res)
|
||||
assertIn('class1', res)
|
||||
|
||||
def test_duplicate_append_attr(self):
|
||||
res = render_field_from_tag('with_cls', 'class+="class1"', 'class+="class2"')
|
||||
assertIn('class0', res)
|
||||
assertIn('class1', res)
|
||||
assertIn('class2', res)
|
||||
|
||||
def test_hyphenated_attributes(self):
|
||||
res = render_field_from_tag('with_cls', 'data-foo="bar"')
|
||||
assertIn('data-foo="bar"', res)
|
||||
assertIn('class="class0"', res)
|
||||
|
||||
|
||||
class RenderFieldWidgetClassesTest(TestCase):
|
||||
def test_use_widget_required_class(self):
|
||||
res = render_form('{% render_field form.simple %}',
|
||||
WIDGET_REQUIRED_CLASS='required_class')
|
||||
self.assertIn('class="required_class"', res)
|
||||
|
||||
def test_use_widget_error_class(self):
|
||||
res = render_form('{% render_field form.simple %}', form=MyForm({}),
|
||||
WIDGET_ERROR_CLASS='error_class')
|
||||
self.assertIn('class="error_class"', res)
|
||||
|
||||
def test_use_widget_error_class_with_other_classes(self):
|
||||
res = render_form('{% render_field form.simple class="blue" %}',
|
||||
form=MyForm({}), WIDGET_ERROR_CLASS='error_class')
|
||||
self.assertIn('class="blue error_class"', res)
|
||||
|
||||
def test_use_widget_required_class_with_other_classes(self):
|
||||
res = render_form('{% render_field form.simple class="blue" %}',
|
||||
form=MyForm({}), WIDGET_REQUIRED_CLASS='required_class')
|
||||
self.assertIn('class="blue required_class"', res)
|
||||
|
||||
|
||||
class RenderFieldTagFieldReuseTest(TestCase):
|
||||
def test_field_double_rendering_simple(self):
|
||||
res = render_form('{{ form.simple }}{% render_field form.simple foo="bar" %}{% render_field form.simple %}')
|
||||
self.assertEqual(res.count("bar"), 1)
|
||||
|
||||
def test_field_double_rendering_simple_css(self):
|
||||
res = render_form('{% render_field form.simple %}{% render_field form.simple class+="bar" %}{% render_field form.simple class+="baz" %}')
|
||||
self.assertEqual(res.count("baz"), 1)
|
||||
self.assertEqual(res.count("bar"), 1)
|
||||
|
||||
def test_field_double_rendering_attrs(self):
|
||||
res = render_form('{% render_field form.with_cls %}{% render_field form.with_cls class+="bar" %}{% render_field form.with_cls %}')
|
||||
self.assertEqual(res.count("class0"), 3)
|
||||
self.assertEqual(res.count("bar"), 1)
|
||||
|
||||
|
||||
class RenderFieldTagUseTemplateVariableTest(TestCase):
|
||||
def test_use_template_variable_in_parametrs(self):
|
||||
res = render_form('{% render_field form.with_attrs egg+="pahaz" placeholder=form.with_attrs.label %}')
|
||||
assertIn('egg="spam pahaz"', res)
|
||||
assertIn('placeholder="With attrs"', res)
|
||||
|
||||
|
||||
class RenderFieldFilter_field_type_widget_type_Test(TestCase):
|
||||
def test_field_type_widget_type_rendering_simple(self):
|
||||
res = render_form('<div class="{{ form.simple|field_type }} {{ form.simple|widget_type }} {{ form.simple.html_name }}">{{ form.simple }}</div>')
|
||||
assertIn('class="charfield textinput simple"', res)
|
||||
Reference in New Issue
Block a user