Compare commits

..

3 Commits

Author SHA1 Message Date
Tom Price
a3cdef8570 Tidy up the querying and move into model
This should really be in the model not in the view
2017-05-08 23:49:33 +01:00
Tom Price
7ec1c726a6 Optimise down to just the one SQL query for waiting invoices.
Cuts down on all the unnecessary queries and selects everything in one go.
Down from ~6 queries per invoice
2017-03-01 02:43:53 +00:00
Tom Price
4773b43081 Refactor outstanding invoice query to use django annotations.
Considerably improves performance and also removes the explict PSQL code.

Can be further improved by reducing the number of hits to the db in the template.
2017-03-01 02:05:06 +00:00
125 changed files with 2011 additions and 5019 deletions

1
.gitignore vendored
View File

@@ -53,7 +53,6 @@ coverage.xml
# Django stuff: # Django stuff:
*.log *.log
db.sqlite3
# Sphinx documentation # Sphinx documentation
docs/_build/ docs/_build/

View File

@@ -1,32 +1,21 @@
language: python language: python
python: python:
"3.6" "2.7"
cache: pip
addons: before_install:
chrome: stable - "export DISPLAY=:99.0"
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
install: install:
- wget https://chromedriver.storage.googleapis.com/2.36/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- export PATH=$PATH:$(pwd)
- chmod +x chromedriver
- pip install -r requirements.txt - pip install -r requirements.txt
- pip install coveralls codeclimate-test-reporter pep8 - pip install coveralls codeclimate-test-reporter
before_script: before_script:
- export PATH=$PATH:/usr/lib/chromium-browser/
- python manage.py collectstatic --noinput - python manage.py collectstatic --noinput
script: script:
- pep8 . --exclude=migrations,importer* - coverage run manage.py test RIGS
- python manage.py check
- python manage.py makemigrations --check --dry-run
- coverage run manage.py test --verbosity=2
after_success: after_success:
- coveralls - coveralls
- codeclimate-test-reporter - codeclimate-test-reporter
notifications:
webhooks: https://fathomless-fjord-24024.herokuapp.com/notify

View File

@@ -1,12 +0,0 @@
FROM python:3.6
WORKDIR /app
ADD . /app
RUN pip install -r requirements.txt && \
python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

View File

@@ -1,7 +1,8 @@
from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import render from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse from django.core.urlresolvers import reverse
from RIGS import models from RIGS import models
@@ -23,17 +24,17 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
def _checklogin(request, *args, **kwargs): def _checklogin(request, *args, **kwargs):
if test_func(request.user): if test_func(request.user):
return view_func(request, *args, **kwargs) return view_func(request, *args, **kwargs)
elif not request.user.is_authenticated: elif not request.user.is_authenticated():
if oembed_view is not None: if oembed_view is not None:
context = {} extra_context = {}
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs)) extra_context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))
context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) extra_context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path())
resp = render(request, 'login_redirect.html', context=context) resp = render_to_response('login_redirect.html', extra_context, context_instance=RequestContext(request))
return resp return resp
else: else:
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
else: else:
resp = render(request, '403.html') resp = render_to_response('403.html', context_instance=RequestContext(request))
resp.status_code = 403 resp.status_code = 403
return resp return resp
_checklogin.__doc__ = view_func.__doc__ _checklogin.__doc__ = view_func.__doc__
@@ -61,7 +62,7 @@ def api_key_required(function):
userid = kwargs.get('api_pk') userid = kwargs.get('api_pk')
key = kwargs.get('api_key') key = kwargs.get('api_key')
error_resp = render(request, '403.html') error_resp = render_to_response('403.html', context_instance=RequestContext(request))
error_resp.status_code = 403 error_resp.status_code = 403
if key is None: if key is None:
@@ -78,17 +79,3 @@ def api_key_required(function):
return error_resp return error_resp
return function(request, *args, **kwargs) return function(request, *args, **kwargs)
return wrap return wrap
def nottinghamtec_address_required(function):
"""
Checks that the current user has an email address ending @nottinghamtec.co.uk
"""
def wrap(request, *args, **kwargs):
# Fail if current user's email address isn't @nottinghamtec.co.uk
if not request.user.email.endswith('@nottinghamtec.co.uk'):
error_resp = render(request, 'RIGS/eventauthorisation_request_error.html')
return error_resp
return function(request, *args, **kwargs)
return wrap

View File

@@ -1,5 +1,5 @@
from __future__ import unicode_literals
DATETIME_FORMAT = ('d/m/Y H:i') DATETIME_FORMAT = ('d/m/Y H:i')
DATE_FORMAT = ('d/m/Y') DATE_FORMAT = ('d/m/Y')
TIME_FORMAT = ('H:i') TIME_FORMAT = ('H:i')

View File

@@ -10,37 +10,29 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
import raven
import secrets
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get( 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'
'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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True
STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com'] ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
if STAGING: if STAGING:
ALLOWED_HOSTS.append('.herokuapp.com') ALLOWED_HOSTS.append('.herokuapp.com')
if DEBUG:
ALLOWED_HOSTS.append('localhost')
ALLOWED_HOSTS.append('example.com')
ALLOWED_HOSTS.append('127.0.0.1')
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
if not DEBUG: if not DEBUG:
SECURE_SSL_REDIRECT = True # Redirect all http requests to https SECURE_SSL_REDIRECT = True # Redirect all http requests to https
INTERNAL_IPS = ['127.0.0.1'] INTERNAL_IPS = ['127.0.0.1']
@@ -48,6 +40,7 @@ ADMINS = (
('Tom Price', 'tomtom5152@gmail.com') ('Tom Price', 'tomtom5152@gmail.com')
) )
# Application definition # Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
@@ -67,15 +60,15 @@ INSTALLED_APPS = (
'raven.contrib.django.raven_compat', 'raven.contrib.django.raven_compat',
) )
MIDDLEWARE = ( MIDDLEWARE_CLASSES = (
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', 'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'reversion.middleware.RevisionMiddleware', 'reversion.middleware.RevisionMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
) )
@@ -84,6 +77,7 @@ ROOT_URLCONF = 'PyRIGS.urls'
WSGI_APPLICATION = 'PyRIGS.wsgi.application' WSGI_APPLICATION = 'PyRIGS.wsgi.application'
# Database # Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases # https://docs.djangoproject.com/en/1.7/ref/settings/#databases
DATABASES = { DATABASES = {
@@ -95,10 +89,9 @@ DATABASES = {
if not DEBUG: if not DEBUG:
import dj_database_url import dj_database_url
DATABASES['default'] = dj_database_url.config() DATABASES['default'] = dj_database_url.config()
# Logging # Logging
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,
@@ -126,12 +119,12 @@ LOGGING = {
'mail_admins': { 'mail_admins': {
'class': 'django.utils.log.AdminEmailHandler', 'class': 'django.utils.log.AdminEmailHandler',
'level': 'ERROR', 'level': 'ERROR',
# But the emails are plain text by default - HTML is nicer # But the emails are plain text by default - HTML is nicer
'include_html': True, 'include_html': True,
}, },
}, },
'loggers': { 'loggers': {
# Again, default Django configuration to email unhandled exceptions # Again, default Django configuration to email unhandled exceptions
'django.request': { 'django.request': {
'handlers': ['mail_admins'], 'handlers': ['mail_admins'],
'level': 'ERROR', 'level': 'ERROR',
@@ -146,6 +139,8 @@ LOGGING = {
} }
} }
import raven
RAVEN_CONFIG = { RAVEN_CONFIG = {
'dsn': os.environ.get('RAVEN_DSN'), 'dsn': os.environ.get('RAVEN_DSN'),
# If you are using git, you can also automatically configure the # If you are using git, you can also automatically configure the
@@ -157,14 +152,14 @@ RAVEN_CONFIG = {
AUTH_USER_MODEL = 'RIGS.Profile' AUTH_USER_MODEL = 'RIGS.Profile'
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/user/login/' LOGIN_URL = '/user/login'
LOGOUT_URL = '/user/logout/' LOGOUT_URL = '/user/logout'
ACCOUNT_ACTIVATION_DAYS = 7 ACCOUNT_ACTIVATION_DAYS = 7
# reCAPTCHA settings # reCAPTCHA settings
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY', "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY', None)
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY', "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY', None)
NOCAPTCHA = True NOCAPTCHA = True
# Email # Email
@@ -172,7 +167,7 @@ EMAILER_TEST = False
if not DEBUG or EMAILER_TEST: if not DEBUG or EMAILER_TEST:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST') EMAIL_HOST = os.environ.get('EMAIL_HOST')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25)) EMAIL_PORT = int(os.environ.get('EMAIL_PORT'))
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = bool(int(os.environ.get('EMAIL_USE_TLS', 0))) EMAIL_USE_TLS = bool(int(os.environ.get('EMAIL_USE_TLS', 0)))
@@ -196,7 +191,19 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S') DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M','%Y-%m-%dT%H:%M:%S')
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.core.context_processors.request",
"django.contrib.messages.context_processors.messages",
)
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/ # https://docs.djangoproject.com/en/1.7/howto/static-files/
@@ -207,34 +214,10 @@ STATIC_DIRS = (
os.path.join(BASE_DIR, 'static/') os.path.join(BASE_DIR, 'static/')
) )
TEMPLATES = [ TEMPLATE_DIRS = (
{ os.path.join(BASE_DIR, 'templates'),
'BACKEND': 'django.template.backends.django.DjangoTemplates', )
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.template.context_processors.request",
"django.contrib.messages.context_processors.messages",
],
'debug': DEBUG
},
},
]
USE_GRAVATAR = True USE_GRAVATAR=True
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf" TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
RISK_ASSESSMENT_URL = os.environ.get('RISK_ASSESSMENT_URL') if os.environ.get(
'RISK_ASSESSMENT_URL') else "http://example.com"
RISK_ASSESSMENT_SECRET = os.environ.get('RISK_ASSESSMENT_SECRET') if os.environ.get(
'RISK_ASSESSMENT_SECRET') else secrets.token_hex(15)

View File

@@ -1,4 +1,4 @@
from django.conf.urls import include, url from django.conf.urls import patterns, include, url
from django.contrib import admin from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.conf import settings from django.conf import settings
@@ -6,24 +6,19 @@ from registration.backends.default.views import RegistrationView
import RIGS import RIGS
from RIGS import regbackend from RIGS import regbackend
urlpatterns = [ urlpatterns = patterns('',
# Examples: # Examples:
# url(r'^$', 'PyRIGS.views.home', name='home'), # url(r'^$', 'PyRIGS.views.home', name='home'),
# url(r'^blog/', include('blog.urls')), # url(r'^blog/', include('blog.urls')),
url(r'^', include('RIGS.urls')), url(r'^', include('RIGS.urls')),
url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail), 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('django.contrib.auth.urls')),
url('^user/', include('registration.backends.default.urls')), url('^user/', include('registration.backends.default.urls')),
url(r'^admin/', admin.site.urls), url(r'^admin/', include(admin.site.urls)),
] )
if settings.DEBUG: if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns() urlpatterns += staticfiles_urlpatterns()
import debug_toolbar
urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)),
] + urlpatterns

View File

@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
import os import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "PyRIGS.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "PyRIGS.settings")
from django.core.wsgi import get_wsgi_application # noqa from django.core.wsgi import get_wsgi_application
from dj_static import Cling # noqa from dj_static import Cling
application = Cling(get_wsgi_application()) application = Cling(get_wsgi_application())

View File

@@ -1,8 +1,6 @@
# TEC PA & Lighting - PyRIGS # # TEC PA & Lighting - PyRIGS #
[![Build Status](https://travis-ci.org/nottinghamtec/PyRIGS.svg?branch=develop)](https://travis-ci.org/nottinghamtec/PyRIGS) [![Build Status](https://travis-ci.org/nottinghamtec/PyRIGS.svg?branch=develop)](https://travis-ci.org/nottinghamtec/PyRIGS)
[![Coverage Status](https://coveralls.io/repos/github/nottinghamtec/PyRIGS/badge.svg?branch=develop)](https://coveralls.io/github/nottinghamtec/PyRIGS?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/nottinghamtec/PyRIGS/badge.svg?branch=develop)](https://coveralls.io/github/nottinghamtec/PyRIGS?branch=develop)
[![Dependency Status](https://gemnasium.com/badges/github.com/nottinghamtec/PyRIGS.svg)](https://gemnasium.com/github.com/nottinghamtec/PyRIGS)
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. 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.
@@ -77,13 +75,6 @@ python manage.py runserver
``` ```
Please refer to Django documentation for a full list of options available here. Please refer to Django documentation for a full list of options available here.
### Development using docker
```
docker build . -t pyrigs
docker run -it --rm -p=8000:8000 -v $(pwd):/app pyrigs
```
### Sample Data ### ### Sample Data ###
Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty: Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty:
``` ```
@@ -102,18 +93,5 @@ python manage.py generateSampleData
|keyholder|keyholder| |keyholder|keyholder|
|basic |basic | |basic |basic |
### Testing ###
Tests are contained in 3 files. `RIGS/test_models.py` contains tests for logic within the data models. `RIGS/test_unit.py` contains "Live server" tests, using raw web requests. `RIGS/test_integration.py` contains user interface tests which take control of a web browser. For automated Travis tests, we use [Sauce Labs](https://saucelabs.com). When debugging locally, ensure that you have the latest version of Google Chrome installed, then install [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) and ensure it is on the `PATH`.
You can run the entire test suite, or you can run specific sections individually. For example, in order of specificity:
```
python manage.py test
python manage.py test RIGS.test_models
python manage.py test RIGS.test_models.EventTestCase
python manage.py test RIGS.test_models.EventTestCase.test_current_events
```
### Committing, pushing and testing ### ### 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 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. 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.

View File

@@ -1 +0,0 @@
default_app_config = 'RIGS.apps.RIGSAppConfig'

View File

@@ -2,7 +2,7 @@ from django.contrib import admin
from RIGS import models, forms from RIGS import models, forms
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from reversion.admin import VersionAdmin import reversion
from django.contrib.admin import helpers from django.contrib.admin import helpers
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
@@ -12,12 +12,10 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count from django.db.models import Count
from django.forms import ModelForm from django.forms import ModelForm
from reversion import revisions as reversion
# Register your models here. # Register your models here.
admin.site.register(models.VatRate, VersionAdmin) admin.site.register(models.VatRate, reversion.VersionAdmin)
admin.site.register(models.Event, VersionAdmin) admin.site.register(models.Event, reversion.VersionAdmin)
admin.site.register(models.EventItem, VersionAdmin) admin.site.register(models.EventItem, reversion.VersionAdmin)
admin.site.register(models.Invoice) admin.site.register(models.Invoice)
admin.site.register(models.Payment) admin.site.register(models.Payment)
@@ -43,7 +41,7 @@ class ProfileAdmin(UserAdmin):
add_form = forms.ProfileCreationForm add_form = forms.ProfileCreationForm
class AssociateAdmin(VersionAdmin): class AssociateAdmin(reversion.VersionAdmin):
list_display = ('id', 'name', 'number_of_events') list_display = ('id', 'name', 'number_of_events')
search_fields = ['id', 'name'] search_fields = ['id', 'name']
list_display_links = ['id', 'name'] list_display_links = ['id', 'name']
@@ -95,7 +93,8 @@ class AssociateAdmin(VersionAdmin):
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
'forms': forms 'forms': forms
} }
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context) return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context,
current_app=self.admin_site.name)
@admin.register(models.Person) @admin.register(models.Person)

View File

@@ -1,8 +0,0 @@
from django.apps import AppConfig
class RIGSAppConfig(AppConfig):
name = 'RIGS'
def ready(self):
import RIGS.signals

View File

@@ -1,22 +1,21 @@
import cStringIO as StringIO
import datetime import datetime
import re import re
from decimal import Decimal
from django.contrib import messages from django.contrib import messages
from django.urls import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.db.models import Count, Sum, F, FloatField, Q, Value
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import get_template from django.template.loader import get_template
from django.views import generic from django.views import generic
from django.db.models import Q
from z3c.rml import rml2pdf from z3c.rml import rml2pdf
from RIGS import models from RIGS import models
from django import forms
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
class InvoiceIndex(generic.ListView): class InvoiceIndex(generic.ListView):
model = models.Invoice model = models.Invoice
@@ -32,19 +31,8 @@ class InvoiceIndex(generic.ListView):
return context return context
def get_queryset(self): def get_queryset(self):
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must query = self.model.objects.outstanding().select_related('event', 'event__organisation', 'event__person',
sql = "SELECT * FROM " \ 'event__venue', 'event__mic')
"(SELECT " \
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
"AS sub " \
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
"ORDER BY invoice_date"
query = self.model.objects.raw(sql)
return query return query
@@ -57,8 +45,8 @@ class InvoicePrint(generic.View):
invoice = get_object_or_404(models.Invoice, pk=pk) invoice = get_object_or_404(models.Invoice, pk=pk)
object = invoice.event object = invoice.event
template = get_template('RIGS/event_print.xml') template = get_template('RIGS/event_print.xml')
copies = ('TEC', 'Client')
context = { context = RequestContext(request, {
'object': object, 'object': object,
'fonts': { 'fonts': {
'opensans': { 'opensans': {
@@ -68,9 +56,10 @@ class InvoicePrint(generic.View):
}, },
'invoice': invoice, 'invoice': invoice,
'current_user': request.user, 'current_user': request.user,
} })
rml = template.render(context) rml = template.render(context)
buffer = StringIO.StringIO()
buffer = rml2pdf.parseString(rml) buffer = rml2pdf.parseString(rml)
@@ -79,7 +68,7 @@ class InvoicePrint(generic.View):
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name) escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name)
response = HttpResponse(content_type='application/pdf') response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = "filename=Invoice %05d - N%05d | %s.pdf" % (invoice.pk, invoice.event.pk, escapedEventName) response['Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, escapedEventName)
response.write(pdfData) response.write(pdfData)
return response return response
@@ -146,8 +135,8 @@ class InvoiceWaiting(generic.ListView):
( (
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
Q(end_date__lte=datetime.date.today()) # Has end date, finishes before Q(end_date__lte=datetime.date.today()) # Has end date, finishes before
) & Q(invoice__isnull=True) & # Has not already been invoiced ) & Q(invoice__isnull=True) # Has not already been invoiced
Q(is_rig=True) # Is a rig (not non-rig) & Q(is_rig=True) # Is a rig (not non-rig)
).order_by('start_date') \ ).order_by('start_date') \
.select_related('person', .select_related('person',
@@ -178,7 +167,7 @@ class PaymentCreate(generic.CreateView):
def get_initial(self): def get_initial(self):
initial = super(generic.CreateView, self).get_initial() initial = super(generic.CreateView, self).get_initial()
invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None)) invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None))
if invoicepk is None: if invoicepk == None:
raise Http404() raise Http404()
invoice = get_object_or_404(models.Invoice, pk=invoicepk) invoice = get_object_or_404(models.Invoice, pk=invoicepk)
initial.update({'invoice': invoice}) initial.update({'invoice': invoice})

View File

@@ -1,3 +1,4 @@
__author__ = 'Ghost'
from django import forms from django import forms
from django.utils import formats from django.utils import formats
from django.conf import settings from django.conf import settings
@@ -9,14 +10,8 @@ import simplejson
from RIGS import models from RIGS import models
# Override the django form defaults to use the HTML date/time/datetime UI elements
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
forms.TimeField.widget = forms.TextInput(attrs={'type': 'time'})
forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'})
# Registration # Registration
class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail): class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
captcha = ReCaptchaField() captcha = ReCaptchaField()
@@ -33,12 +28,7 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
return self.cleaned_data['initials'] return self.cleaned_data['initials']
# Embedded Login form - remove the autofocus # Login form
class EmbeddedAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.pop('autofocus', None)
class PasswordReset(PasswordResetForm): class PasswordReset(PasswordResetForm):
captcha = ReCaptchaField(label='Captcha') captcha = ReCaptchaField(label='Captcha')
@@ -55,7 +45,7 @@ class ProfileChangeForm(UserChangeForm):
# Events Shit # Events Shit
class EventForm(forms.ModelForm): class EventForm(forms.ModelForm):
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + list(settings.DATETIME_INPUT_FORMATS) datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + settings.DATETIME_INPUT_FORMATS
meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False) meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
access_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False) access_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
@@ -150,32 +140,4 @@ class EventForm(forms.ModelForm):
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date', fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic', 'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status', 'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
'purchase_order', 'collector'] 'collector', 'purchase_order']
class BaseClientEventAuthorisationForm(forms.ModelForm):
tos = forms.BooleanField(required=True, label="Terms of hire")
name = forms.CharField(label="Your Name")
def clean(self):
if self.cleaned_data.get('amount') != self.instance.event.total:
self.add_error('amount', 'The amount authorised must equal the total for the event (inc VAT).')
return super(BaseClientEventAuthorisationForm, self).clean()
class Meta:
abstract = True
class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
def __init__(self, **kwargs):
super(InternalClientEventAuthorisationForm, self).__init__(**kwargs)
self.fields['uni_id'].required = True
self.fields['account_code'].required = True
class Meta:
model = models.EventAuthorisation
fields = ('tos', 'name', 'amount', 'uni_id', 'account_code')
class EventAuthorisationRequestForm(forms.Form):
email = forms.EmailField(required=True, label='Authoriser Email')

View File

@@ -1,19 +1,17 @@
from RIGS import models, forms from RIGS import models, forms
from django_ical.views import ICalFeed from django_ical.views import ICalFeed
from django.db.models import Q from django.db.models import Q
from django.urls import reverse_lazy, reverse, NoReverseMatch from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
import datetime import datetime, pytz
import pytz
class CalendarICS(ICalFeed): class CalendarICS(ICalFeed):
""" """
A simple event calender A simple event calender
""" """
# Metadata which is passed on to clients #Metadata which is passed on to clients
product_id = 'RIGS' product_id = 'RIGS'
title = 'RIGS Calendar' title = 'RIGS Calendar'
timezone = settings.TIME_ZONE timezone = settings.TIME_ZONE
@@ -29,39 +27,39 @@ class CalendarICS(ICalFeed):
def get_object(self, request, *args, **kwargs): def get_object(self, request, *args, **kwargs):
params = {} params = {}
params['dry-hire'] = request.GET.get('dry-hire', 'true') == 'true' params['dry-hire'] = request.GET.get('dry-hire','true') == 'true'
params['non-rig'] = request.GET.get('non-rig', 'true') == 'true' params['non-rig'] = request.GET.get('non-rig','true') == 'true'
params['rig'] = request.GET.get('rig', 'true') == 'true' params['rig'] = request.GET.get('rig','true') == 'true'
params['cancelled'] = request.GET.get('cancelled', 'false') == 'true' params['cancelled'] = request.GET.get('cancelled','false') == 'true'
params['provisional'] = request.GET.get('provisional', 'true') == 'true' params['provisional'] = request.GET.get('provisional','true') == 'true'
params['confirmed'] = request.GET.get('confirmed', 'true') == 'true' params['confirmed'] = request.GET.get('confirmed','true') == 'true'
return params return params
def description(self, params): def description(self,params):
desc = "Calendar generated by RIGS system. This includes event types: " + ('Rig, ' if params['rig'] else '') + ('Non-rig, ' if params['non-rig'] else '') + ('Dry Hire ' if params['dry-hire'] else '') + '\n' desc = "Calendar generated by RIGS system. This includes event types: " + ('Rig, ' if params['rig'] else '') + ('Non-rig, ' if params['non-rig'] else '') + ('Dry Hire ' if params['dry-hire'] else '') + '\n'
desc = desc + "Includes events with status: " + ('Cancelled, ' if params['cancelled'] else '') + ('Provisional, ' if params['provisional'] else '') + ('Confirmed/Booked, ' if params['confirmed'] else '') desc = desc + "Includes events with status: " + ('Cancelled, ' if params['cancelled'] else '') + ('Provisional, ' if params['provisional'] else '') + ('Confirmed/Booked, ' if params['confirmed'] else '')
return desc return desc
def items(self, params): def items(self, params):
# include events from up to 1 year ago #include events from up to 1 year ago
start = datetime.datetime.now() - datetime.timedelta(days=365) start = datetime.datetime.now() - datetime.timedelta(days=365)
filter = Q(start_date__gte=start) filter = Q(start_date__gte=start)
typeFilters = Q(pk=None) # Need something that is false for every entry typeFilters = Q(pk=None) #Need something that is false for every entry
if params['dry-hire']: if params['dry-hire']:
typeFilters = typeFilters | Q(dry_hire=True, is_rig=True) typeFilters = typeFilters | Q(dry_hire=True, is_rig=True)
if params['non-rig']: if params['non-rig']:
typeFilters = typeFilters | Q(is_rig=False) typeFilters = typeFilters | Q(is_rig=False)
if params['rig']: if params['rig']:
typeFilters = typeFilters | Q(is_rig=True, dry_hire=False) typeFilters = typeFilters | Q(is_rig=True, dry_hire=False)
statusFilters = Q(pk=None) # Need something that is false for every entry statusFilters = Q(pk=None) #Need something that is false for every entry
if params['cancelled']: if params['cancelled']:
statusFilters = statusFilters | Q(status=models.Event.CANCELLED) statusFilters = statusFilters | Q(status=models.Event.CANCELLED)
@@ -89,9 +87,9 @@ class CalendarICS(ICalFeed):
# Add the rig name # Add the rig name
title += item.name title += item.name
# Add the status # Add the status
title += ' (' + str(item.get_status_display()) + ')' title += ' ('+str(item.get_status_display())+')'
return title return title
@@ -99,12 +97,12 @@ class CalendarICS(ICalFeed):
return item.earliest_time return item.earliest_time
def item_end_datetime(self, item): def item_end_datetime(self, item):
if type(item.latest_time) == datetime.date: # Ical end_datetime is non-inclusive, so add a day if type(item.latest_time) is datetime.date: # Ical end_datetime is non-inclusive, so add a day
return item.latest_time + datetime.timedelta(days=1) return item.latest_time + datetime.timedelta(days=1)
return item.latest_time return item.latest_time
def item_location(self, item): def item_location(self,item):
return item.venue return item.venue
def item_description(self, item): def item_description(self, item):
@@ -113,33 +111,34 @@ class CalendarICS(ICalFeed):
tz = pytz.timezone(self.timezone) tz = pytz.timezone(self.timezone)
desc = 'Rig ID = ' + str(item.pk) + '\n' desc = 'Rig ID = '+str(item.pk)+'\n'
desc += 'Event = ' + item.name + '\n' desc += 'Event = ' + item.name + '\n'
desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n' desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n'
if item.is_rig and item.person: if item.is_rig and item.person:
desc += 'Client = ' + item.person.name + ((' for ' + item.organisation.name) if item.organisation else '') + '\n' desc += 'Client = ' + item.person.name + ( (' for '+item.organisation.name) if item.organisation else '') + '\n'
desc += 'Status = ' + str(item.get_status_display()) + '\n' desc += 'Status = ' + str(item.get_status_display()) + '\n'
desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n' desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n'
desc += '\n' desc += '\n'
if item.meet_at: if item.meet_at:
desc += 'Crew Meet = ' + (item.meet_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n' desc += 'Crew Meet = ' + (item.meet_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.meet_at else '---') + '\n'
if item.access_at: if item.access_at:
desc += 'Access At = ' + (item.access_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.access_at else '---') + '\n' desc += 'Access At = ' + (item.access_at.astimezone(tz).strftime('%Y-%m-%d %H:%M') if item.access_at else '---') + '\n'
if item.start_date: 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' 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: 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 += 'Event End = ' + item.end_date.strftime('%Y-%m-%d') + ((' '+item.end_time.strftime('%H:%M')) if item.has_end_time else '') + '\n'
desc += '\n' desc += '\n'
if item.description: if item.description:
desc += 'Event Description:\n' + item.description + '\n\n' desc += 'Event Description:\n'+item.description+'\n\n'
# if item.notes: // Need to add proper keyholder checks before this gets put back # if item.notes: // Need to add proper keyholder checks before this gets put back
# desc += 'Notes:\n'+item.notes+'\n\n' # desc += 'Notes:\n'+item.notes+'\n\n'
base_url = "https://rigs.nottinghamtec.co.uk"
desc += 'URL = ' + base_url + str(item.get_absolute_url())
base_url = "http://rigs.nottinghamtec.co.uk"
desc += 'URL = '+base_url+str(item.get_absolute_url())
return desc return desc
def item_link(self, item): def item_link(self, item):
@@ -150,8 +149,8 @@ class CalendarICS(ICalFeed):
# def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) - not really necessary though # def item_created(self, item): #TODO - Implement created date-time (using django-reversion?) - not really necessary though
# return '' # return ''
def item_updated(self, item): # some ical clients will display this def item_updated(self, item): # some ical clients will display this
return item.last_edited_at return item.last_edited_at
def item_guid(self, item): # use the rig-id as the ical unique event identifier def item_guid(self, item): # use the rig-id as the ical unique event identifier
return item.pk return item.pk

View File

@@ -1,14 +1,12 @@
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group, Permission from django.contrib.auth.models import Group, Permission
from django.db import transaction from django.db import transaction
from reversion import revisions as reversion import reversion
import datetime import datetime
import random import random
from RIGS import models from RIGS import models
class Command(BaseCommand): class Command(BaseCommand):
help = 'Adds sample data to use for testing' help = 'Adds sample data to use for testing'
can_import_settings = True can_import_settings = True
@@ -21,32 +19,32 @@ class Command(BaseCommand):
keyholder_group = None keyholder_group = None
finance_group = None finance_group = None
def handle(self, *args, **options): def handle(self, *args, **options):
from django.conf import settings from django.conf import settings
if not (settings.DEBUG or settings.STAGING): if not (settings.DEBUG or settings.STAGING):
raise CommandError('You cannot run this command in production') raise CommandError('You cannot run this command in production')
random.seed('Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests random.seed('Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests
with transaction.atomic(): with transaction.atomic():
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1')
self.setupGenericProfiles() self.setupGenericProfiles()
self.setupPeople() self.setupPeople()
self.setupOrganisations() self.setupOrganisations()
self.setupVenues() self.setupVenues()
self.setupGroups() self.setupGroups()
self.setupEvents() self.setupEvents()
self.setupUsefulProfiles() self.setupUsefulProfiles()
def setupPeople(self): def setupPeople(self):
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe", "Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore", "Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan", "Nicolas Flamel", "Cornelius Fudge", "Goyle", "Gregory Goyle", "Hermione Granger", "Rubeus Hagrid", "Igor Karkaroff", "Viktor Krum", "Bellatrix Lestrange", "Alice Longbottom", "Frank Longbottom", "Neville Longbottom", "Luna Lovegood", "Xenophilius Lovegood", # noqa names = ["Regulus Black","Sirius Black","Lavender Brown","Cho Chang","Vincent Crabbe","Vincent Crabbe","Bartemius Crouch","Fleur Delacour","Cedric Diggory","Alberforth Dumbledore","Albus Dumbledore","Dudley Dursley","Petunia Dursley","Vernon Dursley","Argus Filch","Seamus Finnigan","Nicolas Flamel","Cornelius Fudge","Goyle","Gregory Goyle","Hermione Granger","Rubeus Hagrid","Igor Karkaroff","Viktor Krum","Bellatrix Lestrange","Alice Longbottom","Frank Longbottom","Neville Longbottom","Luna Lovegood","Xenophilius Lovegood","Remus Lupin","Draco Malfoy","Lucius Malfoy","Narcissa Malfoy","Olympe Maxime","Minerva McGonagall","Mad-Eye Moody","Peter Pettigrew","Harry Potter","James Potter","Lily Potter","Quirinus Quirrell","Tom Riddle","Mary Riddle","Lord Voldemort","Rita Skeeter","Severus Snape","Nymphadora Tonks","Dolores Janes Umbridge","Arthur Weasley","Bill Weasley","Charlie Weasley","Fred Weasley","George Weasley","Ginny Weasley","Molly Weasley","Percy Weasley","Ron Weasley","Dobby","Fluffy","Hedwig","Moaning Myrtle","Aragog","Grawp"]
"Remus Lupin", "Draco Malfoy", "Lucius Malfoy", "Narcissa Malfoy", "Olympe Maxime", "Minerva McGonagall", "Mad-Eye Moody", "Peter Pettigrew", "Harry Potter", "James Potter", "Lily Potter", "Quirinus Quirrell", "Tom Riddle", "Mary Riddle", "Lord Voldemort", "Rita Skeeter", "Severus Snape", "Nymphadora Tonks", "Dolores Janes Umbridge", "Arthur Weasley", "Bill Weasley", "Charlie Weasley", "Fred Weasley", "George Weasley", "Ginny Weasley", "Molly Weasley", "Percy Weasley", "Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa
for i, name in enumerate(names): for i, name in enumerate(names):
with reversion.create_revision(): with reversion.create_revision():
reversion.set_user(random.choice(self.profiles)) reversion.set_user(random.choice(self.profiles))
@@ -68,8 +66,7 @@ class Command(BaseCommand):
self.people.append(newPerson) self.people.append(newPerson)
def setupOrganisations(self): def setupOrganisations(self):
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa names = ["Acme, inc.","Widget Corp","123 Warehousing","Demo Company","Smith and Co.","Foo Bars","ABC Telecom","Fake Brothers","QWERTY Logistics","Demo, inc.","Sample Company","Sample, inc","Acme Corp","Allied Biscuit","Ankh-Sto Associates","Extensive Enterprise","Galaxy Corp","Globo-Chem","Mr. Sparkle","Globex Corporation","LexCorp","LuthorCorp","North Central Positronics","Omni Consimer Products","Praxis Corporation","Sombra Corporation","Sto Plains Holdings","Tessier-Ashpool","Wayne Enterprises","Wentworth Industries","ZiffCorp","Bluth Company","Strickland Propane","Thatherton Fuels","Three Waters","Water and Power","Western Gas & Electric","Mammoth Pictures","Mooby Corp","Gringotts","Thrift Bank","Flowers By Irene","The Legitimate Businessmens Club","Osato Chemicals","Transworld Consortium","Universal Export","United Fried Chicken","Virtucon","Kumatsu Motors","Keedsler Motors","Powell Motors","Industrial Automation","Sirius Cybernetics Corporation","U.S. Robotics and Mechanical Men","Colonial Movers","Corellian Engineering Corporation","Incom Corporation","General Products","Leeding Engines Ltd.","Blammo","Input, Inc.","Mainway Toys","Videlectrix","Zevo Toys","Ajax","Axis Chemical Co.","Barrytron","Carrys Candles","Cogswell Cogs","Spacely Sprockets","General Forge and Foundry","Duff Brewing Company","Dunder Mifflin","General Services Corporation","Monarch Playing Card Co.","Krustyco","Initech","Roboto Industries","Primatech","Sonky Rubber Goods","St. Anky Beer","Stay Puft Corporation","Vandelay Industries","Wernham Hogg","Gadgetron","Burleigh and Stronginthearm","BLAND Corporation","Nordyne Defense Dynamics","Petrox Oil Company","Roxxon","McMahon and Tate","Sixty Second Avenue","Charles Townsend Agency","Spade and Archer","Megadodo Publications","Rouster and Sideways","C.H. Lavatory and Sons","Globo Gym American Corp","The New Firm","SpringShield","Compuglobalhypermeganet","Data Systems","Gizmonic Institute","Initrode","Taggart Transcontinental","Atlantic Northern","Niagular","Plow King","Big Kahuna Burger","Big T Burgers and Fries","Chez Quis","Chotchkies","The Frying Dutchman","Klimpys","The Krusty Krab","Monks Diner","Milliways","Minuteman Cafe","Taco Grande","Tip Top Cafe","Moes Tavern","Central Perk","Chasers"]
"Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
for i, name in enumerate(names): for i, name in enumerate(names):
with reversion.create_revision(): with reversion.create_revision():
reversion.set_user(random.choice(self.profiles)) reversion.set_user(random.choice(self.profiles))
@@ -93,8 +90,7 @@ class Command(BaseCommand):
self.organisations.append(newOrganisation) self.organisations.append(newOrganisation)
def setupVenues(self): def setupVenues(self):
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch", "The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands", "The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins", "The Vale of Arryn", "The Whispering Wood", "White Harbor", "Winterfell", "The Arbor", "Ashemark", "Brightwater Keep", "Casterly Rock", "Clegane's Keep", "Dragonstone", "Dorne", "God's Eye", "The Golden Tooth", # noqa names = ["Bear Island","Crossroads Inn","Deepwood Motte","The Dreadfort","The Eyrie","Greywater Watch","The Iron Islands","Karhold","Moat Cailin","Oldstones","Raventree Hall","Riverlands","The Ruby Ford","Saltpans","Seagard","Torrhen's Square","The Trident","The Twins","The Vale of Arryn","The Whispering Wood","White Harbor","Winterfell","The Arbor","Ashemark","Brightwater Keep","Casterly Rock","Clegane's Keep","Dragonstone","Dorne","God's Eye","The Golden Tooth","Harrenhal","Highgarden","Horn Hill","Fingers","King's Landing","Lannisport","Oldtown","Rainswood","Storm's End","Summerhall","Sunspear","Tarth","Castle Black","Craster's Keep","Fist of the First Men","The Frostfangs","The Gift","The Skirling Pass","The Wall","Asshai","Astapor","Braavos","The Dothraki Sea","Lys","Meereen","Myr","Norvos","Pentos","Qarth","Qohor","The Red Waste","Tyrosh","Vaes Dothrak","Valyria","Village of the Lhazareen","Volantis","Yunkai"]
"Harrenhal", "Highgarden", "Horn Hill", "Fingers", "King's Landing", "Lannisport", "Oldtown", "Rainswood", "Storm's End", "Summerhall", "Sunspear", "Tarth", "Castle Black", "Craster's Keep", "Fist of the First Men", "The Frostfangs", "The Gift", "The Skirling Pass", "The Wall", "Asshai", "Astapor", "Braavos", "The Dothraki Sea", "Lys", "Meereen", "Myr", "Norvos", "Pentos", "Qarth", "Qohor", "The Red Waste", "Tyrosh", "Vaes Dothrak", "Valyria", "Village of the Lhazareen", "Volantis", "Yunkai"] # noqa
for i, name in enumerate(names): for i, name in enumerate(names):
with reversion.create_revision(): with reversion.create_revision():
reversion.set_user(random.choice(self.profiles)) reversion.set_user(random.choice(self.profiles))
@@ -121,8 +117,8 @@ class Command(BaseCommand):
self.keyholder_group = Group.objects.create(name='Keyholders') self.keyholder_group = Group.objects.create(name='Keyholders')
self.finance_group = Group.objects.create(name='Finance') self.finance_group = Group.objects.create(name='Finance')
keyholderPerms = ["add_event", "change_event", "view_event", "add_eventitem", "change_eventitem", "delete_eventitem", "add_organisation", "change_organisation", "view_organisation", "add_person", "change_person", "view_person", "view_profile", "add_venue", "change_venue", "view_venue"] keyholderPerms = ["add_event","change_event","view_event","add_eventitem","change_eventitem","delete_eventitem","add_organisation","change_organisation","view_organisation","add_person","change_person","view_person","view_profile","add_venue","change_venue","view_venue"]
financePerms = ["change_event", "view_event", "add_eventitem", "change_eventitem", "add_invoice", "change_invoice", "view_invoice", "add_organisation", "change_organisation", "view_organisation", "add_payment", "change_payment", "delete_payment", "add_person", "change_person", "view_person"] financePerms = ["change_event","view_event","add_eventitem","change_eventitem","add_invoice","change_invoice","view_invoice","add_organisation","change_organisation","view_organisation","add_payment","change_payment","delete_payment","add_person","change_person","view_person"]
for permId in keyholderPerms: for permId in keyholderPerms:
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId)) self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
@@ -131,11 +127,11 @@ class Command(BaseCommand):
self.finance_group.permissions.add(Permission.objects.get(codename=permId)) self.finance_group.permissions.add(Permission.objects.get(codename=permId))
def setupGenericProfiles(self): def setupGenericProfiles(self):
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble", "Jack Harkness", "Mickey Smith", "Rose Tyler"] names = ["Clara Oswin Oswald","Rory Williams","Amy Pond","River Song","Martha Jones","Donna Noble","Jack Harkness","Mickey Smith","Rose Tyler"]
for i, name in enumerate(names): for i, name in enumerate(names):
newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0], last_name=name.split(" ")[-1], newProfile = models.Profile.objects.create(username=name.replace(" ",""), first_name=name.split(" ")[0], last_name=name.split(" ")[-1],
email=name.replace(" ", "") + "@example.com", email=name.replace(" ","")+"@example.com",
initials="".join([j[0].upper() for j in name.split()])) initials="".join([ j[0].upper() for j in name.split() ]))
if i % 2 == 0: if i % 2 == 0:
newProfile.phone = "01234 567894" newProfile.phone = "01234 567894"
@@ -143,110 +139,110 @@ class Command(BaseCommand):
self.profiles.append(newProfile) self.profiles.append(newProfile)
def setupUsefulProfiles(self): def setupUsefulProfiles(self):
superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User", initials="SU", superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User", initials="SU",
email="superuser@example.com", is_superuser=True, is_active=True, is_staff=True) email="superuser@example.com", is_superuser=True, is_active=True, is_staff=True)
superUser.set_password('superuser') superUser.set_password('superuser')
superUser.save() superUser.save()
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User", initials="FU", financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User", initials="FU",
email="financeuser@example.com", is_active=True) email="financeuser@example.com", is_active=True)
financeUser.groups.add(self.finance_group) financeUser.groups.add(self.finance_group)
financeUser.groups.add(self.keyholder_group) financeUser.groups.add(self.keyholder_group)
financeUser.set_password('finance') financeUser.set_password('finance')
financeUser.save() financeUser.save()
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU", keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU",
email="keyholderuser@example.com", is_active=True) email="keyholderuser@example.com", is_active=True)
keyholderUser.groups.add(self.keyholder_group) keyholderUser.groups.add(self.keyholder_group)
keyholderUser.set_password('keyholder') keyholderUser.set_password('keyholder')
keyholderUser.save() keyholderUser.save()
basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU", basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU",
email="basicuser@example.com", is_active=True) email="basicuser@example.com", is_active=True)
basicUser.set_password('basic') basicUser.set_password('basic')
basicUser.save() basicUser.save()
def setupEvents(self): def setupEvents(self):
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball", "Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event", names = ["Outdoor Concert","Hall Open Mic Night","Festival","Weekend Event","Magic Show","Society Ball","Evening Show","Talent Show","Acoustic Evening","Hire of Things","SU Event","End of Term Show","Theatre Show","Outdoor Fun Day","Summer Carnival","Open Days","Magic Show","Awards Ceremony","Debating Event","Club Night","DJ Evening","Building Projection","Choir Concert"]
"End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show", "Awards Ceremony", "Debating Event", "Club Night", "DJ Evening", "Building Projection", "Choir Concert"] descriptions = ["A brief desciption of the event","This event is boring","Probably wont happen","Warning: this has lots of kit"]
descriptions = ["A brief desciption of the event", "This event is boring", "Probably wont happen", "Warning: this has lots of kit"] notes = ["The client came into the office at some point","Who knows if this will happen", "Probably should check this event", "Maybe not happening", "Run away!"]
notes = ["The client came into the office at some point", "Who knows if this will happen", "Probably should check this event", "Maybe not happening", "Run away!"]
itemOptions = [{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2, 'cost': 200.00}, itemOptions = [{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2, 'cost': 200.00},
{'name': 'Projector', 'description': 'Some kind of video thinamejig, probably with unnecessary processing for free', 'quantity': 1, 'cost': 500.00}, {'name': 'Projector', 'description': 'Some kind of video thinamejig, probably with unnecessary processing for free', 'quantity': 1, 'cost': 500.00},
{'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1, 'cost': 200.52}, {'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1, 'cost': 200.52},
{'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00}, {'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00},
{'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5, 'cost': 0.50}, {'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5, 'cost': 0.50},
{'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1, 'cost': 100.00}, {'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1, 'cost': 100.00},
{'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00}, {'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00},
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00}, {'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}] {'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
dayDelta = -120 # start adding events from 4 months ago dayDelta = -120 # start adding events from 4 months ago
for i in range(150): # Let's add 100 events for i in range(150): # Let's add 100 events
with reversion.create_revision(): with reversion.create_revision():
reversion.set_user(random.choice(self.profiles)) reversion.set_user(random.choice(self.profiles))
name = names[i % len(names)] name = names[i%len(names)]
startDate = datetime.date.today() + datetime.timedelta(days=dayDelta) startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
dayDelta = dayDelta + random.randint(0, 3) dayDelta = dayDelta + random.randint(0,3)
newEvent = models.Event.objects.create(name=name, start_date=startDate) newEvent = models.Event.objects.create(name=name, start_date=startDate)
if random.randint(0, 2) > 1: # 1 in 3 have a start time if random.randint(0,2) > 1: # 1 in 3 have a start time
newEvent.start_time = datetime.time(random.randint(15, 20)) newEvent.start_time = datetime.time(random.randint(15,20))
if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day if random.randint(0,2) > 1: # of those, 1 in 3 have an end time on the same day
newEvent.end_time = datetime.time(random.randint(21, 23)) newEvent.end_time = datetime.time(random.randint(21,23))
elif random.randint(0, 1) > 0: # half of the others finish early the next day elif random.randint(0,1)>0: # half of the others finish early the next day
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1) newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1)
newEvent.end_time = datetime.time(random.randint(0, 5)) newEvent.end_time = datetime.time(random.randint(0,5))
elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead elif random.randint(0,2)>1: # 1 in 3 of the others finish a few days ahead
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4)) newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1,4))
if random.randint(0, 6) > 0: # 5 in 6 have MIC
if random.randint(0,6) > 0: # 5 in 6 have MIC
newEvent.mic = random.choice(self.profiles) newEvent.mic = random.choice(self.profiles)
if random.randint(0, 6) > 0: # 5 in 6 have organisation if random.randint(0,6) > 0: # 5 in 6 have organisation
newEvent.organisation = random.choice(self.organisations) newEvent.organisation = random.choice(self.organisations)
if random.randint(0, 6) > 0: # 5 in 6 have person if random.randint(0,6) > 0: # 5 in 6 have person
newEvent.person = random.choice(self.people) newEvent.person = random.choice(self.people)
if random.randint(0, 6) > 0: # 5 in 6 have venue if random.randint(0,6) > 0: # 5 in 6 have venue
newEvent.venue = random.choice(self.venues) newEvent.venue = random.choice(self.venues)
# Could have any status, equally weighted # Could have any status, equally weighted
newEvent.status = random.choice([models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED]) newEvent.status = random.choice([models.Event.BOOKED,models.Event.CONFIRMED,models.Event.PROVISIONAL, models.Event.CANCELLED])
newEvent.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire newEvent.dry_hire = (random.randint(0,7)==0) # 1 in 7 are dry hire
if random.randint(0, 1) > 0: # 1 in 2 have description if random.randint(0,1) > 0: # 1 in 2 have description
newEvent.description = random.choice(descriptions) newEvent.description = random.choice(descriptions)
if random.randint(0, 1) > 0: # 1 in 2 have notes if random.randint(0,1) > 0: # 1 in 2 have notes
newEvent.notes = random.choice(notes) newEvent.notes = random.choice(notes)
newEvent.save() newEvent.save()
# Now add some items # Now add some items
for j in range(random.randint(1, 5)): for j in range(random.randint(1,5)):
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)] itemData = itemOptions[random.randint(0,len(itemOptions)-1)]
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData) newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
newItem.save() newItem.save()
while newEvent.sum_total < 0: while newEvent.sum_total < 0:
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)] itemData = itemOptions[random.randint(0,len(itemOptions)-1)]
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData) newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
newItem.save() newItem.save()
with reversion.create_revision(): with reversion.create_revision():
reversion.set_user(random.choice(self.profiles)) reversion.set_user(random.choice(self.profiles))
if newEvent.start_date < datetime.date.today(): # think about adding an invoice if newEvent.start_date < datetime.date.today(): # think about adding an invoice
if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury if random.randint(0,2) > 0: # 2 in 3 have had paperwork sent to treasury
newInvoice = models.Invoice.objects.create(event=newEvent) newInvoice = models.Invoice.objects.create(event=newEvent)
if newEvent.status is models.Event.CANCELLED: # void cancelled events if newEvent.status is models.Event.CANCELLED: # void cancelled events
newInvoice.void = True newInvoice.void = True
elif random.randint(0, 2) > 1: # 1 in 3 have been paid elif random.randint(0,2)>1: # 1 in 3 have been paid
models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance, date=datetime.date.today()) models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance, date=datetime.date.today())

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import django.core.validators import django.core.validators

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
from django.conf import settings from django.conf import settings
@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('postedAt', models.DateTimeField(auto_now=True)), ('postedAt', models.DateTimeField(auto_now=True)),
('message', models.TextField()), ('message', models.TextField()),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
}, },

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import RIGS.models import RIGS.models

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import RIGS.models import RIGS.models

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
from django.conf import settings from django.conf import settings
@@ -33,11 +33,11 @@ class Migration(migrations.Migration):
('payment_method', models.CharField(blank=True, null=True, max_length=255)), ('payment_method', models.CharField(blank=True, null=True, max_length=255)),
('payment_received', models.CharField(blank=True, null=True, max_length=255)), ('payment_received', models.CharField(blank=True, null=True, max_length=255)),
('purchase_order', models.CharField(blank=True, null=True, max_length=255)), ('purchase_order', models.CharField(blank=True, null=True, max_length=255)),
('based_on', models.ForeignKey(to='RIGS.Event', related_name='future_events', on_delete=models.CASCADE)), ('based_on', models.ForeignKey(to='RIGS.Event', related_name='future_events')),
('checked_in_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', on_delete=models.CASCADE)), ('checked_in_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in')),
('mic', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', on_delete=models.CASCADE)), ('mic', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic')),
('organisation', models.ForeignKey(to='RIGS.Organisation', on_delete=models.CASCADE)), ('organisation', models.ForeignKey(to='RIGS.Organisation')),
('person', models.ForeignKey(to='RIGS.Person', on_delete=models.CASCADE)), ('person', models.ForeignKey(to='RIGS.Person')),
], ],
options={ options={
}, },
@@ -52,7 +52,7 @@ class Migration(migrations.Migration):
('quantity', models.IntegerField()), ('quantity', models.IntegerField()),
('cost', models.DecimalField(max_digits=10, decimal_places=2)), ('cost', models.DecimalField(max_digits=10, decimal_places=2)),
('order', models.IntegerField()), ('order', models.IntegerField()),
('event', models.ForeignKey(to='RIGS.Event', related_name='item', on_delete=models.CASCADE)), ('event', models.ForeignKey(to='RIGS.Event', related_name='item')),
], ],
options={ options={
}, },
@@ -75,7 +75,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='event', model_name='event',
name='venue', name='venue',
field=models.ForeignKey(to='RIGS.Venue', on_delete=models.CASCADE), field=models.ForeignKey(to='RIGS.Venue'),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
from django.conf import settings from django.conf import settings
@@ -14,26 +14,26 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='based_on', name='based_on',
field=models.ForeignKey(to='RIGS.Event', related_name='future_events', blank=True, null=True, on_delete=models.CASCADE), field=models.ForeignKey(to='RIGS.Event', related_name='future_events', blank=True, null=True),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='checked_in_by', name='checked_in_by',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True,
null=True, on_delete=models.CASCADE), null=True),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='mic', name='mic',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True, on_delete=models.CASCADE), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='organisation', name='organisation',
field=models.ForeignKey(to='RIGS.Organisation', blank=True, null=True, on_delete=models.CASCADE), field=models.ForeignKey(to='RIGS.Organisation', blank=True, null=True),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
from django.conf import settings from django.conf import settings
@@ -19,8 +19,8 @@ class Migration(migrations.Migration):
('run', models.BooleanField(default=False)), ('run', models.BooleanField(default=False)),
('derig', models.BooleanField(default=False)), ('derig', models.BooleanField(default=False)),
('notes', models.TextField(blank=True, null=True)), ('notes', models.TextField(blank=True, null=True)),
('event', models.ForeignKey(related_name='crew', to='RIGS.Event', on_delete=models.CASCADE)), ('event', models.ForeignKey(related_name='crew', to='RIGS.Event')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
}, },
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='eventitem', model_name='eventitem',
name='event', name='event',
field=models.ForeignKey(related_name='items', to='RIGS.Event', on_delete=models.CASCADE), field=models.ForeignKey(related_name='items', to='RIGS.Event'),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
@@ -14,7 +14,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='person', name='person',
field=models.ForeignKey(blank=True, null=True, to='RIGS.Person', on_delete=models.CASCADE), field=models.ForeignKey(blank=True, null=True, to='RIGS.Person'),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
@@ -14,13 +14,13 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='venue', name='venue',
field=models.ForeignKey(blank=True, to='RIGS.Venue', null=True, on_delete=models.CASCADE), field=models.ForeignKey(blank=True, to='RIGS.Venue', null=True),
preserve_default=True, preserve_default=True,
), ),
migrations.AlterField( migrations.AlterField(
model_name='eventitem', model_name='eventitem',
name='event', name='event',
field=models.ForeignKey(related_name='items', blank=True, to='RIGS.Event', on_delete=models.CASCADE), field=models.ForeignKey(related_name='items', blank=True, to='RIGS.Event'),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
from django.conf import settings from django.conf import settings
@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('invoice_date', models.DateField(auto_now_add=True)), ('invoice_date', models.DateField(auto_now_add=True)),
('void', models.BooleanField()), ('void', models.BooleanField()),
('event', models.OneToOneField(to='RIGS.Event', on_delete=models.CASCADE)), ('event', models.OneToOneField(to='RIGS.Event')),
], ],
options={ options={
}, },
@@ -31,7 +31,7 @@ class Migration(migrations.Migration):
('date', models.DateField()), ('date', models.DateField()),
('amount', models.DecimalField(help_text=b'Please use ex. VAT', max_digits=10, decimal_places=2)), ('amount', models.DecimalField(help_text=b'Please use ex. VAT', max_digits=10, decimal_places=2)),
('method', models.CharField(max_length=2, choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'), (b'SU', b'SU Core'), (b'M', b'Members')])), ('method', models.CharField(max_length=2, choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'), (b'SU', b'SU Core'), (b'M', b'Members')])),
('invoice', models.ForeignKey(to='RIGS.Invoice', on_delete=models.CASCADE)), ('invoice', models.ForeignKey(to='RIGS.Invoice')),
], ],
options={ options={
}, },
@@ -40,7 +40,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='mic', name='mic',
field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE), field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import django.core.validators import django.core.validators

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import django.db.models.deletion import django.db.models.deletion

View File

@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-03-31 12:02
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0024_auto_20160229_2042'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
),
migrations.AlterField(
model_name='vatrate',
name='start_at',
field=models.DateField(),
),
]

View File

@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0024_auto_20160229_2042'),
]
operations = [
migrations.CreateModel(
name='EventAuthorisation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('email', models.EmailField(max_length=254)),
('name', models.CharField(max_length=255)),
('uni_id', models.CharField(max_length=10, null=True, verbose_name=b'University ID', blank=True)),
('account_code', models.CharField(max_length=50, null=True, blank=True)),
('amount', models.DecimalField(verbose_name=b'authorisation amount', max_digits=10, decimal_places=2)),
('created_at', models.DateTimeField(auto_now_add=True)),
('event', models.ForeignKey(related_name='authroisations', to='RIGS.Event', on_delete=models.CASCADE)),
],
),
]

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-10 17:46
import django.contrib.auth.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0025_auto_20160331_1302'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'),
),
]

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0025_eventauthorisation'),
]
operations = [
migrations.RemoveField(
model_name='eventauthorisation',
name='created_at',
),
]

View File

@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0026_remove_eventauthorisation_created_at'),
]
operations = [
migrations.AlterField(
model_name='eventauthorisation',
name='event',
field=models.OneToOneField(related_name='authorisation', to='RIGS.Event', on_delete=models.CASCADE),
),
]

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0027_eventauthorisation_event_singular'),
]
operations = [
migrations.AddField(
model_name='eventauthorisation',
name='sent_by',
field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
preserve_default=False,
),
]

View File

@@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0029_eventauthorisation_sent_by'),
]
operations = [
migrations.AddField(
model_name='event',
name='auth_request_at',
field=models.DateTimeField(null=True, blank=True),
),
migrations.AddField(
model_name='event',
name='auth_request_by',
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='event',
name='auth_request_to',
field=models.EmailField(max_length=254, null=True, blank=True),
),
]

View File

@@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-12 20:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0030_auth_request_sending'),
('RIGS', '0026_auto_20170510_1846'),
]
operations = [
]

View File

@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-09-04 22:55
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('reversion', '0001_squashed_0004_auto_20160611_1202'),
('RIGS', '0031_merge_20170512_2102'),
]
operations = [
migrations.CreateModel(
name='RIGSVersion',
fields=[
],
options={
'indexes': [],
'proxy': True,
},
bases=('reversion.version',),
),
migrations.AlterField(
model_name='event',
name='collector',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='collected by'),
),
migrations.AlterField(
model_name='event',
name='mic',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='event_mic', to=settings.AUTH_USER_MODEL, verbose_name='MIC'),
),
migrations.AlterField(
model_name='event',
name='purchase_order',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='PO'),
),
migrations.AlterField(
model_name='eventauthorisation',
name='amount',
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='authorisation amount'),
),
migrations.AlterField(
model_name='eventauthorisation',
name='uni_id',
field=models.CharField(blank=True, max_length=10, null=True, verbose_name='University ID'),
),
migrations.AlterField(
model_name='payment',
name='amount',
field=models.DecimalField(decimal_places=2, help_text='Please use ex. VAT', max_digits=10),
),
migrations.AlterField(
model_name='payment',
name='method',
field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('SU', 'SU Core'), ('T', 'TEC Adjustment')], max_length=2, null=True),
),
migrations.AlterField(
model_name='profile',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.0.3 on 2018-03-25 00:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0032_auto_20170904_2355'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='last_name',
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 2.0.5 on 2019-07-28 21:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0033_auto_20180325_0016'),
]
operations = [
migrations.AddField(
model_name='event',
name='risk_assessment_edit_url',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -1,24 +1,19 @@
import datetime import datetime
import hashlib import hashlib
import datetime
import pytz
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.conf import settings
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.encoding import python_2_unicode_compatible
from reversion import revisions as reversion
from reversion.models import Version
import string
import random import random
import string
from collections import Counter from collections import Counter
from decimal import Decimal from decimal import Decimal
import pytz
import reversion
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.urls import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.functional import cached_property
# Create your models here. # Create your models here.
@@ -33,13 +28,13 @@ class Profile(AbstractUser):
size = 20 size = 20
chars = string.ascii_letters + string.digits chars = string.ascii_letters + string.digits
new_api_key = ''.join(random.choice(chars) for x in range(size)) new_api_key = ''.join(random.choice(chars) for x in range(size))
return new_api_key return new_api_key;
@property @property
def profile_picture(self): def profile_picture(self):
url = "" url = ""
if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None: if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None:
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email.encode('utf-8')).hexdigest() + "?d=wavatar&s=500" url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email).hexdigest() + "?d=wavatar&s=500"
return url return url
@property @property
@@ -63,31 +58,32 @@ class Profile(AbstractUser):
class RevisionMixin(object): class RevisionMixin(object):
@property
def current_version(self):
version = Version.objects.get_for_object(self).select_related('revision').first()
return version
@property @property
def last_edited_at(self): def last_edited_at(self):
version = self.current_version versions = reversion.get_for_object(self)
if version is None: if versions:
version = reversion.get_for_object(self)[0]
return version.revision.date_created
else:
return None return None
return version.revision.date_created
@property @property
def last_edited_by(self): def last_edited_by(self):
version = self.current_version versions = reversion.get_for_object(self)
if version is None: if versions:
version = reversion.get_for_object(self)[0]
return version.revision.user
else:
return None return None
return version.revision.user
@property @property
def current_version_id(self): def current_version_id(self):
version = self.current_version versions = reversion.get_for_object(self)
if version is None: if versions:
version = reversion.get_for_object(self)[0]
return "V{0} | R{1}".format(version.pk, version.revision.pk)
else:
return None return None
return "V{0} | R{1}".format(version.pk, version.revision.pk)
@reversion.register @reversion.register
@@ -179,7 +175,7 @@ class Organisation(models.Model, RevisionMixin):
class VatManager(models.Manager): class VatManager(models.Manager):
def current_rate(self): def current_rate(self):
return self.find_rate(timezone.now()) return self.find_rate(datetime.datetime.now())
def find_rate(self, date): def find_rate(self, date):
# return self.filter(startAt__lte=date).latest() # return self.filter(startAt__lte=date).latest()
@@ -194,7 +190,7 @@ class VatManager(models.Manager):
@reversion.register @reversion.register
@python_2_unicode_compatible @python_2_unicode_compatible
class VatRate(models.Model, RevisionMixin): class VatRate(models.Model, RevisionMixin):
start_at = models.DateField() start_at = models.DateTimeField()
rate = models.DecimalField(max_digits=6, decimal_places=6) rate = models.DecimalField(max_digits=6, decimal_places=6)
comment = models.CharField(max_length=255) comment = models.CharField(max_length=255)
@@ -245,12 +241,18 @@ class Venue(models.Model, RevisionMixin):
class EventManager(models.Manager): class EventManager(models.Manager):
def current_events(self): def current_events(self):
events = self.filter( events = self.filter(
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end (models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after status=Event.CANCELLED)) | # Starts after with no end
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire (models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT status=Event.CANCELLED)) | # Ends after
models.Q(status=Event.CANCELLED, start_date__gte=timezone.now().date()) # Canceled but not started (models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic') status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
models.Q(status=Event.CANCELLED, start_date__gte=datetime.date.today()) # Canceled but not started
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
'organisation',
'venue', 'mic')
return events return events
def events_in_bounds(self, start, end): def events_in_bounds(self, start, end):
@@ -273,12 +275,12 @@ class EventManager(models.Manager):
def rig_count(self): def rig_count(self):
event_count = self.filter( event_count = self.filter(
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False, (models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False,
is_rig=True) & ~models.Q( is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Starts after with no end status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q( (models.Q(end_date__gte=datetime.date.today(), dry_hire=False, is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Ends after status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q( (models.Q(dry_hire=True, start_date__gte=datetime.date.today(), is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Active dry hire status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True, is_rig=True) & ( (models.Q(dry_hire=True, checked_in_by__isnull=True, is_rig=True) & (
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) # Active dry hire GT models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) # Active dry hire GT
@@ -302,9 +304,9 @@ class Event(models.Model, RevisionMixin):
) )
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE) person = models.ForeignKey('Person', null=True, blank=True)
organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE) organisation = models.ForeignKey('Organisation', blank=True, null=True)
venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE) venue = models.ForeignKey('Venue', blank=True, null=True)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, null=True) notes = models.TextField(blank=True, null=True)
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL) status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
@@ -323,9 +325,9 @@ class Event(models.Model, RevisionMixin):
meet_info = models.CharField(max_length=255, blank=True, null=True) meet_info = models.CharField(max_length=255, blank=True, null=True)
# Crew management # Crew management
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True, on_delete=models.CASCADE) checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True)
mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True, mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True,
verbose_name="MIC", on_delete=models.CASCADE) verbose_name="MIC")
# Monies # Monies
payment_method = models.CharField(max_length=255, blank=True, null=True) payment_method = models.CharField(max_length=255, blank=True, null=True)
@@ -333,14 +335,6 @@ class Event(models.Model, RevisionMixin):
purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO') purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO')
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by') collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
# Authorisation request details
auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
auth_request_at = models.DateTimeField(null=True, blank=True)
auth_request_to = models.EmailField(null=True, blank=True)
# Risk assessment info
risk_assessment_edit_url = models.CharField(verbose_name="risk assessment", max_length=255, blank=True, null=True)
# Calculated values # Calculated values
""" """
EX Vat EX Vat
@@ -373,7 +367,7 @@ class Event(models.Model, RevisionMixin):
@property @property
def vat(self): def vat(self):
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01')) return self.sum_total * self.vat_rate.rate
""" """
Inc VAT Inc VAT
@@ -381,7 +375,7 @@ class Event(models.Model, RevisionMixin):
@property @property
def total(self): def total(self):
return Decimal(self.sum_total + self.vat).quantize(Decimal('.01')) return self.sum_total + self.vat
@property @property
def cancelled(self): def cancelled(self):
@@ -391,10 +385,6 @@ class Event(models.Model, RevisionMixin):
def confirmed(self): def confirmed(self):
return (self.status == self.BOOKED or self.status == self.CONFIRMED) return (self.status == self.BOOKED or self.status == self.CONFIRMED)
@property
def authorised(self):
return not self.internal and self.purchase_order or self.authorisation.amount == self.total
@property @property
def has_start_time(self): def has_start_time(self):
return self.start_time is not None return self.start_time is not None
@@ -455,17 +445,13 @@ class Event(models.Model, RevisionMixin):
else: else:
return endDate return endDate
@property
def internal(self):
return self.organisation and self.organisation.union_account
objects = EventManager() objects = EventManager()
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.pk}) return reverse_lazy('event_detail', kwargs={'pk': self.pk})
def __str__(self): def __str__(self):
return str(self.pk) + ": " + self.name return unicode(self.pk) + ": " + self.name
def clean(self): def clean(self):
if self.end_date and self.start_date > self.end_date: if self.end_date and self.start_date > self.end_date:
@@ -488,7 +474,7 @@ class Event(models.Model, RevisionMixin):
class EventItem(models.Model): class EventItem(models.Model):
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE) event = models.ForeignKey('Event', related_name='items', blank=True)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
quantity = models.IntegerField() quantity = models.IntegerField()
@@ -507,40 +493,42 @@ class EventItem(models.Model):
class EventCrew(models.Model): class EventCrew(models.Model):
event = models.ForeignKey('Event', related_name='crew', on_delete=models.CASCADE) event = models.ForeignKey('Event', related_name='crew')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) user = models.ForeignKey(settings.AUTH_USER_MODEL)
rig = models.BooleanField(default=False) rig = models.BooleanField(default=False)
run = models.BooleanField(default=False) run = models.BooleanField(default=False)
derig = models.BooleanField(default=False) derig = models.BooleanField(default=False)
notes = models.TextField(blank=True, null=True) notes = models.TextField(blank=True, null=True)
@reversion.register class InvoiceManager(models.Manager):
class EventAuthorisation(models.Model, RevisionMixin): def outstanding(self):
event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE) return self.annotate(
email = models.EmailField() _payment_total=models.Sum(models.F('payment__amount'))
name = models.CharField(max_length=255) ).annotate(
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID") _sum_total=models.Sum(models.F('event__items__cost') * models.F('event__items__quantity'),
account_code = models.CharField(max_length=50, blank=True, null=True) output_field=models.DecimalField(decimal_places=2))
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount") # ).annotate(
sent_by = models.ForeignKey('RIGS.Profile', on_delete=models.CASCADE) # _balance=models.ExpressionWrapper(models.F('_sum_total') - models.F('_payment_total'),
# models.DecimalField(decimal_places=2))
def get_absolute_url(self): # ).filter(
return reverse_lazy('event_detail', kwargs={'pk': self.event.pk}) # models.Q(_balance__isnull=True) |
# ~models.Q(_sum_total=models.F('_payment_total'))
@property )
def activity_feed_string(self):
return str("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')')
@python_2_unicode_compatible @python_2_unicode_compatible
class Invoice(models.Model): class Invoice(models.Model):
event = models.OneToOneField('Event', on_delete=models.CASCADE) event = models.OneToOneField('Event')
invoice_date = models.DateField(auto_now_add=True) invoice_date = models.DateField(auto_now_add=True)
void = models.BooleanField(default=False) void = models.BooleanField(default=False)
objects = InvoiceManager()
@property @property
def sum_total(self): def sum_total(self):
if getattr(self, '_sum_total', None):
return Decimal(getattr(self, '_sum_total'))
return self.event.sum_total return self.event.sum_total
@property @property
@@ -549,6 +537,10 @@ class Invoice(models.Model):
@property @property
def payment_total(self): def payment_total(self):
if hasattr(self, '_payment_total') and hasattr(self, '_payment_count'):
if getattr(self, '_payment_count') == 0:
return Decimal(0.00)
return Decimal(getattr(self, '_payment_total', 0.00))
total = self.payment_set.aggregate(total=models.Sum('amount'))['total'] total = self.payment_set.aggregate(total=models.Sum('amount'))['total']
if total: if total:
return total return total
@@ -587,7 +579,7 @@ class Payment(models.Model):
(ADJUSTMENT, 'TEC Adjustment'), (ADJUSTMENT, 'TEC Adjustment'),
) )
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE) invoice = models.ForeignKey('Invoice')
date = models.DateField() date = models.DateField()
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT') amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True) method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)

View File

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

View File

@@ -1,31 +1,23 @@
import os
import cStringIO as StringIO
from io import BytesIO from io import BytesIO
import urllib.request import urllib2
import urllib.error
import urllib.parse
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.views import generic from django.views import generic
from django.urls import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import get_template from django.template.loader import get_template
from django.conf import settings from django.conf import settings
from django.urls import reverse from django.core.urlresolvers import reverse
from django.core import signing
from django.http import HttpResponse from django.http import HttpResponse
from django.core.exceptions import SuspiciousOperation
from django.db.models import Q from django.db.models import Q
from django.contrib import messages from django.contrib import messages
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from z3c.rml import rml2pdf from z3c.rml import rml2pdf
from PyPDF2 import PdfFileMerger, PdfFileReader from PyPDF2 import PdfFileMerger, PdfFileReader
import simplejson import simplejson
import premailer
from RIGS import models, forms from RIGS import models, forms
from PyRIGS import decorators
import datetime import datetime
import re import re
import copy import copy
@@ -44,17 +36,15 @@ class RigboardIndex(generic.TemplateView):
context['events'] = models.Event.objects.current_events() context['events'] = models.Event.objects.current_events()
return context return context
class WebCalendar(generic.TemplateView): class WebCalendar(generic.TemplateView):
template_name = 'RIGS/calendar.html' template_name = 'RIGS/calendar.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(WebCalendar, self).get_context_data(**kwargs) context = super(WebCalendar, self).get_context_data(**kwargs)
context['view'] = kwargs.get('view', '') context['view'] = kwargs.get('view','')
context['date'] = kwargs.get('date', '') context['date'] = kwargs.get('date','')
return context return context
class EventDetail(generic.DetailView): class EventDetail(generic.DetailView):
model = models.Event model = models.Event
@@ -63,6 +53,7 @@ class EventOembed(generic.View):
model = models.Event model = models.Event
def get(self, request, pk=None): def get(self, request, pk=None):
embed_url = reverse('event_embed', args=[pk]) embed_url = reverse('event_embed', args=[pk])
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
@@ -70,7 +61,6 @@ class EventOembed(generic.View):
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url), 'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
'version': '1.0', 'version': '1.0',
'type': 'rich', 'type': 'rich',
'height': '250'
} }
json = simplejson.JSONEncoderForHTML().encode(data) json = simplejson.JSONEncoderForHTML().encode(data)
@@ -81,24 +71,6 @@ class EventEmbed(EventDetail):
template_name = 'RIGS/event_embed.html' template_name = 'RIGS/event_embed.html'
class EventRA(generic.base.RedirectView):
permanent = False
def get_redirect_url(self, *args, **kwargs):
event = get_object_or_404(models.Event, pk=kwargs['pk'])
if event.risk_assessment_edit_url:
return event.risk_assessment_edit_url
params = {
'entry.708610078': f'N{event.pk:05}',
'entry.905899507': event.name,
'entry.139491562': event.venue.name if event.venue else '',
'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + ((' - ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''),
'entry.902421165': event.mic.name if event.mic else ''
}
return settings.RISK_ASSESSMENT_URL + "?" + urllib.parse.urlencode(params)
class EventCreate(generic.CreateView): class EventCreate(generic.CreateView):
model = models.Event model = models.Event
form_class = forms.EventForm form_class = forms.EventForm
@@ -112,8 +84,9 @@ class EventCreate(generic.CreateView):
if re.search('"-\d+"', form['items_json'].value()): if re.search('"-\d+"', form['items_json'].value()):
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.") messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
# Get some other objects to include in the form. Used when there are errors but also nice and quick. # Get some other objects to include in the form. Used when there are errors but also nice and quick.
for field, model in form.related_models.items(): for field, model in form.related_models.iteritems():
value = form[field].value() value = form[field].value()
if value is not None and value != '': if value is not None and value != '':
context[field] = model.objects.get(pk=value) context[field] = model.objects.get(pk=value)
@@ -134,43 +107,24 @@ class EventUpdate(generic.UpdateView):
form = context['form'] form = context['form']
# Get some other objects to include in the form. Used when there are errors but also nice and quick. # Get some other objects to include in the form. Used when there are errors but also nice and quick.
for field, model in form.related_models.items(): for field, model in form.related_models.iteritems():
value = form[field].value() value = form[field].value()
if value is not None and value != '': if value is not None and value != '':
context[field] = model.objects.get(pk=value) context[field] = model.objects.get(pk=value)
# If this event has already been emailed to a client, show a warning
if self.object.auth_request_at is not None:
messages.info(self.request, 'This event has already been sent to the client for authorisation, any changes you make will be visible to them immediately.')
if hasattr(self.object, 'authorised'):
messages.warning(self.request, 'This event has already been authorised by client, any changes to price will require reauthorisation.')
return context return context
def get_success_url(self): def get_success_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.object.pk}) return reverse_lazy('event_detail', kwargs={'pk': self.object.pk})
class EventDuplicate(EventUpdate): class EventDuplicate(EventUpdate):
def get_object(self, queryset=None): def get_object(self, queryset=None):
old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating) old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating)
new = copy.copy(old) # Make a copy of the object in memory new = copy.copy(old) # Make a copy of the object in memory
new.based_on = old # Make the new event based on the old event new.based_on = old # Make the new event based on the old event
new.purchase_order = None # Remove old PO new.purchase_order = None
# Clear checked in by if it's a dry hire if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
if new.dry_hire is True: new.pk = None # This means a new event will be created on save, and all items will be re-created
new.checked_in_by = None
# Remove all the authorisation information from the new event
new.auth_request_to = None
new.auth_request_by = None
new.auth_request_at = None
if self.request.method in (
'POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
new.pk = None # This means a new event will be created on save, and all items will be re-created
else: else:
messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.') messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.')
@@ -181,34 +135,41 @@ class EventDuplicate(EventUpdate):
context["duplicate"] = True context["duplicate"] = True
return context return context
class EventPrint(generic.View): class EventPrint(generic.View):
def get(self, request, pk): def get(self, request, pk):
object = get_object_or_404(models.Event, pk=pk) object = get_object_or_404(models.Event, pk=pk)
template = get_template('RIGS/event_print.xml') template = get_template('RIGS/event_print.xml')
copies = ('TEC', 'Client')
merger = PdfFileMerger() merger = PdfFileMerger()
context = { for copy in copies:
'object': object,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'quote': True,
'current_user': request.user,
}
rml = template.render(context) 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,
'current_user':request.user,
})
buffer = rml2pdf.parseString(rml) # context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3
merger.append(PdfFileReader(buffer))
buffer.close()
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL) rml = template.render(context)
merger.append(BytesIO(terms.read())) buffer = StringIO.StringIO()
buffer = rml2pdf.parseString(rml)
merger.append(PdfFileReader(buffer))
buffer.close()
terms = urllib2.urlopen(settings.TERMS_OF_HIRE_URL)
merger.append(StringIO.StringIO(terms.read()))
merged = BytesIO() merged = BytesIO()
merger.write(merged) merger.write(merged)
@@ -221,7 +182,6 @@ class EventPrint(generic.View):
response.write(merged.getvalue()) response.write(merged.getvalue())
return response return response
class EventArchive(generic.ArchiveIndexView): class EventArchive(generic.ArchiveIndexView):
model = models.Event model = models.Event
date_field = "start_date" date_field = "start_date"
@@ -258,173 +218,3 @@ class EventArchive(generic.ArchiveIndexView):
messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.") messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.")
return qs return qs
class EventAuthorise(generic.UpdateView):
template_name = 'RIGS/eventauthorisation_form.html'
success_template = 'RIGS/eventauthorisation_success.html'
def form_valid(self, form):
self.object = form.save()
self.template_name = self.success_template
messages.add_message(self.request, messages.SUCCESS,
'Success! Your event has been authorised. ' +
'You will also receive email confirmation to %s.' % (self.object.email))
return self.render_to_response(self.get_context_data())
@property
def event(self):
return models.Event.objects.select_related('organisation', 'person', 'venue').get(pk=self.kwargs['pk'])
def get_object(self, queryset=None):
return getattr(self.event, 'authorisation', None)
def get_form_class(self):
return forms.InternalClientEventAuthorisationForm
def get_context_data(self, **kwargs):
context = super(EventAuthorise, self).get_context_data(**kwargs)
context['event'] = self.event
context['tos_url'] = settings.TERMS_OF_HIRE_URL
return context
def get(self, request, *args, **kwargs):
if self.get_object() is not None and self.get_object().pk is not None:
if self.event.authorised:
messages.add_message(self.request, messages.WARNING,
"This event has already been authorised. "
"Reauthorising is not necessary at this time.")
else:
messages.add_message(self.request, messages.WARNING,
"This event has already been authorised, but the amount has changed. " +
"Please check the amount and reauthorise.")
return super(EventAuthorise, self).get(request, *args, **kwargs)
def get_form(self, **kwargs):
form = super(EventAuthorise, self).get_form(**kwargs)
form.instance.event = self.event
form.instance.email = self.request.email
form.instance.sent_by = self.request.sent_by
return form
def dispatch(self, request, *args, **kwargs):
# Verify our signature matches up and all is well with the integrity of the URL
try:
data = signing.loads(kwargs.get('hmac'))
assert int(kwargs.get('pk')) == int(data.get('pk'))
request.email = data['email']
request.sent_by = models.Profile.objects.get(pk=data['sent_by'])
except (signing.BadSignature, AssertionError, KeyError, models.Profile.DoesNotExist):
raise SuspiciousOperation(
"This URL is invalid. Please ask your TEC contact for a new URL")
return super(EventAuthorise, self).dispatch(request, *args, **kwargs)
class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin):
model = models.Event
form_class = forms.EventAuthorisationRequestForm
template_name = 'RIGS/eventauthorisation_request.html'
@method_decorator(decorators.nottinghamtec_address_required)
def dispatch(self, *args, **kwargs):
return super(EventAuthorisationRequest, self).dispatch(*args, **kwargs)
@property
def object(self):
return self.get_object()
def get_success_url(self):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
messages.info(self.request, "location.reload()")
else:
url = reverse_lazy('event_detail', kwargs={
'pk': self.object.pk,
})
messages.add_message(self.request, messages.SUCCESS, "Authorisation request successfully sent.")
return url
def form_valid(self, form):
email = form.cleaned_data['email']
event = self.object
event.auth_request_by = self.request.user
event.auth_request_at = datetime.datetime.now()
event.auth_request_to = email
event.save()
context = {
'object': self.object,
'request': self.request,
'hmac': signing.dumps({
'pk': self.object.pk,
'email': email,
'sent_by': self.request.user.pk,
}),
}
if event.person is not None and email == event.person.email:
context['to_name'] = event.person.name
elif event.organisation is not None and email == event.organisation.email:
context['to_name'] = event.organisation.name
msg = EmailMultiAlternatives(
"N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name),
get_template("RIGS/eventauthorisation_client_request.txt").render(context),
to=[email],
reply_to=[self.request.user.email],
)
css = staticfiles_storage.path('css/email.css')
html = premailer.Premailer(get_template("RIGS/eventauthorisation_client_request.html").render(context),
external_styles=css).transform()
msg.attach_alternative(html, 'text/html')
msg.send()
return super(EventAuthorisationRequest, self).form_valid(form)
class EventAuthoriseRequestEmailPreview(generic.DetailView):
template_name = "RIGS/eventauthorisation_client_request.html"
model = models.Event
def render_to_response(self, context, **response_kwargs):
from django.contrib.staticfiles.storage import staticfiles_storage
css = staticfiles_storage.path('css/email.css')
response = super(EventAuthoriseRequestEmailPreview, self).render_to_response(context, **response_kwargs)
assert isinstance(response, HttpResponse)
response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
return response
def get_context_data(self, **kwargs):
context = super(EventAuthoriseRequestEmailPreview, self).get_context_data(**kwargs)
context['hmac'] = signing.dumps({
'pk': self.object.pk,
'email': self.request.GET.get('email', 'hello@world.test'),
'sent_by': self.request.user.pk,
})
context['to_name'] = self.request.GET.get('to_name', None)
return context
@method_decorator(csrf_exempt, name='dispatch')
class LogRiskAssessment(generic.View):
http_method_names = ["post"]
def post(self, request, **kwargs):
data = request.POST
shared_secret = data.get("secret")
edit_url = data.get("editUrl")
rig_number = data.get("rigNum")
if shared_secret is None or edit_url is None or rig_number is None:
return HttpResponse(status=422)
if shared_secret != settings.RISK_ASSESSMENT_SECRET:
return HttpResponse(status=403)
rig_number = int(re.sub("[^0-9]", "", rig_number))
event = get_object_or_404(models.Event, pk=rig_number)
event.risk_assessment_edit_url = edit_url
event.save()
return HttpResponse(status=200)

View File

@@ -1,104 +0,0 @@
import re
import urllib.request
import urllib.error
import urllib.parse
from io import BytesIO
from django.db.models.signals import post_save
from PyPDF2 import PdfFileReader, PdfFileMerger
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.template.loader import get_template
from premailer import Premailer
from z3c.rml import rml2pdf
from RIGS import models
def send_eventauthorisation_success_email(instance):
# Generate PDF first to prevent context conflicts
context = {
'object': instance.event,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'receipt': True,
'current_user': False,
}
template = get_template('RIGS/event_print.xml')
merger = PdfFileMerger()
rml = template.render(context)
buffer = rml2pdf.parseString(rml)
merger.append(PdfFileReader(buffer))
buffer.close()
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
merger.append(BytesIO(terms.read()))
merged = BytesIO()
merger.write(merged)
# Produce email content
context = {
'object': instance,
}
if instance.event.person is not None and instance.email == instance.event.person.email:
context['to_name'] = instance.event.person.name
elif instance.event.organisation is not None and instance.email == instance.event.organisation.email:
context['to_name'] = instance.event.organisation.name
subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name)
client_email = EmailMultiAlternatives(
subject,
get_template("RIGS/eventauthorisation_client_success.txt").render(context),
to=[instance.email],
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
)
css = staticfiles_storage.path('css/email.css')
html = Premailer(get_template("RIGS/eventauthorisation_client_success.html").render(context),
external_styles=css).transform()
client_email.attach_alternative(html, 'text/html')
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', instance.event.name)
client_email.attach('N%05d - %s - CONFIRMATION.pdf' % (instance.event.pk, escapedEventName),
merged.getvalue(),
'application/pdf'
)
if instance.event.mic:
mic_email_address = instance.event.mic.email
else:
mic_email_address = settings.AUTHORISATION_NOTIFICATION_ADDRESS
mic_email = EmailMessage(
subject,
get_template("RIGS/eventauthorisation_mic_success.txt").render(context),
to=[mic_email_address]
)
# Now we have both emails successfully generated, send them out
client_email.send(fail_silently=True)
mic_email.send(fail_silently=True)
# Set event to booked now that it's authorised
instance.event.status = models.Event.BOOKED
instance.event.save()
def on_revision_commit(sender, instance, created, **kwargs):
if created:
send_eventauthorisation_success_email(instance)
post_save.connect(on_revision_commit, sender=models.EventAuthorisation)

12
RIGS/static/css/bootstrap-select.min.css vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
body{margin:0px}.main-table{width:100%;border-collapse:collapse}.client-header{background-image:url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");background-color:#222;background-repeat:no-repeat;background-position:center;width:100%;margin-bottom:28px}.client-header .logos{width:100%;max-width:640px}.client-header img{height:110px}.content-container{width:100%}.content-container .content{font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;width:100%;max-width:600px;padding:10px;text-align:left}.content-container .content .button-container{width:100%}.content-container .content .button-container .button{padding:6px 12px;background-color:#357ebf;border-radius:4px}.content-container .content .button-container .button a{color:#fff;text-decoration:none}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

12
RIGS/static/js/affix.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: affix.js v3.3.7 * Bootstrap: affix.js v3.3.2
* http://getbootstrap.com/javascript/#affix * http://getbootstrap.com/javascript/#affix
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -21,14 +21,14 @@
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
this.$element = $(element) this.$element = $(element)
this.affixed = null this.affixed =
this.unpin = null this.unpin =
this.pinnedOffset = null this.pinnedOffset = null
this.checkPosition() this.checkPosition()
} }
Affix.VERSION = '3.3.7' Affix.VERSION = '3.3.2'
Affix.RESET = 'affix affix-top affix-bottom' Affix.RESET = 'affix affix-top affix-bottom'
@@ -78,7 +78,7 @@
var offset = this.options.offset var offset = this.options.offset
var offsetTop = offset.top var offsetTop = offset.top
var offsetBottom = offset.bottom var offsetBottom = offset.bottom
var scrollHeight = Math.max($(document).height(), $(document.body).height()) var scrollHeight = $('body').height()
if (typeof offset != 'object') offsetBottom = offsetTop = offset if (typeof offset != 'object') offsetBottom = offsetTop = offset
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)

84
RIGS/static/js/ajax-bootstrap-select.js Normal file → Executable file
View File

@@ -3,16 +3,16 @@
* *
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON. * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
* *
* @version 1.4.1 * @version 1.3.1
* @author Adam Heim - https://github.com/truckingsim * @author Adam Heim - https://github.com/truckingsim
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
* @copyright 2017 Adam Heim * @copyright 2015 Adam Heim
* @license Released under the MIT license. * @license Released under the MIT license.
* *
* Contributors: * Contributors:
* Mark Carver - https://github.com/markcarver * Mark Carver - https://github.com/markcarver
* *
* Last build: 2017-07-21 1:08:54 PM GMT-0400 * Last build: 2015-01-06 8:43:11 PM EST
*/ */
!(function ($, window) { !(function ($, window) {
@@ -186,25 +186,10 @@ var AjaxBootstrapSelect = function (element, options) {
if (dataKeys.length) { if (dataKeys.length) {
// Object containing the data attribute options. // Object containing the data attribute options.
var dataOptions = {}; var dataOptions = {};
var flattenedOptions = ['locale'];
for (i = 0, l = dataKeys.length; i < l; i++) { for (i = 0, l = dataKeys.length; i < l; i++) {
var name = dataKeys[i].replace(/^abs([A-Z])/, matchToLowerCase).replace(/([A-Z])/g, '-$1').toLowerCase(); var name = dataKeys[i].replace(/^abs([A-Z])/, matchToLowerCase).replace(/([A-Z])/g, '-$1').toLowerCase();
var keys = name.split('-');
// Certain options should be flattened to a single object
// and not fully expanded (such as Locale).
if (keys[0] && keys.length > 1 && flattenedOptions.indexOf(keys[0]) !== -1) {
var newKeys = [keys.shift()];
var property = '';
// Combine the remaining keys as a single property.
for (var ii = 0; ii < keys.length; ii++) {
property += (ii === 0 ? keys[ii] : keys[ii].charAt(0).toUpperCase() + keys[ii].slice(1));
}
newKeys.push(property);
keys = newKeys;
}
this.log(this.LOG_DEBUG, 'Processing data attribute "data-abs-' + name + '":', data[dataKeys[i]]); this.log(this.LOG_DEBUG, 'Processing data attribute "data-abs-' + name + '":', data[dataKeys[i]]);
expandObject(keys, data[dataKeys[i]], dataOptions); expandObject(name.split('-'), data[dataKeys[i]], dataOptions);
} }
this.options = $.extend(true, {}, this.options, dataOptions); this.options = $.extend(true, {}, this.options, dataOptions);
this.log(this.LOG_DEBUG, 'Merged in the data attribute options: ', dataOptions, this.options); this.log(this.LOG_DEBUG, 'Merged in the data attribute options: ', dataOptions, this.options);
@@ -330,12 +315,6 @@ AjaxBootstrapSelect.prototype.init = function () {
plugin.log(plugin.LOG_DEBUG, 'Key ignored.'); plugin.log(plugin.LOG_DEBUG, 'Key ignored.');
return; return;
} }
// Don't process if below minimum query length
if (query.length < plugin.options.minLength) {
plugin.list.setStatus(plugin.t('statusTooShort'));
return;
}
// Clear out any existing timer. // Clear out any existing timer.
clearTimeout(requestDelayTimer); clearTimeout(requestDelayTimer);
@@ -594,25 +573,8 @@ var AjaxBootstrapSelectList = function (plugin) {
this.title = null; this.title = null;
this.selectedTextFormat = plugin.selectpicker.options.selectedTextFormat; this.selectedTextFormat = plugin.selectpicker.options.selectedTextFormat;
// Save initial options
var initial_options = [];
plugin.$element.find('option').each(function() {
var $option = $(this);
var value = $option.attr('value');
initial_options.push({
value: value,
text: $option.text(),
'class': $option.attr('class') || '',
data: $option.data() || {},
preserved: plugin.options.preserveSelected,
selected: !!$option.attr('selected')
});
});
this.cacheSet(/*query=*/'', initial_options);
// Preserve selected options. // Preserve selected options.
if (plugin.options.preserveSelected) { if (plugin.options.preserveSelected) {
that.selected = initial_options;
plugin.$element.on('change.abs.preserveSelected', function (e) { plugin.$element.on('change.abs.preserveSelected', function (e) {
var $selected = plugin.$element.find(':selected'); var $selected = plugin.$element.find(':selected');
that.selected = []; that.selected = [];
@@ -682,7 +644,7 @@ AjaxBootstrapSelectList.prototype.build = function (data) {
} }
// Set various properties. // Set various properties.
$option.val(item.value).text(item.text).attr('title', item.text); $option.val(item.value).text(item.text);
if (item['class'].length) { if (item['class'].length) {
$option.attr('class', item['class']); $option.attr('class', item['class']);
} }
@@ -770,13 +732,7 @@ AjaxBootstrapSelectList.prototype.refresh = function (triggerChange) {
if (!this.plugin.$element.find('option').length && emptyTitle && emptyTitle.length) { if (!this.plugin.$element.find('option').length && emptyTitle && emptyTitle.length) {
this.setTitle(emptyTitle); this.setTitle(emptyTitle);
} }
else if ( else if (this.title) {
this.title ||
(
this.selectedTextFormat !== 'static' &&
this.selectedTextFormat !== this.plugin.selectpicker.options.selectedTextFormat
)
) {
this.restoreTitle(); this.restoreTitle();
} }
this.plugin.selectpicker.refresh(); this.plugin.selectpicker.refresh();
@@ -1268,14 +1224,6 @@ $.fn.ajaxSelectPicker.defaults = {
} }
}, },
/**
* @member $.fn.ajaxSelectPicker.defaults
* @cfg {Number} minLength = 0
* @markdown
* Invoke a request for empty search values.
*/
minLength: 0,
/** /**
* @member $.fn.ajaxSelectPicker.defaults * @member $.fn.ajaxSelectPicker.defaults
* @cfg {String} ajaxSearchUrl * @cfg {String} ajaxSearchUrl
@@ -1348,7 +1296,8 @@ $.fn.ajaxSelectPicker.defaults = {
* 39: "right", * 39: "right",
* 38: "up", * 38: "up",
* 40: "down", * 40: "down",
* 91: "meta" * 91: "meta",
* 229: "unknown"
* } * }
* ``` * ```
*/ */
@@ -1362,7 +1311,8 @@ $.fn.ajaxSelectPicker.defaults = {
39: "right", 39: "right",
38: "up", 38: "up",
40: "down", 40: "down",
91: "meta" 91: "meta",
229: "unknown"
}, },
/** /**
@@ -1456,7 +1406,7 @@ $.fn.ajaxSelectPicker.defaults = {
* } * }
* ``` * ```
*/ */
preprocessData: function () { }, preprocessData: function(){},
/** /**
* @member $.fn.ajaxSelectPicker.defaults * @member $.fn.ajaxSelectPicker.defaults
@@ -1484,7 +1434,7 @@ $.fn.ajaxSelectPicker.defaults = {
* @markdown * @markdown
* Process the data returned after this plugin, but before the list is built. * Process the data returned after this plugin, but before the list is built.
*/ */
processData: function () { }, processData: function(){},
/** /**
* @member $.fn.ajaxSelectPicker.defaults * @member $.fn.ajaxSelectPicker.defaults
@@ -1597,15 +1547,7 @@ $.fn.ajaxSelectPicker.locale['en-US'] = {
* @markdown * @markdown
* The text to use in the status container when a request is being initiated. * The text to use in the status container when a request is being initiated.
*/ */
statusSearching: 'Searching...', statusSearching: 'Searching...'
/**
* @member $.fn.ajaxSelectPicker.locale
* @cfg {String} statusToShort = 'Please enter more characters'
* @markdown
* The text used in the status container when the request returns no results.
*/
statusTooShort: 'Please enter more characters'
}; };
$.fn.ajaxSelectPicker.locale.en = $.fn.ajaxSelectPicker.locale['en-US']; $.fn.ajaxSelectPicker.locale.en = $.fn.ajaxSelectPicker.locale['en-US'];

8
RIGS/static/js/alert.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: alert.js v3.3.7 * Bootstrap: alert.js v3.3.2
* http://getbootstrap.com/javascript/#alerts * http://getbootstrap.com/javascript/#alerts
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -18,7 +18,7 @@
$(el).on('click', dismiss, this.close) $(el).on('click', dismiss, this.close)
} }
Alert.VERSION = '3.3.7' Alert.VERSION = '3.3.2'
Alert.TRANSITION_DURATION = 150 Alert.TRANSITION_DURATION = 150
@@ -31,7 +31,7 @@
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
} }
var $parent = $(selector === '#' ? [] : selector) var $parent = $(selector)
if (e) e.preventDefault() if (e) e.preventDefault()

File diff suppressed because it is too large Load Diff

37
RIGS/static/js/button.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: button.js v3.3.7 * Bootstrap: button.js v3.3.2
* http://getbootstrap.com/javascript/#buttons * http://getbootstrap.com/javascript/#buttons
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -19,7 +19,7 @@
this.isLoading = false this.isLoading = false
} }
Button.VERSION = '3.3.7' Button.VERSION = '3.3.2'
Button.DEFAULTS = { Button.DEFAULTS = {
loadingText: 'loading...' loadingText: 'loading...'
@@ -31,7 +31,7 @@
var val = $el.is('input') ? 'val' : 'html' var val = $el.is('input') ? 'val' : 'html'
var data = $el.data() var data = $el.data()
state += 'Text' state = state + 'Text'
if (data.resetText == null) $el.data('resetText', $el[val]()) if (data.resetText == null) $el.data('resetText', $el[val]())
@@ -41,10 +41,10 @@
if (state == 'loadingText') { if (state == 'loadingText') {
this.isLoading = true this.isLoading = true
$el.addClass(d).attr(d, d).prop(d, true) $el.addClass(d).attr(d, d)
} else if (this.isLoading) { } else if (this.isLoading) {
this.isLoading = false this.isLoading = false
$el.removeClass(d).removeAttr(d).prop(d, false) $el.removeClass(d).removeAttr(d)
} }
}, this), 0) }, this), 0)
} }
@@ -56,19 +56,15 @@
if ($parent.length) { if ($parent.length) {
var $input = this.$element.find('input') var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') { if ($input.prop('type') == 'radio') {
if ($input.prop('checked')) changed = false if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
$parent.find('.active').removeClass('active') else $parent.find('.active').removeClass('active')
this.$element.addClass('active')
} else if ($input.prop('type') == 'checkbox') {
if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
this.$element.toggleClass('active')
} }
$input.prop('checked', this.$element.hasClass('active')) if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
if (changed) $input.trigger('change')
} else { } else {
this.$element.attr('aria-pressed', !this.$element.hasClass('active')) this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
this.$element.toggleClass('active')
} }
if (changed) this.$element.toggleClass('active')
} }
@@ -108,15 +104,10 @@
$(document) $(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target).closest('.btn') var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
Plugin.call($btn, 'toggle') Plugin.call($btn, 'toggle')
if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { e.preventDefault()
// Prevent double click on radios, and the double selections (so cancellation) on checkboxes
e.preventDefault()
// The target component still receive the focus
if ($btn.is('input,button')) $btn.trigger('focus')
else $btn.find('input:visible,button:visible').first().trigger('focus')
}
}) })
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))

14
RIGS/static/js/carousel.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: carousel.js v3.3.7 * Bootstrap: carousel.js v3.3.2
* http://getbootstrap.com/javascript/#carousel * http://getbootstrap.com/javascript/#carousel
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -17,10 +17,10 @@
this.$element = $(element) this.$element = $(element)
this.$indicators = this.$element.find('.carousel-indicators') this.$indicators = this.$element.find('.carousel-indicators')
this.options = options this.options = options
this.paused = null this.paused =
this.sliding = null this.sliding =
this.interval = null this.interval =
this.$active = null this.$active =
this.$items = null this.$items = null
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
@@ -30,7 +30,7 @@
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
} }
Carousel.VERSION = '3.3.7' Carousel.VERSION = '3.3.2'
Carousel.TRANSITION_DURATION = 600 Carousel.TRANSITION_DURATION = 600

17
RIGS/static/js/collapse.js Executable file → Normal file
View File

@@ -1,12 +1,11 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: collapse.js v3.3.7 * Bootstrap: collapse.js v3.3.2
* http://getbootstrap.com/javascript/#collapse * http://getbootstrap.com/javascript/#collapse
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
/* jshint latedef: false */
+function ($) { +function ($) {
'use strict'; 'use strict';
@@ -17,8 +16,7 @@
var Collapse = function (element, options) { var Collapse = function (element, options) {
this.$element = $(element) this.$element = $(element)
this.options = $.extend({}, Collapse.DEFAULTS, options) this.options = $.extend({}, Collapse.DEFAULTS, options)
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + this.$trigger = $(this.options.trigger).filter('[href="#' + element.id + '"], [data-target="#' + element.id + '"]')
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
this.transitioning = null this.transitioning = null
if (this.options.parent) { if (this.options.parent) {
@@ -30,12 +28,13 @@
if (this.options.toggle) this.toggle() if (this.options.toggle) this.toggle()
} }
Collapse.VERSION = '3.3.7' Collapse.VERSION = '3.3.2'
Collapse.TRANSITION_DURATION = 350 Collapse.TRANSITION_DURATION = 350
Collapse.DEFAULTS = { Collapse.DEFAULTS = {
toggle: true toggle: true,
trigger: '[data-toggle="collapse"]'
} }
Collapse.prototype.dimension = function () { Collapse.prototype.dimension = function () {
@@ -173,7 +172,7 @@
var data = $this.data('bs.collapse') var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false if (!data && options.toggle && option == 'show') options.toggle = false
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]() if (typeof option == 'string') data[option]()
}) })
@@ -204,7 +203,7 @@
var $target = getTargetFromTrigger($this) var $target = getTargetFromTrigger($this)
var data = $target.data('bs.collapse') var data = $target.data('bs.collapse')
var option = data ? 'toggle' : $this.data() var option = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this })
Plugin.call($target, option) Plugin.call($target, option)
}) })

94
RIGS/static/js/dropdown.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: dropdown.js v3.3.7 * Bootstrap: dropdown.js v3.3.2
* http://getbootstrap.com/javascript/#dropdowns * http://getbootstrap.com/javascript/#dropdowns
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -19,41 +19,7 @@
$(element).on('click.bs.dropdown', this.toggle) $(element).on('click.bs.dropdown', this.toggle)
} }
Dropdown.VERSION = '3.3.7' Dropdown.VERSION = '3.3.2'
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
return $parent && $parent.length ? $parent : $this.parent()
}
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
})
}
Dropdown.prototype.toggle = function (e) { Dropdown.prototype.toggle = function (e) {
var $this = $(this) var $this = $(this)
@@ -68,10 +34,7 @@
if (!isActive) { if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate // if mobile we use a backdrop because click events don't delegate
$(document.createElement('div')) $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
.addClass('dropdown-backdrop')
.insertAfter($(this))
.on('click', clearMenus)
} }
var relatedTarget = { relatedTarget: this } var relatedTarget = { relatedTarget: this }
@@ -85,7 +48,7 @@
$parent $parent
.toggleClass('open') .toggleClass('open')
.trigger($.Event('shown.bs.dropdown', relatedTarget)) .trigger('shown.bs.dropdown', relatedTarget)
} }
return false return false
@@ -104,25 +67,57 @@
var $parent = getParent($this) var $parent = getParent($this)
var isActive = $parent.hasClass('open') var isActive = $parent.hasClass('open')
if (!isActive && e.which != 27 || isActive && e.which == 27) { if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
if (e.which == 27) $parent.find(toggle).trigger('focus') if (e.which == 27) $parent.find(toggle).trigger('focus')
return $this.trigger('click') return $this.trigger('click')
} }
var desc = ' li:not(.disabled):visible a' var desc = ' li:not(.divider):visible a'
var $items = $parent.find('.dropdown-menu' + desc) var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
if (!$items.length) return if (!$items.length) return
var index = $items.index(e.target) var index = $items.index(e.target)
if (e.which == 38 && index > 0) index-- // up if (e.which == 38 && index > 0) index-- // up
if (e.which == 40 && index < $items.length - 1) index++ // down if (e.which == 40 && index < $items.length - 1) index++ // down
if (!~index) index = 0 if (!~index) index = 0
$items.eq(index).trigger('focus') $items.eq(index).trigger('focus')
} }
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this.attr('aria-expanded', 'false')
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
})
}
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
return $parent && $parent.length ? $parent : $this.parent()
}
// DROPDOWN PLUGIN DEFINITION // DROPDOWN PLUGIN DEFINITION
// ========================== // ==========================
@@ -160,6 +155,7 @@
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
}(jQuery); }(jQuery);

View File

@@ -136,4 +136,4 @@ $("#item-table tbody").sortable({
}); });
} }
}); });

10
RIGS/static/js/modal.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: modal.js v3.3.7 * Bootstrap: modal.js v3.3.5
* http://getbootstrap.com/javascript/#modals * http://getbootstrap.com/javascript/#modals
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -33,7 +33,7 @@
} }
} }
Modal.VERSION = '3.3.7' Modal.VERSION = '3.3.5'
Modal.TRANSITION_DURATION = 300 Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150 Modal.BACKDROP_TRANSITION_DURATION = 150
@@ -140,9 +140,7 @@
$(document) $(document)
.off('focusin.bs.modal') // guard against infinite focus loop .off('focusin.bs.modal') // guard against infinite focus loop
.on('focusin.bs.modal', $.proxy(function (e) { .on('focusin.bs.modal', $.proxy(function (e) {
if (document !== e.target && if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
this.$element[0] !== e.target &&
!this.$element.has(e.target).length) {
this.$element.trigger('focus') this.$element.trigger('focus')
} }
}, this)) }, this))

13
RIGS/static/js/popover.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: popover.js v3.3.7 * Bootstrap: popover.js v3.3.2
* http://getbootstrap.com/javascript/#popovers * http://getbootstrap.com/javascript/#popovers
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -19,7 +19,7 @@
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
Popover.VERSION = '3.3.7' Popover.VERSION = '3.3.2'
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
placement: 'right', placement: 'right',
@@ -75,6 +75,11 @@
return (this.$arrow = this.$arrow || this.tip().find('.arrow')) return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
} }
Popover.prototype.tip = function () {
if (!this.$tip) this.$tip = $(this.options.template)
return this.$tip
}
// POPOVER PLUGIN DEFINITION // POPOVER PLUGIN DEFINITION
// ========================= // =========================
@@ -85,7 +90,7 @@
var data = $this.data('bs.popover') var data = $this.data('bs.popover')
var options = typeof option == 'object' && option var options = typeof option == 'object' && option
if (!data && /destroy|hide/.test(option)) return if (!data && option == 'destroy') return
if (!data) $this.data('bs.popover', (data = new Popover(this, options))) if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
if (typeof option == 'string') data[option]() if (typeof option == 'string') data[option]()
}) })

39
RIGS/static/js/scrollspy.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: scrollspy.js v3.3.7 * Bootstrap: scrollspy.js v3.3.2
* http://getbootstrap.com/javascript/#scrollspy * http://getbootstrap.com/javascript/#scrollspy
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -14,8 +14,10 @@
// ========================== // ==========================
function ScrollSpy(element, options) { function ScrollSpy(element, options) {
this.$body = $(document.body) var process = $.proxy(this.process, this)
this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
this.$body = $('body')
this.$scrollElement = $(element).is('body') ? $(window) : $(element)
this.options = $.extend({}, ScrollSpy.DEFAULTS, options) this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
this.selector = (this.options.target || '') + ' .nav li > a' this.selector = (this.options.target || '') + ' .nav li > a'
this.offsets = [] this.offsets = []
@@ -23,12 +25,12 @@
this.activeTarget = null this.activeTarget = null
this.scrollHeight = 0 this.scrollHeight = 0
this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) this.$scrollElement.on('scroll.bs.scrollspy', process)
this.refresh() this.refresh()
this.process() this.process()
} }
ScrollSpy.VERSION = '3.3.7' ScrollSpy.VERSION = '3.3.2'
ScrollSpy.DEFAULTS = { ScrollSpy.DEFAULTS = {
offset: 10 offset: 10
@@ -39,19 +41,20 @@
} }
ScrollSpy.prototype.refresh = function () { ScrollSpy.prototype.refresh = function () {
var that = this var offsetMethod = 'offset'
var offsetMethod = 'offset' var offsetBase = 0
var offsetBase = 0
this.offsets = []
this.targets = []
this.scrollHeight = this.getScrollHeight()
if (!$.isWindow(this.$scrollElement[0])) { if (!$.isWindow(this.$scrollElement[0])) {
offsetMethod = 'position' offsetMethod = 'position'
offsetBase = this.$scrollElement.scrollTop() offsetBase = this.$scrollElement.scrollTop()
} }
this.offsets = []
this.targets = []
this.scrollHeight = this.getScrollHeight()
var self = this
this.$body this.$body
.find(this.selector) .find(this.selector)
.map(function () { .map(function () {
@@ -66,8 +69,8 @@
}) })
.sort(function (a, b) { return a[0] - b[0] }) .sort(function (a, b) { return a[0] - b[0] })
.each(function () { .each(function () {
that.offsets.push(this[0]) self.offsets.push(this[0])
that.targets.push(this[1]) self.targets.push(this[1])
}) })
} }
@@ -96,7 +99,7 @@
for (i = offsets.length; i--;) { for (i = offsets.length; i--;) {
activeTarget != targets[i] activeTarget != targets[i]
&& scrollTop >= offsets[i] && scrollTop >= offsets[i]
&& (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
&& this.activate(targets[i]) && this.activate(targets[i])
} }
} }
@@ -107,8 +110,8 @@
this.clear() this.clear()
var selector = this.selector + var selector = this.selector +
'[data-target="' + target + '"],' + '[data-target="' + target + '"],' +
this.selector + '[href="' + target + '"]' this.selector + '[href="' + target + '"]'
var active = $(selector) var active = $(selector)
.parents('li') .parents('li')

12
RIGS/static/js/tab.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: tab.js v3.3.7 * Bootstrap: tab.js v3.3.2
* http://getbootstrap.com/javascript/#tabs * http://getbootstrap.com/javascript/#tabs
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -14,12 +14,10 @@
// ==================== // ====================
var Tab = function (element) { var Tab = function (element) {
// jscs:disable requireDollarBeforejQueryAssignment
this.element = $(element) this.element = $(element)
// jscs:enable requireDollarBeforejQueryAssignment
} }
Tab.VERSION = '3.3.7' Tab.VERSION = '3.3.2'
Tab.TRANSITION_DURATION = 150 Tab.TRANSITION_DURATION = 150
@@ -67,7 +65,7 @@
var $active = container.find('> .active') var $active = container.find('> .active')
var transition = callback var transition = callback
&& $.support.transition && $.support.transition
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)
function next() { function next() {
$active $active
@@ -90,7 +88,7 @@
element.removeClass('fade') element.removeClass('fade')
} }
if (element.parent('.dropdown-menu').length) { if (element.parent('.dropdown-menu')) {
element element
.closest('li.dropdown') .closest('li.dropdown')
.addClass('active') .addClass('active')

120
RIGS/static/js/tooltip.js Executable file → Normal file
View File

@@ -1,9 +1,9 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: tooltip.js v3.3.7 * Bootstrap: tooltip.js v3.3.2
* http://getbootstrap.com/javascript/#tooltip * http://getbootstrap.com/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame * Inspired by the original jQuery.tipsy by Jason Frame
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */
@@ -15,18 +15,17 @@
// =============================== // ===============================
var Tooltip = function (element, options) { var Tooltip = function (element, options) {
this.type = null this.type =
this.options = null this.options =
this.enabled = null this.enabled =
this.timeout = null this.timeout =
this.hoverState = null this.hoverState =
this.$element = null this.$element = null
this.inState = null
this.init('tooltip', element, options) this.init('tooltip', element, options)
} }
Tooltip.VERSION = '3.3.7' Tooltip.VERSION = '3.3.2'
Tooltip.TRANSITION_DURATION = 150 Tooltip.TRANSITION_DURATION = 150
@@ -51,12 +50,7 @@
this.type = type this.type = type
this.$element = $(element) this.$element = $(element)
this.options = this.getOptions(options) this.options = this.getOptions(options)
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
this.inState = { click: false, hover: false, focus: false }
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
}
var triggers = this.options.trigger.split(' ') var triggers = this.options.trigger.split(' ')
@@ -111,20 +105,16 @@
var self = obj instanceof this.constructor ? var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type) obj : $(obj.currentTarget).data('bs.' + this.type)
if (self && self.$tip && self.$tip.is(':visible')) {
self.hoverState = 'in'
return
}
if (!self) { if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
$(obj.currentTarget).data('bs.' + this.type, self) $(obj.currentTarget).data('bs.' + this.type, self)
} }
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
}
if (self.tip().hasClass('in') || self.hoverState == 'in') {
self.hoverState = 'in'
return
}
clearTimeout(self.timeout) clearTimeout(self.timeout)
self.hoverState = 'in' self.hoverState = 'in'
@@ -136,14 +126,6 @@
}, self.options.delay.show) }, self.options.delay.show)
} }
Tooltip.prototype.isInStateTrue = function () {
for (var key in this.inState) {
if (this.inState[key]) return true
}
return false
}
Tooltip.prototype.leave = function (obj) { Tooltip.prototype.leave = function (obj) {
var self = obj instanceof this.constructor ? var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type) obj : $(obj.currentTarget).data('bs.' + this.type)
@@ -153,12 +135,6 @@
$(obj.currentTarget).data('bs.' + this.type, self) $(obj.currentTarget).data('bs.' + this.type, self)
} }
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
}
if (self.isInStateTrue()) return
clearTimeout(self.timeout) clearTimeout(self.timeout)
self.hoverState = 'out' self.hoverState = 'out'
@@ -205,7 +181,6 @@
.data('bs.' + this.type, this) .data('bs.' + this.type, this)
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
this.$element.trigger('inserted.bs.' + this.type)
var pos = this.getPosition() var pos = this.getPosition()
var actualWidth = $tip[0].offsetWidth var actualWidth = $tip[0].offsetWidth
@@ -213,12 +188,13 @@
if (autoPlace) { if (autoPlace) {
var orgPlacement = placement var orgPlacement = placement
var viewportDim = this.getPosition(this.$viewport) var $container = this.options.container ? $(this.options.container) : this.$element.parent()
var containerDim = this.getPosition($container)
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
placement placement
$tip $tip
@@ -259,8 +235,8 @@
if (isNaN(marginTop)) marginTop = 0 if (isNaN(marginTop)) marginTop = 0
if (isNaN(marginLeft)) marginLeft = 0 if (isNaN(marginLeft)) marginLeft = 0
offset.top += marginTop offset.top = offset.top + marginTop
offset.left += marginLeft offset.left = offset.left + marginLeft
// $.fn.offset doesn't round pixel values // $.fn.offset doesn't round pixel values
// so we use setOffset directly with our own function B-0 // so we use setOffset directly with our own function B-0
@@ -296,10 +272,10 @@
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
} }
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
this.arrow() this.arrow()
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
.css(isVertical ? 'top' : 'left', '') .css(isHorizontal ? 'top' : 'left', '')
} }
Tooltip.prototype.setContent = function () { Tooltip.prototype.setContent = function () {
@@ -312,16 +288,14 @@
Tooltip.prototype.hide = function (callback) { Tooltip.prototype.hide = function (callback) {
var that = this var that = this
var $tip = $(this.$tip) var $tip = this.tip()
var e = $.Event('hide.bs.' + this.type) var e = $.Event('hide.bs.' + this.type)
function complete() { function complete() {
if (that.hoverState != 'in') $tip.detach() if (that.hoverState != 'in') $tip.detach()
if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. that.$element
that.$element .removeAttr('aria-describedby')
.removeAttr('aria-describedby') .trigger('hidden.bs.' + that.type)
.trigger('hidden.bs.' + that.type)
}
callback && callback() callback && callback()
} }
@@ -331,7 +305,7 @@
$tip.removeClass('in') $tip.removeClass('in')
$.support.transition && $tip.hasClass('fade') ? $.support.transition && this.$tip.hasClass('fade') ?
$tip $tip
.one('bsTransitionEnd', complete) .one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
@@ -344,7 +318,7 @@
Tooltip.prototype.fixTitle = function () { Tooltip.prototype.fixTitle = function () {
var $e = this.$element var $e = this.$element
if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '') $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
} }
} }
@@ -364,10 +338,7 @@
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
} }
var isSvg = window.SVGElement && el instanceof window.SVGElement var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
// Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
// See https://github.com/twbs/bootstrap/issues/20280
var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
@@ -402,7 +373,7 @@
var rightEdgeOffset = pos.left + viewportPadding + actualWidth var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left overflow if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset delta.left = viewportDimensions.left - leftEdgeOffset
} else if (rightEdgeOffset > viewportDimensions.right) { // right overflow } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
} }
} }
@@ -428,13 +399,7 @@
} }
Tooltip.prototype.tip = function () { Tooltip.prototype.tip = function () {
if (!this.$tip) { return (this.$tip = this.$tip || $(this.options.template))
this.$tip = $(this.options.template)
if (this.$tip.length != 1) {
throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
}
}
return this.$tip
} }
Tooltip.prototype.arrow = function () { Tooltip.prototype.arrow = function () {
@@ -463,13 +428,7 @@
} }
} }
if (e) { self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
self.inState.click = !self.inState.click
if (self.isInStateTrue()) self.enter(self)
else self.leave(self)
} else {
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
}
} }
Tooltip.prototype.destroy = function () { Tooltip.prototype.destroy = function () {
@@ -477,13 +436,6 @@
clearTimeout(this.timeout) clearTimeout(this.timeout)
this.hide(function () { this.hide(function () {
that.$element.off('.' + that.type).removeData('bs.' + that.type) that.$element.off('.' + that.type).removeData('bs.' + that.type)
if (that.$tip) {
that.$tip.detach()
}
that.$tip = null
that.$arrow = null
that.$viewport = null
that.$element = null
}) })
} }
@@ -497,7 +449,7 @@
var data = $this.data('bs.tooltip') var data = $this.data('bs.tooltip')
var options = typeof option == 'object' && option var options = typeof option == 'object' && option
if (!data && /destroy|hide/.test(option)) return if (!data && option == 'destroy') return
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]() if (typeof option == 'string') data[option]()
}) })

4
RIGS/static/js/transition.js Executable file → Normal file
View File

@@ -1,8 +1,8 @@
/* ======================================================================== /* ========================================================================
* Bootstrap: transition.js v3.3.7 * Bootstrap: transition.js v3.3.2
* http://getbootstrap.com/javascript/#transitions * http://getbootstrap.com/javascript/#transitions
* ======================================================================== * ========================================================================
* Copyright 2011-2016 Twitter, Inc. * Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */ * ======================================================================== */

View File

@@ -1,64 +0,0 @@
$button_color: #357ebf;
body{
margin: 0px;
}
.main-table{
width: 100%;
border-collapse: collapse;
}
.client-header {
background-image: url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");
background-color: #222;
background-repeat: no-repeat;
background-position: center;
width: 100%;
margin-bottom: 28px;
.logos{
width: 100%;
max-width: 640px;
}
img {
height: 110px;
}
}
.content-container{
width: 100%;
.content {
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 100%;
max-width: 600px;
padding: 10px;
text-align: left;
.button-container{
width: 100%;
.button {
padding: 6px 12px;
background-color: $button_color;
border-radius: 4px;
a {
color: #fff;
text-decoration: none;
}
}
}
}
}

View File

@@ -149,19 +149,16 @@ ins {
} }
html.embedded{ html.embedded{
display: flex; min-height:100%;
flex-direction: column; display: table;
width: 100%; width: 100%;
height: 100%;
max-height: 100%;
overflow: hidden;
justify-content: center;
body{ body{
padding:0; padding:0;
display: table-cell;
vertical-align: middle;
width:100%; width:100%;
background:none; background:none;
overflow: scroll;
} }
.embed_container{ .embed_container{

View File

@@ -33,13 +33,13 @@
{% endif %} {% endif %}
<p> <p>
<small> <small>
{% if version.changes.old == None %} {% if version.old == None %}
Created Created
{% else %} {% else %}
Changed {% include 'RIGS/version_changes.html' %} in Changed {% include 'RIGS/version_changes.html' %} in
{% endif %} {% endif %}
{% include 'RIGS/object_button.html' with object=version.changes.new %} {% include 'RIGS/object_button.html' with object=version.new %}
{% if version.revision.comment %} {% if version.revision.comment %}
({{ version.revision.comment }}) ({{ version.revision.comment }})
{% endif %} {% endif %}

View File

@@ -67,16 +67,16 @@
<tr> <tr>
<td>{{ version.revision.date_created }}</td> <td>{{ version.revision.date_created }}</td>
<td><a href="{{ version.changes.new.get_absolute_url }}">{{version.changes.new|to_class_name}} {{ version.changes.new.pk|stringformat:"05d" }}</a></td> <td><a href="{{ version.new.get_absolute_url }}">{{version.new|to_class_name}} {{ version.new.pk|stringformat:"05d" }}</a></td>
<td>{{ version.pk }}|{{ version.revision.pk }}</td> <td>{{ version.version.pk }}|{{ version.revision.pk }}</td>
<td>{{ version.revision.user.name }}</td> <td>{{ version.revision.user.name }}</td>
<td> <td>
{% if version.changes.old == None %} {% if version.old == None %}
{{version.changes.new|to_class_name}} Created {{version.new|to_class_name}} Created
{% else %} {% else %}
{% include 'RIGS/version_changes.html' %} {% include 'RIGS/version_changes.html' %}
{% endif %} </td> {% endif %} </td>
<td>{{ version.changes.revision.comment }}</td> <td>{{ version.revision.comment }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -1,78 +0,0 @@
<div class="row">
<div class="col-sm-12 col-md-6 col-lg-5">
<div class="panel panel-default">
<div class="panel-heading">Contact Details</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Person</dt>
<dd>
{% if event.person %}
{{ event.person.name }}
{% endif %}
</dd>
<dt>Email</dt>
<dd>
<span class="overflow-ellipsis">{{ event.person.email }}</span>
</dd>
<dt>Phone Number</dt>
<dd>{{ event.person.phone }}</dd>
</dl>
</div>
</div>
{% if event.organisation %}
<div class="panel panel-default">
<div class="panel-heading">Organisation</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Organisation</dt>
<dd>
{{ event.organisation.name }}
</dd>
<dt>Phone Number</dt>
<dd>{{ object.organisation.phone }}</dd>
</dl>
</div>
</div>
{% endif %}
</div>
<div class="col-sm-12 col-md-6 col-lg-7">
<div class="panel panel-info">
<div class="panel-heading">Event Info</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Event Venue</dt>
<dd>
{% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
</a>
{% endif %}
</dd>
<dt>Status</dt>
<dd>{{ event.get_status_display }}</dd>
<dd>&nbsp;</dd>
<dt>Access From</dt>
<dd>{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
<dt>Event Starts</dt>
<dd>{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
<dt>Event Ends</dt>
<dd>{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
<dd>&nbsp;</dd>
<dt>Event Description</dt>
<dd>{{ event.description|linebreaksbr }}</dd>
</dl>
</div>
</div>
</div>
</div>

View File

@@ -11,9 +11,36 @@
</h1> </h1>
</div> </div>
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'RIGS/event_detail_buttons.html' %} <div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span> <span
class="hidden-xs">Print</span></a>
{% endif %}
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
{% if event.invoice and event.invoice.is_closed %}
btn-success
{% elif event.invoice %}
btn-warning
{% else %}
btn-danger
{% endif %}
" title="Invoice Rig"><span
class="glyphicon glyphicon-gbp"></span>
<span class="hidden-xs">Invoice</span></a>
{% endif %}
{% endif %}
</div>
</div> </div>
{% endif %} {% endif %}
{% if object.is_rig %} {% if object.is_rig %}
{# only need contact details for a rig #} {# only need contact details for a rig #}
@@ -70,39 +97,6 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if event.is_rig and event.internal %}
<div class="panel panel-default">
<div class="panel-heading">Client Authorisation</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Authorised</dt>
<dd>{{ object.authorised|yesno:"Yes,No" }}</dd>
<dt>Authorised by</dt>
<dd>
{% if object.authorisation %}
{{ object.authorisation.name }}
(<a href="mailto:{{ object.authorisation.email }}">{{ object.authorisation.email }}</a>)
{% endif %}
</dd>
<dt>Authorised at</dt>
<dd>{{ object.authorisation.last_edited_at }}</dd>
<dt>Authorised amount</dt>
<dd>
{% if object.authorisation %}
£ {{ object.authorisation.amount|floatformat:"2" }}
{% endif %}
</dd>
<dt>Requested by</dt>
<dd>{{ object.authorisation.sent_by }}</dd>
</dl>
</div>
</div>
{% endif %}
</div> </div>
{% endif %} {% endif %}
<div class="col-sm-12 {% if event.is_rig %}col-md-6 col-lg-7{% endif %}"> <div class="col-sm-12 {% if event.is_rig %}col-md-6 col-lg-7{% endif %}">
@@ -181,25 +175,8 @@
{% endif %} {% endif %}
{% if event.is_rig %} {% if event.is_rig %}
<dd>&nbsp;</dd> <dt>PO</dt>
<dd>{{ object.purchase_order }}</dd>
{% if object.internal %}
<dt>Authorisation Request</dt>
<dd>{{ object.auth_request_to|yesno:"Yes,No" }}</dd>
<dt>By</dt>
<dd>{{ object.auth_request_by }}</dd>
<dt>At</dt>
<dd>{{ object.auth_request_at|date:"D d M Y H:i"|default:"" }}</dd>
<dt>To</dt>
<dd>{{ object.auth_request_to }}</dd>
{% else %}
<dt>PO</dt>
<dd>{{ object.purchase_order }}</dd>
{% endif %}
{% endif %} {% endif %}
</dl> </dl>
</div> </div>
@@ -207,7 +184,34 @@
</div> </div>
{% if not request.is_ajax %} {% if not request.is_ajax %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'RIGS/event_detail_buttons.html' %} <div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span> <span
class="hidden-xs">Print</span></a>
{% endif %}
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
{% if event.invoice and event.invoice.is_closed %}
btn-success
{% elif event.invoice %}
btn-warning
{% else %}
btn-danger
{% endif %}
" title="Invoice Rig"><span
class="glyphicon glyphicon-gbp"></span>
<span class="hidden-xs">Invoice</span></a>
{% endif %}
{% endif %}
</div>
</div> </div>
{% endif %} {% endif %}
{% if event.is_rig %} {% if event.is_rig %}
@@ -225,7 +229,34 @@
</div> </div>
{% if not request.is_ajax %} {% if not request.is_ajax %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'RIGS/event_detail_buttons.html' %} <div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span> <span
class="hidden-xs">Print</span></a>
{% endif %}
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if perms.RIGS.add_invoice %}
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
{% if event.invoice and event.invoice.is_closed %}
btn-success
{% elif event.invoice %}
btn-warning
{% else %}
btn-danger
{% endif %}
" title="Invoice Rig"><span
class="glyphicon glyphicon-gbp"></span>
<span class="hidden-xs">Invoice</span></a>
{% endif %}
{% endif %}
</div>
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@@ -1,65 +0,0 @@
<div class="btn-group btn-page">
<a href="{% url 'event_update' event.pk %}" class="btn btn-default"><span
class="glyphicon glyphicon-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% if event.is_rig %}
{% if not event.dry_hire %}
<a href="{% url 'event_ra' event.pk %}" class="btn btn-default
{% if event.risk_assessment_edit_url %}
btn-success
{% else %}
btn-warning
{% endif %}
"><span
class="glyphicon glyphicon-paperclip"></span> <span
class="hidden-xs">RA</span></a>
{% endif %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-default"><span
class="glyphicon glyphicon-print"></span> <span
class="hidden-xs">Print</span></a>
{% endif %}
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-default" title="Duplicate Rig"><span
class="glyphicon glyphicon-duplicate"></span> <span
class="hidden-xs">Duplicate</span></a>
{% if event.is_rig %}
{% if event.internal %}
<a class="btn btn-default item-add modal-href event-authorise-request
{% if event.authorised %}
btn-success
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
btn-warning
{% elif event.auth_request_to %}
btn-info
{% endif %}
"
href="{% url 'event_authorise_request' object.pk %}">
<span class="glyphicon glyphicon-send"></span>
<span class="hidden-xs">
{% if event.authorised %}
Authorised
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
Authorisation Issue
{% elif event.auth_request_to %}
Awaiting Authorisation
{% else %}
Request Authorisation
{% endif %}
</span>
</a>
{% endif %}
{% if perms.RIGS.add_invoice %}
<a id="invoiceDropdownLabel" href="{% url 'invoice_event' event.pk %}" class="btn
{% if event.invoice and event.invoice.is_closed %}
btn-success
{% elif event.invoice %}
btn-warning
{% else %}
btn-danger
{% endif %}
" title="Invoice Rig"><span
class="glyphicon glyphicon-gbp"></span>
<span class="hidden-xs">Invoice</span></a>
{% endif %}
{% endif %}
</div>

View File

@@ -289,10 +289,10 @@
<div class="col-sm-8"> <div class="col-sm-8">
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="Start date for event, required"> <div class="col-sm-12 col-md-7" data-toggle="tooltip" title="Start date for event, required">
{% render_field form.start_date class+="form-control" %} {% render_field form.start_date type="date" class+="form-control" %}
</div> </div>
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="Start time of event, can be left blank"> <div class="col-sm-12 col-md-5" data-toggle="tooltip" title="Start time of event, can be left blank">
{% render_field form.start_time class+="form-control" %} {% render_field form.start_time type="time" class+="form-control" %}
</div> </div>
</div> </div>
</div> </div>
@@ -304,10 +304,10 @@
<div class="col-sm-8"> <div class="col-sm-8">
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="End date of event, leave blank if unknown or same as start date"> <div class="col-sm-12 col-md-7" data-toggle="tooltip" title="End date of event, leave blank if unknown or same as start date">
{% render_field form.end_date class+="form-control" %} {% render_field form.end_date type="date" class+="form-control" %}
</div> </div>
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="End time of event, leave blank if unknown"> <div class="col-sm-12 col-md-5" data-toggle="tooltip" title="End time of event, leave blank if unknown">
{% render_field form.end_time class+="form-control" %} {% render_field form.end_time type="time" class+="form-control" %}
</div> </div>
</div> </div>
@@ -329,7 +329,7 @@
class="col-sm-4 control-label">{{ form.access_at.label }}</label> class="col-sm-4 control-label">{{ form.access_at.label }}</label>
<div class="col-sm-8"> <div class="col-sm-8">
{% render_field form.access_at class+="form-control" %} {% render_field form.access_at type="datetime-local" class+="form-control" %}
</div> </div>
</div> </div>
<div class="form-group" data-toggle="tooltip" title="The date/time at which crew should meet for this event"> <div class="form-group" data-toggle="tooltip" title="The date/time at which crew should meet for this event">
@@ -337,7 +337,7 @@
class="col-sm-4 control-label">{{ form.meet_at.label }}</label> class="col-sm-4 control-label">{{ form.meet_at.label }}</label>
<div class="col-sm-8"> <div class="col-sm-8">
{% render_field form.meet_at class+="form-control" %} {% render_field form.meet_at type="datetime-local" class+="form-control" %}
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -398,7 +398,6 @@
{% render_field form.collector class+="form-control" %} {% render_field form.collector class+="form-control" %}
</div> </div>
</div> </div>
<div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)"> <div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)">
<label for="{{ form.purchase_order.id_for_label }}" <label for="{{ form.purchase_order.id_for_label }}"
class="col-sm-4 control-label">{{ form.purchase_order.label }}</label> class="col-sm-4 control-label">{{ form.purchase_order.label }}</label>

View File

@@ -54,22 +54,20 @@
<td><a href="{% url 'event_detail' object.pk %}">N{{ object.pk|stringformat:"05d" }}</a><br> <td><a href="{% url 'event_detail' object.pk %}">N{{ object.pk|stringformat:"05d" }}</a><br>
<span class="text-muted">{{ object.get_status_display }}</span></td> <span class="text-muted">{{ object.get_status_display }}</span></td>
<td>{{ object.start_date }}</td> <td>{{ object.start_date }}</td>
<td>{{ object.name }}</td>
<td> <td>
{{ object.name }} {% if object.organisation %}
{% if object.is_rig and perms.RIGS.view_event and object.authorised %} {{ object.organisation.name }}
<span class="glyphicon glyphicon-check"></span> <br>
<span class="text-muted">{{ object.organisation.union_account|yesno:'Internal,External' }}</span>
{% else %}
{{ object.person.name }}
<br>
<span class="text-muted">External</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>{{ object.sum_total|floatformat:2 }}</td>
{{ object.organisation.name }}
<br>
<span class="text-muted">{{ object.internal|yesno:'Internal,External' }}</span>
</td>
<td>
{{ object.sum_total|floatformat:2 }}
<br />
<span class="text-muted">{% if not object.internal %}{{ object.purchase_order }}{% endif %}</span>
</td>
<td class="text-center"> <td class="text-center">
{% if object.mic %} {% if object.mic %}
{{ object.mic.initials }}<br> {{ object.mic.initials }}<br>
@@ -79,10 +77,7 @@
{% endif %} {% endif %}
</td> </td>
<td class="text-right"> <td class="text-right">
<a href="{% url 'invoice_event' object.pk %}" <a href="{% url 'invoice_event' object.pk %}" class="btn btn-default" data-toggle="tooltip" title="'Invoice' this event - click this when paperwork has been sent to treasury">
class="btn btn-default"
data-toggle="tooltip"
title="'Invoice' this event - click this when paperwork has been sent to treasury">
<span class="glyphicon glyphicon-gbp"></span> <span class="glyphicon glyphicon-gbp"></span>
</a> </a>
</td> </td>
@@ -97,4 +92,4 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -22,21 +22,21 @@
<paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="10" spaceAfter="2"/> <paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="10" spaceAfter="2"/>
<paraStyle name="style.Heading3" fontName="OpenSans" fontSize="10" spaceAfter="0"/> <paraStyle name="style.Heading3" fontName="OpenSans" fontSize="10" spaceAfter="0"/>
<paraStyle name="center" alignment="center"/> <paraStyle name="center" alignment="center"/>
<paraStyle name="page-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/> <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.event_description" fontName="OpenSans" textColor="DarkGray" />
<paraStyle name="style.item_description" fontName="OpenSans" textColor="DarkGray" leftIndent="10" /> <paraStyle name="style.item_description" fontName="OpenSans" textColor="DarkGray" leftIndent="10" />
<paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10" /> <paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10" />
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" /> <paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
<paraStyle name="style.head_titles" fontName="OpenSans-Bold" fontSize="10" /> <paraStyle name="style.invoice_titles" fontName="OpenSans-Bold" fontSize="10" />
<paraStyle name="style.head_numbers" fontName="OpenSans" fontSize="10" /> <paraStyle name="style.invoice_numbers" fontName="OpenSans" fontSize="10" />
<blockTableStyle id="eventSpecifics"> <blockTableStyle id="eventSpecifics">
<blockValign value="top"/> <blockValign value="top"/>
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/> <lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
</blockTableStyle> </blockTableStyle>
<blockTableStyle id="headLayout"> <blockTableStyle id="invoiceLayout">
<blockValign value="top"/> <blockValign value="top"/>
</blockTableStyle> </blockTableStyle>
@@ -100,11 +100,10 @@
<setFont name="OpenSans" size="10" /> <setFont name="OpenSans" size="10" />
{% if not invoice %}<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>{% endif %}
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString> <drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
<setFont name="OpenSans" size="7" /> <setFont name="OpenSans" size="7" />
<drawCenteredString x="302.5" y="26"> <drawCenteredString x="302.5" y="26">[Paperwork generated by {{current_user.name}} | {% now "d/m/Y H:i" %} | {{object.current_version_id}}]</drawCenteredString>
[Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
</drawCenteredString>
</pageGraphics> </pageGraphics>
<frame id="main" x1="50" y1="65" width="495" height="645"/> <frame id="main" x1="50" y1="65" width="495" height="645"/>
@@ -116,11 +115,10 @@
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/> <image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
<setFont name="OpenSans" size="10"/> <setFont name="OpenSans" size="10"/>
{% if not invoice %}<drawCenteredString x="302.5" y="50">[{{ copy }} Copy]</drawCenteredString>{% endif %}
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString> <drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
<setFont name="OpenSans" size="7" /> <setFont name="OpenSans" size="7" />
<drawCenteredString x="302.5" y="26"> <drawCenteredString x="302.5" y="26">[Paperwork generated by {{current_user.name}} | {% now "d/m/Y H:i" %} | {{object.current_version_id}}]</drawCenteredString>
[Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
</drawCenteredString>
</pageGraphics> </pageGraphics>
<frame id="main" x1="50" y1="65" width="495" height="727"/> <frame id="main" x1="50" y1="65" width="495" height="727"/>
</pageTemplate> </pageTemplate>
@@ -130,4 +128,4 @@
{% include "RIGS/event_print_page.xml" %} {% include "RIGS/event_print_page.xml" %}
</story> </story>
</document> </document>

View File

@@ -1,70 +1,59 @@
<setNextFrame name="main"/> <setNextFrame name="main"/>
<nextFrame/> <nextFrame/>
{% if invoice %}
<blockTable style="headLayout" colWidths="330,165"> <blockTable style="invoiceLayout" colWidths="330,165">
<tr> <tr>
<td> <td>
<h1><b>N{{ object.pk|stringformat:"05d" }}:</b> '{{ object.name }}'<small></small></h1> {% endif %}
<para style="style.event_description"> <h1><b>N{{ object.pk|stringformat:"05d" }}:</b> '{{ object.name }}'<small></small></h1>
<b>{{object.start_date|date:"D jS N Y"}}</b>
<para style="style.event_description">
<b>{{object.start_date|date:"D jS N Y"}}</b>
</para>
<keepInFrame>
<para style="style.event_description">
{{ object.description|default_if_none:""|linebreaksbr }}
</para> </para>
</keepInFrame>
{% if invoice %}
<keepInFrame>
<para style="style.event_description">
{{ object.description|default_if_none:""|linebreaksbr }}
</para>
</keepInFrame>
</td> </td>
<td> <td>
{% if invoice %} <para style="invoice-head">INVOICE</para>
<para style="page-head">INVOICE</para> <spacer length="10"/>
<spacer length="10"/> <blockTable style="eventDetails" colWidths="100,175">
<blockTable style="eventDetails" colWidths="100,175"> <tr>
<tr> <td><para style="invoice_titles">Invoice Number</para></td>
<td><para style="head_titles">Invoice Number</para></td> <td>
<td> <para style="invoice_numbers">{{ invoice.pk|stringformat:"05d" }}</para>
<para style="head_numbers">{{ invoice.pk|stringformat:"05d" }}</para> </td>
</td> </tr>
</tr> <tr>
<tr> <td><para style="invoice_titles">Invoice Date</para></td>
<td><para style="head_titles">Invoice Date</para></td> <td>
<td> <para style="invoice_numbers">{{ invoice.invoice_date|date:"d/m/Y" }}</para>
<para style="head_numbers">{{ invoice.invoice_date|date:"d/m/Y" }}</para> </td>
</td> </tr>
</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>
{% if not object.internal %} </blockTable>
<tr>
<td><para style="head_titles">PO</para></td>
<td><para style="head_numbers">{{ object.purchase_order }}</para></td>
</tr>
{% endif %}
</blockTable>
{% elif quote %}
<para style="page-head">QUOTE</para>
<spacer length="10"/>
<blockTable style="eventDetails" colWidths="100,175">
<tr>
<td><para style="head_titles">Quote Date</para></td>
<td>
<para style="head_numbers">{% now "d/m/Y" %}</para>
</td>
</tr>
</blockTable>
{% elif receipt %}
<para style="page-head">CONFIRMATION</para>
{% endif %}
</td> </td>
</tr> </tr>
</blockTable> </blockTable>
{% endif %}
<spacer length="15"/> <spacer length="15"/>
<blockTable style="eventSpecifics" colWidths="165,165,165"> <blockTable style="eventSpecifics" colWidths="165,165,165">
<tr> <tr>
@@ -200,16 +189,16 @@
<keepTogether> <keepTogether>
<blockTable style="totalTable" colWidths="300,115,80"> <blockTable style="totalTable" colWidths="300,115,80">
<tr> <tr>
<td>{% if quote %}VAT Registration Number: 170734807{% endif %}</td> <td>{% if not invoice %}VAT Registration Number: 170734807{% endif %}</td>
<td>Total (ex. VAT)</td> <td>Total (ex. VAT)</td>
<td>£ {{ object.sum_total|floatformat:2 }}</td> <td>£ {{ object.sum_total|floatformat:2 }}</td>
</tr> </tr>
<tr> <tr>
<td> <td>
{% if quote %} {% if not invoice %}
<para> <para>
This quote is valid for 30 days unless otherwise arranged. <b>The full hire fee is payable at least 10 days before the event.</b>
</para> </para>
{% endif %} {% endif %}
</td> </td>
<td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td> <td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td>
@@ -217,140 +206,113 @@
</tr> </tr>
<tr> <tr>
<td> <td>
{% if quote %}
<para> <para>
<b>The full hire fee is payable at least 10 days before the event.</b> {% if invoice %}
</para> VAT Registration Number: 170734807
{% endif %} {% 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> </td>
{% if invoice %}
<td>Total</td>
<td>£ {{ object.total|floatformat:2 }}</td>
{% else %}
<td>
<para>
<b>Total</b>
</para>
</td>
<td>
<para>
<b>£ {{ object.total|floatformat:2 }}</b>
</para>
</td>
{% endif %}
</tr> </tr>
</blockTable> </blockTable>
</keepTogether> </keepTogether>
{% if not invoice %}
{% if invoice %}
<spacer length="15"/>
<keepTogether> <keepTogether>
<h2>Payments</h2> <blockTable style="infoTable">
<blockTable style="itemTable" colWidths="300,115,80">
<tr>
<td>
<para>
<b>Method</b>
</para>
</td>
<td>
<para>
<b>Date</b>
</para>
</td>
<td>
<para>
<b>Amount</b>
</para>
</td>
</tr>
{% for payment in object.invoice.payment_set.all %}
<tr>
<td>{{ payment.get_method_display }}</td>
<td>{{ payment.date }}</td>
<td>£ {{ payment.amount|floatformat:2 }}</td>
</tr>
{% endfor %}
</blockTable>
<blockTable style="totalTable" colWidths="300,115,80">
<tr>
<td></td>
<td>Payment Total</td>
<td>£ {{ object.invoice.payment_total|floatformat:2 }}</td>
</tr>
<tr>
<td></td>
<td>
<para>
<b>Balance</b> (ex. VAT)
</para>
</td>
<td>
<para>
<b>£ {{ object.invoice.balance|floatformat:2 }}</b>
</para>
</td>
</tr>
</blockTable>
</keepTogether>
{% endif %}
<keepTogether>
<blockTable style="infoTable">>
{% if quote %}
<tr><td><spacer length="15" /></td></tr>
<tr>
<td>
{% if object.internal %}
<para>Bookings will
<b>not</b>
be confirmed until the event is authorised online.
</para>
{% else %}
<para>Bookings will
<b>not</b>
be confirmed until we have received written confirmation and a Purchase Order.
</para>
{% endif %}
</td>
</tr>
<tr>
<td>24 Hour Emergency Contacts: 07825 065681 and 07825 065678</td>
</tr>
{% else %}
<tr>
<td>
<para>VAT Registration Number: 170734807</para>
</td>
</tr>
{% endif %}
<tr><td><spacer length="15" /></td></tr>
<tr> <tr>
<td> <td>
{% if object.internal and object.authorised %} <para>Bookings will
<para> <b>not</b>
Event authorised online by {{ object.authorisation.name }} ({{ object.authorisation.email }}) at be confirmed until payment is received and the contract is signed.
{{ object.authorisation.last_edited_at }}. </para>
</para>
<blockTable colWidths="165,165,165">
<tr>
<td><para><b>University ID</b></para></td>
<td><para><b>Account Code</b></para></td>
<td><para><b>Authorised Amount</b></para></td>
</tr>
<tr>
<td>{{ object.authorisation.uni_id }}</td>
<td>{{ object.authorisation.account_code }}</td>
<td>£ {{ object.authorisation.amount|floatformat:2 }}</td>
</tr>
</blockTable>
{% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>24 Hour Emergency Contacts: 07825 065681 and 07825 065678</td>
</tr>
</blockTable> </blockTable>
</keepTogether> </keepTogether>
<spacer length="15"/>
<keepTogether>
<namedString id="lastPage"><pageNumber/></namedString> <para style="blockPara">
<b>To be signed on booking:</b>
</para>
{% if object.organisation.union_account %}
<para style="blockPara">
<i>
I agree that am authorised to sign this invoice. I agree that I am the President/Treasurer of the hirer, or
that I have provided written permission from either the President or Treasurer of the hirer stating that I can
sign for this invoice.
</i>
</para>
<para style="blockPara">
<i>
I have read, understood and fully accepted the current conditions of hire. I agree to return any dry hire
items to TEC PA &amp; Lighting in the same condition at the end of the hire period.
</i>
</para>
<para style="blockPara">
<b>
Conditions of hire attached and available on the TEC PA &amp; Lighting website. E&amp;OE
</b>
</para>
<para style="blockPara">
Please return this form directly to TEC PA &amp; Lighting and not the Students' Union Finance Department.
</para>
<blockTable style="signatureTable" colWidths="70,100,325">
<tr>
<td>Account Code</td>
<td></td>
<td></td>
</tr>
</blockTable>
{% else %}
<para style="blockPara">
<i>
I, the hirer, have read, understand and fully accept the current conditions of hire. This document forms a
binding contract between TEC PA &amp; Lighting and the hirer, the aforementioned conditions of hire forming
an integral part of it.
</i>
</para>
<para style="blockPara">
<b>
Conditions of hire attached and available on the TEC PA &amp; Lighting website. E&amp;OE
</b>
</para>
{% include "RIGS/event_print_signature.xml" %}
<spacer length="10"/>
<para style="blockPara">
<b>To be signed on the day of the event/hire:</b>
</para>
<para style="blockPara">
<i>
I, the hirer, have received the goods/services as requested and in good order. I agree to return any dry hire
items to TEC PA &amp; Lighting in a similar condition at the end of the hire period.
</i>
</para>
{% endif %}
{% include "RIGS/event_print_signature.xml" %}
</keepTogether>
{% endif %}
<namedString id="lastPage"><pageNumber/></namedString>

View File

@@ -33,18 +33,13 @@
</td> </td>
<td> <td>
<h4> <h4>
<a {% if perms.RIGS.view_event %}href="{% url 'event_detail' event.pk %}" {% endif %}> <a {% if perms.RIGS.view_event %}href="{% url 'event_detail' event.pk %}" {% endif %}>{{ event.name }}</a>
{{ event.name }}
</a>
{% if event.venue %} {% if event.venue %}
<small>at {{ event.venue }}</small> <small>at {{ event.venue }}</small>
{% endif %} {% endif %}
{% if event.dry_hire %} {% if event.dry_hire %}
<span class="label label-default">Dry Hire</span> <span class="label label-default">Dry Hire</span>
{% endif %} {% endif %}
{% if event.is_rig and perms.RIGS.view_event and event.authorised %}
<span class="glyphicon glyphicon-check"></span>
{% endif %}
</h4> </h4>
{% if event.is_rig and not event.cancelled %} {% if event.is_rig and not event.cancelled %}
<h5> <h5>
@@ -104,4 +99,4 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@@ -1,41 +0,0 @@
{% extends 'base_client_email.html' %}
{% block content %}
<p>Hi {{ to_name|default:"there" }},</p>
<p><b>{{ request.user.get_full_name }}</b> has requested that you authorise <b>N{{ object.pk|stringformat:"05d" }}
| {{ object.name }}</b>{% if not to_name %} on behalf of <b>{{ object.person.name }}</b>{% endif %}.</p>
<p>
Please find the link below to complete the event booking process.
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward
this
email on.
{% endif %}
</p>
<table class="button-container" width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="button" align="center">
<a href="{{ request.scheme }}://{{ request.get_host }}{% url 'event_authorise' object.pk hmac %}">
Complete Authorisation Form
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>Your event will not be booked until you complete this form.</p>
<p>TEC PA &amp; Lighting<br/>
{% endblock %}

View File

@@ -1,16 +0,0 @@
Hi {{ to_name|default:"there" }},
{{ request.user.get_full_name }} has requested that you authorise N{{ object.pk|stringformat:"05d" }}| {{ object.name }}{% if not to_name %} on behalf of {{ object.person.name }}{% endif %}.
Please find the link below to complete the event booking process.
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward
this
email on.
{% endif %}
{{ request.scheme }}://{{ request.get_host }}{% url 'event_authorise' object.pk hmac %}
Please note you event will not be booked until you complete this form.
TEC PA & Lighting

View File

@@ -1,21 +0,0 @@
{% extends 'base_client_email.html' %}
{% block content %}
<p>Hi {{ to_name|default:"there" }},</p>
<p>
Your event <b>N{{ object.event.pk|stringformat:"05d" }}</b> has been successfully authorised
for <b>&pound;{{ object.amount }}</b>
by <b>{{ object.name }}</b> as of <b>{{ object.last_edited_at }}</b>.
</p>
<p>
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
Your event is now fully booked and payment will be processed by the finance department automatically.
{% else %}{# external #}
Your event is now fully booked and our finance department will be contact to arrange payment.
{% endif %}
</p>
<p>TEC PA &amp; Lighting</p>
{% endblock %}

View File

@@ -1,11 +0,0 @@
Hi {{ to_name|default:"there" }},
Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.last_edited_at}}.
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
Your event is now fully booked and payment will be processed by the finance department automatically.
{% else %}{# external #}
Your event is now fully booked and our finance department will be contact to arrange payment.
{% endif %}
TEC PA & Lighting

View File

@@ -1,133 +0,0 @@
{% extends 'base_client.html' %}
{% load widget_tweaks %}
{% load static %}
{% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip();
});
$('form').on('submit', function () {
$('#loading-modal').modal({
backdrop: 'static',
show: true
});
});
</script>
{% endblock %}
{% block title %}
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %}
| {{ event.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
</h1>
</div>
</div>
<div class="row">
{% include 'RIGS/client_eventdetails.html' %}
</div>
<div class="row">
<div class="col-sm-12">
{% with object=event %}
{% include 'RIGS/item_table.html' %}
{% endwith %}
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading" id="eventauth">Event Authorisation</div>
<div class="panel-body">
<form class="form-horizontal itemised_form" role="form" method="POST" action="#eventauth">
{% csrf_token %}
{% include 'form_errors.html' %}
<div class="row">
<div class="col-sm-12">
<p>
I agree that I am authorised to approve this event. I agree that I am the
<strong>President/Treasurer or account holder</strong> of the hirer, or that I
have the written permission of the
<strong>President/Treasurer or account holder</strong> of the hirer stating that
I can authorise this event.
</p>
</div>
<div class="col-sm-12 col-md-6">
<div class="col-sm-12 form-group" data-toggle="tooltip"
title="Your name as the person authorising the event.">
<label for="{{ form.name.id_for_label }}"
class="col-sm-4 control-label">{{ form.name.label }}</label>
<div class="col-sm-8">
{% render_field form.name class+="form-control" %}
</div>
</div>
<div class="col-sm-12 form-group" data-toggle="tooltip"
title="Your Student ID or Staff username as the person authorising the event.">
<label for="{{ form.uni_id.id_for_label }}"
class="col-sm-4 control-label">{{ form.uni_id.label }}</label>
<div class="col-sm-8">
{% render_field form.uni_id class+="form-control" %}
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="col-sm-12 form-group" data-toggle="tooltip"
title="The Students' Union account code you wish this event to be charged to.">
<label for="{{ form.account_code.id_for_label }}"
class="col-sm-4 control-label">{{ form.account_code.label }}</label>
<div class="col-sm-8">
{% render_field form.account_code class+="form-control" %}
</div>
</div>
<div class="col-sm-12 form-group" data-toggle="tooltip"
title="The full amount chargable for this event as displayed above, including VAT.">
<label for="{{ form.amount.id_for_label }}"
class="col-sm-4 control-label">{{ form.amount.label }}</label>
<div class="col-sm-8">
{% render_field form.amount class+="form-control" %}
</div>
</div>
</div>
<div class="col-sm-12">
<div class="col-sm-12 col-md-8 form-group">
<div class="col-sm-offset-4 col-sm-8 col-md-offset-3" data-toggle="tooltip"
title="In order to book an event you must agree to the TEC Terms of Hire.">
<div class="checkbox">
<label>
{% render_field form.tos %} I have read and agree to the TEC
<a href="{{ tos_url }}">Terms of Hire</a>. E&OE.
</label>
</div>
</div>
</div>
<div class="col-sm-12 col-md-4 text-right">
<div class="btn-group">
<button class="btn btn-primary" type="submit">Authorise</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,5 +0,0 @@
Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}},
Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.last_edited_at}}.
The TEC Rig Information Gathering System

View File

@@ -1,59 +0,0 @@
{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
{% load widget_tweaks %}
{% block title %}Request Authorisation{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="alert alert-warning">
<h1>Send authorisation request email.</h1>
<p>Pressing send will email the address provided. Please triple check everything before continuing.</p>
</div>
<div class="alert alert-info">
<dl class="dl-horizontal">
<dt>Person Email</dt>
<dd>{{ object.person.email }}</dd>
<dt>Organisation Email</dt>
<dd>{{ object.organisation.email }}</dd>
</dl>
</div>
</div>
<div class="col-sm-12 col-md-10 col-md-offset-1">
<form action="{{ form.action|default:request.path }}" method="post" class="form-horizontal"
id="auth-request-form">
{% csrf_token %}
<div class="row">
{% include 'form_errors.html' %}
<div class="form-group">
<label class="col-sm-3 control-label"
for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
<div class="col-sm-9">
{% render_field form.email type="email" class+="form-control" %}
</div>
</div>
<div class="text-right col-sm-3 col-sm-offset-9">
<div class="form-group">
<button type="submit" class="form-control btn btn-primary">
<span class="glyphicon glyphicon-send"></span>
Send
</button>
</div>
</div>
</div>
</form>
</div>
</div>
<script>
$('#auth-request-form').on('submit', function () {
$('#auth-request-form button').attr('disabled', true);
});
</script>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More