mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-09 00:09:44 +00:00
Compare commits
288 Commits
dependabot
...
ce0e47786f
| Author | SHA1 | Date | |
|---|---|---|---|
| ce0e47786f | |||
| 0ed7dc4834 | |||
| 4ca4ea9812 | |||
| fe38d5cf36 | |||
| 72221e46bc | |||
| 0e656b4c41 | |||
| a0c6793cf6 | |||
| 80837c3d9a | |||
| c027182962 | |||
| dd95447008 | |||
| 13fcadaf79 | |||
| 124b7a8e51 | |||
| dfdbcb651f | |||
| e0615f318c | |||
|
97b29bc549
|
|||
|
0aa10d86cd
|
|||
|
06e22b0d61
|
|||
|
ac916247a6
|
|||
|
bbc4d1d390
|
|||
|
4b76b77806
|
|||
|
44fef39a2e
|
|||
|
d06895bf96
|
|||
|
e9a29f9444
|
|||
|
14e12f0fb9
|
|||
|
7f9a7bba8b
|
|||
|
20d4ddd5cf
|
|||
|
e6eed9f2f2
|
|||
|
320ace1a0e
|
|||
|
ab2c2f65f0
|
|||
|
8a4fcdcecb
|
|||
|
529941224e
|
|||
|
59756ab86a
|
|||
|
7736a2d3cc
|
|||
|
30df597144
|
|||
|
4bb1c0a2a4
|
|||
|
848e8c8ccd
|
|||
|
0fee753284
|
|||
|
bb4d31477e
|
|||
|
3d7ff435c9
|
|||
|
a950b941ca
|
|||
|
f57dc9f765
|
|||
| 8b2f2a9354 | |||
|
d255e1f89f
|
|||
|
fd85d50679
|
|||
|
2cb5453b82
|
|||
|
0f019e26a0
|
|||
|
e926731e67
|
|||
|
b69883cc90
|
|||
|
a0e1702de4
|
|||
|
8c8c580bfb
|
|||
|
49a2bc83ab
|
|||
|
026e2a0b03
|
|||
|
3cb0d82130
|
|||
|
5a5bb4328d
|
|||
|
58b1867a13
|
|||
|
d7678f6b6f
|
|||
|
7f3a169875
|
|||
|
5a99560310
|
|||
|
12a60e1f50
|
|||
|
934c07be72
|
|||
| 3602da9203 | |||
| f41064abfa | |||
|
ee9dbf8944
|
|||
|
565e757758
|
|||
|
5af075946a
|
|||
|
5d56f4f7b0
|
|||
|
3903481b3d
|
|||
|
af7d3c4070
|
|||
|
025a31f15a
|
|||
|
2db2cc6659
|
|||
|
cce0ad0f9f
|
|||
|
ae13cabe09
|
|||
|
abf3cfe1ce
|
|||
|
7c79a6afdd
|
|||
|
350a301b36
|
|||
|
2f8e09906b
|
|||
|
50ae3022cd
|
|||
|
f147f19140
|
|||
|
0b751d62df
|
|||
|
acf814e49e
|
|||
|
f70421b8ca
|
|||
|
84c4ec03de
|
|||
|
813b1dac85
|
|||
|
0117002b01
|
|||
|
932180f99f
|
|||
|
70de16ed5c
|
|||
|
8424424d49
|
|||
|
a78bb19015
|
|||
|
bea762b12b
|
|||
|
70cc554094
|
|||
|
1dad8d2ba5
|
|||
|
c38105a76e
|
|||
|
6936b94ce6
|
|||
|
74066e9484
|
|||
|
018397d28e
|
|||
|
143b654210
|
|||
|
2a1bb57c74
|
|||
|
77c82efce6
|
|||
|
1ea8090668
|
|||
| 31f63ba5c7 | |||
| 9739af765f | |||
| b90be708d0 | |||
| dcc0e53062 | |||
|
ce5a92dfa8
|
|||
|
fd926aef85
|
|||
|
1ecc508b0d
|
|||
|
a5516ee350
|
|||
|
5487b73006
|
|||
|
6426880708
|
|||
|
aa0184a5dc
|
|||
|
eb7d8e49e8
|
|||
|
b959ca13a5
|
|||
|
d8e6f2f9c1
|
|||
|
1cf910752f
|
|||
|
cf7ada3d9e
|
|||
|
ba6dbc6980
|
|||
|
6d836ee4dd
|
|||
|
e602058771
|
|||
|
c1182efa54
|
|||
|
87e831a468
|
|||
|
1d5429052f
|
|||
|
6ee9efa39e
|
|||
|
945e521feb
|
|||
|
689124a891
|
|||
|
8842c2c3d9
|
|||
|
f3c2ce2519
|
|||
|
bfe80db85e
|
|||
|
9198724748
|
|||
|
dbe0d35400
|
|||
|
1feb9449ed
|
|||
|
d708207ab9
|
|||
|
8ea96674db
|
|||
|
de04498517
|
|||
|
828964ecb6
|
|||
|
3438489934
|
|||
| 9cf081efc7 | |||
| 8bb08724b6 | |||
| da60cad911 | |||
| a6ac55baaf | |||
|
d3f55523da
|
|||
|
d3d7c052af
|
|||
|
902476ebab
|
|||
|
4514de137a
|
|||
|
92377227e0
|
|||
|
b88554a57f
|
|||
|
9d6948e326
|
|||
|
67bf60e246
|
|||
| 2e60c5e7bf | |||
| d09f3994fc | |||
| 79eb0cbff0 | |||
| d800a781a5 | |||
| 721439d095 | |||
| b57b918247 | |||
| 1dfaa4d7a8 | |||
|
1138cfcde5
|
|||
|
cd168d93ed
|
|||
|
a4ef69af3c
|
|||
|
be48dd31f9
|
|||
|
a6cee78198
|
|||
|
d50d2bd0ad
|
|||
|
3ceb48876d
|
|||
|
8820ed1e67
|
|||
|
86fe8a8e1b
|
|||
|
8a9eefb722
|
|||
|
b28377e1f5
|
|||
|
141e1c94d7
|
|||
|
eafb394f55
|
|||
|
84618deac0
|
|||
|
e4e8823a1a
|
|||
|
cb1a8e1627
|
|||
|
c65330cb91
|
|||
| 6e7fa267bc | |||
|
9e1d146b7a
|
|||
|
aedb4c24db
|
|||
|
1239fbf185
|
|||
|
7035731655
|
|||
|
a7c4b90161
|
|||
|
9e93d895ba
|
|||
|
d31900c5c3
|
|||
|
3768f4a613
|
|||
|
6867359146
|
|||
|
e45324f5b4
|
|||
| 156e639bac | |||
| 0e2adf3f0d | |||
| eb0c027ff9 | |||
| 93504997fe | |||
| d3b3d1c9d7 | |||
| 4cfd83eeb3 | |||
| 00226e9c22 | |||
| b75b6a6736 | |||
| 1b1775d0f5 | |||
| 14d211326e | |||
| 4081918598 | |||
| b742d3f8c4 | |||
| 06d7ef0b79 | |||
| 06f67f4f32 | |||
|
|
170c2f6d8c | ||
|
|
f37228e058 | ||
|
|
6d47be72fe | ||
| 6ded711dd5 | |||
| 920ea0d058 | |||
| e45e58321c | |||
| 3f78b9f05f | |||
| 0bbc23853d | |||
| 7356d020b2 | |||
| 08f40bce9e | |||
| 1d0e8e14e5 | |||
| b52709f412 | |||
| c4c4291050 | |||
| 07641a2520 | |||
| fd6aee83cf | |||
| 177c37ffbc | |||
| d070f97696 | |||
| 2f7389d8bb | |||
| 18fde7c16a | |||
| f06dc56b40 | |||
| e6d06db2a1 | |||
| 544b6df35c | |||
| c9ea1bb75d | |||
| 928d5cd8e6 | |||
| cc225b2eb7 | |||
| 3c0005ddb0 | |||
|
|
8ffb5ab23e | ||
|
34bf49876b
|
|||
|
813db2c474
|
|||
|
919975e1ba
|
|||
|
0d0c783e07
|
|||
|
c23d18cd45
|
|||
|
5a3547ea74
|
|||
|
e3c1da9d13
|
|||
|
3f48c51aeb
|
|||
|
abb8dc25ec
|
|||
|
70995a0d0b
|
|||
|
5e60675115
|
|||
|
f308a095f3
|
|||
|
2bf643cd7a
|
|||
|
c8d0c0d5d0
|
|||
|
176324ed79
|
|||
|
d216dd4c74
|
|||
|
55e37d8c69
|
|||
|
7aa19cc7ab
|
|||
|
4c40226bcf
|
|||
|
d351d9eb7b
|
|||
|
de210caa36
|
|||
|
0271840f4d
|
|||
|
7a08f2d889
|
|||
|
ee7ba3ea19
|
|||
|
41b0387e49
|
|||
|
50ca782569
|
|||
|
0144bd37fc
|
|||
|
959097286c
|
|||
|
264b306b2f
|
|||
|
e42989637e
|
|||
|
12d8b46f86
|
|||
|
89ddb09459
|
|||
|
f4f8c6b417
|
|||
|
36556dea33
|
|||
|
b9d318e675
|
|||
|
b8931a64c8
|
|||
|
cfe0a264e7
|
|||
|
8fbe9f9026
|
|||
|
fe0e4063d7
|
|||
|
0bd4b281d1
|
|||
|
7de778a57e
|
|||
|
88b34740f6
|
|||
|
e0e4e8d11e
|
|||
|
c1d277be9c
|
|||
|
4a71dd0d95
|
|||
|
2bfecb9c0f
|
|||
|
3814f5abfc
|
|||
|
4c34e4e43e
|
|||
|
3f36f66b8a
|
|||
|
7cef4d03c0
|
|||
|
6970c5c490
|
|||
|
b8ea3d3d42
|
|||
|
366a14408b
|
|||
|
270b1fc5bb
|
|||
|
7786512dc2
|
|||
|
88ac1b93ae
|
|||
|
4d845309c9
|
|||
|
3b8789e49e
|
|||
|
1526a2f22b
|
|||
|
fe71f7640f
|
|||
|
b57716f7fc
|
|||
|
320c43e472
|
|||
|
1e5fcbdba0
|
|||
|
1df1784d02
|
|||
|
6c72f070f2
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -25,6 +25,7 @@ var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
node_modules/
|
||||
|
||||
# Continer extras
|
||||
.vagrant
|
||||
@@ -54,7 +55,6 @@ coverage.xml
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
db.sqlite3
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
@@ -108,4 +108,5 @@ atlassian-ide-plugin.xml
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
.vscode/
|
||||
.vscode/
|
||||
screenshots/
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
language: python
|
||||
python:
|
||||
"3.6"
|
||||
"3.8"
|
||||
cache: pip
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
|
||||
before_install:
|
||||
- export LANGUAGE=en_GB.UTF-8
|
||||
|
||||
install:
|
||||
- wget https://chromedriver.storage.googleapis.com/2.36/chromedriver_linux64.zip
|
||||
- |
|
||||
latest=$(wget -qO- https://chromedriver.storage.googleapis.com/LATEST_RELEASE)
|
||||
wget https://chromedriver.storage.googleapis.com/$latest/chromedriver_linux64.zip
|
||||
- unzip chromedriver_linux64.zip
|
||||
- export PATH=$PATH:$(pwd)
|
||||
- chmod +x chromedriver
|
||||
|
||||
@@ -8,7 +8,8 @@ from RIGS import models
|
||||
|
||||
def get_oembed(login_url, request, oembed_view, kwargs):
|
||||
context = {}
|
||||
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))
|
||||
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())
|
||||
resp = render(request, 'login_redirect.html', context=context)
|
||||
return resp
|
||||
@@ -28,9 +29,11 @@ def has_oembed(oembed_view, login_url=None):
|
||||
return get_oembed(login_url, request, oembed_view, kwargs)
|
||||
else:
|
||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
||||
|
||||
_checklogin.__doc__ = view_func.__doc__
|
||||
_checklogin.__dict__ = view_func.__dict__
|
||||
return _checklogin
|
||||
|
||||
return _dec
|
||||
|
||||
|
||||
@@ -60,9 +63,11 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
|
||||
resp = render(request, '403.html')
|
||||
resp.status_code = 403
|
||||
return resp
|
||||
|
||||
_checklogin.__doc__ = view_func.__doc__
|
||||
_checklogin.__dict__ = view_func.__dict__
|
||||
return _checklogin
|
||||
|
||||
return _dec
|
||||
|
||||
|
||||
@@ -80,6 +85,7 @@ def api_key_required(function):
|
||||
Failed users will be given a 403 error.
|
||||
Should only be used for urls which include <api_pk> and <api_key> kwargs
|
||||
"""
|
||||
|
||||
def wrap(request, *args, **kwargs):
|
||||
|
||||
userid = kwargs.get('api_pk')
|
||||
@@ -101,6 +107,7 @@ def api_key_required(function):
|
||||
if user_object.api_key != key:
|
||||
return error_resp
|
||||
return function(request, *args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
@@ -108,11 +115,13 @@ 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')
|
||||
error_resp = render(request, 'eventauthorisation_request_error.html')
|
||||
return error_resp
|
||||
|
||||
return function(request, *args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
DATETIME_FORMAT = ('d/m/Y H:i')
|
||||
DATE_FORMAT = ('d/m/Y')
|
||||
TIME_FORMAT = ('H:i')
|
||||
|
||||
0
PyRIGS/forms.py
Normal file
0
PyRIGS/forms.py
Normal file
@@ -26,9 +26,10 @@ SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get(
|
||||
# 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
|
||||
|
||||
|
||||
STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False
|
||||
|
||||
CI = bool(int(os.environ.get('CI'))) if os.environ.get('CI') else False
|
||||
|
||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
||||
|
||||
if STAGING:
|
||||
@@ -45,7 +46,8 @@ if not DEBUG:
|
||||
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
|
||||
ADMINS = [('Tom Price', 'tomtom5152@gmail.com'), ('IT Manager', 'it@nottinghamtec.co.uk'), ('Arona Jones', 'arona.jones@nottinghamtec.co.uk')]
|
||||
ADMINS = [('Tom Price', 'tomtom5152@gmail.com'), ('IT Manager', 'it@nottinghamtec.co.uk'),
|
||||
('Arona Jones', 'arona.jones@nottinghamtec.co.uk')]
|
||||
if DEBUG:
|
||||
ADMINS.append(('Testing Superuser', 'superuser@example.com'))
|
||||
|
||||
@@ -57,6 +59,9 @@ INSTALLED_APPS = (
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'versioning',
|
||||
'users',
|
||||
'RIGS',
|
||||
'assets',
|
||||
|
||||
@@ -147,6 +152,27 @@ LOGGING = {
|
||||
}
|
||||
}
|
||||
|
||||
# Tests lock up SQLite otherwise
|
||||
if STAGING or CI:
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
|
||||
}
|
||||
}
|
||||
elif DEBUG:
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
|
||||
}
|
||||
}
|
||||
else:
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
|
||||
'LOCATION': 'cache_table',
|
||||
}
|
||||
}
|
||||
|
||||
RAVEN_CONFIG = {
|
||||
'dsn': os.environ.get('RAVEN_DSN'),
|
||||
# If you are using git, you can also automatically configure the
|
||||
@@ -164,8 +190,10 @@ LOGOUT_URL = '/user/logout/'
|
||||
ACCOUNT_ACTIVATION_DAYS = 7
|
||||
|
||||
# reCAPTCHA settings
|
||||
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY', "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
|
||||
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY', "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
|
||||
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY',
|
||||
"6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
|
||||
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY',
|
||||
"6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
|
||||
NOCAPTCHA = True
|
||||
|
||||
SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']
|
||||
@@ -201,10 +229,10 @@ USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
# Need to allow seconds as datetime-local input type spits out a time that has seconds
|
||||
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
|
||||
@@ -216,7 +244,7 @@ TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, 'templates'),
|
||||
os.path.join(BASE_DIR, 'templates')
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
@@ -243,3 +271,6 @@ 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)
|
||||
|
||||
IMGUR_UPLOAD_CLIENT_ID = os.environ.get('IMGUR_UPLOAD_CLIENT_ID', '')
|
||||
IMGUR_UPLOAD_CLIENT_SECRET = os.environ.get('IMGUR_UPLOAD_CLIENT_SECRET', '')
|
||||
|
||||
@@ -3,13 +3,31 @@ from selenium import webdriver
|
||||
from RIGS import models as rigsmodels
|
||||
from . import pages
|
||||
import os
|
||||
import pytz
|
||||
from datetime import date, time, datetime, timedelta
|
||||
from django.conf import settings
|
||||
import imgurpython
|
||||
import PyRIGS.settings
|
||||
import sys
|
||||
import pathlib
|
||||
import inspect
|
||||
|
||||
|
||||
def create_datetime(year, month, day, hour, min):
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
return tz.localize(datetime(year, month, day, hour, min)).astimezone(pytz.utc)
|
||||
|
||||
|
||||
def create_browser():
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument("--window-size=1920,1080")
|
||||
# No caching, please and thank you
|
||||
options.add_argument("--aggressive-cache-discard")
|
||||
options.add_argument("--disk-cache-size=0")
|
||||
# God Save The Queen
|
||||
options.add_argument("--lang=en_GB")
|
||||
options.add_argument("--headless")
|
||||
if os.environ.get('CI', False):
|
||||
options.add_argument("--headless")
|
||||
options.add_argument("--no-sandbox")
|
||||
driver = webdriver.Chrome(options=options)
|
||||
return driver
|
||||
@@ -34,3 +52,48 @@ class AutoLoginTest(BaseTest):
|
||||
self.profile.save()
|
||||
loginPage = pages.LoginPage(self.driver, self.live_server_url).open()
|
||||
loginPage.login("EventTest", "EventTestPassword")
|
||||
|
||||
|
||||
def screenshot_failure(func):
|
||||
def wrapper_func(self, *args, **kwargs):
|
||||
try:
|
||||
func(self, *args, **kwargs)
|
||||
except Exception as e:
|
||||
screenshot_name = func.__module__ + "." + func.__qualname__
|
||||
screenshot_file = "screenshots/" + func.__qualname__ + ".png"
|
||||
if not pathlib.Path("screenshots").is_dir():
|
||||
os.mkdir("screenshots")
|
||||
self.driver.save_screenshot(screenshot_file)
|
||||
|
||||
if settings.IMGUR_UPLOAD_CLIENT_ID != "":
|
||||
config = {
|
||||
'album': None,
|
||||
'name': screenshot_name,
|
||||
'title': screenshot_name,
|
||||
'description': ""
|
||||
}
|
||||
client = imgurpython.ImgurClient(settings.IMGUR_UPLOAD_CLIENT_ID, settings.IMGUR_UPLOAD_CLIENT_SECRET)
|
||||
image = client.upload_from_path(screenshot_file, config=config)
|
||||
print("Error in test {} is at url {}".format(screenshot_name, image['link']), file=sys.stderr)
|
||||
else:
|
||||
print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr)
|
||||
raise e
|
||||
return wrapper_func
|
||||
|
||||
|
||||
def screenshot_failure_cls(cls):
|
||||
for attr in cls.__dict__:
|
||||
if callable(getattr(cls, attr)) and attr.startswith("test"):
|
||||
setattr(cls, attr, screenshot_failure(getattr(cls, attr)))
|
||||
return cls
|
||||
|
||||
|
||||
# Checks if animation is done
|
||||
class animation_is_finished():
|
||||
def __call__(self, driver):
|
||||
numberAnimating = driver.execute_script('return $(":animated").length')
|
||||
finished = numberAnimating == 0
|
||||
if finished:
|
||||
import time
|
||||
time.sleep(0.1)
|
||||
return finished
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pypom import Page, Region
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver import Chrome
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
@@ -30,14 +31,19 @@ class BasePage(Page):
|
||||
|
||||
class FormPage(BasePage):
|
||||
_errors_selector = (By.CLASS_NAME, "alert-danger")
|
||||
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
|
||||
|
||||
def remove_all_required(self):
|
||||
self.driver.execute_script("Array.from(document.getElementsByTagName(\"input\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
|
||||
self.driver.execute_script("Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
|
||||
self.driver.execute_script(
|
||||
"Array.from(document.getElementsByTagName(\"input\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
|
||||
self.driver.execute_script(
|
||||
"Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
|
||||
|
||||
def submit(self):
|
||||
previous_errors = self.errors
|
||||
self.find_element(*self._submit_locator).click()
|
||||
submit = self.find_element(*self._submit_locator)
|
||||
ActionChains(self.driver).move_to_element(submit).perform()
|
||||
submit.click()
|
||||
self.wait.until(lambda x: self.errors != previous_errors or self.success)
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from pypom import Region
|
||||
from django.utils import timezone
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support.select import Select
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
import datetime
|
||||
|
||||
|
||||
@@ -16,11 +19,22 @@ def parse_bool_from_string(string):
|
||||
else:
|
||||
return False
|
||||
|
||||
# 12-Hour vs 24-Hour Time. Affects widget display
|
||||
|
||||
|
||||
def get_time_format():
|
||||
# Default
|
||||
time_format = "%H:%M"
|
||||
# If system is 12hr
|
||||
if timezone.now().strftime("%p"):
|
||||
time_format = "%I:%M %p"
|
||||
return time_format
|
||||
|
||||
|
||||
class BootstrapSelectElement(Region):
|
||||
_main_button_locator = (By.CSS_SELECTOR, 'button.dropdown-toggle')
|
||||
_option_box_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu')
|
||||
_option_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu.inner>li>a[role=option]')
|
||||
_option_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu.inner>li>a.dropdown-item')
|
||||
_select_all_locator = (By.CLASS_NAME, 'bs-select-all')
|
||||
_deselect_all_locator = (By.CLASS_NAME, 'bs-deselect-all')
|
||||
_search_locator = (By.CSS_SELECTOR, '.bs-searchbox>input')
|
||||
@@ -32,12 +46,12 @@ class BootstrapSelectElement(Region):
|
||||
|
||||
def toggle(self):
|
||||
original_state = self.is_open
|
||||
return self.find_element(*self._main_button_locator).click()
|
||||
option_box = self.find_element(*self._option_box_locator)
|
||||
if original_state:
|
||||
self.wait.until(expected_conditions.invisibility_of_element_located(option_box))
|
||||
if not original_state:
|
||||
self.wait.until(expected_conditions.invisibility_of_element(option_box))
|
||||
else:
|
||||
self.wait.until(expected_conditions.visibility_of_element_located(option_box))
|
||||
self.wait.until(expected_conditions.visibility_of(option_box))
|
||||
return self.find_element(*self._main_button_locator).click()
|
||||
|
||||
def open(self):
|
||||
if not self.is_open:
|
||||
@@ -55,6 +69,7 @@ class BootstrapSelectElement(Region):
|
||||
|
||||
def search(self, query):
|
||||
search_box = self.find_element(*self._search_locator)
|
||||
self.open()
|
||||
search_box.clear()
|
||||
search_box.send_keys(query)
|
||||
status_text = self.find_element(*self._status_locator)
|
||||
@@ -112,6 +127,22 @@ class CheckBox(Region):
|
||||
self.toggle()
|
||||
|
||||
|
||||
class RadioSelect(Region): # Currently only works for yes/no radio selects
|
||||
def set_value(self, value):
|
||||
if value:
|
||||
value = "0"
|
||||
else:
|
||||
value = "1"
|
||||
self.find_element(By.XPATH, "//label[@for='{}_{}']".format(self.root.get_attribute("id"), value)).click()
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
try:
|
||||
return parse_bool_from_string(self.find_element(By.CSS_SELECTOR, '.custom-control-input:checked').get_attribute("value").lower())
|
||||
except NoSuchElementException:
|
||||
return None
|
||||
|
||||
|
||||
class DatePicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
@@ -122,6 +153,32 @@ class DatePicker(Region):
|
||||
self.root.send_keys(value.strftime("%d%m%Y"))
|
||||
|
||||
|
||||
class TimePicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
return datetime.datetime.strptime(self.root.get_attribute("value"), get_time_format())
|
||||
|
||||
def set_value(self, value):
|
||||
self.root.clear()
|
||||
self.root.send_keys(value.strftime(get_time_format()))
|
||||
|
||||
|
||||
class DateTimePicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
return datetime.datetime.strptime(self.root.get_attribute("value"), "%Y-%m-%d " + get_time_format())
|
||||
|
||||
def set_value(self, value):
|
||||
self.root.clear()
|
||||
|
||||
date = value.date().strftime("%d%m%Y")
|
||||
time = value.time().strftime(get_time_format())
|
||||
|
||||
self.root.send_keys(date)
|
||||
self.root.send_keys(Keys.TAB)
|
||||
self.root.send_keys(time)
|
||||
|
||||
|
||||
class SingleSelectPicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
@@ -155,3 +212,39 @@ class ErrorPage(Region):
|
||||
for error in error_items:
|
||||
errors[error.field_name] = error.errors
|
||||
return errors
|
||||
|
||||
|
||||
class Modal(Region):
|
||||
_submit_locator = (By.CSS_SELECTOR, '.btn-primary')
|
||||
_header_selector = (By.TAG_NAME, 'h4')
|
||||
|
||||
form_items = {
|
||||
'name': (TextBox, (By.ID, 'id_name'))
|
||||
}
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self.find_element(*self._header_selector).text
|
||||
|
||||
@property
|
||||
def is_open(self):
|
||||
return self.root.is_displayed()
|
||||
|
||||
def submit(self):
|
||||
self.root.find_element(*self._submit_locator).click()
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in self.form_items:
|
||||
element = self.form_items[name]
|
||||
form_element = element[0](self, self.find_element(*element[1]))
|
||||
return form_element.value
|
||||
else:
|
||||
return super().__getattribute__(name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self.form_items:
|
||||
element = self.form_items[name]
|
||||
form_element = element[0](self, self.find_element(*element[1]))
|
||||
form_element.set_value(value)
|
||||
else:
|
||||
self.__dict__[name] = value
|
||||
|
||||
@@ -2,24 +2,36 @@ from django.urls import path
|
||||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.conf import settings
|
||||
from registration.backends.default.views import RegistrationView
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.views.generic import TemplateView
|
||||
from PyRIGS.decorators import permission_required_with_403
|
||||
import RIGS
|
||||
from RIGS import regbackend
|
||||
import users
|
||||
import versioning
|
||||
from PyRIGS import views
|
||||
|
||||
urlpatterns = [
|
||||
# Examples:
|
||||
# url(r'^$', 'PyRIGS.views.home', name='home'),
|
||||
# url(r'^blog/', include('blog.urls')),
|
||||
path('', include('versioning.urls')),
|
||||
path('', include('RIGS.urls')),
|
||||
path('assets/', include('assets.urls')),
|
||||
|
||||
url(r'^', include('RIGS.urls')),
|
||||
url('^assets/', include('assets.urls')),
|
||||
url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail),
|
||||
name="registration_register"),
|
||||
path('user/', include('django.contrib.auth.urls')),
|
||||
path('user/', include('registration.backends.default.urls')),
|
||||
path('', login_required(views.Index.as_view()), name='index'),
|
||||
|
||||
url(r'^admin/', admin.site.urls),
|
||||
# API
|
||||
path('api/<str:model>/', login_required(views.SecureAPIRequest.as_view()),
|
||||
name="api_secure"),
|
||||
path('api/<str:model>/<int:pk>/', login_required(views.SecureAPIRequest.as_view()),
|
||||
name="api_secure"),
|
||||
|
||||
path('closemodal/', views.CloseModal.as_view(), name='closemodal'),
|
||||
path('search_help/', views.SearchHelp.as_view(), name='search_help'),
|
||||
|
||||
path('', include('users.urls')),
|
||||
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
@@ -28,4 +40,5 @@ if settings.DEBUG:
|
||||
import debug_toolbar
|
||||
urlpatterns = [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
|
||||
] + urlpatterns
|
||||
|
||||
247
PyRIGS/views.py
Normal file
247
PyRIGS/views.py
Normal file
@@ -0,0 +1,247 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.views import generic
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.core import serializers
|
||||
from django.conf import settings
|
||||
import simplejson
|
||||
from django.contrib import messages
|
||||
import datetime
|
||||
import pytz
|
||||
import operator
|
||||
from registration.views import RegistrationView
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from RIGS import models, forms
|
||||
from assets import models as asset_models
|
||||
from functools import reduce
|
||||
|
||||
from django.views.decorators.cache import never_cache, cache_page
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
|
||||
# Displays the current rig count along with a few other bits and pieces
|
||||
class Index(generic.TemplateView):
|
||||
template_name = 'index.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(Index, self).get_context_data(**kwargs)
|
||||
context['rig_count'] = models.Event.objects.rig_count()
|
||||
return context
|
||||
|
||||
|
||||
class SecureAPIRequest(generic.View):
|
||||
models = {
|
||||
'venue': models.Venue,
|
||||
'person': models.Person,
|
||||
'organisation': models.Organisation,
|
||||
'profile': models.Profile,
|
||||
'event': models.Event,
|
||||
'supplier': asset_models.Supplier
|
||||
}
|
||||
|
||||
perms = {
|
||||
'venue': 'RIGS.view_venue',
|
||||
'person': 'RIGS.view_person',
|
||||
'organisation': 'RIGS.view_organisation',
|
||||
'profile': 'RIGS.view_profile',
|
||||
'event': None,
|
||||
'supplier': None
|
||||
}
|
||||
|
||||
'''
|
||||
Validate the request is allowed based on user permissions.
|
||||
Raises 403 if denied.
|
||||
Potential to add API key validation at a later date.
|
||||
'''
|
||||
|
||||
def __validate__(self, request, key, perm):
|
||||
if request.user.is_active:
|
||||
if request.user.is_superuser or perm is None:
|
||||
return True
|
||||
elif request.user.has_perm(perm):
|
||||
return True
|
||||
raise PermissionDenied()
|
||||
|
||||
def get(self, request, model, pk=None, param=None):
|
||||
# Request permission validation things
|
||||
key = request.GET.get('apikey', None)
|
||||
perm = self.perms[model]
|
||||
self.__validate__(request, key, perm)
|
||||
|
||||
# Response format where applicable
|
||||
format = request.GET.get('format', 'json')
|
||||
fields = request.GET.get('fields', None)
|
||||
if fields:
|
||||
fields = fields.split(",")
|
||||
|
||||
# Supply data for one record
|
||||
if pk:
|
||||
object = get_object_or_404(self.models[model], pk=pk)
|
||||
data = serializers.serialize(format, [object], fields=fields)
|
||||
return HttpResponse(data, content_type="application/" + format)
|
||||
|
||||
# Supply data for autocomplete ajax request in json form
|
||||
term = request.GET.get('q', None)
|
||||
if term:
|
||||
if fields is None: # Default to just name
|
||||
fields = ['name']
|
||||
|
||||
# Build a list of Q objects for use later
|
||||
queries = []
|
||||
for part in term.split(" "):
|
||||
qs = []
|
||||
for field in fields:
|
||||
q = Q(**{field + "__icontains": part})
|
||||
qs.append(q)
|
||||
queries.append(reduce(operator.or_, qs))
|
||||
|
||||
# Build the data response list
|
||||
results = []
|
||||
query = reduce(operator.and_, queries)
|
||||
objects = self.models[model].objects.filter(query)
|
||||
for o in objects:
|
||||
data = {
|
||||
'pk': o.pk,
|
||||
'value': o.pk,
|
||||
'text': o.name,
|
||||
}
|
||||
try: # See if there is a valid update URL
|
||||
data['update'] = reverse("%s_update" % model, kwargs={'pk': o.pk})
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
results.append(data)
|
||||
|
||||
# return a data response
|
||||
json = simplejson.dumps(results)
|
||||
return HttpResponse(json, content_type="application/json") # Always json
|
||||
|
||||
start = request.GET.get('start', None)
|
||||
end = request.GET.get('end', None)
|
||||
|
||||
if model == "event" and start and end:
|
||||
# Probably a calendar request
|
||||
start_datetime = datetime.datetime.strptime(start, "%Y-%m-%dT%H:%M:%S")
|
||||
end_datetime = datetime.datetime.strptime(end, "%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
objects = self.models[model].objects.events_in_bounds(start_datetime, end_datetime)
|
||||
|
||||
results = []
|
||||
for item in objects:
|
||||
data = {
|
||||
'pk': item.pk,
|
||||
'title': item.name,
|
||||
'is_rig': item.is_rig,
|
||||
'status': str(item.get_status_display()),
|
||||
'earliest': item.earliest_time.isoformat(),
|
||||
'latest': item.latest_time.isoformat(),
|
||||
'url': str(item.get_absolute_url())
|
||||
}
|
||||
|
||||
results.append(data)
|
||||
json = simplejson.dumps(results)
|
||||
return HttpResponse(json, content_type="application/json") # Always json
|
||||
|
||||
return HttpResponse(model)
|
||||
|
||||
|
||||
class ModalURLMixin:
|
||||
def get_close_url(self, update, detail):
|
||||
if self.request.is_ajax():
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy(update, kwargs={'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
|
||||
messages.info(self.request, "modalobject[0]['update_url']='" + update_url + "'")
|
||||
else:
|
||||
url = reverse_lazy(detail, kwargs={
|
||||
'pk': self.object.pk,
|
||||
})
|
||||
return url
|
||||
|
||||
|
||||
class GenericListView(generic.ListView):
|
||||
template_name = 'generic_list.html'
|
||||
paginate_by = 20
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericListView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = self.model.__name__ + "s"
|
||||
if self.request.is_ajax():
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
q = self.request.GET.get('q', "")
|
||||
|
||||
filter = Q(name__icontains=q) | Q(email__icontains=q) | Q(address__icontains=q) | Q(notes__icontains=q) | Q(
|
||||
phone__startswith=q) | Q(phone__endswith=q)
|
||||
|
||||
# try and parse an int
|
||||
try:
|
||||
val = int(q)
|
||||
filter = filter | Q(pk=val)
|
||||
except: # noqa
|
||||
# not an integer
|
||||
pass
|
||||
|
||||
object_list = self.model.objects.filter(filter)
|
||||
|
||||
orderBy = self.request.GET.get('orderBy', "name")
|
||||
if orderBy != "":
|
||||
object_list = object_list.order_by(orderBy)
|
||||
return object_list
|
||||
|
||||
|
||||
class GenericDetailView(generic.DetailView):
|
||||
template_name = "generic_detail.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericDetailView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name)
|
||||
if self.request.is_ajax():
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
|
||||
class GenericUpdateView(generic.UpdateView):
|
||||
template_name = "generic_form.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericUpdateView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Edit {}".format(self.model.__name__)
|
||||
if self.request.is_ajax():
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
|
||||
class GenericCreateView(generic.CreateView):
|
||||
template_name = "generic_form.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericCreateView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Create {}".format(self.model.__name__)
|
||||
if self.request.is_ajax():
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
|
||||
class SearchHelp(generic.TemplateView):
|
||||
template_name = 'search_help.html'
|
||||
|
||||
|
||||
"""
|
||||
Called from a modal window (e.g. when an item is submitted to an event/invoice).
|
||||
May optionally also include some javascript in a success message to cause a load of
|
||||
the new information onto the page.
|
||||
"""
|
||||
|
||||
|
||||
class CloseModal(generic.TemplateView):
|
||||
template_name = 'closemodal.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return {'messages': messages.get_messages(self.request)}
|
||||
@@ -7,6 +7,7 @@ For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
||||
"""
|
||||
import os
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "PyRIGS.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application # noqa
|
||||
|
||||
114
README.md
114
README.md
@@ -1,111 +1,17 @@
|
||||
# TEC PA & Lighting - PyRIGS #
|
||||
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
||||
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
||||
[](https://coveralls.io/github/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 & Lighting's PyRIGS program. This is a reimplementation of the previous Rig Information Gathering System (RIGS) that was developed using Ruby on Rails. PyRIGS is our in house app for the centralisation of information on our events and now assets.
|
||||
|
||||
The purpose of this project is to make the system more compatible and easier to understand such that should future changes be needed they can be made without having to understand the intricacies of Rails.
|
||||
For setup information and other such helpful stuff check the [Wiki](https://github.com/nottinghamtec/PyRIGS/wiki)
|
||||
|
||||
### What is this repository for? ###
|
||||
When a significant feature is developed on a branch, raise a pull request and it can be reviewed before being put into production.
|
||||
|
||||
Most of the documents here assume a basic knowledge of how Python and Django work (hint, if I don't say something, Google it, you will find 10000's of answers). The documentation is purely to be specific to TEC's application of the framework.
|
||||
|
||||
### Editing ###
|
||||
It is recommended that you use the PyCharm IDE by JetBrains. Whilst other editors are available, this is the best for integration with Django as it can automatically manage all the pesky admin commands that frequently need running, as well as nice integration with git.
|
||||
|
||||
For the more experienced developer/somebody who doesn't want a full IDE and wants it to open in less than the age of the universe, I can strongly recommend [Sublime Text](http://www.sublimetext.com/). It has a bit of a steeper learning curve, and won't manage anything Django/git related out of the box, but once you get the hang of it is by far the fastest and most powerful editor I have used (for any type of project).
|
||||
|
||||
Please contact TJP for details on how to acquire these.
|
||||
|
||||
### Python Environment ###
|
||||
Whilst the Python version used is not critical to the running of the application, using the same version usually helps avoid a lot of issues. Orginally written with the C implementation of Python 2 (CPython 2, specifically the Python 2.7 standard), the application now runs in Python 3.
|
||||
|
||||
Once you have your Python distribution installed, go ahead an follow the steps to set up a virtualenv, which will isolate the project from the system environment.
|
||||
|
||||
#### PyCharm ####
|
||||
If you are using the prefered PyCharm IDE, then this should be quite easy.
|
||||
|
||||
1. Select "File/Settings" -> "Project Interpreter"
|
||||
2. Click the small cog in the top right
|
||||
3. Select "Create VirtualEnv"
|
||||
4. Enter a name and a location. This doesn't matter where, just make sure it makes sense and you remember it incase you need it later (I recommend calling it "pyrigs" in "~/.virtualenvs/pyrigs")
|
||||
5. Select the base interpreter to your Python 3 base interpreter (Python 2 will work, just be careful)
|
||||
6. Click OK, you *don't* want to inherit global packages or make it available to all projects.
|
||||
7. Open a file such as manage.py. PyCharm should winge that dependances aren't installed. This might take a while to register, but give it change. When it does, click the button to install them and let it do it's thing. If for some reason PyCharm should decide that it doesn't want to help you here, see below for the console instructions on how to do this manually.
|
||||
|
||||
To run the Django application follow these steps
|
||||
|
||||
1. Select "Run/Edit Configurations"
|
||||
2. Create a new "Django server", give it a sensible name for when you need it later.
|
||||
3. You might need to set the interpreter to be your virtualenv.
|
||||
4. Click "OK"
|
||||
5. Run the application
|
||||
|
||||
#### Console Based ####
|
||||
If you aren't using PyCharm, or want to use a console for some reason, this is really easy, there is even [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) to help things along. Simply run
|
||||
```
|
||||
virtualenv <dir>
|
||||
```
|
||||
Where dir is the directory you wish to create the virtualenv in.
|
||||
|
||||
Next activate the virtualenv.
|
||||
```
|
||||
Windows
|
||||
<virtualenv_dir>/Scripts/activate.bat
|
||||
|
||||
Unix
|
||||
source <virtualenv_dir>/bin/activate
|
||||
```
|
||||
Finally install the requirements using pip
|
||||
```
|
||||
cd <pyrigs project directory>
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
This might take a while, but be patient and you should then be ready to go.
|
||||
|
||||
To run the server under normal conditions when you are already in the virtualenv (see above)
|
||||
```
|
||||
python manage.py runserver
|
||||
```
|
||||
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 is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty:
|
||||
```
|
||||
python manage.py flush
|
||||
```
|
||||
Then load the sample data using the command:
|
||||
```
|
||||
python manage.py generateSampleData
|
||||
```
|
||||
4 user accounts are created for convenience:
|
||||
|
||||
|Username |Password |
|
||||
|---------|---------|
|
||||
|superuser|superuser|
|
||||
|finance |finance |
|
||||
|keyholder|keyholder|
|
||||
|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
|
||||
|
||||
```
|
||||
# Apps
|
||||
- PyRIGS: Base app, stores 'global' information
|
||||
- RIGS: Rigboard stuff - event calendar etc
|
||||
- assets: Database of our kit, testing data etc
|
||||
- versioning: Our custom logic built on top of django-reversion. Semi-modular.
|
||||
- users: Our custom logic for registration and profiles. Semi-modular.
|
||||
- training: SoonTM
|
||||
|
||||
[](https://forthebadge.com) [](https://forthebadge.com)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from RIGS import models, forms
|
||||
from users import forms as user_forms
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from reversion.admin import VersionAdmin
|
||||
@@ -18,8 +19,7 @@ from reversion import revisions as reversion
|
||||
admin.site.register(models.VatRate, VersionAdmin)
|
||||
admin.site.register(models.Event, VersionAdmin)
|
||||
admin.site.register(models.EventItem, VersionAdmin)
|
||||
admin.site.register(models.Invoice)
|
||||
admin.site.register(models.Payment)
|
||||
admin.site.register(models.Invoice, VersionAdmin)
|
||||
|
||||
|
||||
def approve_user(modeladmin, request, queryset):
|
||||
@@ -48,8 +48,8 @@ class ProfileAdmin(UserAdmin):
|
||||
'fields': ('username', 'password1', 'password2'),
|
||||
}),
|
||||
)
|
||||
form = forms.ProfileChangeForm
|
||||
add_form = forms.ProfileCreationForm
|
||||
form = user_forms.ProfileChangeForm
|
||||
add_form = user_forms.ProfileCreationForm
|
||||
actions = [approve_user]
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ class AssociateAdmin(VersionAdmin):
|
||||
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
|
||||
'forms': forms
|
||||
}
|
||||
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context)
|
||||
return TemplateResponse(request, 'admin_associate_merge.html', context)
|
||||
|
||||
|
||||
@admin.register(models.Person)
|
||||
@@ -124,3 +124,13 @@ class VenueAdmin(AssociateAdmin):
|
||||
class OrganisationAdmin(AssociateAdmin):
|
||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||
merge_fields = ['name', 'phone', 'email', 'address', 'notes', 'union_account']
|
||||
|
||||
|
||||
@admin.register(models.RiskAssessment)
|
||||
class RiskAssessmentAdmin(VersionAdmin):
|
||||
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
||||
|
||||
|
||||
@admin.register(models.EventChecklist)
|
||||
class EventChecklistAdmin(VersionAdmin):
|
||||
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
||||
|
||||
@@ -13,23 +13,27 @@ from django.db.models import Q
|
||||
from z3c.rml import rml2pdf
|
||||
from django.db.models import Q
|
||||
|
||||
from django.db import transaction
|
||||
import reversion
|
||||
|
||||
from RIGS import models
|
||||
|
||||
from django import forms
|
||||
|
||||
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
||||
|
||||
|
||||
class InvoiceIndex(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'RIGS/invoice_list_active.html'
|
||||
template_name = 'invoice_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceIndex, self).get_context_data(**kwargs)
|
||||
total = 0
|
||||
for i in context['object_list']:
|
||||
total += i.balance
|
||||
context['total'] = total
|
||||
context['count'] = len(list(context['object_list']))
|
||||
context['page_title'] = "Outstanding Invoices ({} Events, £{:.2f})".format(len(list(context['object_list'])), total)
|
||||
context['description'] = "Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger"
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -51,13 +55,19 @@ class InvoiceIndex(generic.ListView):
|
||||
|
||||
class InvoiceDetail(generic.DetailView):
|
||||
model = models.Invoice
|
||||
template_name = 'invoice_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceDetail, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Invoice {} ({})".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
|
||||
return context
|
||||
|
||||
|
||||
class InvoicePrint(generic.View):
|
||||
def get(self, request, pk):
|
||||
invoice = get_object_or_404(models.Invoice, pk=pk)
|
||||
object = invoice.event
|
||||
template = get_template('RIGS/event_print.xml')
|
||||
template = get_template('event_print.xml')
|
||||
|
||||
context = {
|
||||
'object': object,
|
||||
@@ -69,6 +79,7 @@ class InvoicePrint(generic.View):
|
||||
},
|
||||
'invoice': invoice,
|
||||
'current_user': request.user,
|
||||
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
|
||||
}
|
||||
|
||||
rml = template.render(context)
|
||||
@@ -77,10 +88,8 @@ class InvoicePrint(generic.View):
|
||||
|
||||
pdfData = buffer.read()
|
||||
|
||||
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||
|
||||
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="{}"'.format(context['filename'])
|
||||
response.write(pdfData)
|
||||
return response
|
||||
|
||||
@@ -99,6 +108,7 @@ class InvoiceVoid(generic.View):
|
||||
|
||||
class InvoiceDelete(generic.DeleteView):
|
||||
model = models.Invoice
|
||||
template_name = 'invoice_confirm_delete.html'
|
||||
|
||||
def get(self, request, pk):
|
||||
obj = self.get_object()
|
||||
@@ -120,9 +130,15 @@ class InvoiceDelete(generic.DeleteView):
|
||||
|
||||
class InvoiceArchive(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'RIGS/invoice_list_archive.html'
|
||||
template_name = 'invoice_list_archive.html'
|
||||
paginate_by = 25
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceArchive, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Invoice Archive"
|
||||
context['description'] = "This page displays all invoices: outstanding, paid, and void"
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
q = self.request.GET.get('q', "")
|
||||
|
||||
@@ -155,15 +171,14 @@ class InvoiceArchive(generic.ListView):
|
||||
class InvoiceWaiting(generic.ListView):
|
||||
model = models.Event
|
||||
paginate_by = 25
|
||||
template_name = 'RIGS/event_invoice.html'
|
||||
template_name = 'invoice_list_waiting.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
|
||||
total = 0
|
||||
for obj in self.get_objects():
|
||||
total += obj.sum_total
|
||||
context['total'] = total
|
||||
context['count'] = len(self.get_objects())
|
||||
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(self.get_objects()), total)
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -188,7 +203,10 @@ class InvoiceWaiting(generic.ListView):
|
||||
|
||||
|
||||
class InvoiceEvent(generic.View):
|
||||
@transaction.atomic()
|
||||
@reversion.create_revision()
|
||||
def get(self, *args, **kwargs):
|
||||
reversion.set_user(self.request.user)
|
||||
epk = kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
invoice, created = models.Invoice.objects.get_or_create(event=event)
|
||||
@@ -197,12 +215,18 @@ class InvoiceEvent(generic.View):
|
||||
invoice.invoice_date = datetime.date.today()
|
||||
messages.success(self.request, 'Invoice created successfully')
|
||||
|
||||
if kwargs.get('void'):
|
||||
invoice.void = not invoice.void
|
||||
invoice.save()
|
||||
messages.warning(self.request, 'Invoice voided')
|
||||
|
||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk}))
|
||||
|
||||
|
||||
class PaymentCreate(generic.CreateView):
|
||||
model = models.Payment
|
||||
fields = ['invoice', 'date', 'amount', 'method']
|
||||
template_name = 'payment_form.html'
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(generic.CreateView, self).get_initial()
|
||||
@@ -213,6 +237,13 @@ class PaymentCreate(generic.CreateView):
|
||||
initial.update({'invoice': invoice})
|
||||
return initial
|
||||
|
||||
@transaction.atomic()
|
||||
@reversion.create_revision()
|
||||
def form_valid(self, form, *args, **kwargs):
|
||||
reversion.add_to_revision(form.cleaned_data['invoice'])
|
||||
reversion.set_comment("Payment added")
|
||||
return super().form_valid(form, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
messages.info(self.request, "location.reload()")
|
||||
return reverse_lazy('closemodal')
|
||||
@@ -220,6 +251,14 @@ class PaymentCreate(generic.CreateView):
|
||||
|
||||
class PaymentDelete(generic.DeleteView):
|
||||
model = models.Payment
|
||||
template_name = 'payment_confirm_delete.html'
|
||||
|
||||
@transaction.atomic()
|
||||
@reversion.create_revision()
|
||||
def delete(self, *args, **kwargs):
|
||||
reversion.add_to_revision(self.get_object().invoice)
|
||||
reversion.set_comment("Payment removed")
|
||||
return super().delete(*args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.POST.get('next')
|
||||
|
||||
190
RIGS/forms.py
190
RIGS/forms.py
@@ -4,69 +4,26 @@ from django.conf import settings
|
||||
from django.core import serializers
|
||||
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm
|
||||
from django.db import transaction
|
||||
from registration.forms import RegistrationFormUniqueEmail
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from captcha.fields import ReCaptchaField
|
||||
from reversion import revisions as reversion
|
||||
import simplejson
|
||||
from datetime import datetime
|
||||
from django.utils import timezone
|
||||
|
||||
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
|
||||
|
||||
|
||||
class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
|
||||
captcha = ReCaptchaField()
|
||||
|
||||
class Meta:
|
||||
model = models.Profile
|
||||
fields = ('username', 'email', 'first_name', 'last_name', 'initials')
|
||||
|
||||
def clean_initials(self):
|
||||
"""
|
||||
Validate that the supplied initials are unique.
|
||||
"""
|
||||
if models.Profile.objects.filter(initials__iexact=self.cleaned_data['initials']):
|
||||
raise forms.ValidationError("These initials are already in use. Please supply different initials.")
|
||||
return self.cleaned_data['initials']
|
||||
|
||||
|
||||
class CheckApprovedForm(AuthenticationForm):
|
||||
def confirm_login_allowed(self, user):
|
||||
if user.is_approved or user.is_superuser:
|
||||
return AuthenticationForm.confirm_login_allowed(self, user)
|
||||
else:
|
||||
raise forms.ValidationError("Your account hasn't been approved by an administrator yet. Please check back in a few minutes!")
|
||||
|
||||
|
||||
# Embedded Login form - remove the autofocus
|
||||
class EmbeddedAuthenticationForm(CheckApprovedForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['username'].widget.attrs.pop('autofocus', None)
|
||||
|
||||
|
||||
class PasswordReset(PasswordResetForm):
|
||||
captcha = ReCaptchaField(label='Captcha')
|
||||
|
||||
|
||||
class ProfileCreationForm(UserCreationForm):
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = models.Profile
|
||||
|
||||
|
||||
class ProfileChangeForm(UserChangeForm):
|
||||
class Meta(UserChangeForm.Meta):
|
||||
model = models.Profile
|
||||
forms.TimeField.widget = forms.TimeInput(attrs={'type': 'time'}, format='%H:%M')
|
||||
forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M')
|
||||
|
||||
|
||||
# Events Shit
|
||||
class EventForm(forms.ModelForm):
|
||||
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + list(settings.DATETIME_INPUT_FORMATS)
|
||||
datetime_input_formats = list(settings.DATETIME_INPUT_FORMATS)
|
||||
meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
|
||||
access_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
|
||||
|
||||
@@ -140,8 +97,11 @@ class EventForm(forms.ModelForm):
|
||||
return item
|
||||
|
||||
def clean(self):
|
||||
if self.cleaned_data.get("is_rig") and not (self.cleaned_data.get('person') or self.cleaned_data.get('organisation')):
|
||||
raise forms.ValidationError('You haven\'t provided any client contact details. Please add a person or organisation.', code='contact')
|
||||
if self.cleaned_data.get("is_rig") and not (
|
||||
self.cleaned_data.get('person') or self.cleaned_data.get('organisation')):
|
||||
raise forms.ValidationError(
|
||||
'You haven\'t provided any client contact details. Please add a person or organisation.',
|
||||
code='contact')
|
||||
return super(EventForm, self).clean()
|
||||
|
||||
def save(self, commit=True):
|
||||
@@ -195,3 +155,129 @@ class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
|
||||
|
||||
class EventAuthorisationRequestForm(forms.Form):
|
||||
email = forms.EmailField(required=True, label='Authoriser Email')
|
||||
|
||||
|
||||
class EventRiskAssessmentForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EventRiskAssessmentForm, self).__init__(*args, **kwargs)
|
||||
for name, field in self.fields.items():
|
||||
if str(name) == 'supervisor_consulted':
|
||||
field.widget = forms.CheckboxInput()
|
||||
elif field.__class__ == forms.BooleanField:
|
||||
field.widget = forms.RadioSelect(choices=[
|
||||
(True, 'Yes'),
|
||||
(False, 'No')
|
||||
], attrs={'class': 'custom-control-input', 'required': 'true'})
|
||||
|
||||
def clean(self):
|
||||
# Check expected values
|
||||
unexpected_values = []
|
||||
for field, value in models.RiskAssessment.expected_values.items():
|
||||
if self.cleaned_data.get(field) != value:
|
||||
unexpected_values.append("<li>{}</li>".format(self._meta.model._meta.get_field(field).help_text))
|
||||
if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'):
|
||||
raise forms.ValidationError("Your answers to these questions: <ul>{}</ul> require consulting with a supervisor.".format(''.join([str(elem) for elem in unexpected_values])), code='unusual_answers')
|
||||
return super(EventRiskAssessmentForm, self).clean()
|
||||
|
||||
class Meta:
|
||||
model = models.RiskAssessment
|
||||
fields = '__all__'
|
||||
exclude = ['reviewed_at', 'reviewed_by']
|
||||
|
||||
|
||||
class EventChecklistForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EventChecklistForm, self).__init__(*args, **kwargs)
|
||||
self.fields['date'].widget.format = '%Y-%m-%d'
|
||||
for name, field in self.fields.items():
|
||||
if field.__class__ == forms.NullBooleanField:
|
||||
# Only display yes/no to user, the 'none' is only ever set in the background
|
||||
field.widget = forms.CheckboxInput()
|
||||
# Parsed from incoming form data by clean, then saved into models when the form is saved
|
||||
items = {}
|
||||
|
||||
related_models = {
|
||||
'venue': models.Venue,
|
||||
'power_mic': models.Profile,
|
||||
}
|
||||
|
||||
# Two possible formats
|
||||
def parsedatetime(self, date_string):
|
||||
try:
|
||||
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S'))
|
||||
except ValueError:
|
||||
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M'))
|
||||
|
||||
# There's probably a thousand better ways to do this, but this one is mine
|
||||
def clean(self):
|
||||
vehicles = {key: val for key, val in self.data.items()
|
||||
if key.startswith('vehicle')}
|
||||
for key in vehicles:
|
||||
pk = int(key.split('_')[1])
|
||||
driver_key = 'driver_' + str(pk)
|
||||
if(self.data[driver_key] == ''):
|
||||
raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
|
||||
else:
|
||||
try:
|
||||
item = models.EventChecklistVehicle.objects.get(pk=pk)
|
||||
except models.EventChecklistVehicle.DoesNotExist:
|
||||
item = models.EventChecklistVehicle()
|
||||
|
||||
item.vehicle = vehicles['vehicle_' + str(pk)]
|
||||
item.driver = models.Profile.objects.get(pk=self.data[driver_key])
|
||||
item.full_clean('checklist')
|
||||
|
||||
# item does not have a database pk yet as it isn't saved
|
||||
self.items['v' + str(pk)] = item
|
||||
|
||||
crewmembers = {key: val for key, val in self.data.items()
|
||||
if key.startswith('crewmember')}
|
||||
other_fields = ['start', 'role', 'end']
|
||||
for key in crewmembers:
|
||||
pk = int(key.split('_')[1])
|
||||
|
||||
for field in other_fields:
|
||||
value = self.data['{}_{}'.format(field, pk)]
|
||||
if value == '':
|
||||
raise forms.ValidationError('Add a {} to crewmember {}'.format(field, pk), code='{}_mismatch'.format(field))
|
||||
|
||||
try:
|
||||
item = models.EventChecklistCrew.objects.get(pk=pk)
|
||||
except models.EventChecklistCrew.DoesNotExist:
|
||||
item = models.EventChecklistCrew()
|
||||
|
||||
item.crewmember = models.Profile.objects.get(pk=self.data['crewmember_' + str(pk)])
|
||||
item.start = self.parsedatetime(self.data['start_' + str(pk)])
|
||||
item.role = self.data['role_' + str(pk)]
|
||||
item.end = self.parsedatetime(self.data['end_' + str(pk)])
|
||||
item.full_clean('checklist')
|
||||
|
||||
# item does not have a database pk yet as it isn't saved
|
||||
self.items['c' + str(pk)] = item
|
||||
|
||||
return super(EventChecklistForm, self).clean()
|
||||
|
||||
def save(self, commit=True):
|
||||
checklist = super(EventChecklistForm, self).save(commit=False)
|
||||
if (commit):
|
||||
# Remove all existing, to be recreated from the form
|
||||
checklist.vehicles.all().delete()
|
||||
checklist.crew.all().delete()
|
||||
checklist.save()
|
||||
|
||||
for key in self.items:
|
||||
item = self.items[key]
|
||||
reversion.add_to_revision(item)
|
||||
# finish and save new database items
|
||||
item.checklist = checklist
|
||||
item.full_clean()
|
||||
item.save()
|
||||
|
||||
self.items.clear()
|
||||
|
||||
return checklist
|
||||
|
||||
class Meta:
|
||||
model = models.EventChecklist
|
||||
fields = '__all__'
|
||||
exclude = ['reviewed_at', 'reviewed_by']
|
||||
|
||||
217
RIGS/hs.py
Normal file
217
RIGS/hs.py
Normal file
@@ -0,0 +1,217 @@
|
||||
from RIGS import models, forms
|
||||
from django.views import generic
|
||||
from django.utils import timezone
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy
|
||||
from reversion import revisions as reversion
|
||||
from django.db.models import AutoField, ManyToOneRel
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
class EventRiskAssessmentCreate(generic.CreateView):
|
||||
model = models.RiskAssessment
|
||||
template_name = 'risk_assessment_form.html'
|
||||
form_class = forms.EventRiskAssessmentForm
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
epk = kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
|
||||
# Check if RA exists
|
||||
ra = models.RiskAssessment.objects.filter(event=event).first()
|
||||
|
||||
if ra is not None:
|
||||
return HttpResponseRedirect(reverse_lazy('ra_edit', kwargs={'pk': ra.pk}))
|
||||
|
||||
return super(EventRiskAssessmentCreate, self).get(self)
|
||||
|
||||
def get_form(self, **kwargs):
|
||||
form = super(EventRiskAssessmentCreate, self).get_form(**kwargs)
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
form.instance.event = event
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentCreate, self).get_context_data(**kwargs)
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
context['event'] = event
|
||||
context['page_title'] = 'Create Risk Assessment for Event {}'.format(event.display_id)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
class EventRiskAssessmentEdit(generic.UpdateView):
|
||||
model = models.RiskAssessment
|
||||
template_name = 'risk_assessment_form.html'
|
||||
form_class = forms.EventRiskAssessmentForm
|
||||
|
||||
def get_success_url(self):
|
||||
ra = self.get_object()
|
||||
ra.reviewed_by = None
|
||||
ra.reviewed_at = None
|
||||
ra.save()
|
||||
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentEdit, self).get_context_data(**kwargs)
|
||||
rpk = self.kwargs.get('pk')
|
||||
ra = models.RiskAssessment.objects.get(pk=rpk)
|
||||
context['event'] = ra.event
|
||||
context['edit'] = True
|
||||
context['page_title'] = 'Edit Risk Assessment for Event {}'.format(ra.event.display_id)
|
||||
return context
|
||||
|
||||
|
||||
class EventRiskAssessmentDetail(generic.DetailView):
|
||||
model = models.RiskAssessment
|
||||
template_name = 'risk_assessment_detail.html'
|
||||
|
||||
|
||||
class EventRiskAssessmentList(generic.ListView):
|
||||
paginate_by = 20
|
||||
model = models.RiskAssessment
|
||||
template_name = 'hs_object_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
|
||||
context['title'] = 'Risk Assessment'
|
||||
context['view'] = 'ra_detail'
|
||||
context['edit'] = 'ra_edit'
|
||||
context['review'] = 'ra_review'
|
||||
context['perm'] = 'perms.RIGS.review_riskassessment'
|
||||
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||
return context
|
||||
|
||||
|
||||
class EventRiskAssessmentReview(generic.View):
|
||||
def get(self, *args, **kwargs):
|
||||
rpk = kwargs.get('pk')
|
||||
ra = models.RiskAssessment.objects.get(pk=rpk)
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(self.request.user)
|
||||
ra.reviewed_by = self.request.user
|
||||
ra.reviewed_at = timezone.now()
|
||||
ra.save()
|
||||
return HttpResponseRedirect(reverse_lazy('ra_list'))
|
||||
|
||||
|
||||
class EventChecklistDetail(generic.DetailView):
|
||||
model = models.EventChecklist
|
||||
template_name = 'event_checklist_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistDetail, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Event Checklist for Event {} {}".format(self.object.event.display_id, self.object.event.name)
|
||||
return context
|
||||
|
||||
|
||||
class EventChecklistEdit(generic.UpdateView):
|
||||
model = models.EventChecklist
|
||||
template_name = 'event_checklist_form.html'
|
||||
form_class = forms.EventChecklistForm
|
||||
|
||||
def get_success_url(self):
|
||||
ec = self.get_object()
|
||||
ec.reviewed_by = None
|
||||
ec.reviewed_at = None
|
||||
ec.save()
|
||||
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistEdit, self).get_context_data(**kwargs)
|
||||
pk = self.kwargs.get('pk')
|
||||
ec = models.EventChecklist.objects.get(pk=pk)
|
||||
context['event'] = ec.event
|
||||
context['edit'] = True
|
||||
context['page_title'] = 'Edit Event Checklist for Event {}'.format(ec.event.display_id)
|
||||
form = context['form']
|
||||
# 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():
|
||||
value = form[field].value()
|
||||
if value is not None and value != '':
|
||||
context[field] = model.objects.get(pk=value)
|
||||
return context
|
||||
|
||||
|
||||
class EventChecklistCreate(generic.CreateView):
|
||||
model = models.EventChecklist
|
||||
template_name = 'event_checklist_form.html'
|
||||
form_class = forms.EventChecklistForm
|
||||
|
||||
# From both business logic and programming POVs, RAs must exist before ECs!
|
||||
def get(self, *args, **kwargs):
|
||||
epk = kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
|
||||
# Check if RA exists
|
||||
ra = models.RiskAssessment.objects.filter(event=event).first()
|
||||
|
||||
if ra is None:
|
||||
messages.error(self.request, 'A Risk Assessment must exist prior to creating any Event Checklists for {}! Please create one now.'.format(event))
|
||||
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
|
||||
|
||||
return super(EventChecklistCreate, self).get(self)
|
||||
|
||||
def get_form(self, **kwargs):
|
||||
form = super(EventChecklistCreate, self).get_form(**kwargs)
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
form.instance.event = event
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistCreate, self).get_context_data(**kwargs)
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
context['event'] = event
|
||||
context['page_title'] = 'Create Event Checklist for Event {}'.format(event.display_id)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
class EventChecklistList(generic.ListView):
|
||||
paginate_by = 20
|
||||
model = models.EventChecklist
|
||||
template_name = 'hs_object_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistList, self).get_context_data(**kwargs)
|
||||
context['title'] = 'Event Checklist'
|
||||
context['view'] = 'ec_detail'
|
||||
context['edit'] = 'ec_edit'
|
||||
context['review'] = 'ec_review'
|
||||
context['perm'] = 'perms.RIGS.review_eventchecklist'
|
||||
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||
return context
|
||||
|
||||
|
||||
class EventChecklistReview(generic.View):
|
||||
def get(self, *args, **kwargs):
|
||||
rpk = kwargs.get('pk')
|
||||
ec = models.EventChecklist.objects.get(pk=rpk)
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(self.request.user)
|
||||
ec.reviewed_by = self.request.user
|
||||
ec.reviewed_at = timezone.now()
|
||||
ec.save()
|
||||
return HttpResponseRedirect(reverse_lazy('ec_list'))
|
||||
|
||||
|
||||
class HSList(generic.ListView):
|
||||
paginate_by = 20
|
||||
model = models.Event
|
||||
template_name = 'hs_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return models.Event.objects.all().order_by('-start_date')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(HSList, self).get_context_data(**kwargs)
|
||||
context['page_title'] = 'H&S Overview'
|
||||
return context
|
||||
26
RIGS/ical.py
26
RIGS/ical.py
@@ -40,8 +40,10 @@ class CalendarICS(ICalFeed):
|
||||
return 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 = desc + "Includes events with status: " + ('Cancelled, ' if params['cancelled'] else '') + ('Provisional, ' if params['provisional'] else '') + ('Confirmed/Booked, ' if params['confirmed'] else '')
|
||||
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 '')
|
||||
|
||||
return desc
|
||||
|
||||
@@ -72,7 +74,8 @@ class CalendarICS(ICalFeed):
|
||||
|
||||
filter = filter & typeFilters & statusFilters
|
||||
|
||||
return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation',
|
||||
'venue', 'mic')
|
||||
|
||||
def item_title(self, item):
|
||||
title = ''
|
||||
@@ -99,7 +102,7 @@ class CalendarICS(ICalFeed):
|
||||
return item.earliest_time
|
||||
|
||||
def item_end_datetime(self, item):
|
||||
if type(item.latest_time) == datetime.date: # Ical end_datetime is non-inclusive, so add a day
|
||||
if isinstance(item.latest_time, datetime.date): # Ical end_datetime is non-inclusive, so add a day
|
||||
return item.latest_time + datetime.timedelta(days=1)
|
||||
|
||||
return item.latest_time
|
||||
@@ -117,19 +120,24 @@ class CalendarICS(ICalFeed):
|
||||
desc += 'Event = ' + item.name + '\n'
|
||||
desc += 'Venue = ' + (item.venue.name if item.venue else '---') + '\n'
|
||||
if item.is_rig and item.person:
|
||||
desc += 'Client = ' + item.person.name + ((' for ' + item.organisation.name) if item.organisation else '') + '\n'
|
||||
desc += 'Client = ' + item.person.name + (
|
||||
(' for ' + item.organisation.name) if item.organisation else '') + '\n'
|
||||
desc += 'Status = ' + str(item.get_status_display()) + '\n'
|
||||
desc += 'MIC = ' + (item.mic.name if item.mic else '---') + '\n'
|
||||
|
||||
desc += '\n'
|
||||
if item.meet_at:
|
||||
desc += 'Crew Meet = ' + (item.meet_at.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:
|
||||
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:
|
||||
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:
|
||||
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'
|
||||
if item.description:
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
__author__ = 'ghost'
|
||||
|
||||
import unittest
|
||||
from importer import fix_email
|
||||
|
||||
|
||||
class EmailFixerTest(unittest.TestCase):
|
||||
def test_correct(self):
|
||||
e = fix_email("tom@ghost.uk.net")
|
||||
self.assertEqual(e, "tom@ghost.uk.net")
|
||||
|
||||
def test_partial(self):
|
||||
e = fix_email("psytp")
|
||||
self.assertEqual(e, "psytp@nottingham.ac.uk")
|
||||
|
||||
def test_none(self):
|
||||
old = None
|
||||
new = fix_email(old)
|
||||
self.assertEqual(old, new)
|
||||
|
||||
def test_empty(self):
|
||||
old = ""
|
||||
new = fix_email(old)
|
||||
self.assertEqual(old, new)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -20,6 +20,7 @@ class Command(BaseCommand):
|
||||
|
||||
keyholder_group = None
|
||||
finance_group = None
|
||||
hs_group = None
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from django.conf import settings
|
||||
@@ -27,7 +28,8 @@ class Command(BaseCommand):
|
||||
if not (settings.DEBUG or settings.STAGING):
|
||||
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():
|
||||
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||
@@ -45,8 +47,18 @@ class Command(BaseCommand):
|
||||
self.setupUsefulProfiles()
|
||||
|
||||
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
|
||||
"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
|
||||
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
|
||||
"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):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
@@ -68,8 +80,32 @@ class Command(BaseCommand):
|
||||
self.people.append(newPerson)
|
||||
|
||||
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
|
||||
"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
|
||||
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
|
||||
"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):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
@@ -93,8 +129,18 @@ class Command(BaseCommand):
|
||||
self.organisations.append(newOrganisation)
|
||||
|
||||
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
|
||||
"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
|
||||
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
|
||||
"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):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
@@ -120,6 +166,7 @@ class Command(BaseCommand):
|
||||
def setupGroups(self):
|
||||
self.keyholder_group = Group.objects.create(name='Keyholders')
|
||||
self.finance_group = Group.objects.create(name='Finance')
|
||||
self.hs_group = Group.objects.create(name='H&S')
|
||||
|
||||
keyholderPerms = ["add_event", "change_event", "view_event",
|
||||
"add_eventitem", "change_eventitem", "delete_eventitem",
|
||||
@@ -127,10 +174,17 @@ class Command(BaseCommand):
|
||||
"add_person", "change_person", "view_person", "view_profile",
|
||||
"add_venue", "change_venue", "view_venue",
|
||||
"add_asset", "change_asset", "delete_asset",
|
||||
"asset_finance", "view_asset", "view_supplier", "asset_finance",
|
||||
"add_supplier"]
|
||||
"view_asset", "view_supplier", "change_supplier", "asset_finance",
|
||||
"add_supplier", "view_cabletype", "change_cabletype",
|
||||
"add_cabletype", "view_eventchecklist", "change_eventchecklist",
|
||||
"add_eventchecklist", "view_riskassessment", "change_riskassessment",
|
||||
"add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew",
|
||||
"delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle",
|
||||
"change_eventchecklistvehicle",
|
||||
"delete_eventchecklistvehicle", "view_eventchecklistvehicle", ]
|
||||
financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
|
||||
"add_payment", "change_payment", "delete_payment"]
|
||||
hsPerms = keyholderPerms + ["review_riskassessment", "review_eventchecklist"]
|
||||
|
||||
for permId in keyholderPerms:
|
||||
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
@@ -138,10 +192,15 @@ class Command(BaseCommand):
|
||||
for permId in financePerms:
|
||||
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
|
||||
for permId in hsPerms:
|
||||
self.hs_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
|
||||
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):
|
||||
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",
|
||||
initials="".join([j[0].upper() for j in name.split()]))
|
||||
if i % 2 == 0:
|
||||
@@ -151,44 +210,68 @@ class Command(BaseCommand):
|
||||
self.profiles.append(newProfile)
|
||||
|
||||
def setupUsefulProfiles(self):
|
||||
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)
|
||||
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)
|
||||
superUser.set_password('superuser')
|
||||
superUser.save()
|
||||
|
||||
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User", initials="FU",
|
||||
email="financeuser@example.com", is_active=True)
|
||||
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
|
||||
initials="FU",
|
||||
email="financeuser@example.com", is_active=True, is_approved=True)
|
||||
financeUser.groups.add(self.finance_group)
|
||||
financeUser.groups.add(self.keyholder_group)
|
||||
financeUser.set_password('finance')
|
||||
financeUser.save()
|
||||
|
||||
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU",
|
||||
email="keyholderuser@example.com", is_active=True)
|
||||
hsUser = models.Profile.objects.create(username="hs", first_name="HS", last_name="User",
|
||||
initials="HSU",
|
||||
email="hsuser@example.com", is_active=True, is_approved=True)
|
||||
hsUser.groups.add(self.hs_group)
|
||||
hsUser.groups.add(self.keyholder_group)
|
||||
hsUser.set_password('hs')
|
||||
hsUser.save()
|
||||
|
||||
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
|
||||
initials="KU",
|
||||
email="keyholderuser@example.com", is_active=True, is_approved=True)
|
||||
keyholderUser.groups.add(self.keyholder_group)
|
||||
keyholderUser.set_password('keyholder')
|
||||
keyholderUser.save()
|
||||
|
||||
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, is_approved=True)
|
||||
basicUser.set_password('basic')
|
||||
basicUser.save()
|
||||
|
||||
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",
|
||||
"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"]
|
||||
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!"]
|
||||
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"]
|
||||
descriptions = ["A brief description 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!"]
|
||||
|
||||
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': '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': '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': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.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}]
|
||||
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': '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': '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': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.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}]
|
||||
|
||||
dayDelta = -120 # start adding events from 4 months ago
|
||||
|
||||
@@ -226,7 +309,8 @@ class Command(BaseCommand):
|
||||
newEvent.venue = random.choice(self.venues)
|
||||
|
||||
# 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
|
||||
|
||||
@@ -257,4 +341,5 @@ class Command(BaseCommand):
|
||||
if newEvent.status is models.Event.CANCELLED: # void cancelled events
|
||||
newInvoice.void = True
|
||||
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())
|
||||
|
||||
190
RIGS/migrations/0039_auto_20210123_1910.py
Normal file
190
RIGS/migrations/0039_auto_20210123_1910.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# Generated by Django 3.1.2 on 2021-01-23 19:10
|
||||
|
||||
import RIGS.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0038_auto_20200306_2000'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EventChecklist',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField()),
|
||||
('safe_parking', models.BooleanField(blank=True, help_text='Vehicles parked safely?<br><small>(does not obstruct venue access)</small>', null=True)),
|
||||
('safe_packing', models.BooleanField(blank=True, help_text='Equipment packed away safely?<br><small>(including flightcases)</small>', null=True)),
|
||||
('exits', models.BooleanField(blank=True, help_text='Emergency exits clear?', null=True)),
|
||||
('trip_hazard', models.BooleanField(blank=True, help_text='Appropriate barriers around kit and cabling secured?', null=True)),
|
||||
('warning_signs', models.BooleanField(blank=True, help_text='Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>')),
|
||||
('ear_plugs', models.BooleanField(blank=True, help_text='Ear plugs issued to crew where needed?', null=True)),
|
||||
('hs_location', models.CharField(blank=True, help_text='Location of Safety Bag/Box', max_length=255, null=True)),
|
||||
('extinguishers_location', models.CharField(blank=True, help_text='Location of fire extinguishers', max_length=255, null=True)),
|
||||
('rcds', models.BooleanField(blank=True, help_text='RCDs installed where needed and tested?', null=True)),
|
||||
('supply_test', models.BooleanField(blank=True, help_text='Electrical supplies tested?<br><small>(using socket tester)</small>', null=True)),
|
||||
('earthing', models.BooleanField(blank=True, help_text='Equipment appropriately earthed?<br><small>(truss, stage, generators etc)</small>', null=True)),
|
||||
('pat', models.BooleanField(blank=True, help_text='All equipment in PAT period?', null=True)),
|
||||
('source_rcd', models.BooleanField(blank=True, help_text='Source RCD protected?<br><small>(if cable is more than 3m long) </small>', null=True)),
|
||||
('labelling', models.BooleanField(blank=True, help_text='Appropriate and clear labelling on distribution and cabling?', null=True)),
|
||||
('fd_voltage_l1', models.IntegerField(blank=True, help_text='L1 - N', null=True, verbose_name='First Distro Voltage L1-N')),
|
||||
('fd_voltage_l2', models.IntegerField(blank=True, help_text='L2 - N', null=True, verbose_name='First Distro Voltage L2-N')),
|
||||
('fd_voltage_l3', models.IntegerField(blank=True, help_text='L3 - N', null=True, verbose_name='First Distro Voltage L3-N')),
|
||||
('fd_phase_rotation', models.BooleanField(blank=True, help_text='Phase Rotation<br><small>(if required)</small>', null=True, verbose_name='Phase Rotation')),
|
||||
('fd_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True, verbose_name='Earth Fault Loop Impedance')),
|
||||
('fd_pssc', models.IntegerField(blank=True, help_text='Prospective Short Circuit Current', null=True, verbose_name='PSCC')),
|
||||
('w1_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
|
||||
('w1_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
|
||||
('w1_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
|
||||
('w1_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
|
||||
('w2_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
|
||||
('w2_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
|
||||
('w2_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
|
||||
('w2_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
|
||||
('w3_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
|
||||
('w3_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
|
||||
('w3_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
|
||||
('w3_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
|
||||
('all_rcds_tested', models.BooleanField(blank=True, help_text='All circuit RCDs tested?<br><small>(using test button)</small>', null=True)),
|
||||
('public_sockets_tested', models.BooleanField(blank=True, help_text='Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>', null=True)),
|
||||
('reviewed_at', models.DateTimeField(null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['event'],
|
||||
'permissions': [('review_eventchecklist', 'Can review Event Checklists')],
|
||||
},
|
||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventChecklistCrew',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('role', models.CharField(max_length=255)),
|
||||
('start', models.DateTimeField()),
|
||||
('end', models.DateTimeField()),
|
||||
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')),
|
||||
],
|
||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventChecklistVehicle',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('vehicle', models.CharField(max_length=255)),
|
||||
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')),
|
||||
],
|
||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RiskAssessment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('nonstandard_equipment', models.BooleanField(help_text="Does the event require any hired in equipment or use of equipment that is not covered by <a href='https://nottinghamtec.sharepoint.com/:f:/g/HealthAndSafety/Eo4xED_DrqFFsfYIjKzMZIIB6Gm_ZfR-a8l84RnzxtBjrA?e=Bf0Haw'>TEC's standard risk assessments and method statements?</a>")),
|
||||
('nonstandard_use', models.BooleanField(help_text='Are TEC using their equipment in a way that is abnormal?<br><small>i.e. Not covered by TECs standard health and safety documentation</small>')),
|
||||
('contractors', models.BooleanField(help_text='Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>')),
|
||||
('other_companies', models.BooleanField(help_text='Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>')),
|
||||
('crew_fatigue', models.BooleanField(help_text='Is crew fatigue likely to be a risk at any point during this event?')),
|
||||
('general_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
|
||||
('big_power', models.BooleanField(help_text='Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?')),
|
||||
('outside', models.BooleanField(help_text='Is the event outdoors?')),
|
||||
('generators', models.BooleanField(help_text='Will generators be used?')),
|
||||
('other_companies_power', models.BooleanField(help_text='Will TEC be supplying power to any other companies?')),
|
||||
('nonstandard_equipment_power', models.BooleanField(help_text='Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?')),
|
||||
('multiple_electrical_environments', models.BooleanField(help_text='Will the electrical installation occupy more than one electrical environment?')),
|
||||
('power_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
|
||||
('power_plan', models.URLField(blank=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", null=True, validators=[RIGS.models.validate_url])),
|
||||
('noise_monitoring', models.BooleanField(help_text='Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?')),
|
||||
('sound_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
|
||||
('known_venue', models.BooleanField(help_text='Is this venue new to you (the MIC) or new to TEC?')),
|
||||
('safe_loading', models.BooleanField(help_text='Are there any issues preventing a safe load in or out? (e.g. sufficient lighting, flat, not in a crowded area etc.)')),
|
||||
('safe_storage', models.BooleanField(help_text='Are there any problems with safe and secure equipment storage?')),
|
||||
('area_outside_of_control', models.BooleanField(help_text="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?")),
|
||||
('barrier_required', models.BooleanField(help_text='Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?')),
|
||||
('nonstandard_emergency_procedure', models.BooleanField(help_text="Does the emergency procedure for the event differ from TEC's standard procedures?")),
|
||||
('special_structures', models.BooleanField(help_text='Does the event require use of winch stands, motors, MPT Towers, or staging?')),
|
||||
('suspended_structures', models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")),
|
||||
('persons_responsible_structures', models.TextField(blank=True, help_text='Who are the persons on site responsible for their use?', null=True)),
|
||||
('rigging_plan', models.URLField(blank=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", null=True, validators=[RIGS.models.validate_url])),
|
||||
('reviewed_at', models.DateTimeField(null=True)),
|
||||
('supervisor_consulted', models.BooleanField(null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['event'],
|
||||
'permissions': [('review_riskassessment', 'Can review Risk Assessments')],
|
||||
},
|
||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventcrew',
|
||||
name='event',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventcrew',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='RIGSVersion',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='event',
|
||||
name='risk_assessment_edit_url',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='first_name',
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EventCrew',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='riskassessment',
|
||||
name='event',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='RIGS.event'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='riskassessment',
|
||||
name='power_mic',
|
||||
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='power_mic', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='riskassessment',
|
||||
name='reviewed_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklistvehicle',
|
||||
name='driver',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklistcrew',
|
||||
name='crewmember',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crewed', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklist',
|
||||
name='event',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to='RIGS.event'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklist',
|
||||
name='power_mic',
|
||||
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC?', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklist',
|
||||
name='reviewed_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklist',
|
||||
name='venue',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='RIGS.venue'),
|
||||
),
|
||||
]
|
||||
365
RIGS/models.py
365
RIGS/models.py
@@ -1,8 +1,8 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.conf import settings
|
||||
@@ -19,14 +19,16 @@ from decimal import Decimal
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class Profile(AbstractUser):
|
||||
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
|
||||
is_approved = models.BooleanField(default=False)
|
||||
last_emailed = models.DateTimeField(blank=True, null=True) # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
|
||||
last_emailed = models.DateTimeField(blank=True,
|
||||
null=True) # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
|
||||
|
||||
@classmethod
|
||||
def make_api_key(cls):
|
||||
@@ -39,7 +41,8 @@ class Profile(AbstractUser):
|
||||
def profile_picture(self):
|
||||
url = ""
|
||||
if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None:
|
||||
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email.encode('utf-8')).hexdigest() + "?d=wavatar&s=500"
|
||||
url = "https://www.gravatar.com/avatar/" + hashlib.md5(
|
||||
self.email.encode('utf-8')).hexdigest() + "?d=wavatar&s=500"
|
||||
return url
|
||||
|
||||
@property
|
||||
@@ -64,8 +67,15 @@ class Profile(AbstractUser):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
# TODO move to versioning - currently get import errors with that
|
||||
|
||||
|
||||
class RevisionMixin(object):
|
||||
@property
|
||||
def is_first_version(self):
|
||||
versions = Version.objects.get_for_object(self)
|
||||
return len(versions) == 1
|
||||
|
||||
@property
|
||||
def current_version(self):
|
||||
version = Version.objects.get_for_object(self).select_related('revision').first()
|
||||
@@ -93,7 +103,6 @@ class RevisionMixin(object):
|
||||
return "V{0} | R{1}".format(version.pk, version.revision.pk)
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Person(models.Model, RevisionMixin):
|
||||
name = models.CharField(max_length=50)
|
||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
||||
@@ -130,7 +139,6 @@ class Person(models.Model, RevisionMixin):
|
||||
return reverse_lazy('person_detail', kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Organisation(models.Model, RevisionMixin):
|
||||
name = models.CharField(max_length=50)
|
||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
||||
@@ -190,6 +198,8 @@ class VatRate(models.Model, RevisionMixin):
|
||||
|
||||
objects = VatManager()
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
@property
|
||||
def as_percent(self):
|
||||
return self.rate * 100
|
||||
@@ -202,7 +212,6 @@ class VatRate(models.Model, RevisionMixin):
|
||||
return self.comment + " " + str(self.start_at) + " @ " + str(self.as_percent) + "%"
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Venue(models.Model, RevisionMixin):
|
||||
name = models.CharField(max_length=255)
|
||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
||||
@@ -229,12 +238,18 @@ class Venue(models.Model, RevisionMixin):
|
||||
class EventManager(models.Manager):
|
||||
def current_events(self):
|
||||
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(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(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(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(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(
|
||||
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=timezone.now().date()) # Canceled but not started
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic')
|
||||
return events
|
||||
|
||||
def events_in_bounds(self, start, end):
|
||||
@@ -263,9 +278,7 @@ class EventManager(models.Manager):
|
||||
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Active dry hire
|
||||
(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
|
||||
status=Event.CANCELLED)) # Active dry hire
|
||||
).count()
|
||||
return event_count
|
||||
|
||||
@@ -306,7 +319,8 @@ class Event(models.Model, RevisionMixin):
|
||||
meet_info = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
# 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,
|
||||
on_delete=models.CASCADE)
|
||||
mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True,
|
||||
verbose_name="MIC", on_delete=models.CASCADE)
|
||||
|
||||
@@ -321,8 +335,12 @@ class Event(models.Model, RevisionMixin):
|
||||
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)
|
||||
@property
|
||||
def display_id(self):
|
||||
if self.is_rig:
|
||||
return str("N%05d" % self.pk)
|
||||
else:
|
||||
return self.pk
|
||||
|
||||
# Calculated values
|
||||
"""
|
||||
@@ -331,17 +349,6 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def sum_total(self):
|
||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||
# if connection.vendor == 'postgresql':
|
||||
# sql = "SELECT SUM(quantity * cost) AS sum_total FROM \"RIGS_eventitem\" WHERE event_id=%i" % self.id
|
||||
# else:
|
||||
# sql = "SELECT id, SUM(quantity * cost) AS sum_total FROM RIGS_eventitem WHERE event_id=%i" % self.id
|
||||
# total = self.items.raw(sql)[0]
|
||||
# if total.sum_total:
|
||||
# return total.sum_total
|
||||
# total = 0.0
|
||||
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
||||
# total += item.sum
|
||||
total = EventItem.objects.filter(event=self).aggregate(
|
||||
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
||||
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||
@@ -375,8 +382,8 @@ class Event(models.Model, RevisionMixin):
|
||||
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
|
||||
def hs_done(self):
|
||||
return self.riskassessment is not None and len(self.checklists.all()) > 0
|
||||
|
||||
@property
|
||||
def has_start_time(self):
|
||||
@@ -440,7 +447,14 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def internal(self):
|
||||
return self.organisation and self.organisation.union_account
|
||||
return bool(self.organisation and self.organisation.union_account)
|
||||
|
||||
@property
|
||||
def authorised(self):
|
||||
if self.internal:
|
||||
return self.authorisation.amount == self.total
|
||||
else:
|
||||
return bool(self.purchase_order)
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
@@ -448,16 +462,26 @@ class Event(models.Model, RevisionMixin):
|
||||
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return str(self.pk) + ": " + self.name
|
||||
return "{}: {}".format(self.display_id, self.name)
|
||||
|
||||
def clean(self):
|
||||
errdict = {}
|
||||
if self.end_date and self.start_date > self.end_date:
|
||||
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
|
||||
errdict['end_date'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
|
||||
|
||||
startEndSameDay = not self.end_date or self.end_date == self.start_date
|
||||
hasStartAndEnd = self.has_start_time and self.has_end_time
|
||||
if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time:
|
||||
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
|
||||
errdict['end_time'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
|
||||
|
||||
if self.access_at is not None:
|
||||
if self.access_at.date() > self.start_date:
|
||||
errdict['access_at'] = ['Regardless of what some clients might think, access time cannot be after the event has started.']
|
||||
elif self.start_time is not None and self.start_date == self.access_at.date() and self.access_at.time() > self.start_time:
|
||||
errdict['access_at'] = ['Regardless of what some clients might think, access time cannot be after the event has started.']
|
||||
|
||||
if errdict != {}: # If there was an error when validation
|
||||
raise ValidationError(errdict)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Call :meth:`full_clean` before saving."""
|
||||
@@ -465,7 +489,8 @@ class Event(models.Model, RevisionMixin):
|
||||
super(Event, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class EventItem(models.Model):
|
||||
@reversion.register
|
||||
class EventItem(models.Model, RevisionMixin):
|
||||
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
@@ -473,6 +498,8 @@ class EventItem(models.Model):
|
||||
cost = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
order = models.IntegerField()
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
@property
|
||||
def total_cost(self):
|
||||
return self.cost * self.quantity
|
||||
@@ -483,14 +510,9 @@ class EventItem(models.Model):
|
||||
def __str__(self):
|
||||
return str(self.event.pk) + "." + str(self.order) + ": " + self.event.name + " | " + self.name
|
||||
|
||||
|
||||
class EventCrew(models.Model):
|
||||
event = models.ForeignKey('Event', related_name='crew', on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
rig = models.BooleanField(default=False)
|
||||
run = models.BooleanField(default=False)
|
||||
derig = models.BooleanField(default=False)
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str("item {}".format(self.name))
|
||||
|
||||
|
||||
@reversion.register
|
||||
@@ -508,14 +530,17 @@ class EventAuthorisation(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')')
|
||||
return "{} (requested by {})".format(self.event.display_id, self.sent_by.initials)
|
||||
|
||||
|
||||
class Invoice(models.Model):
|
||||
@reversion.register(follow=['payment_set'])
|
||||
class Invoice(models.Model, RevisionMixin):
|
||||
event = models.OneToOneField('Event', on_delete=models.CASCADE)
|
||||
invoice_date = models.DateField(auto_now_add=True)
|
||||
void = models.BooleanField(default=False)
|
||||
|
||||
reversion_perm = 'RIGS.view_invoice'
|
||||
|
||||
@property
|
||||
def sum_total(self):
|
||||
return self.event.sum_total
|
||||
@@ -539,14 +564,26 @@ class Invoice(models.Model):
|
||||
def is_closed(self):
|
||||
return self.balance == 0 or self.void
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('invoice_detail', kwargs={'pk': self.pk})
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return "#{} for Event {}".format(self.display_id, "N%05d" % self.event.pk)
|
||||
|
||||
def __str__(self):
|
||||
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
||||
|
||||
@property
|
||||
def display_id(self):
|
||||
return "{:05d}".format(self.pk)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-invoice_date']
|
||||
|
||||
|
||||
class Payment(models.Model):
|
||||
@reversion.register
|
||||
class Payment(models.Model, RevisionMixin):
|
||||
CASH = 'C'
|
||||
INTERNAL = 'I'
|
||||
EXTERNAL = 'E'
|
||||
@@ -565,5 +602,239 @@ class Payment(models.Model):
|
||||
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)
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %d" % (self.get_method_display(), self.amount)
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str("payment of £{}".format(self.amount))
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
if not value:
|
||||
return # Required error is done the field
|
||||
obj = urlparse(value)
|
||||
if obj.hostname not in ('nottinghamtec.sharepoint.com'):
|
||||
raise ValidationError('URL must point to a location on the TEC Sharepoint')
|
||||
|
||||
|
||||
@reversion.register
|
||||
class RiskAssessment(models.Model, RevisionMixin):
|
||||
SMALL = (0, 'Small')
|
||||
MEDIUM = (1, 'Medium')
|
||||
LARGE = (2, 'Large')
|
||||
SIZES = (SMALL, MEDIUM, LARGE)
|
||||
|
||||
event = models.OneToOneField('Event', on_delete=models.CASCADE)
|
||||
# General
|
||||
nonstandard_equipment = models.BooleanField(help_text="Does the event require any hired in equipment or use of equipment that is not covered by <a href='https://nottinghamtec.sharepoint.com/:f:/g/HealthAndSafety/Eo4xED_DrqFFsfYIjKzMZIIB6Gm_ZfR-a8l84RnzxtBjrA?e=Bf0Haw'>"
|
||||
"TEC's standard risk assessments and method statements?</a>")
|
||||
nonstandard_use = models.BooleanField(help_text="Are TEC using their equipment in a way that is abnormal?<br><small>i.e. Not covered by TECs standard health and safety documentation</small>")
|
||||
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
|
||||
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
|
||||
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
|
||||
general_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||
|
||||
# Power
|
||||
# event_size = models.IntegerField(blank=True, null=True, choices=SIZES)
|
||||
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
|
||||
# If yes to the above two, you must answer...
|
||||
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
|
||||
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)")
|
||||
outside = models.BooleanField(help_text="Is the event outdoors?")
|
||||
generators = models.BooleanField(help_text="Will generators be used?")
|
||||
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
|
||||
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
|
||||
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
|
||||
power_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||
power_plan = models.URLField(blank=True, null=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
||||
|
||||
# Sound
|
||||
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
|
||||
sound_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||
|
||||
# Site
|
||||
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
|
||||
safe_loading = models.BooleanField(help_text="Are there any issues preventing a safe load in or out? (e.g. sufficient lighting, flat, not in a crowded area etc.)")
|
||||
safe_storage = models.BooleanField(help_text="Are there any problems with safe and secure equipment storage?")
|
||||
area_outside_of_control = models.BooleanField(help_text="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?")
|
||||
barrier_required = models.BooleanField(help_text="Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?")
|
||||
nonstandard_emergency_procedure = models.BooleanField(help_text="Does the emergency procedure for the event differ from TEC's standard procedures?")
|
||||
|
||||
# Structures
|
||||
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
|
||||
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
|
||||
persons_responsible_structures = models.TextField(blank=True, null=True, help_text="Who are the persons on site responsible for their use?")
|
||||
rigging_plan = models.URLField(blank=True, null=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
||||
|
||||
# Blimey that was a lot of options
|
||||
|
||||
reviewed_at = models.DateTimeField(null=True)
|
||||
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||
verbose_name="Reviewer", on_delete=models.CASCADE)
|
||||
|
||||
supervisor_consulted = models.BooleanField(null=True)
|
||||
|
||||
expected_values = {
|
||||
'nonstandard_equipment': False,
|
||||
'nonstandard_use': False,
|
||||
'contractors': False,
|
||||
'other_companies': False,
|
||||
'crew_fatigue': False,
|
||||
'big_power': False,
|
||||
'generators': False,
|
||||
'other_companies_power': False,
|
||||
'nonstandard_equipment_power': False,
|
||||
'multiple_electrical_environments': False,
|
||||
'noise_monitoring': False,
|
||||
'known_venue': False,
|
||||
'safe_loading': False,
|
||||
'safe_storage': False,
|
||||
'area_outside_of_control': False,
|
||||
'barrier_required': False,
|
||||
'nonstandard_emergency_procedure': False,
|
||||
'special_structures': False,
|
||||
'suspended_structures': False,
|
||||
}
|
||||
inverted_fields = {key: value for (key, value) in expected_values.items() if not value}.keys()
|
||||
|
||||
def clean(self):
|
||||
# Check for idiots
|
||||
if not self.outside and self.generators:
|
||||
raise forms.ValidationError("Engage brain, please. <strong>No generators indoors!(!)</strong>")
|
||||
|
||||
class Meta:
|
||||
ordering = ['event']
|
||||
permissions = [
|
||||
('review_riskassessment', 'Can review Risk Assessments')
|
||||
]
|
||||
|
||||
@property
|
||||
def event_size(self):
|
||||
# Confirm event size. Check all except generators, since generators entails outside
|
||||
if self.outside or self.other_companies_power or self.nonstandard_equipment_power or self.multiple_electrical_environments:
|
||||
return self.LARGE[0]
|
||||
elif self.big_power:
|
||||
return self.MEDIUM[0]
|
||||
else:
|
||||
return self.SMALL[0]
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str(self.event)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('ra_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "%i - %s" % (self.pk, self.event)
|
||||
|
||||
|
||||
@reversion.register(follow=['vehicles', 'crew'])
|
||||
class EventChecklist(models.Model, RevisionMixin):
|
||||
event = models.ForeignKey('Event', related_name='checklists', on_delete=models.CASCADE)
|
||||
|
||||
# General
|
||||
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='checklists',
|
||||
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC?")
|
||||
venue = models.ForeignKey('Venue', on_delete=models.CASCADE)
|
||||
date = models.DateField()
|
||||
|
||||
# Safety Checks
|
||||
safe_parking = models.BooleanField(blank=True, null=True, help_text="Vehicles parked safely?<br><small>(does not obstruct venue access)</small>")
|
||||
safe_packing = models.BooleanField(blank=True, null=True, help_text="Equipment packed away safely?<br><small>(including flightcases)</small>")
|
||||
exits = models.BooleanField(blank=True, null=True, help_text="Emergency exits clear?")
|
||||
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
|
||||
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
|
||||
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
|
||||
hs_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of Safety Bag/Box")
|
||||
extinguishers_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of fire extinguishers")
|
||||
|
||||
# Small Electrical Checks
|
||||
rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?")
|
||||
supply_test = models.BooleanField(blank=True, null=True, help_text="Electrical supplies tested?<br><small>(using socket tester)</small>")
|
||||
|
||||
# Shared electrical checks
|
||||
earthing = models.BooleanField(blank=True, null=True, help_text="Equipment appropriately earthed?<br><small>(truss, stage, generators etc)</small>")
|
||||
pat = models.BooleanField(blank=True, null=True, help_text="All equipment in PAT period?")
|
||||
|
||||
# Medium Electrical Checks
|
||||
source_rcd = models.BooleanField(blank=True, null=True, help_text="Source RCD protected?<br><small>(if cable is more than 3m long) </small>")
|
||||
labelling = models.BooleanField(blank=True, null=True, help_text="Appropriate and clear labelling on distribution and cabling?")
|
||||
# First Distro
|
||||
fd_voltage_l1 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L1-N", help_text="L1 - N")
|
||||
fd_voltage_l2 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L2-N", help_text="L2 - N")
|
||||
fd_voltage_l3 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L3-N", help_text="L3 - N")
|
||||
fd_phase_rotation = models.BooleanField(blank=True, null=True, verbose_name="Phase Rotation", help_text="Phase Rotation<br><small>(if required)</small>")
|
||||
fd_earth_fault = models.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
|
||||
# Worst case points
|
||||
w1_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
|
||||
w1_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||
w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
||||
w1_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
w2_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
|
||||
w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
||||
w2_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
w3_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
|
||||
w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
||||
w3_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
|
||||
all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>")
|
||||
public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>")
|
||||
|
||||
reviewed_at = models.DateTimeField(null=True)
|
||||
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||
verbose_name="Reviewer", on_delete=models.CASCADE)
|
||||
|
||||
inverted_fields = []
|
||||
|
||||
class Meta:
|
||||
ordering = ['event']
|
||||
permissions = [
|
||||
('review_eventchecklist', 'Can review Event Checklists')
|
||||
]
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str(self.event)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('ec_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "%i - %s" % (self.pk, self.event)
|
||||
|
||||
|
||||
@reversion.register
|
||||
class EventChecklistVehicle(models.Model, RevisionMixin):
|
||||
checklist = models.ForeignKey('EventChecklist', related_name='vehicles', blank=True, on_delete=models.CASCADE)
|
||||
vehicle = models.CharField(max_length=255)
|
||||
driver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='vehicles', on_delete=models.CASCADE)
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
def __str__(self):
|
||||
return "{} driven by {}".format(self.vehicle, str(self.driver))
|
||||
|
||||
|
||||
@reversion.register
|
||||
class EventChecklistCrew(models.Model, RevisionMixin):
|
||||
checklist = models.ForeignKey('EventChecklist', related_name='crew', blank=True, on_delete=models.CASCADE)
|
||||
crewmember = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='crewed', on_delete=models.CASCADE)
|
||||
role = models.CharField(max_length=255)
|
||||
start = models.DateTimeField()
|
||||
end = models.DateTimeField()
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
def clean(self):
|
||||
if self.start > self.end:
|
||||
raise ValidationError('Unless you\'ve invented time travel, crew can\'t finish before they have started.')
|
||||
|
||||
def __str__(self):
|
||||
return "{} ({})".format(str(self.crewmember), self.role)
|
||||
|
||||
112
RIGS/rigboard.py
112
RIGS/rigboard.py
@@ -6,12 +6,13 @@ 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.urls import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.urls import reverse_lazy
|
||||
from django.core import signing
|
||||
from django.http import HttpResponse
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
@@ -19,6 +20,7 @@ from django.db.models import Q
|
||||
from django.contrib import messages
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils import timezone
|
||||
from z3c.rml import rml2pdf
|
||||
from PyPDF2 import PdfFileMerger, PdfFileReader
|
||||
import simplejson
|
||||
@@ -34,7 +36,7 @@ __author__ = 'ghost'
|
||||
|
||||
|
||||
class RigboardIndex(generic.TemplateView):
|
||||
template_name = 'RIGS/rigboard.html'
|
||||
template_name = 'rigboard.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# get super context
|
||||
@@ -42,11 +44,12 @@ class RigboardIndex(generic.TemplateView):
|
||||
|
||||
# call out method to get current events
|
||||
context['events'] = models.Event.objects.current_events()
|
||||
context['page_title'] = "Rigboard"
|
||||
return context
|
||||
|
||||
|
||||
class WebCalendar(generic.TemplateView):
|
||||
template_name = 'RIGS/calendar.html'
|
||||
template_name = 'calendar.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(WebCalendar, self).get_context_data(**kwargs)
|
||||
@@ -56,6 +59,7 @@ class WebCalendar(generic.TemplateView):
|
||||
|
||||
|
||||
class EventDetail(generic.DetailView):
|
||||
template_name = 'event_detail.html'
|
||||
model = models.Event
|
||||
|
||||
|
||||
@@ -78,39 +82,22 @@ class EventOembed(generic.View):
|
||||
|
||||
|
||||
class EventEmbed(EventDetail):
|
||||
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)
|
||||
template_name = 'event_embed.html'
|
||||
|
||||
|
||||
class EventCreate(generic.CreateView):
|
||||
model = models.Event
|
||||
form_class = forms.EventForm
|
||||
template_name = 'event_form.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventCreate, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "New Event"
|
||||
context['edit'] = True
|
||||
context['currentVAT'] = models.VatRate.objects.current_rate()
|
||||
|
||||
form = context['form']
|
||||
if re.search(r'"-\d+"', form['items_json'].value()):
|
||||
if hasattr(form, 'items_json') and re.search(r'"-\d+"', form['items_json'].value()):
|
||||
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.
|
||||
@@ -127,9 +114,11 @@ class EventCreate(generic.CreateView):
|
||||
class EventUpdate(generic.UpdateView):
|
||||
model = models.Event
|
||||
form_class = forms.EventForm
|
||||
template_name = 'event_form.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventUpdate, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Event {}".format(self.object.display_id)
|
||||
context['edit'] = True
|
||||
|
||||
form = context['form']
|
||||
@@ -143,13 +132,15 @@ class EventUpdate(generic.UpdateView):
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
if not hasattr(context, 'duplicate'):
|
||||
if hasattr(context, 'duplicate') and not context['duplicate']:
|
||||
# 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.')
|
||||
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.')
|
||||
messages.warning(self.request,
|
||||
'This event has already been authorised by the client, any changes to the price will require reauthorisation.')
|
||||
return super(EventUpdate, self).render_to_response(context, **response_kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
@@ -162,6 +153,7 @@ class EventDuplicate(EventUpdate):
|
||||
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.purchase_order = None # Remove old PO
|
||||
new.status = new.PROVISIONAL # Return status to provisional
|
||||
|
||||
# Clear checked in by if it's a dry hire
|
||||
if new.dry_hire is True:
|
||||
@@ -182,6 +174,7 @@ class EventDuplicate(EventUpdate):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventDuplicate, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Duplicate of Event {}".format(self.object.display_id)
|
||||
context["duplicate"] = True
|
||||
return context
|
||||
|
||||
@@ -189,7 +182,7 @@ class EventDuplicate(EventUpdate):
|
||||
class EventPrint(generic.View):
|
||||
def get(self, request, pk):
|
||||
object = get_object_or_404(models.Event, pk=pk)
|
||||
template = get_template('RIGS/event_print.xml')
|
||||
template = get_template('event_print.xml')
|
||||
|
||||
merger = PdfFileMerger()
|
||||
|
||||
@@ -197,12 +190,13 @@ class EventPrint(generic.View):
|
||||
'object': object,
|
||||
'fonts': {
|
||||
'opensans': {
|
||||
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||
'regular': 'static/fonts/OPENSANS-REGULAR.TTF',
|
||||
'bold': 'static/fonts/OPENSANS-BOLD.TTF',
|
||||
}
|
||||
},
|
||||
'quote': True,
|
||||
'current_user': request.user,
|
||||
'filename': 'Event {} {} {}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
|
||||
}
|
||||
|
||||
rml = template.render(context)
|
||||
@@ -217,18 +211,15 @@ class EventPrint(generic.View):
|
||||
merger.write(merged)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
|
||||
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||
|
||||
response['Content-Disposition'] = "filename=N%05d | %s.pdf" % (object.pk, escapedEventName)
|
||||
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
|
||||
response.write(merged.getvalue())
|
||||
return response
|
||||
|
||||
|
||||
class EventArchive(generic.ListView):
|
||||
template_name = "event_archive.html"
|
||||
model = models.Event
|
||||
paginate_by = 25
|
||||
template_name = "RIGS/event_archive.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# get super context
|
||||
@@ -236,6 +227,8 @@ class EventArchive(generic.ListView):
|
||||
|
||||
context['start'] = self.request.GET.get('start', None)
|
||||
context['end'] = self.request.GET.get('end', datetime.date.today().strftime('%Y-%m-%d'))
|
||||
context['statuses'] = models.Event.EVENT_STATUS_CHOICES
|
||||
context['page_title'] = 'Event Archive'
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -256,7 +249,7 @@ class EventArchive(generic.ListView):
|
||||
|
||||
q = self.request.GET.get('q', "")
|
||||
|
||||
if q is not "":
|
||||
if q != "":
|
||||
qfilter = Q(name__icontains=q) | Q(description__icontains=q) | Q(notes__icontains=q)
|
||||
|
||||
# try and parse an int
|
||||
@@ -275,6 +268,11 @@ class EventArchive(generic.ListView):
|
||||
|
||||
filter &= qfilter
|
||||
|
||||
status = self.request.GET.getlist('status', "")
|
||||
|
||||
if len(status) > 0:
|
||||
filter &= Q(status__in=status)
|
||||
|
||||
qs = self.model.objects.filter(filter).order_by('-start_date')
|
||||
|
||||
# Preselect related for efficiency
|
||||
@@ -287,8 +285,8 @@ class EventArchive(generic.ListView):
|
||||
|
||||
|
||||
class EventAuthorise(generic.UpdateView):
|
||||
template_name = 'RIGS/eventauthorisation_form.html'
|
||||
success_template = 'RIGS/eventauthorisation_success.html'
|
||||
template_name = 'eventauthorisation_form.html'
|
||||
success_template = 'eventauthorisation_success.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
@@ -312,8 +310,10 @@ class EventAuthorise(generic.UpdateView):
|
||||
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
|
||||
context['page_title'] = "{}: {}".format(self.event.display_id, self.event.name)
|
||||
if self.event.dry_hire:
|
||||
context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>'
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@@ -351,7 +351,7 @@ class EventAuthorise(generic.UpdateView):
|
||||
class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin):
|
||||
model = models.Event
|
||||
form_class = forms.EventAuthorisationRequestForm
|
||||
template_name = 'RIGS/eventauthorisation_request.html'
|
||||
template_name = 'eventauthorisation_request.html'
|
||||
|
||||
@method_decorator(decorators.nottinghamtec_address_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
@@ -376,7 +376,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
||||
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_at = timezone.now()
|
||||
event.auth_request_to = email
|
||||
event.save()
|
||||
|
||||
@@ -396,12 +396,12 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
"N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name),
|
||||
get_template("RIGS/eventauthorisation_client_request.txt").render(context),
|
||||
get_template("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),
|
||||
html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context),
|
||||
external_styles=css).transform()
|
||||
msg.attach_alternative(html, 'text/html')
|
||||
|
||||
@@ -411,7 +411,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
||||
|
||||
|
||||
class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
||||
template_name = "RIGS/eventauthorisation_client_request.html"
|
||||
template_name = "eventauthorisation_client_request.html"
|
||||
model = models.Event
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
@@ -431,27 +431,3 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
||||
})
|
||||
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)
|
||||
|
||||
@@ -10,6 +10,7 @@ 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.core.cache import cache
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
@@ -18,6 +19,7 @@ from premailer import Premailer
|
||||
from z3c.rml import rml2pdf
|
||||
|
||||
from RIGS import models
|
||||
from reversion import revisions as reversion
|
||||
|
||||
|
||||
def send_eventauthorisation_success_email(instance):
|
||||
@@ -34,7 +36,7 @@ def send_eventauthorisation_success_email(instance):
|
||||
'current_user': False,
|
||||
}
|
||||
|
||||
template = get_template('RIGS/event_print.xml')
|
||||
template = get_template('event_print.xml')
|
||||
merger = PdfFileMerger()
|
||||
|
||||
rml = template.render(context)
|
||||
@@ -63,13 +65,13 @@ def send_eventauthorisation_success_email(instance):
|
||||
|
||||
client_email = EmailMultiAlternatives(
|
||||
subject,
|
||||
get_template("RIGS/eventauthorisation_client_success.txt").render(context),
|
||||
get_template("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),
|
||||
html = Premailer(get_template("eventauthorisation_client_success.html").render(context),
|
||||
external_styles=css).transform()
|
||||
client_email.attach_alternative(html, 'text/html')
|
||||
|
||||
@@ -87,7 +89,7 @@ def send_eventauthorisation_success_email(instance):
|
||||
|
||||
mic_email = EmailMessage(
|
||||
subject,
|
||||
get_template("RIGS/eventauthorisation_mic_success.txt").render(context),
|
||||
get_template("eventauthorisation_mic_success.txt").render(context),
|
||||
to=[mic_email_address]
|
||||
)
|
||||
|
||||
@@ -122,12 +124,12 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
|
||||
|
||||
email = EmailMultiAlternatives(
|
||||
"%s new users awaiting approval on RIGS" % (context['number_of_users']),
|
||||
get_template("RIGS/admin_awaiting_approval.txt").render(context),
|
||||
get_template("admin_awaiting_approval.txt").render(context),
|
||||
to=[admin.email],
|
||||
reply_to=[user.email],
|
||||
)
|
||||
css = staticfiles_storage.path('css/email.css')
|
||||
html = Premailer(get_template("RIGS/admin_awaiting_approval.html").render(context),
|
||||
html = Premailer(get_template("admin_awaiting_approval.html").render(context),
|
||||
external_styles=css).transform()
|
||||
email.attach_alternative(html, 'text/html')
|
||||
email.send()
|
||||
@@ -138,3 +140,11 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
|
||||
|
||||
|
||||
user_activated.connect(send_admin_awaiting_approval_email)
|
||||
|
||||
|
||||
def update_cache(sender, instance, created, **kwargs):
|
||||
cache.clear()
|
||||
|
||||
|
||||
for model in reversion.get_registered_models():
|
||||
post_save.connect(update_cache, sender=model)
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Require any additional compass plugins here.
|
||||
require 'bootstrap-sass'
|
||||
|
||||
# Set this to the root of your project when deployed:
|
||||
http_path = "/static/"
|
||||
css_dir = "css"
|
||||
sass_dir = "scss"
|
||||
images_dir = "img"
|
||||
javascripts_dir = "js"
|
||||
fonts_dir = "fonts"
|
||||
|
||||
# You can select your preferred output style here (can be overridden via the command line):
|
||||
# output_style = :expanded or :nested or :compact or :compressed
|
||||
output_style = :compressed
|
||||
|
||||
# To enable relative paths to assets via compass helper functions. Uncomment:
|
||||
# relative_assets = true
|
||||
|
||||
# To disable debugging comments that display the original location of your selectors. Uncomment:
|
||||
# line_comments = false
|
||||
|
||||
|
||||
# If you prefer the indented syntax, you might want to regenerate this
|
||||
# project again passing --syntax sass, or you can uncomment this:
|
||||
# preferred_syntax = :sass
|
||||
# and then run:
|
||||
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
|
||||
11
RIGS/static/css/ajax-bootstrap-select.css
Executable file → Normal file
11
RIGS/static/css/ajax-bootstrap-select.css
Executable file → Normal 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.
|
||||
*
|
||||
* @version 1.3.1
|
||||
* @version 1.4.5
|
||||
* @author Adam Heim - https://github.com/truckingsim
|
||||
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||
* @copyright 2015 Adam Heim
|
||||
* @copyright 2019 Adam Heim
|
||||
* @license Released under the MIT license.
|
||||
*
|
||||
* Contributors:
|
||||
* Mark Carver - https://github.com/markcarver
|
||||
*
|
||||
* Last build: 2015-01-06 8:43:11 PM EST
|
||||
* Last build: 2019-04-23 12:18:56 PM EDT
|
||||
*/
|
||||
.bootstrap-select .status {
|
||||
background: #f0f0f0;
|
||||
@@ -23,5 +23,6 @@
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-bottom: -5px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
padding: 10px 20px; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztFQWVFO0FBQ0Y7RUFDRSxtQkFBbUI7RUFDbkIsV0FBVztFQUNYLFdBQVc7RUFDWCxlQUFlO0VBQ2Ysa0JBQWtCO0VBQ2xCLGdCQUFnQjtFQUNoQixjQUFjO0VBQ2QsbUJBQW1CO0VBQ25CLGtCQUFrQixFQUFBIiwiZmlsZSI6ImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIvKiFcbiAqIEFqYXggQm9vdHN0cmFwIFNlbGVjdFxuICpcbiAqIEV4dGVuZHMgZXhpc3RpbmcgW0Jvb3RzdHJhcCBTZWxlY3RdIGltcGxlbWVudGF0aW9ucyBieSBhZGRpbmcgdGhlIGFiaWxpdHkgdG8gc2VhcmNoIHZpYSBBSkFYIHJlcXVlc3RzIGFzIHlvdSB0eXBlLiBPcmlnaW5hbGx5IGZvciBDUk9TQ09OLlxuICpcbiAqIEB2ZXJzaW9uIDEuNC41XG4gKiBAYXV0aG9yIEFkYW0gSGVpbSAtIGh0dHBzOi8vZ2l0aHViLmNvbS90cnVja2luZ3NpbVxuICogQGxpbmsgaHR0cHM6Ly9naXRodWIuY29tL3RydWNraW5nc2ltL0FqYXgtQm9vdHN0cmFwLVNlbGVjdFxuICogQGNvcHlyaWdodCAyMDE5IEFkYW0gSGVpbVxuICogQGxpY2Vuc2UgUmVsZWFzZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlLlxuICpcbiAqIENvbnRyaWJ1dG9yczpcbiAqICAgTWFyayBDYXJ2ZXIgLSBodHRwczovL2dpdGh1Yi5jb20vbWFya2NhcnZlclxuICpcbiAqIExhc3QgYnVpbGQ6IDIwMTktMDQtMjMgMTI6MTg6NTYgUE0gRURUXG4gKi9cbi5ib290c3RyYXAtc2VsZWN0IC5zdGF0dXMge1xuICBiYWNrZ3JvdW5kOiAjZjBmMGYwO1xuICBjbGVhcjogYm90aDtcbiAgY29sb3I6ICM5OTk7XG4gIGZvbnQtc2l6ZTogMTFweDtcbiAgZm9udC1zdHlsZTogaXRhbGljO1xuICBmb250LXdlaWdodDogNTAwO1xuICBsaW5lLWhlaWdodDogMTtcbiAgbWFyZ2luLWJvdHRvbTogLTVweDtcbiAgcGFkZGluZzogMTBweCAyMHB4O1xufVxuIl19 */
|
||||
|
||||
28
RIGS/static/css/ajax-bootstrap-select.min.css
vendored
Normal file
28
RIGS/static/css/ajax-bootstrap-select.min.css
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* Ajax Bootstrap Select
|
||||
*
|
||||
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
|
||||
*
|
||||
* @version 1.4.5
|
||||
* @author Adam Heim - https://github.com/truckingsim
|
||||
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||
* @copyright 2019 Adam Heim
|
||||
* @license Released under the MIT license.
|
||||
*
|
||||
* Contributors:
|
||||
* Mark Carver - https://github.com/markcarver
|
||||
*
|
||||
* Last build: 2019-04-23 12:18:56 PM EDT
|
||||
*/
|
||||
.bootstrap-select .status {
|
||||
background: #f0f0f0;
|
||||
clear: both;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-bottom: -5px;
|
||||
padding: 10px 20px; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5taW4uY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7RUFlRTtBQUFDO0VBQTBCLG1CQUFrQjtFQUFDLFdBQVU7RUFBQyxXQUFVO0VBQUMsZUFBYztFQUFDLGtCQUFpQjtFQUFDLGdCQUFlO0VBQUMsY0FBYTtFQUFDLG1CQUFrQjtFQUFDLGtCQUFpQixFQUFBIiwiZmlsZSI6ImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5taW4uY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLyohXG4gKiBBamF4IEJvb3RzdHJhcCBTZWxlY3RcbiAqXG4gKiBFeHRlbmRzIGV4aXN0aW5nIFtCb290c3RyYXAgU2VsZWN0XSBpbXBsZW1lbnRhdGlvbnMgYnkgYWRkaW5nIHRoZSBhYmlsaXR5IHRvIHNlYXJjaCB2aWEgQUpBWCByZXF1ZXN0cyBhcyB5b3UgdHlwZS4gT3JpZ2luYWxseSBmb3IgQ1JPU0NPTi5cbiAqXG4gKiBAdmVyc2lvbiAxLjQuNVxuICogQGF1dGhvciBBZGFtIEhlaW0gLSBodHRwczovL2dpdGh1Yi5jb20vdHJ1Y2tpbmdzaW1cbiAqIEBsaW5rIGh0dHBzOi8vZ2l0aHViLmNvbS90cnVja2luZ3NpbS9BamF4LUJvb3RzdHJhcC1TZWxlY3RcbiAqIEBjb3B5cmlnaHQgMjAxOSBBZGFtIEhlaW1cbiAqIEBsaWNlbnNlIFJlbGVhc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZS5cbiAqXG4gKiBDb250cmlidXRvcnM6XG4gKiAgIE1hcmsgQ2FydmVyIC0gaHR0cHM6Ly9naXRodWIuY29tL21hcmtjYXJ2ZXJcbiAqXG4gKiBMYXN0IGJ1aWxkOiAyMDE5LTA0LTIzIDEyOjE4OjU2IFBNIEVEVFxuICovLmJvb3RzdHJhcC1zZWxlY3QgLnN0YXR1c3tiYWNrZ3JvdW5kOiNmMGYwZjA7Y2xlYXI6Ym90aDtjb2xvcjojOTk5O2ZvbnQtc2l6ZToxMXB4O2ZvbnQtc3R5bGU6aXRhbGljO2ZvbnQtd2VpZ2h0OjUwMDtsaW5lLWhlaWdodDoxO21hcmdpbi1ib3R0b206LTVweDtwYWRkaW5nOjEwcHggMjBweH0iXX0= */
|
||||
23
RIGS/static/css/autocomplete.css
Normal file
23
RIGS/static/css/autocomplete.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.autocomplete {
|
||||
background: white;
|
||||
z-index: 1000;
|
||||
font: 14px/22px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(50, 50, 50, 0.6); }
|
||||
|
||||
.autocomplete * {
|
||||
font: inherit; }
|
||||
|
||||
.autocomplete > div {
|
||||
padding: 0 4px; }
|
||||
|
||||
.autocomplete .group {
|
||||
background: #eee; }
|
||||
|
||||
.autocomplete > div:hover:not(.group),
|
||||
.autocomplete > div.selected {
|
||||
background: #81ca91;
|
||||
cursor: pointer; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImF1dG9jb21wbGV0ZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0E7RUFDSSxpQkFBaUI7RUFDakIsYUFBYTtFQUNiLDRHQUE0RztFQUM1RyxjQUFjO0VBQ2Qsc0JBQXNCO0VBQ3RCLHVDQUF1QyxFQUFBOztBQUczQztFQUNJLGFBQWEsRUFBQTs7QUFHakI7RUFDSSxjQUFjLEVBQUE7O0FBR2xCO0VBQ0ksZ0JBQWdCLEVBQUE7O0FBR3BCOztFQUVJLG1CQUFtQjtFQUNuQixlQUFlLEVBQUEiLCJmaWxlIjoiYXV0b2NvbXBsZXRlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIlxyXG4uYXV0b2NvbXBsZXRlIHtcclxuICAgIGJhY2tncm91bmQ6IHdoaXRlO1xyXG4gICAgei1pbmRleDogMTAwMDtcclxuICAgIGZvbnQ6IDE0cHgvMjJweCBcIi1hcHBsZS1zeXN0ZW1cIiwgQmxpbmtNYWNTeXN0ZW1Gb250LCBcIlNlZ29lIFVJXCIsIFJvYm90bywgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBBcmlhbCwgc2Fucy1zZXJpZjtcclxuICAgIG92ZXJmbG93OiBhdXRvO1xyXG4gICAgYm94LXNpemluZzogYm9yZGVyLWJveDtcclxuICAgIGJvcmRlcjogMXB4IHNvbGlkIHJnYmEoNTAsIDUwLCA1MCwgMC42KTtcclxufVxyXG5cclxuLmF1dG9jb21wbGV0ZSAqIHtcclxuICAgIGZvbnQ6IGluaGVyaXQ7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgPiBkaXYge1xyXG4gICAgcGFkZGluZzogMCA0cHg7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgLmdyb3VwIHtcclxuICAgIGJhY2tncm91bmQ6ICNlZWU7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgPiBkaXY6aG92ZXI6bm90KC5ncm91cCksXHJcbi5hdXRvY29tcGxldGUgPiBkaXYuc2VsZWN0ZWQge1xyXG4gICAgYmFja2dyb3VuZDogIzgxY2E5MTtcclxuICAgIGN1cnNvcjogcG9pbnRlcjtcclxufVxyXG5cclxuIl19 */
|
||||
584
RIGS/static/css/bootstrap-datetimepicker.min.css
vendored
584
RIGS/static/css/bootstrap-datetimepicker.min.css
vendored
File diff suppressed because one or more lines are too long
455
RIGS/static/css/bootstrap-select.css
vendored
Normal file
455
RIGS/static/css/bootstrap-select.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6
RIGS/static/css/bootstrap-select.min.css
vendored
6
RIGS/static/css/bootstrap-select.min.css
vendored
File diff suppressed because one or more lines are too long
6650
RIGS/static/css/dark_screen.css
Normal file
6650
RIGS/static/css/dark_screen.css
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1,39 @@
|
||||
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}
|
||||
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; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImVtYWlsLnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7RUFDRSxXQUFXLEVBQUE7O0FBR2I7RUFDRSxXQUFXO0VBQ1gseUJBQXlCLEVBQUE7O0FBSTNCO0VBQ0UsMkVBQTJFO0VBQzNFLHNCQUFzQjtFQUN0Qiw0QkFBNEI7RUFDNUIsMkJBQTJCO0VBRTNCLFdBQVc7RUFFWCxtQkFBbUIsRUFBQTtFQVJyQjtJQVdJLFdBQVc7SUFDWCxnQkFBZ0IsRUFBQTtFQVpwQjtJQWdCSSxhQUFhLEVBQUE7O0FBSWpCO0VBQ0UsV0FBVyxFQUFBO0VBRGI7SUFJSSx3RUFBd0U7SUFFeEUsV0FBVztJQUNYLGdCQUFnQjtJQUNoQixhQUFhO0lBQ2IsZ0JBQWdCLEVBQUE7SUFUcEI7TUFZTSxXQUFXLEVBQUE7TUFaakI7UUFlUSxpQkFBaUI7UUFDakIseUJBaERjO1FBaURkLGtCQUFrQixFQUFBO1FBakIxQjtVQW9CVSxXQUFXO1VBQ1gscUJBQXFCLEVBQUEiLCJmaWxlIjoiZW1haWwuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiJGJ1dHRvbl9jb2xvcjogIzM1N2ViZjtcblxuYm9keXtcbiAgbWFyZ2luOiAwcHg7XG59XG5cbi5tYWluLXRhYmxle1xuICB3aWR0aDogMTAwJTtcbiAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTtcblxufVxuXG4uY2xpZW50LWhlYWRlciB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybChcImh0dHBzOi8vd3d3Lm5vdHRpbmdoYW10ZWMuY28udWsvaW1ncy93b2YyMDE0LTEuanBnXCIpO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMjIyO1xuICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0O1xuICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiBjZW50ZXI7XG5cbiAgd2lkdGg6IDEwMCU7XG5cbiAgbWFyZ2luLWJvdHRvbTogMjhweDtcblxuICAubG9nb3N7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgbWF4LXdpZHRoOiA2NDBweDtcbiAgfVxuXG4gIGltZyB7XG4gICAgaGVpZ2h0OiAxMTBweDtcbiAgfVxufVxuXG4uY29udGVudC1jb250YWluZXJ7XG4gIHdpZHRoOiAxMDAlO1xuXG4gIC5jb250ZW50IHtcbiAgICBmb250LWZhbWlseTogXCJPcGVuIFNhbnNcIiwgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmO1xuXG4gICAgd2lkdGg6IDEwMCU7XG4gICAgbWF4LXdpZHRoOiA2MDBweDtcbiAgICBwYWRkaW5nOiAxMHB4O1xuICAgIHRleHQtYWxpZ246IGxlZnQ7XG5cbiAgICAuYnV0dG9uLWNvbnRhaW5lcntcbiAgICAgIHdpZHRoOiAxMDAlO1xuXG4gICAgICAuYnV0dG9uIHtcbiAgICAgICAgcGFkZGluZzogNnB4IDEycHg7XG4gICAgICAgIGJhY2tncm91bmQtY29sb3I6ICRidXR0b25fY29sb3I7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDRweDtcblxuICAgICAgICBhIHtcbiAgICAgICAgICBjb2xvcjogI2ZmZjtcbiAgICAgICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gICAgICAgIH1cblxuICAgICAgfVxuXG4gICAgfVxuXG4gIH1cbn1cblxuIl19 */
|
||||
|
||||
788
RIGS/static/css/flatpickr.css
Normal file
788
RIGS/static/css/flatpickr.css
Normal file
File diff suppressed because one or more lines are too long
1657
RIGS/static/css/fullcalendar.css
Executable file → Normal file
1657
RIGS/static/css/fullcalendar.css
Executable file → Normal file
File diff suppressed because one or more lines are too long
178
RIGS/static/css/fullcalendar.print.css
Executable file → Normal file
178
RIGS/static/css/fullcalendar.print.css
Executable file → Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJpZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6W119 */
|
||||
|
||||
269
RIGS/static/css/main.css
Normal file
269
RIGS/static/css/main.css
Normal file
File diff suppressed because one or more lines are too long
1047
RIGS/static/css/main.min.css
vendored
Normal file
1047
RIGS/static/css/main.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 104 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,162 +0,0 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: affix.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#affix
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// AFFIX CLASS DEFINITION
|
||||
// ======================
|
||||
|
||||
var Affix = function (element, options) {
|
||||
this.options = $.extend({}, Affix.DEFAULTS, options)
|
||||
|
||||
this.$target = $(this.options.target)
|
||||
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
|
||||
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
|
||||
|
||||
this.$element = $(element)
|
||||
this.affixed = null
|
||||
this.unpin = null
|
||||
this.pinnedOffset = null
|
||||
|
||||
this.checkPosition()
|
||||
}
|
||||
|
||||
Affix.VERSION = '3.3.7'
|
||||
|
||||
Affix.RESET = 'affix affix-top affix-bottom'
|
||||
|
||||
Affix.DEFAULTS = {
|
||||
offset: 0,
|
||||
target: window
|
||||
}
|
||||
|
||||
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
|
||||
var scrollTop = this.$target.scrollTop()
|
||||
var position = this.$element.offset()
|
||||
var targetHeight = this.$target.height()
|
||||
|
||||
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
|
||||
|
||||
if (this.affixed == 'bottom') {
|
||||
if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
|
||||
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
|
||||
}
|
||||
|
||||
var initializing = this.affixed == null
|
||||
var colliderTop = initializing ? scrollTop : position.top
|
||||
var colliderHeight = initializing ? targetHeight : height
|
||||
|
||||
if (offsetTop != null && scrollTop <= offsetTop) return 'top'
|
||||
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Affix.prototype.getPinnedOffset = function () {
|
||||
if (this.pinnedOffset) return this.pinnedOffset
|
||||
this.$element.removeClass(Affix.RESET).addClass('affix')
|
||||
var scrollTop = this.$target.scrollTop()
|
||||
var position = this.$element.offset()
|
||||
return (this.pinnedOffset = position.top - scrollTop)
|
||||
}
|
||||
|
||||
Affix.prototype.checkPositionWithEventLoop = function () {
|
||||
setTimeout($.proxy(this.checkPosition, this), 1)
|
||||
}
|
||||
|
||||
Affix.prototype.checkPosition = function () {
|
||||
if (!this.$element.is(':visible')) return
|
||||
|
||||
var height = this.$element.height()
|
||||
var offset = this.options.offset
|
||||
var offsetTop = offset.top
|
||||
var offsetBottom = offset.bottom
|
||||
var scrollHeight = Math.max($(document).height(), $(document.body).height())
|
||||
|
||||
if (typeof offset != 'object') offsetBottom = offsetTop = offset
|
||||
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
|
||||
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
|
||||
|
||||
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
|
||||
|
||||
if (this.affixed != affix) {
|
||||
if (this.unpin != null) this.$element.css('top', '')
|
||||
|
||||
var affixType = 'affix' + (affix ? '-' + affix : '')
|
||||
var e = $.Event(affixType + '.bs.affix')
|
||||
|
||||
this.$element.trigger(e)
|
||||
|
||||
if (e.isDefaultPrevented()) return
|
||||
|
||||
this.affixed = affix
|
||||
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
|
||||
|
||||
this.$element
|
||||
.removeClass(Affix.RESET)
|
||||
.addClass(affixType)
|
||||
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
|
||||
}
|
||||
|
||||
if (affix == 'bottom') {
|
||||
this.$element.offset({
|
||||
top: scrollHeight - height - offsetBottom
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// AFFIX PLUGIN DEFINITION
|
||||
// =======================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.affix')
|
||||
var options = typeof option == 'object' && option
|
||||
|
||||
if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.affix
|
||||
|
||||
$.fn.affix = Plugin
|
||||
$.fn.affix.Constructor = Affix
|
||||
|
||||
|
||||
// AFFIX NO CONFLICT
|
||||
// =================
|
||||
|
||||
$.fn.affix.noConflict = function () {
|
||||
$.fn.affix = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// AFFIX DATA-API
|
||||
// ==============
|
||||
|
||||
$(window).on('load', function () {
|
||||
$('[data-spy="affix"]').each(function () {
|
||||
var $spy = $(this)
|
||||
var data = $spy.data()
|
||||
|
||||
data.offset = data.offset || {}
|
||||
|
||||
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
|
||||
if (data.offsetTop != null) data.offset.top = data.offsetTop
|
||||
|
||||
Plugin.call($spy, data)
|
||||
})
|
||||
})
|
||||
|
||||
}(jQuery);
|
||||
File diff suppressed because one or more lines are too long
100
RIGS/static/js/alert.js
Executable file → Normal file
100
RIGS/static/js/alert.js
Executable file → Normal file
@@ -1,94 +1,6 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: alert.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#alerts
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// ALERT CLASS DEFINITION
|
||||
// ======================
|
||||
|
||||
var dismiss = '[data-dismiss="alert"]'
|
||||
var Alert = function (el) {
|
||||
$(el).on('click', dismiss, this.close)
|
||||
}
|
||||
|
||||
Alert.VERSION = '3.3.7'
|
||||
|
||||
Alert.TRANSITION_DURATION = 150
|
||||
|
||||
Alert.prototype.close = function (e) {
|
||||
var $this = $(this)
|
||||
var selector = $this.attr('data-target')
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
||||
}
|
||||
|
||||
var $parent = $(selector === '#' ? [] : selector)
|
||||
|
||||
if (e) e.preventDefault()
|
||||
|
||||
if (!$parent.length) {
|
||||
$parent = $this.closest('.alert')
|
||||
}
|
||||
|
||||
$parent.trigger(e = $.Event('close.bs.alert'))
|
||||
|
||||
if (e.isDefaultPrevented()) return
|
||||
|
||||
$parent.removeClass('in')
|
||||
|
||||
function removeElement() {
|
||||
// detach from parent, fire event then clean up data
|
||||
$parent.detach().trigger('closed.bs.alert').remove()
|
||||
}
|
||||
|
||||
$.support.transition && $parent.hasClass('fade') ?
|
||||
$parent
|
||||
.one('bsTransitionEnd', removeElement)
|
||||
.emulateTransitionEnd(Alert.TRANSITION_DURATION) :
|
||||
removeElement()
|
||||
}
|
||||
|
||||
|
||||
// ALERT PLUGIN DEFINITION
|
||||
// =======================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.alert')
|
||||
|
||||
if (!data) $this.data('bs.alert', (data = new Alert(this)))
|
||||
if (typeof option == 'string') data[option].call($this)
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.alert
|
||||
|
||||
$.fn.alert = Plugin
|
||||
$.fn.alert.Constructor = Alert
|
||||
|
||||
|
||||
// ALERT NO CONFLICT
|
||||
// =================
|
||||
|
||||
$.fn.alert.noConflict = function () {
|
||||
$.fn.alert = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// ALERT DATA-API
|
||||
// ==============
|
||||
|
||||
$(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
|
||||
|
||||
}(jQuery);
|
||||
/*!
|
||||
* Bootstrap alert.js v4.5.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("jquery"),require("./util.js")):"function"==typeof define&&define.amd?define(["jquery","./util.js"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Alert=t(e.jQuery,e.Util)}(this,(function(e,t){"use strict";function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e,t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t;var r=e.fn.alert,o=function(){function r(e){this._element=e}var o,l,i,a=r.prototype;return a.close=function(e){var t=this._element;e&&(t=this._getRootElement(e)),this._triggerCloseEvent(t).isDefaultPrevented()||this._removeElement(t)},a.dispose=function(){e.removeData(this._element,"bs.alert"),this._element=null},a._getRootElement=function(n){var r=t.getSelectorFromElement(n),o=!1;return r&&(o=document.querySelector(r)),o||(o=e(n).closest(".alert")[0]),o},a._triggerCloseEvent=function(t){var n=e.Event("close.bs.alert");return e(t).trigger(n),n},a._removeElement=function(n){var r=this;if(e(n).removeClass("show"),e(n).hasClass("fade")){var o=t.getTransitionDurationFromElement(n);e(n).one(t.TRANSITION_END,(function(e){return r._destroyElement(n,e)})).emulateTransitionEnd(o)}else this._destroyElement(n)},a._destroyElement=function(t){e(t).detach().trigger("closed.bs.alert").remove()},r._jQueryInterface=function(t){return this.each((function(){var n=e(this),o=n.data("bs.alert");o||(o=new r(this),n.data("bs.alert",o)),"close"===t&&o[t](this)}))},r._handleDismiss=function(e){return function(t){t&&t.preventDefault(),e.close(this)}},o=r,i=[{key:"VERSION",get:function(){return"4.5.2"}}],(l=null)&&n(o.prototype,l),i&&n(o,i),r}();return e(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',o._handleDismiss(new o)),e.fn.alert=o._jQueryInterface,e.fn.alert.Constructor=o,e.fn.alert.noConflict=function(){return e.fn.alert=r,o._jQueryInterface},o}));
|
||||
5
RIGS/static/js/all.js
Normal file
5
RIGS/static/js/all.js
Normal file
File diff suppressed because one or more lines are too long
74
RIGS/static/js/asteroids.min.js
vendored
74
RIGS/static/js/asteroids.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,104 +1 @@
|
||||
$(document).ready(function() {
|
||||
clearSelectionLabel = '(no selection)';
|
||||
|
||||
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
|
||||
//console.log('Changing selected value');
|
||||
obj.find('option').remove(); //Remove all the available options
|
||||
obj.append( //Add the new option
|
||||
$("<option></option>")
|
||||
.attr("value",pk)
|
||||
.text(text)
|
||||
.data('update_url',update_url)
|
||||
);
|
||||
obj.selectpicker('render'); //Re-render the UI
|
||||
obj.selectpicker('refresh'); //Re-render the UI
|
||||
obj.selectpicker('val', pk); //Set the new value to be selected
|
||||
obj.change(); //Trigger the change function manually
|
||||
}
|
||||
|
||||
function refreshUpdateHref(obj) {
|
||||
//console.log('Refreshing Update URL');
|
||||
targetObject = $('#'+obj.attr('id')+'-update');
|
||||
update_url = $('option:selected', obj).data('update_url');
|
||||
|
||||
if (update_url=="") { //Probably "clear selection" has been chosen
|
||||
// console.log('Trying to disable');
|
||||
targetObject.attr('disabled', true);
|
||||
} else {
|
||||
targetObject.attr('href', update_url);
|
||||
targetObject.attr('disabled', false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(".selectpicker").each(function() {
|
||||
|
||||
var options = {
|
||||
ajax: {
|
||||
url: $(this).data('sourceurl'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
// Use "{{{q}}}" as a placeholder and Ajax Bootstrap Select will
|
||||
// automatically replace it with the value of the search query.
|
||||
data: {
|
||||
term: '{{{q}}}'
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
emptyTitle: ''
|
||||
},
|
||||
clearOnEmpty:false,
|
||||
//log: 3,
|
||||
preprocessData: function (data) {
|
||||
var i, l = data.length, array = [];
|
||||
array.push({
|
||||
text: clearSelectionLabel,
|
||||
value: '',
|
||||
data:{
|
||||
update_url: '',
|
||||
subtext:''
|
||||
}
|
||||
});
|
||||
|
||||
if (l) {
|
||||
for(i = 0; i < l; i++){
|
||||
array.push($.extend(true, data[i], {
|
||||
text: data[i]['label'],
|
||||
value: data[i]['pk'],
|
||||
data:{
|
||||
update_url: data[i]['update'],
|
||||
subtext:''
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
};
|
||||
|
||||
$(this).prepend($("<option></option>")
|
||||
.attr("value",'')
|
||||
.text(clearSelectionLabel)
|
||||
.data('update_url','')); //Add "clear selection" option
|
||||
|
||||
|
||||
$(this).selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
|
||||
|
||||
$(this).change(function(){ //on change, update the edit button href
|
||||
// console.log('Selectbox Changed');
|
||||
refreshUpdateHref($(this));
|
||||
});
|
||||
|
||||
refreshUpdateHref($(this)); //Ensure href is correct at the beginning
|
||||
|
||||
});
|
||||
|
||||
//When update/edit modal box submitted
|
||||
$('#modal').on('hide.bs.modal', function (e) {
|
||||
if (modaltarget != undefined && modalobject != "") {
|
||||
//Update the selector with new values
|
||||
changeSelectedValue($(modaltarget),modalobject[0]['pk'],modalobject[0]['fields']['name'],modalobject[0]['update_url']);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
function changeSelectedValue(e,t,a,r){e.find("option").remove(),e.append($("<option></option>").attr("value",t).text(a).data("update_url",r)),e.selectpicker("render"),e.selectpicker("refresh"),e.selectpicker("val",t),e.change()}function refreshUpdateHref(e){targetObject=$("#"+e.attr("id")+"-update"),update_url=$("option:selected",e).data("update_url"),""==update_url?(targetObject.removeAttr("href"),targetObject.addClass("disabled")):(targetObject.prop("href",update_url),targetObject.removeClass("disabled"))}function initPicker(e){var t={ajax:{url:e.data("sourceurl"),type:"GET",dataType:"json",data:{term:"{{{q}}}"}},locale:{emptyTitle:""},clearOnEmpty:!1,preprocessData:function(e){var t,a=e.length,r=[];if(r.push({text:clearSelectionLabel,value:"",data:{update_url:"",subtext:""}}),a)for(t=0;t<a;t++)r.push($.extend(!0,e[t],{text:e[t].label,value:e[t].pk,data:{update_url:e[t].update,subtext:""}}));return r}};e.prepend($("<option></option>").attr("value","").text(clearSelectionLabel).data("update_url","")),e.selectpicker().ajaxSelectPicker(t),e.change((function(){refreshUpdateHref(e)})),refreshUpdateHref(e)}$(document).ready((function(){clearSelectionLabel="(no selection)",$(".selectpicker").each((function(){initPicker($(this))})),$("#modal").on("hide.bs.modal",(function(e){null!=modaltarget&&""!=modalobject&&changeSelectedValue($(modaltarget),modalobject[0].pk,modalobject[0].fields.name,modalobject[0].update_url)}))}));
|
||||
1
RIGS/static/js/bootstrap-datetimepicker.js
vendored
Normal file
1
RIGS/static/js/bootstrap-datetimepicker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1853
RIGS/static/js/bootstrap-select.js
vendored
Executable file → Normal file
1853
RIGS/static/js/bootstrap-select.js
vendored
Executable file → Normal file
File diff suppressed because one or more lines are too long
@@ -1,125 +0,0 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: button.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#buttons
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// BUTTON PUBLIC CLASS DEFINITION
|
||||
// ==============================
|
||||
|
||||
var Button = function (element, options) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, Button.DEFAULTS, options)
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
Button.VERSION = '3.3.7'
|
||||
|
||||
Button.DEFAULTS = {
|
||||
loadingText: 'loading...'
|
||||
}
|
||||
|
||||
Button.prototype.setState = function (state) {
|
||||
var d = 'disabled'
|
||||
var $el = this.$element
|
||||
var val = $el.is('input') ? 'val' : 'html'
|
||||
var data = $el.data()
|
||||
|
||||
state += 'Text'
|
||||
|
||||
if (data.resetText == null) $el.data('resetText', $el[val]())
|
||||
|
||||
// push to event loop to allow forms to submit
|
||||
setTimeout($.proxy(function () {
|
||||
$el[val](data[state] == null ? this.options[state] : data[state])
|
||||
|
||||
if (state == 'loadingText') {
|
||||
this.isLoading = true
|
||||
$el.addClass(d).attr(d, d).prop(d, true)
|
||||
} else if (this.isLoading) {
|
||||
this.isLoading = false
|
||||
$el.removeClass(d).removeAttr(d).prop(d, false)
|
||||
}
|
||||
}, this), 0)
|
||||
}
|
||||
|
||||
Button.prototype.toggle = function () {
|
||||
var changed = true
|
||||
var $parent = this.$element.closest('[data-toggle="buttons"]')
|
||||
|
||||
if ($parent.length) {
|
||||
var $input = this.$element.find('input')
|
||||
if ($input.prop('type') == 'radio') {
|
||||
if ($input.prop('checked')) changed = false
|
||||
$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.trigger('change')
|
||||
} else {
|
||||
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
|
||||
this.$element.toggleClass('active')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// BUTTON PLUGIN DEFINITION
|
||||
// ========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.button')
|
||||
var options = typeof option == 'object' && option
|
||||
|
||||
if (!data) $this.data('bs.button', (data = new Button(this, options)))
|
||||
|
||||
if (option == 'toggle') data.toggle()
|
||||
else if (option) data.setState(option)
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.button
|
||||
|
||||
$.fn.button = Plugin
|
||||
$.fn.button.Constructor = Button
|
||||
|
||||
|
||||
// BUTTON NO CONFLICT
|
||||
// ==================
|
||||
|
||||
$.fn.button.noConflict = function () {
|
||||
$.fn.button = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// BUTTON DATA-API
|
||||
// ===============
|
||||
|
||||
$(document)
|
||||
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
|
||||
var $btn = $(e.target).closest('.btn')
|
||||
Plugin.call($btn, 'toggle')
|
||||
if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) {
|
||||
// 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) {
|
||||
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
|
||||
})
|
||||
|
||||
}(jQuery);
|
||||
@@ -1,237 +0,0 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: carousel.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#carousel
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// CAROUSEL CLASS DEFINITION
|
||||
// =========================
|
||||
|
||||
var Carousel = function (element, options) {
|
||||
this.$element = $(element)
|
||||
this.$indicators = this.$element.find('.carousel-indicators')
|
||||
this.options = options
|
||||
this.paused = null
|
||||
this.sliding = null
|
||||
this.interval = null
|
||||
this.$active = null
|
||||
this.$items = null
|
||||
|
||||
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
|
||||
|
||||
this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
|
||||
.on('mouseenter.bs.carousel', $.proxy(this.pause, this))
|
||||
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
|
||||
}
|
||||
|
||||
Carousel.VERSION = '3.3.7'
|
||||
|
||||
Carousel.TRANSITION_DURATION = 600
|
||||
|
||||
Carousel.DEFAULTS = {
|
||||
interval: 5000,
|
||||
pause: 'hover',
|
||||
wrap: true,
|
||||
keyboard: true
|
||||
}
|
||||
|
||||
Carousel.prototype.keydown = function (e) {
|
||||
if (/input|textarea/i.test(e.target.tagName)) return
|
||||
switch (e.which) {
|
||||
case 37: this.prev(); break
|
||||
case 39: this.next(); break
|
||||
default: return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
Carousel.prototype.cycle = function (e) {
|
||||
e || (this.paused = false)
|
||||
|
||||
this.interval && clearInterval(this.interval)
|
||||
|
||||
this.options.interval
|
||||
&& !this.paused
|
||||
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Carousel.prototype.getItemIndex = function (item) {
|
||||
this.$items = item.parent().children('.item')
|
||||
return this.$items.index(item || this.$active)
|
||||
}
|
||||
|
||||
Carousel.prototype.getItemForDirection = function (direction, active) {
|
||||
var activeIndex = this.getItemIndex(active)
|
||||
var willWrap = (direction == 'prev' && activeIndex === 0)
|
||||
|| (direction == 'next' && activeIndex == (this.$items.length - 1))
|
||||
if (willWrap && !this.options.wrap) return active
|
||||
var delta = direction == 'prev' ? -1 : 1
|
||||
var itemIndex = (activeIndex + delta) % this.$items.length
|
||||
return this.$items.eq(itemIndex)
|
||||
}
|
||||
|
||||
Carousel.prototype.to = function (pos) {
|
||||
var that = this
|
||||
var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
|
||||
|
||||
if (pos > (this.$items.length - 1) || pos < 0) return
|
||||
|
||||
if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
|
||||
if (activeIndex == pos) return this.pause().cycle()
|
||||
|
||||
return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
|
||||
}
|
||||
|
||||
Carousel.prototype.pause = function (e) {
|
||||
e || (this.paused = true)
|
||||
|
||||
if (this.$element.find('.next, .prev').length && $.support.transition) {
|
||||
this.$element.trigger($.support.transition.end)
|
||||
this.cycle(true)
|
||||
}
|
||||
|
||||
this.interval = clearInterval(this.interval)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Carousel.prototype.next = function () {
|
||||
if (this.sliding) return
|
||||
return this.slide('next')
|
||||
}
|
||||
|
||||
Carousel.prototype.prev = function () {
|
||||
if (this.sliding) return
|
||||
return this.slide('prev')
|
||||
}
|
||||
|
||||
Carousel.prototype.slide = function (type, next) {
|
||||
var $active = this.$element.find('.item.active')
|
||||
var $next = next || this.getItemForDirection(type, $active)
|
||||
var isCycling = this.interval
|
||||
var direction = type == 'next' ? 'left' : 'right'
|
||||
var that = this
|
||||
|
||||
if ($next.hasClass('active')) return (this.sliding = false)
|
||||
|
||||
var relatedTarget = $next[0]
|
||||
var slideEvent = $.Event('slide.bs.carousel', {
|
||||
relatedTarget: relatedTarget,
|
||||
direction: direction
|
||||
})
|
||||
this.$element.trigger(slideEvent)
|
||||
if (slideEvent.isDefaultPrevented()) return
|
||||
|
||||
this.sliding = true
|
||||
|
||||
isCycling && this.pause()
|
||||
|
||||
if (this.$indicators.length) {
|
||||
this.$indicators.find('.active').removeClass('active')
|
||||
var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
|
||||
$nextIndicator && $nextIndicator.addClass('active')
|
||||
}
|
||||
|
||||
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
|
||||
if ($.support.transition && this.$element.hasClass('slide')) {
|
||||
$next.addClass(type)
|
||||
$next[0].offsetWidth // force reflow
|
||||
$active.addClass(direction)
|
||||
$next.addClass(direction)
|
||||
$active
|
||||
.one('bsTransitionEnd', function () {
|
||||
$next.removeClass([type, direction].join(' ')).addClass('active')
|
||||
$active.removeClass(['active', direction].join(' '))
|
||||
that.sliding = false
|
||||
setTimeout(function () {
|
||||
that.$element.trigger(slidEvent)
|
||||
}, 0)
|
||||
})
|
||||
.emulateTransitionEnd(Carousel.TRANSITION_DURATION)
|
||||
} else {
|
||||
$active.removeClass('active')
|
||||
$next.addClass('active')
|
||||
this.sliding = false
|
||||
this.$element.trigger(slidEvent)
|
||||
}
|
||||
|
||||
isCycling && this.cycle()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// CAROUSEL PLUGIN DEFINITION
|
||||
// ==========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.carousel')
|
||||
var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
var action = typeof option == 'string' ? option : options.slide
|
||||
|
||||
if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
|
||||
if (typeof option == 'number') data.to(option)
|
||||
else if (action) data[action]()
|
||||
else if (options.interval) data.pause().cycle()
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.carousel
|
||||
|
||||
$.fn.carousel = Plugin
|
||||
$.fn.carousel.Constructor = Carousel
|
||||
|
||||
|
||||
// CAROUSEL NO CONFLICT
|
||||
// ====================
|
||||
|
||||
$.fn.carousel.noConflict = function () {
|
||||
$.fn.carousel = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// CAROUSEL DATA-API
|
||||
// =================
|
||||
|
||||
var clickHandler = function (e) {
|
||||
var href
|
||||
var $this = $(this)
|
||||
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
|
||||
if (!$target.hasClass('carousel')) return
|
||||
var options = $.extend({}, $target.data(), $this.data())
|
||||
var slideIndex = $this.attr('data-slide-to')
|
||||
if (slideIndex) options.interval = false
|
||||
|
||||
Plugin.call($target, options)
|
||||
|
||||
if (slideIndex) {
|
||||
$target.data('bs.carousel').to(slideIndex)
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
$(document)
|
||||
.on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
|
||||
.on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
|
||||
|
||||
$(window).on('load', function () {
|
||||
$('[data-ride="carousel"]').each(function () {
|
||||
var $carousel = $(this)
|
||||
Plugin.call($carousel, $carousel.data())
|
||||
})
|
||||
})
|
||||
|
||||
}(jQuery);
|
||||
7
RIGS/static/js/clipboard.min.js
vendored
Normal file
7
RIGS/static/js/clipboard.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
218
RIGS/static/js/collapse.js
Executable file → Normal file
218
RIGS/static/js/collapse.js
Executable file → Normal file
File diff suppressed because one or more lines are too long
1
RIGS/static/js/dark-mode-switch.min.js
vendored
Normal file
1
RIGS/static/js/dark-mode-switch.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(){var t,e=document.getElementById("darkSwitch");e&&(t=null!==localStorage.getItem("darkSwitch")&&"dark"===localStorage.getItem("darkSwitch"),(e.checked=t)?document.body.setAttribute("data-theme","dark"):document.body.removeAttribute("data-theme"),e.addEventListener("change",(function(t){e.checked?(document.body.setAttribute("data-theme","dark"),localStorage.setItem("darkSwitch","dark")):(document.body.removeAttribute("data-theme"),localStorage.removeItem("darkSwitch"))})))}();
|
||||
1
RIGS/static/js/datetime-fix.js
Normal file
1
RIGS/static/js/datetime-fix.js
Normal file
@@ -0,0 +1 @@
|
||||
$(document).ready((function(){var t;(t=document.createElement("input")).setAttribute("type","datetime-local"),("text"===t.type||navigator.userAgent.toLowerCase().indexOf("firefox")>-1)&&($("<link>").appendTo("head").attr({type:"text/css",rel:"stylesheet"}).attr("href",'{% static "css/flatpickr.css" %}'),$.when($.getScript('{% static "js/flatpickr.min.js" %}'),$.Deferred((function(t){$(t.resolve)}))).done((function(){$("input[type=datetime-local]").attr("type","text").flatpickr({dateFormat:"Y-m-dTH:m",enableTime:!0,altInput:!0,altFormat:"d/m/y H:m"})})))}));
|
||||
171
RIGS/static/js/dropdown.js
Executable file → Normal file
171
RIGS/static/js/dropdown.js
Executable file → Normal file
File diff suppressed because one or more lines are too long
2
RIGS/static/js/flatpickr.min.js
vendored
Normal file
2
RIGS/static/js/flatpickr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10791
RIGS/static/js/fullcalendar.js
Executable file → Normal file
10791
RIGS/static/js/fullcalendar.js
Executable file → Normal file
File diff suppressed because one or more lines are too long
@@ -1,139 +1 @@
|
||||
function setupItemTable(items_json) {
|
||||
objectitems = JSON.parse(items_json)
|
||||
$.each(objectitems, function (key, val) {
|
||||
objectitems[key] = JSON.parse(val);
|
||||
})
|
||||
newitem = -1;
|
||||
}
|
||||
|
||||
function nl2br (str, is_xhtml) {
|
||||
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
|
||||
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
|
||||
}
|
||||
|
||||
function escapeHtml (str) {
|
||||
return $('<div/>').text(str).html();
|
||||
}
|
||||
|
||||
function updatePrices() {
|
||||
// individual rows
|
||||
var sum = 0;
|
||||
for (var pk in objectitems) {
|
||||
var fields = objectitems[pk].fields;
|
||||
var sub = fields.cost * fields.quantity;
|
||||
$('#item-' + pk + ' .sub-total').html(parseFloat(sub).toFixed(2)).data('subtotal', sub);
|
||||
|
||||
sum += Number(sub);
|
||||
}
|
||||
|
||||
$('#sumtotal').text(parseFloat(sum).toFixed(2));
|
||||
var vat = sum * Number($('#vat-rate').data('rate'));
|
||||
$('#vat').text(parseFloat(vat).toFixed(2));
|
||||
$('#total').text(parseFloat(sum + vat).toFixed(2));
|
||||
}
|
||||
|
||||
$('#item-table').on('click', '.item-delete', function () {
|
||||
delete objectitems[$(this).data('pk')]
|
||||
$('#item-' + $(this).data('pk')).remove();
|
||||
updatePrices();
|
||||
});
|
||||
|
||||
$('#item-table').on('click', '.item-add', function () {
|
||||
$('#item-form').data('pk', newitem);
|
||||
|
||||
// Set the form values
|
||||
$('#item_name').val('');
|
||||
$('#item_description').val('');
|
||||
$('#item_quantity').val('');
|
||||
$('#item_cost').val('');
|
||||
|
||||
$($(this).data('target')).modal('show');
|
||||
});
|
||||
|
||||
$('#item-table').on('click', '.item-edit', function () {
|
||||
// set the pk as we will need this later
|
||||
var pk = $(this).data('pk');
|
||||
$('#item-form').data('pk', pk);
|
||||
|
||||
// Set the form values
|
||||
var fields = objectitems[pk].fields;
|
||||
$('#item_name').val(fields.name);
|
||||
$('#item_description').val(fields.description);
|
||||
$('#item_quantity').val(fields.quantity);
|
||||
$('#item_cost').val(fields.cost);
|
||||
|
||||
$($(this).data('target')).modal('show');
|
||||
});
|
||||
|
||||
$('body').on('submit', '#item-form', function (e) {
|
||||
e.preventDefault();
|
||||
var pk = $(this).data('pk');
|
||||
$('#itemModal').modal('hide');
|
||||
|
||||
var fields;
|
||||
if (pk == newitem--) {
|
||||
// Create the new data structure and add it on.
|
||||
fields = new Object();
|
||||
fields['name'] = $('#item_name').val()
|
||||
fields['description'] = $('#item_description').val();
|
||||
fields['cost'] = $('#item_cost').val();
|
||||
fields['quantity'] = $('#item_quantity').val();
|
||||
|
||||
var order = 0;
|
||||
for (item in objectitems) {
|
||||
order++;
|
||||
}
|
||||
|
||||
fields['order'] = order;
|
||||
|
||||
objectitems[pk] = new Object();
|
||||
objectitems[pk]['fields'] = fields;
|
||||
|
||||
// Add the new table
|
||||
$('#new-item-row').clone().attr('id', 'item-' + pk).data('pk', pk).appendTo('#item-table-body');
|
||||
$('#item-'+pk+' .item-delete, #item-'+pk+' .item-edit').data('pk', pk)
|
||||
} else {
|
||||
// Existing item
|
||||
// update data structure
|
||||
fields = objectitems[pk].fields;
|
||||
fields.name = $('#item_name').val()
|
||||
fields.description = $('#item_description').val();
|
||||
fields.cost = $('#item_cost').val();
|
||||
fields.quantity = $('#item_quantity').val();
|
||||
objectitems[pk].fields = fields;
|
||||
|
||||
}
|
||||
// update the table
|
||||
$row = $('#item-' + pk);
|
||||
$row.find('.name').html(escapeHtml(fields.name));
|
||||
$row.find('.description').html(nl2br(escapeHtml(fields.description)));
|
||||
$row.find('.cost').html(parseFloat(fields.cost).toFixed(2));
|
||||
$row.find('.quantity').html(fields.quantity);
|
||||
|
||||
updatePrices();
|
||||
});
|
||||
|
||||
$('body').on('submit', '.itemised_form', function (e) {
|
||||
$('#id_items_json').val(JSON.stringify(objectitems));
|
||||
});
|
||||
|
||||
// Return a helper with preserved width of cells
|
||||
var fixHelper = function (e, ui) {
|
||||
ui.children().each(function () {
|
||||
$(this).width($(this).width());
|
||||
});
|
||||
return ui;
|
||||
};
|
||||
|
||||
$("#item-table tbody").sortable({
|
||||
helper: fixHelper,
|
||||
update: function (e, ui) {
|
||||
info = $(this).sortable("toArray");
|
||||
itemorder = new Array();
|
||||
$.each(info, function (key, value) {
|
||||
pk = $('#' + value).data('pk');
|
||||
objectitems[pk].fields.order = key;
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
function setupItemTable(t){objectitems=JSON.parse(t),$.each(objectitems,(function(t,e){objectitems[t]=JSON.parse(e)})),newitem=-1}function nl2br(t,e){return(t+"").replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g,"$1"+(e||void 0===e?"<br />":"<br>")+"$2")}function escapeHtml(t){return $("<div/>").text(t).html()}function updatePrices(){var t=0;for(var e in objectitems){var i=objectitems[e].fields,a=i.cost*i.quantity;$("#item-"+e+" .sub-total").html(parseFloat(a).toFixed(2)).data("subtotal",a),t+=Number(a)}$("#sumtotal").text(parseFloat(t).toFixed(2));var o=t*Number($("#vat-rate").data("rate"));$("#vat").text(parseFloat(o).toFixed(2)),$("#total").text(parseFloat(t+o).toFixed(2))}$("#item-table").on("click",".item-delete",(function(){delete objectitems[$(this).data("pk")],$("#item-"+$(this).data("pk")).remove(),updatePrices()})),$("#item-table").on("click",".item-add",(function(){$("#item-form").data("pk",newitem),$("#item_name").val(""),$("#item_description").val(""),$("#item_quantity").val(""),$("#item_cost").val(""),$($(this).data("target")).modal("show")})),$("#item-table").on("click",".item-edit",(function(){var t=$(this).data("pk");$("#item-form").data("pk",t);var e=objectitems[t].fields;$("#item_name").val(e.name),$("#item_description").val(e.description),$("#item_quantity").val(e.quantity),$("#item_cost").val(e.cost),$($(this).data("target")).modal("show")})),$("body").on("submit","#item-form",(function(t){t.preventDefault();var e,i=$(this).data("pk");if($("#itemModal").modal("hide"),i==newitem--){(e=new Object).name=$("#item_name").val(),e.description=$("#item_description").val(),e.cost=$("#item_cost").val(),e.quantity=$("#item_quantity").val();var a=0;for(item in objectitems)a++;e.order=a,objectitems[i]=new Object,objectitems[i].fields=e,$("#new-item-row").clone().attr("id","item-"+i).data("pk",i).appendTo("#item-table-body"),$("#item-"+i+" .item-delete, #item-"+i+" .item-edit").data("pk",i)}else(e=objectitems[i].fields).name=$("#item_name").val(),e.description=$("#item_description").val(),e.cost=$("#item_cost").val(),e.quantity=$("#item_quantity").val(),objectitems[i].fields=e;$row=$("#item-"+i),$row.find(".name").html(escapeHtml(e.name)),$row.find(".description").html(nl2br(escapeHtml(e.description))),$row.find(".cost").html(parseFloat(e.cost).toFixed(2)),$row.find(".quantity").html(e.quantity),updatePrices()})),$("body").on("submit",".itemised_form",(function(t){$("#id_items_json").val(JSON.stringify(objectitems))}));var fixHelper=function(t,e){return e.children().each((function(){$(this).width($(this).width())})),e};$("#item-table tbody").sortable({helper:fixHelper,update:function(t,e){info=$(this).sortable("toArray"),itemorder=new Array,$.each(info,(function(t,e){pk=$("#"+e).data("pk"),objectitems[pk].fields.order=t}))}});
|
||||
186
RIGS/static/js/jquery-ui.js
vendored
Normal file
186
RIGS/static/js/jquery-ui.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,117 +0,0 @@
|
||||
/*!
|
||||
* jQuery Cookie Plugin v1.4.0
|
||||
* https://github.com/carhartl/jquery-cookie
|
||||
*
|
||||
* Copyright 2013 Klaus Hartl
|
||||
* Released under the MIT license
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as anonymous module.
|
||||
define(['jquery'], factory);
|
||||
} else {
|
||||
// Browser globals.
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
|
||||
var pluses = /\+/g;
|
||||
|
||||
function encode(s) {
|
||||
return config.raw ? s : encodeURIComponent(s);
|
||||
}
|
||||
|
||||
function decode(s) {
|
||||
return config.raw ? s : decodeURIComponent(s);
|
||||
}
|
||||
|
||||
function stringifyCookieValue(value) {
|
||||
return encode(config.json ? JSON.stringify(value) : String(value));
|
||||
}
|
||||
|
||||
function parseCookieValue(s) {
|
||||
if (s.indexOf('"') === 0) {
|
||||
// This is a quoted cookie as according to RFC2068, unescape...
|
||||
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
||||
}
|
||||
|
||||
try {
|
||||
// Replace server-side written pluses with spaces.
|
||||
// If we can't decode the cookie, ignore it, it's unusable.
|
||||
s = decodeURIComponent(s.replace(pluses, ' '));
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// If we can't parse the cookie, ignore it, it's unusable.
|
||||
return config.json ? JSON.parse(s) : s;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function read(s, converter) {
|
||||
var value = config.raw ? s : parseCookieValue(s);
|
||||
return $.isFunction(converter) ? converter(value) : value;
|
||||
}
|
||||
|
||||
var config = $.cookie = function (key, value, options) {
|
||||
|
||||
// Write
|
||||
if (value !== undefined && !$.isFunction(value)) {
|
||||
options = $.extend({}, config.defaults, options);
|
||||
|
||||
if (typeof options.expires === 'number') {
|
||||
var days = options.expires, t = options.expires = new Date();
|
||||
t.setDate(t.getDate() + days);
|
||||
}
|
||||
|
||||
return (document.cookie = [
|
||||
encode(key), '=', stringifyCookieValue(value),
|
||||
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
|
||||
options.path ? '; path=' + options.path : '',
|
||||
options.domain ? '; domain=' + options.domain : '',
|
||||
options.secure ? '; secure' : ''
|
||||
].join(''));
|
||||
}
|
||||
|
||||
// Read
|
||||
|
||||
var result = key ? undefined : {};
|
||||
|
||||
// To prevent the for loop in the first place assign an empty array
|
||||
// in case there are no cookies at all. Also prevents odd result when
|
||||
// calling $.cookie().
|
||||
var cookies = document.cookie ? document.cookie.split('; ') : [];
|
||||
|
||||
for (var i = 0, l = cookies.length; i < l; i++) {
|
||||
var parts = cookies[i].split('=');
|
||||
var name = decode(parts.shift());
|
||||
var cookie = parts.join('=');
|
||||
|
||||
if (key && key === name) {
|
||||
// If second argument (value) is a function it's a converter...
|
||||
result = read(cookie, value);
|
||||
break;
|
||||
}
|
||||
|
||||
// Prevent storing a cookie that we couldn't decode.
|
||||
if (!key && (cookie = read(cookie)) !== undefined) {
|
||||
result[name] = cookie;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
config.defaults = {};
|
||||
|
||||
$.removeCookie = function (key, options) {
|
||||
if ($.cookie(key) !== undefined) {
|
||||
// Must not alter options, thus extending a fresh object...
|
||||
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
}));
|
||||
25
RIGS/static/js/jquery.js
vendored
Normal file
25
RIGS/static/js/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,100 +0,0 @@
|
||||
jQuery(function($) {
|
||||
$
|
||||
.widget(
|
||||
"custom.combobox",
|
||||
{
|
||||
_create : function() {
|
||||
this.wrapper = $("<span>").addClass(
|
||||
"custom-combobox")
|
||||
.insertAfter(this.element);
|
||||
|
||||
this.element.hide();
|
||||
this._createAutocomplete();
|
||||
},
|
||||
|
||||
_createAutocomplete : function() {
|
||||
var selected = this.element.children(":selected"), value = selected
|
||||
.val() ? selected.text() : "";
|
||||
|
||||
this.input = $("<input>").appendTo(this.wrapper)
|
||||
.val(value).attr("title", "").addClass(
|
||||
"form-control").autocomplete({
|
||||
delay : 0,
|
||||
minLength : 3,
|
||||
source : $.proxy(this, "_source")
|
||||
}).tooltip({
|
||||
tooltipClass : "ui-state-highlight"
|
||||
});
|
||||
|
||||
this._on(this.input, {
|
||||
autocompleteselect : function(event, ui) {
|
||||
ui.item.option.selected = true;
|
||||
this._trigger("select", event, {
|
||||
item : ui.item.option
|
||||
});
|
||||
},
|
||||
|
||||
autocompletechange : "_removeIfInvalid"
|
||||
});
|
||||
},
|
||||
|
||||
_source : function(request, response) {
|
||||
var matcher = new RegExp($.ui.autocomplete
|
||||
.escapeRegex(request.term), "i");
|
||||
response(this.element.children("option").map(
|
||||
function() {
|
||||
var text = $(this).text();
|
||||
if (this.value
|
||||
&& (!request.term || matcher
|
||||
.test(text)))
|
||||
return {
|
||||
label : text,
|
||||
value : text,
|
||||
option : this
|
||||
};
|
||||
}));
|
||||
},
|
||||
|
||||
_removeIfInvalid : function(event, ui) {
|
||||
|
||||
// Selected an item, nothing to do
|
||||
if (ui.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for a match (case-insensitive)
|
||||
var value = this.input.val(), valueLowerCase = value
|
||||
.toLowerCase(), valid = false;
|
||||
this.element
|
||||
.children("option")
|
||||
.each(
|
||||
function() {
|
||||
if ($(this).text()
|
||||
.toLowerCase() === valueLowerCase) {
|
||||
this.selected = valid = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Found a match, nothing to do
|
||||
if (valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove invalid value
|
||||
this.input.val("").attr("title",
|
||||
value + " didn't match any item").tooltip(
|
||||
"open");
|
||||
this.element.val("");
|
||||
this._delay(function() {
|
||||
this.input.tooltip("close").attr("title", "");
|
||||
}, 25000);
|
||||
this.input.data("ui-autocomplete").term = "";
|
||||
},
|
||||
|
||||
_destroy : function() {
|
||||
this.wrapper.remove();
|
||||
this.element.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
106
RIGS/static/js/konami.js
Executable file → Normal file
106
RIGS/static/js/konami.js
Executable file → Normal file
@@ -1,105 +1 @@
|
||||
/*
|
||||
* Konami-JS ~
|
||||
* :: Now with support for touch events and multiple instances for
|
||||
* :: those situations that call for multiple easter eggs!
|
||||
* Code: http://konami-js.googlecode.com/
|
||||
* Examples: http://www.snaptortoise.com/konami-js
|
||||
* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
|
||||
* Version: 1.4.2 (9/2/2013)
|
||||
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1 and Dolphin Browser
|
||||
*/
|
||||
|
||||
var Konami = function (callback) {
|
||||
var konami = {
|
||||
addEvent: function (obj, type, fn, ref_obj) {
|
||||
if (obj.addEventListener)
|
||||
obj.addEventListener(type, fn, false);
|
||||
else if (obj.attachEvent) {
|
||||
// IE
|
||||
obj["e" + type + fn] = fn;
|
||||
obj[type + fn] = function () {
|
||||
obj["e" + type + fn](window.event, ref_obj);
|
||||
}
|
||||
obj.attachEvent("on" + type, obj[type + fn]);
|
||||
}
|
||||
},
|
||||
input: "",
|
||||
pattern: "38384040373937396665",
|
||||
load: function (link) {
|
||||
this.addEvent(document, "keydown", function (e, ref_obj) {
|
||||
if (ref_obj) konami = ref_obj; // IE
|
||||
konami.input += e ? e.keyCode : event.keyCode;
|
||||
if (konami.input.length > konami.pattern.length)
|
||||
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
|
||||
if (konami.input == konami.pattern) {
|
||||
konami.code(link);
|
||||
konami.input = "";
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}, this);
|
||||
/*this.iphone.load(link);*/
|
||||
},
|
||||
code: function (link) {
|
||||
window.location = link
|
||||
},
|
||||
iphone: {
|
||||
start_x: 0,
|
||||
start_y: 0,
|
||||
stop_x: 0,
|
||||
stop_y: 0,
|
||||
tap: false,
|
||||
capture: false,
|
||||
orig_keys: "",
|
||||
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
|
||||
code: function (link) {
|
||||
konami.code(link);
|
||||
},
|
||||
load: function (link) {
|
||||
this.orig_keys = this.keys;
|
||||
konami.addEvent(document, "touchmove", function (e) {
|
||||
if (e.touches.length == 1 && konami.iphone.capture == true) {
|
||||
var touch = e.touches[0];
|
||||
konami.iphone.stop_x = touch.pageX;
|
||||
konami.iphone.stop_y = touch.pageY;
|
||||
konami.iphone.tap = false;
|
||||
konami.iphone.capture = false;
|
||||
konami.iphone.check_direction();
|
||||
}
|
||||
});
|
||||
konami.addEvent(document, "touchend", function (evt) {
|
||||
if (konami.iphone.tap == true) konami.iphone.check_direction(link);
|
||||
}, false);
|
||||
konami.addEvent(document, "touchstart", function (evt) {
|
||||
konami.iphone.start_x = evt.changedTouches[0].pageX;
|
||||
konami.iphone.start_y = evt.changedTouches[0].pageY;
|
||||
konami.iphone.tap = true;
|
||||
konami.iphone.capture = true;
|
||||
});
|
||||
},
|
||||
check_direction: function (link) {
|
||||
x_magnitude = Math.abs(this.start_x - this.stop_x);
|
||||
y_magnitude = Math.abs(this.start_y - this.stop_y);
|
||||
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
|
||||
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
|
||||
result = (x_magnitude > y_magnitude) ? x : y;
|
||||
result = (this.tap == true) ? "TAP" : result;
|
||||
|
||||
if (result == this.keys[0]) this.keys = this.keys.slice(1, this.keys.length);
|
||||
if (this.keys.length == 0) {
|
||||
this.keys = this.orig_keys;
|
||||
this.code(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typeof callback === "string" && konami.load(callback);
|
||||
if (typeof callback === "function") {
|
||||
konami.code = callback;
|
||||
konami.load();
|
||||
}
|
||||
|
||||
return konami;
|
||||
};
|
||||
var Konami=function(t){var e={addEvent:function(t,e,n,o){t.addEventListener?t.addEventListener(e,n,!1):t.attachEvent&&(t["e"+e+n]=n,t[e+n]=function(){t["e"+e+n](window.event,o)},t.attachEvent("on"+e,t[e+n]))},removeEvent:function(t,e,n){t.removeEventListener?t.removeEventListener(e,n):t.attachEvent&&t.detachEvent(e)},input:"",pattern:"38384040373937396665",keydownHandler:function(t,n){if(n&&(e=n),e.input+=t?t.keyCode:event.keyCode,e.input.length>e.pattern.length&&(e.input=e.input.substr(e.input.length-e.pattern.length)),e.input===e.pattern)return e.code(e._currentLink),e.input="",t.preventDefault(),!1},load:function(t){this._currentLink=t,this.addEvent(document,"keydown",this.keydownHandler,this),this.iphone.load(t)},unload:function(){this.removeEvent(document,"keydown",this.keydownHandler),this.iphone.unload()},code:function(t){window.location=t},iphone:{start_x:0,start_y:0,stop_x:0,stop_y:0,tap:!1,capture:!1,orig_keys:"",keys:["UP","UP","DOWN","DOWN","LEFT","RIGHT","LEFT","RIGHT","TAP","TAP"],input:[],code:function(t){e.code(t)},touchmoveHandler:function(t){if(1===t.touches.length&&!0===e.iphone.capture){var n=t.touches[0];e.iphone.stop_x=n.pageX,e.iphone.stop_y=n.pageY,e.iphone.tap=!1,e.iphone.capture=!1,e.iphone.check_direction()}},touchendHandler:function(){if(e.iphone.input.push(e.iphone.check_direction()),e.iphone.input.length>e.iphone.keys.length&&e.iphone.input.shift(),e.iphone.input.length===e.iphone.keys.length){for(var t=!0,n=0;n<e.iphone.keys.length;n++)e.iphone.input[n]!==e.iphone.keys[n]&&(t=!1);t&&e.iphone.code(e._currentLink)}},touchstartHandler:function(t){e.iphone.start_x=t.changedTouches[0].pageX,e.iphone.start_y=t.changedTouches[0].pageY,e.iphone.tap=!0,e.iphone.capture=!0},load:function(t){this.orig_keys=this.keys,e.addEvent(document,"touchmove",this.touchmoveHandler),e.addEvent(document,"touchend",this.touchendHandler,!1),e.addEvent(document,"touchstart",this.touchstartHandler)},unload:function(){e.removeEvent(document,"touchmove",this.touchmoveHandler),e.removeEvent(document,"touchend",this.touchendHandler),e.removeEvent(document,"touchstart",this.touchstartHandler)},check_direction:function(){return x_magnitude=Math.abs(this.start_x-this.stop_x),y_magnitude=Math.abs(this.start_y-this.stop_y),x=this.start_x-this.stop_x<0?"RIGHT":"LEFT",y=this.start_y-this.stop_y<0?"DOWN":"UP",result=x_magnitude>y_magnitude?x:y,result=!0===this.tap?"TAP":result,result}}};return"string"==typeof t&&e.load(t),"function"==typeof t&&(e.code=t,e.load()),e};"undefined"!=typeof module&&void 0!==module.exports?module.exports=Konami:"function"==typeof define&&define.amd?define([],(function(){return Konami})):window.Konami=Konami;
|
||||
6
RIGS/static/js/main.js
Normal file
6
RIGS/static/js/main.js
Normal file
File diff suppressed because one or more lines are too long
6
RIGS/static/js/main.min.js
vendored
Normal file
6
RIGS/static/js/main.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
345
RIGS/static/js/modal.js
Executable file → Normal file
345
RIGS/static/js/modal.js
Executable file → Normal file
File diff suppressed because one or more lines are too long
@@ -1,86 +0,0 @@
|
||||
(function() {
|
||||
var day, formats, hour, initialize, minute, second, week;
|
||||
|
||||
second = 1e3;
|
||||
|
||||
minute = 6e4;
|
||||
|
||||
hour = 36e5;
|
||||
|
||||
day = 864e5;
|
||||
|
||||
week = 6048e5;
|
||||
|
||||
formats = {
|
||||
seconds: {
|
||||
short: 's',
|
||||
long: ' sec'
|
||||
},
|
||||
minutes: {
|
||||
short: 'm',
|
||||
long: ' min'
|
||||
},
|
||||
hours: {
|
||||
short: 'h',
|
||||
long: ' hr'
|
||||
},
|
||||
days: {
|
||||
short: 'd',
|
||||
long: ' day'
|
||||
}
|
||||
};
|
||||
|
||||
initialize = function(moment) {
|
||||
var twitterFormat;
|
||||
twitterFormat = function(format) {
|
||||
var diff, num, unit, unitStr;
|
||||
diff = Math.abs(this.diff(moment()));
|
||||
unit = null;
|
||||
num = null;
|
||||
if (diff <= second) {
|
||||
unit = 'seconds';
|
||||
num = 1;
|
||||
} else if (diff < minute) {
|
||||
unit = 'seconds';
|
||||
} else if (diff < hour) {
|
||||
unit = 'minutes';
|
||||
} else if (diff < day) {
|
||||
unit = 'hours';
|
||||
} else if (format === 'short') {
|
||||
if (diff < week) {
|
||||
unit = 'days';
|
||||
} else {
|
||||
return this.format('M/D/YY');
|
||||
}
|
||||
} else {
|
||||
return this.format('MMM D');
|
||||
}
|
||||
if (!(num && unit)) {
|
||||
num = moment.duration(diff)[unit]();
|
||||
}
|
||||
unitStr = unit = formats[unit][format];
|
||||
if (format === 'long' && num > 1) {
|
||||
unitStr += 's';
|
||||
}
|
||||
return num + unitStr;
|
||||
};
|
||||
moment.fn.twitterLong = function() {
|
||||
return twitterFormat.call(this, 'long');
|
||||
};
|
||||
moment.fn.twitter = moment.fn.twitterShort = function() {
|
||||
return twitterFormat.call(this, 'short');
|
||||
};
|
||||
return moment;
|
||||
};
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('moment-twitter', ['moment'], function(moment) {
|
||||
return this.moment = initialize(moment);
|
||||
});
|
||||
} else if (typeof module !== 'undefined') {
|
||||
module.exports = initialize(require('moment'));
|
||||
} else if (typeof window !== "undefined" && window.moment) {
|
||||
this.moment = initialize(this.moment);
|
||||
}
|
||||
|
||||
}).call(this);
|
||||
8
RIGS/static/js/moment.js
Normal file
8
RIGS/static/js/moment.js
Normal file
File diff suppressed because one or more lines are too long
7
RIGS/static/js/moment.min.js
vendored
7
RIGS/static/js/moment.min.js
vendored
File diff suppressed because one or more lines are too long
114
RIGS/static/js/popover.js
Executable file → Normal file
114
RIGS/static/js/popover.js
Executable file → Normal file
@@ -1,108 +1,6 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: popover.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#popovers
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// POPOVER PUBLIC CLASS DEFINITION
|
||||
// ===============================
|
||||
|
||||
var Popover = function (element, options) {
|
||||
this.init('popover', element, options)
|
||||
}
|
||||
|
||||
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
|
||||
|
||||
Popover.VERSION = '3.3.7'
|
||||
|
||||
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
|
||||
placement: 'right',
|
||||
trigger: 'click',
|
||||
content: '',
|
||||
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
|
||||
})
|
||||
|
||||
|
||||
// NOTE: POPOVER EXTENDS tooltip.js
|
||||
// ================================
|
||||
|
||||
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
|
||||
|
||||
Popover.prototype.constructor = Popover
|
||||
|
||||
Popover.prototype.getDefaults = function () {
|
||||
return Popover.DEFAULTS
|
||||
}
|
||||
|
||||
Popover.prototype.setContent = function () {
|
||||
var $tip = this.tip()
|
||||
var title = this.getTitle()
|
||||
var content = this.getContent()
|
||||
|
||||
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
|
||||
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
|
||||
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
|
||||
](content)
|
||||
|
||||
$tip.removeClass('fade top bottom left right in')
|
||||
|
||||
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
|
||||
// this manually by checking the contents.
|
||||
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
|
||||
}
|
||||
|
||||
Popover.prototype.hasContent = function () {
|
||||
return this.getTitle() || this.getContent()
|
||||
}
|
||||
|
||||
Popover.prototype.getContent = function () {
|
||||
var $e = this.$element
|
||||
var o = this.options
|
||||
|
||||
return $e.attr('data-content')
|
||||
|| (typeof o.content == 'function' ?
|
||||
o.content.call($e[0]) :
|
||||
o.content)
|
||||
}
|
||||
|
||||
Popover.prototype.arrow = function () {
|
||||
return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
|
||||
}
|
||||
|
||||
|
||||
// POPOVER PLUGIN DEFINITION
|
||||
// =========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.popover')
|
||||
var options = typeof option == 'object' && option
|
||||
|
||||
if (!data && /destroy|hide/.test(option)) return
|
||||
if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.popover
|
||||
|
||||
$.fn.popover = Plugin
|
||||
$.fn.popover.Constructor = Popover
|
||||
|
||||
|
||||
// POPOVER NO CONFLICT
|
||||
// ===================
|
||||
|
||||
$.fn.popover.noConflict = function () {
|
||||
$.fn.popover = old
|
||||
return this
|
||||
}
|
||||
|
||||
}(jQuery);
|
||||
/*!
|
||||
* Bootstrap popover.js v4.5.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery"),require("./tooltip.js")):"function"==typeof define&&define.amd?define(["jquery","./tooltip.js"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).Popover=e(t.jQuery,t.Tooltip)}(this,(function(t,e){"use strict";function n(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function o(){return(o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t[o]=n[o])}return t}).apply(this,arguments)}t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t,e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e;var r="popover",i=".bs.popover",s=t.fn[r],p=new RegExp("(^|\\s)bs-popover\\S+","g"),u=o({},e.Default,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'}),a=o({},e.DefaultType,{content:"(string|element|function)"}),l={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},c=function(e){var o,s;function c(){return e.apply(this,arguments)||this}s=e,(o=c).prototype=Object.create(s.prototype),o.prototype.constructor=o,o.__proto__=s;var f,h,d,y=c.prototype;return y.isWithContent=function(){return this.getTitle()||this._getContent()},y.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},y.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},y.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(".popover-header"),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(".popover-body"),n),e.removeClass("fade show")},y._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},y._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(p);null!==n&&n.length>0&&e.removeClass(n.join(""))},c._jQueryInterface=function(e){return this.each((function(){var n=t(this).data("bs.popover"),o="object"==typeof e?e:null;if((n||!/dispose|hide/.test(e))&&(n||(n=new c(this,o),t(this).data("bs.popover",n)),"string"==typeof e)){if(void 0===n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},f=c,d=[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return r}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return l}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return a}}],(h=null)&&n(f.prototype,h),d&&n(f,d),c}(e);return t.fn[r]=c._jQueryInterface,t.fn[r].Constructor=c,t.fn[r].noConflict=function(){return t.fn[r]=s,c._jQueryInterface},c}));
|
||||
25
RIGS/static/js/popper.js
Normal file
25
RIGS/static/js/popper.js
Normal file
File diff suppressed because one or more lines are too long
2
RIGS/static/js/raven.js
Normal file
2
RIGS/static/js/raven.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,172 +0,0 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: scrollspy.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#scrollspy
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// SCROLLSPY CLASS DEFINITION
|
||||
// ==========================
|
||||
|
||||
function ScrollSpy(element, options) {
|
||||
this.$body = $(document.body)
|
||||
this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
|
||||
this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
|
||||
this.selector = (this.options.target || '') + ' .nav li > a'
|
||||
this.offsets = []
|
||||
this.targets = []
|
||||
this.activeTarget = null
|
||||
this.scrollHeight = 0
|
||||
|
||||
this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
|
||||
this.refresh()
|
||||
this.process()
|
||||
}
|
||||
|
||||
ScrollSpy.VERSION = '3.3.7'
|
||||
|
||||
ScrollSpy.DEFAULTS = {
|
||||
offset: 10
|
||||
}
|
||||
|
||||
ScrollSpy.prototype.getScrollHeight = function () {
|
||||
return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
|
||||
}
|
||||
|
||||
ScrollSpy.prototype.refresh = function () {
|
||||
var that = this
|
||||
var offsetMethod = 'offset'
|
||||
var offsetBase = 0
|
||||
|
||||
this.offsets = []
|
||||
this.targets = []
|
||||
this.scrollHeight = this.getScrollHeight()
|
||||
|
||||
if (!$.isWindow(this.$scrollElement[0])) {
|
||||
offsetMethod = 'position'
|
||||
offsetBase = this.$scrollElement.scrollTop()
|
||||
}
|
||||
|
||||
this.$body
|
||||
.find(this.selector)
|
||||
.map(function () {
|
||||
var $el = $(this)
|
||||
var href = $el.data('target') || $el.attr('href')
|
||||
var $href = /^#./.test(href) && $(href)
|
||||
|
||||
return ($href
|
||||
&& $href.length
|
||||
&& $href.is(':visible')
|
||||
&& [[$href[offsetMethod]().top + offsetBase, href]]) || null
|
||||
})
|
||||
.sort(function (a, b) { return a[0] - b[0] })
|
||||
.each(function () {
|
||||
that.offsets.push(this[0])
|
||||
that.targets.push(this[1])
|
||||
})
|
||||
}
|
||||
|
||||
ScrollSpy.prototype.process = function () {
|
||||
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
|
||||
var scrollHeight = this.getScrollHeight()
|
||||
var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
|
||||
var offsets = this.offsets
|
||||
var targets = this.targets
|
||||
var activeTarget = this.activeTarget
|
||||
var i
|
||||
|
||||
if (this.scrollHeight != scrollHeight) {
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
if (scrollTop >= maxScroll) {
|
||||
return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
|
||||
}
|
||||
|
||||
if (activeTarget && scrollTop < offsets[0]) {
|
||||
this.activeTarget = null
|
||||
return this.clear()
|
||||
}
|
||||
|
||||
for (i = offsets.length; i--;) {
|
||||
activeTarget != targets[i]
|
||||
&& scrollTop >= offsets[i]
|
||||
&& (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
|
||||
&& this.activate(targets[i])
|
||||
}
|
||||
}
|
||||
|
||||
ScrollSpy.prototype.activate = function (target) {
|
||||
this.activeTarget = target
|
||||
|
||||
this.clear()
|
||||
|
||||
var selector = this.selector +
|
||||
'[data-target="' + target + '"],' +
|
||||
this.selector + '[href="' + target + '"]'
|
||||
|
||||
var active = $(selector)
|
||||
.parents('li')
|
||||
.addClass('active')
|
||||
|
||||
if (active.parent('.dropdown-menu').length) {
|
||||
active = active
|
||||
.closest('li.dropdown')
|
||||
.addClass('active')
|
||||
}
|
||||
|
||||
active.trigger('activate.bs.scrollspy')
|
||||
}
|
||||
|
||||
ScrollSpy.prototype.clear = function () {
|
||||
$(this.selector)
|
||||
.parentsUntil(this.options.target, '.active')
|
||||
.removeClass('active')
|
||||
}
|
||||
|
||||
|
||||
// SCROLLSPY PLUGIN DEFINITION
|
||||
// ===========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.scrollspy')
|
||||
var options = typeof option == 'object' && option
|
||||
|
||||
if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.scrollspy
|
||||
|
||||
$.fn.scrollspy = Plugin
|
||||
$.fn.scrollspy.Constructor = ScrollSpy
|
||||
|
||||
|
||||
// SCROLLSPY NO CONFLICT
|
||||
// =====================
|
||||
|
||||
$.fn.scrollspy.noConflict = function () {
|
||||
$.fn.scrollspy = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// SCROLLSPY DATA-API
|
||||
// ==================
|
||||
|
||||
$(window).on('load.bs.scrollspy.data-api', function () {
|
||||
$('[data-spy="scroll"]').each(function () {
|
||||
var $spy = $(this)
|
||||
Plugin.call($spy, $spy.data())
|
||||
})
|
||||
})
|
||||
|
||||
}(jQuery);
|
||||
1
RIGS/static/js/src/asteroids.min.js
vendored
Normal file
1
RIGS/static/js/src/asteroids.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
102
RIGS/static/js/src/autocompleter.js
Normal file
102
RIGS/static/js/src/autocompleter.js
Normal file
@@ -0,0 +1,102 @@
|
||||
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
|
||||
//console.log('Changing selected value');
|
||||
obj.find('option').remove(); //Remove all the available options
|
||||
obj.append( //Add the new option
|
||||
$("<option></option>")
|
||||
.attr("value",pk)
|
||||
.text(text)
|
||||
.data('update_url',update_url)
|
||||
);
|
||||
obj.selectpicker('render'); //Re-render the UI
|
||||
obj.selectpicker('refresh'); //Re-render the UI
|
||||
obj.selectpicker('val', pk); //Set the new value to be selected
|
||||
obj.change(); //Trigger the change function manually
|
||||
}
|
||||
|
||||
function refreshUpdateHref(obj) {
|
||||
//console.log('Refreshing Update URL');
|
||||
targetObject = $('#'+obj.attr('id')+'-update');
|
||||
update_url = $('option:selected', obj).data('update_url');
|
||||
|
||||
if (update_url=="") { //Probably "clear selection" has been chosen
|
||||
//console.log('Trying to disable');
|
||||
targetObject.removeAttr('href');
|
||||
targetObject.addClass('disabled');
|
||||
} else {
|
||||
targetObject.prop('href', update_url);
|
||||
targetObject.removeClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function initPicker(obj) {
|
||||
var options = {
|
||||
ajax: {
|
||||
url: obj.data('sourceurl'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
// Use "{{{q}}}" as a placeholder and Ajax Bootstrap Select will
|
||||
// automatically replace it with the value of the search query.
|
||||
data: {
|
||||
term: '{{{q}}}'
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
emptyTitle: ''
|
||||
},
|
||||
clearOnEmpty:false,
|
||||
//log: 3,
|
||||
preprocessData: function (data) {
|
||||
var i, l = data.length, array = [];
|
||||
array.push({
|
||||
text: clearSelectionLabel,
|
||||
value: '',
|
||||
data:{
|
||||
update_url: '',
|
||||
subtext:''
|
||||
}
|
||||
});
|
||||
|
||||
if (l) {
|
||||
for(i = 0; i < l; i++){
|
||||
array.push($.extend(true, data[i], {
|
||||
text: data[i]['label'],
|
||||
value: data[i]['pk'],
|
||||
data:{
|
||||
update_url: data[i]['update'],
|
||||
subtext:''
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
};
|
||||
|
||||
obj.prepend($("<option></option>")
|
||||
.attr("value",'')
|
||||
.text(clearSelectionLabel)
|
||||
.data('update_url','')); //Add "clear selection" option
|
||||
|
||||
|
||||
obj.selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
|
||||
obj.change(function(){ //on change, update the edit button href
|
||||
//console.log('Selectbox Changed');
|
||||
refreshUpdateHref(obj);
|
||||
});
|
||||
|
||||
refreshUpdateHref(obj); //Ensure href is correct at the beginning
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
clearSelectionLabel = '(no selection)';
|
||||
|
||||
$(".selectpicker").each(function(){initPicker($(this))});
|
||||
|
||||
//When update/edit modal box submitted
|
||||
$('#modal').on('hide.bs.modal', function (e) {
|
||||
if (modaltarget != undefined && modalobject != "") {
|
||||
//Update the selector with new values
|
||||
changeSelectedValue($(modaltarget),modalobject[0]['pk'],modalobject[0]['fields']['name'],modalobject[0]['update_url']);
|
||||
}
|
||||
});
|
||||
});
|
||||
139
RIGS/static/js/src/interaction.js
Normal file
139
RIGS/static/js/src/interaction.js
Normal file
@@ -0,0 +1,139 @@
|
||||
function setupItemTable(items_json) {
|
||||
objectitems = JSON.parse(items_json)
|
||||
$.each(objectitems, function (key, val) {
|
||||
objectitems[key] = JSON.parse(val);
|
||||
})
|
||||
newitem = -1;
|
||||
}
|
||||
|
||||
function nl2br (str, is_xhtml) {
|
||||
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
|
||||
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
|
||||
}
|
||||
|
||||
function escapeHtml (str) {
|
||||
return $('<div/>').text(str).html();
|
||||
}
|
||||
|
||||
function updatePrices() {
|
||||
// individual rows
|
||||
var sum = 0;
|
||||
for (var pk in objectitems) {
|
||||
var fields = objectitems[pk].fields;
|
||||
var sub = fields.cost * fields.quantity;
|
||||
$('#item-' + pk + ' .sub-total').html(parseFloat(sub).toFixed(2)).data('subtotal', sub);
|
||||
|
||||
sum += Number(sub);
|
||||
}
|
||||
|
||||
$('#sumtotal').text(parseFloat(sum).toFixed(2));
|
||||
var vat = sum * Number($('#vat-rate').data('rate'));
|
||||
$('#vat').text(parseFloat(vat).toFixed(2));
|
||||
$('#total').text(parseFloat(sum + vat).toFixed(2));
|
||||
}
|
||||
|
||||
$('#item-table').on('click', '.item-delete', function () {
|
||||
delete objectitems[$(this).data('pk')]
|
||||
$('#item-' + $(this).data('pk')).remove();
|
||||
updatePrices();
|
||||
});
|
||||
|
||||
$('#item-table').on('click', '.item-add', function () {
|
||||
$('#item-form').data('pk', newitem);
|
||||
|
||||
// Set the form values
|
||||
$('#item_name').val('');
|
||||
$('#item_description').val('');
|
||||
$('#item_quantity').val('');
|
||||
$('#item_cost').val('');
|
||||
|
||||
$($(this).data('target')).modal('show');
|
||||
});
|
||||
|
||||
$('#item-table').on('click', '.item-edit', function () {
|
||||
// set the pk as we will need this later
|
||||
var pk = $(this).data('pk');
|
||||
$('#item-form').data('pk', pk);
|
||||
|
||||
// Set the form values
|
||||
var fields = objectitems[pk].fields;
|
||||
$('#item_name').val(fields.name);
|
||||
$('#item_description').val(fields.description);
|
||||
$('#item_quantity').val(fields.quantity);
|
||||
$('#item_cost').val(fields.cost);
|
||||
|
||||
$($(this).data('target')).modal('show');
|
||||
});
|
||||
|
||||
$('body').on('submit', '#item-form', function (e) {
|
||||
e.preventDefault();
|
||||
var pk = $(this).data('pk');
|
||||
$('#itemModal').modal('hide');
|
||||
|
||||
var fields;
|
||||
if (pk == newitem--) {
|
||||
// Create the new data structure and add it on.
|
||||
fields = new Object();
|
||||
fields['name'] = $('#item_name').val()
|
||||
fields['description'] = $('#item_description').val();
|
||||
fields['cost'] = $('#item_cost').val();
|
||||
fields['quantity'] = $('#item_quantity').val();
|
||||
|
||||
var order = 0;
|
||||
for (item in objectitems) {
|
||||
order++;
|
||||
}
|
||||
|
||||
fields['order'] = order;
|
||||
|
||||
objectitems[pk] = new Object();
|
||||
objectitems[pk]['fields'] = fields;
|
||||
|
||||
// Add the new table
|
||||
$('#new-item-row').clone().attr('id', 'item-' + pk).data('pk', pk).appendTo('#item-table-body');
|
||||
$('#item-'+pk+' .item-delete, #item-'+pk+' .item-edit').data('pk', pk)
|
||||
} else {
|
||||
// Existing item
|
||||
// update data structure
|
||||
fields = objectitems[pk].fields;
|
||||
fields.name = $('#item_name').val()
|
||||
fields.description = $('#item_description').val();
|
||||
fields.cost = $('#item_cost').val();
|
||||
fields.quantity = $('#item_quantity').val();
|
||||
objectitems[pk].fields = fields;
|
||||
|
||||
}
|
||||
// update the table
|
||||
$row = $('#item-' + pk);
|
||||
$row.find('.name').html(escapeHtml(fields.name));
|
||||
$row.find('.description').html(nl2br(escapeHtml(fields.description)));
|
||||
$row.find('.cost').html(parseFloat(fields.cost).toFixed(2));
|
||||
$row.find('.quantity').html(fields.quantity);
|
||||
|
||||
updatePrices();
|
||||
});
|
||||
|
||||
$('body').on('submit', '.itemised_form', function (e) {
|
||||
$('#id_items_json').val(JSON.stringify(objectitems));
|
||||
});
|
||||
|
||||
// Return a helper with preserved width of cells
|
||||
var fixHelper = function (e, ui) {
|
||||
ui.children().each(function () {
|
||||
$(this).width($(this).width());
|
||||
});
|
||||
return ui;
|
||||
};
|
||||
|
||||
$("#item-table tbody").sortable({
|
||||
helper: fixHelper,
|
||||
update: function (e, ui) {
|
||||
info = $(this).sortable("toArray");
|
||||
itemorder = new Array();
|
||||
$.each(info, function (key, value) {
|
||||
pk = $('#' + value).data('pk');
|
||||
objectitems[pk].fields.order = key;
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
@@ -1,155 +0,0 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: tab.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#tabs
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// TAB CLASS DEFINITION
|
||||
// ====================
|
||||
|
||||
var Tab = function (element) {
|
||||
// jscs:disable requireDollarBeforejQueryAssignment
|
||||
this.element = $(element)
|
||||
// jscs:enable requireDollarBeforejQueryAssignment
|
||||
}
|
||||
|
||||
Tab.VERSION = '3.3.7'
|
||||
|
||||
Tab.TRANSITION_DURATION = 150
|
||||
|
||||
Tab.prototype.show = function () {
|
||||
var $this = this.element
|
||||
var $ul = $this.closest('ul:not(.dropdown-menu)')
|
||||
var selector = $this.data('target')
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
||||
}
|
||||
|
||||
if ($this.parent('li').hasClass('active')) return
|
||||
|
||||
var $previous = $ul.find('.active:last a')
|
||||
var hideEvent = $.Event('hide.bs.tab', {
|
||||
relatedTarget: $this[0]
|
||||
})
|
||||
var showEvent = $.Event('show.bs.tab', {
|
||||
relatedTarget: $previous[0]
|
||||
})
|
||||
|
||||
$previous.trigger(hideEvent)
|
||||
$this.trigger(showEvent)
|
||||
|
||||
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
|
||||
|
||||
var $target = $(selector)
|
||||
|
||||
this.activate($this.closest('li'), $ul)
|
||||
this.activate($target, $target.parent(), function () {
|
||||
$previous.trigger({
|
||||
type: 'hidden.bs.tab',
|
||||
relatedTarget: $this[0]
|
||||
})
|
||||
$this.trigger({
|
||||
type: 'shown.bs.tab',
|
||||
relatedTarget: $previous[0]
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Tab.prototype.activate = function (element, container, callback) {
|
||||
var $active = container.find('> .active')
|
||||
var transition = callback
|
||||
&& $.support.transition
|
||||
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
|
||||
|
||||
function next() {
|
||||
$active
|
||||
.removeClass('active')
|
||||
.find('> .dropdown-menu > .active')
|
||||
.removeClass('active')
|
||||
.end()
|
||||
.find('[data-toggle="tab"]')
|
||||
.attr('aria-expanded', false)
|
||||
|
||||
element
|
||||
.addClass('active')
|
||||
.find('[data-toggle="tab"]')
|
||||
.attr('aria-expanded', true)
|
||||
|
||||
if (transition) {
|
||||
element[0].offsetWidth // reflow for transition
|
||||
element.addClass('in')
|
||||
} else {
|
||||
element.removeClass('fade')
|
||||
}
|
||||
|
||||
if (element.parent('.dropdown-menu').length) {
|
||||
element
|
||||
.closest('li.dropdown')
|
||||
.addClass('active')
|
||||
.end()
|
||||
.find('[data-toggle="tab"]')
|
||||
.attr('aria-expanded', true)
|
||||
}
|
||||
|
||||
callback && callback()
|
||||
}
|
||||
|
||||
$active.length && transition ?
|
||||
$active
|
||||
.one('bsTransitionEnd', next)
|
||||
.emulateTransitionEnd(Tab.TRANSITION_DURATION) :
|
||||
next()
|
||||
|
||||
$active.removeClass('in')
|
||||
}
|
||||
|
||||
|
||||
// TAB PLUGIN DEFINITION
|
||||
// =====================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.tab')
|
||||
|
||||
if (!data) $this.data('bs.tab', (data = new Tab(this)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.tab
|
||||
|
||||
$.fn.tab = Plugin
|
||||
$.fn.tab.Constructor = Tab
|
||||
|
||||
|
||||
// TAB NO CONFLICT
|
||||
// ===============
|
||||
|
||||
$.fn.tab.noConflict = function () {
|
||||
$.fn.tab = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// TAB DATA-API
|
||||
// ============
|
||||
|
||||
var clickHandler = function (e) {
|
||||
e.preventDefault()
|
||||
Plugin.call($(this), 'show')
|
||||
}
|
||||
|
||||
$(document)
|
||||
.on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
|
||||
.on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
|
||||
|
||||
}(jQuery);
|
||||
@@ -1,52 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bootstrap Plugin Test Suite</title>
|
||||
|
||||
<!-- jquery -->
|
||||
<!--<script src="http://code.jquery.com/jquery-1.7.min.js"></script>-->
|
||||
<script src="vendor/jquery.js"></script>
|
||||
|
||||
<!-- qunit -->
|
||||
<link rel="stylesheet" href="vendor/qunit.css" media="screen">
|
||||
<script src="vendor/qunit.js"></script>
|
||||
|
||||
<!-- plugin sources -->
|
||||
<script src="../transition.js"></script>
|
||||
<script src="../alert.js"></script>
|
||||
<script src="../button.js"></script>
|
||||
<script src="../carousel.js"></script>
|
||||
<script src="../collapse.js"></script>
|
||||
<script src="../dropdown.js"></script>
|
||||
<script src="../modal.js"></script>
|
||||
<script src="../scrollspy.js"></script>
|
||||
<script src="../tab.js"></script>
|
||||
<script src="../tooltip.js"></script>
|
||||
<script src="../popover.js"></script>
|
||||
<script src="../affix.js"></script>
|
||||
|
||||
<!-- unit tests -->
|
||||
<script src="unit/transition.js"></script>
|
||||
<script src="unit/alert.js"></script>
|
||||
<script src="unit/button.js"></script>
|
||||
<script src="unit/carousel.js"></script>
|
||||
<script src="unit/collapse.js"></script>
|
||||
<script src="unit/dropdown.js"></script>
|
||||
<script src="unit/modal.js"></script>
|
||||
<script src="unit/scrollspy.js"></script>
|
||||
<script src="unit/tab.js"></script>
|
||||
<script src="unit/tooltip.js"></script>
|
||||
<script src="unit/popover.js"></script>
|
||||
<script src="unit/affix.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1 id="qunit-header">Bootstrap Plugin Test Suite</h1>
|
||||
<h2 id="qunit-banner"></h2>
|
||||
<h2 id="qunit-userAgent"></h2>
|
||||
<ol id="qunit-tests"></ol>
|
||||
<div id="qunit-fixture"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,25 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("affix")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var affix = $.fn.affix.noConflict()
|
||||
ok(!$.fn.affix, 'affix was set back to undefined (org value)')
|
||||
$.fn.affix = affix
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
ok($(document.body).affix, 'affix method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
ok($(document.body).affix()[0] == document.body, 'document.body returned')
|
||||
})
|
||||
|
||||
test("should exit early if element is not visible", function () {
|
||||
var $affix = $('<div style="display: none"></div>').affix()
|
||||
$affix.data('bs.affix').checkPosition()
|
||||
ok(!$affix.hasClass('affix'), 'affix class was not added')
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,62 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("alert")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var alert = $.fn.alert.noConflict()
|
||||
ok(!$.fn.alert, 'alert was set back to undefined (org value)')
|
||||
$.fn.alert = alert
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
ok($(document.body).alert, 'alert method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
ok($(document.body).alert()[0] == document.body, 'document.body returned')
|
||||
})
|
||||
|
||||
test("should fade element out on clicking .close", function () {
|
||||
var alertHTML = '<div class="alert-message warning fade in">'
|
||||
+ '<a class="close" href="#" data-dismiss="alert">×</a>'
|
||||
+ '<p><strong>Holy guacamole!</strong> Best check yo self, you\'re not looking too good.</p>'
|
||||
+ '</div>'
|
||||
, alert = $(alertHTML).alert()
|
||||
|
||||
alert.find('.close').click()
|
||||
|
||||
ok(!alert.hasClass('in'), 'remove .in class on .close click')
|
||||
})
|
||||
|
||||
test("should remove element when clicking .close", function () {
|
||||
$.support.transition = false
|
||||
|
||||
var alertHTML = '<div class="alert-message warning fade in">'
|
||||
+ '<a class="close" href="#" data-dismiss="alert">×</a>'
|
||||
+ '<p><strong>Holy guacamole!</strong> Best check yo self, you\'re not looking too good.</p>'
|
||||
+ '</div>'
|
||||
, alert = $(alertHTML).appendTo('#qunit-fixture').alert()
|
||||
|
||||
ok($('#qunit-fixture').find('.alert-message').length, 'element added to dom')
|
||||
|
||||
alert.find('.close').click()
|
||||
|
||||
ok(!$('#qunit-fixture').find('.alert-message').length, 'element removed from dom')
|
||||
})
|
||||
|
||||
test("should not fire closed when close is prevented", function () {
|
||||
$.support.transition = false
|
||||
stop();
|
||||
$('<div class="alert"/>')
|
||||
.on('close.bs.alert', function (e) {
|
||||
e.preventDefault();
|
||||
ok(true);
|
||||
start();
|
||||
})
|
||||
.on('closed.bs.alert', function () {
|
||||
ok(false);
|
||||
})
|
||||
.alert('close')
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,116 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("button")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var button = $.fn.button.noConflict()
|
||||
ok(!$.fn.button, 'button was set back to undefined (org value)')
|
||||
$.fn.button = button
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
ok($(document.body).button, 'button method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
ok($(document.body).button()[0] == document.body, 'document.body returned')
|
||||
})
|
||||
|
||||
test("should return set state to loading", function () {
|
||||
var btn = $('<button class="btn" data-loading-text="fat">mdo</button>')
|
||||
equal(btn.html(), 'mdo', 'btn text equals mdo')
|
||||
btn.button('loading')
|
||||
equal(btn.html(), 'fat', 'btn text equals fat')
|
||||
stop()
|
||||
setTimeout(function () {
|
||||
ok(btn.attr('disabled'), 'btn is disabled')
|
||||
ok(btn.hasClass('disabled'), 'btn has disabled class')
|
||||
start()
|
||||
}, 0)
|
||||
})
|
||||
|
||||
test("should return reset state", function () {
|
||||
var btn = $('<button class="btn" data-loading-text="fat">mdo</button>')
|
||||
equal(btn.html(), 'mdo', 'btn text equals mdo')
|
||||
btn.button('loading')
|
||||
equal(btn.html(), 'fat', 'btn text equals fat')
|
||||
stop()
|
||||
setTimeout(function () {
|
||||
ok(btn.attr('disabled'), 'btn is disabled')
|
||||
ok(btn.hasClass('disabled'), 'btn has disabled class')
|
||||
start()
|
||||
stop()
|
||||
btn.button('reset')
|
||||
equal(btn.html(), 'mdo', 'btn text equals mdo')
|
||||
setTimeout(function () {
|
||||
ok(!btn.attr('disabled'), 'btn is not disabled')
|
||||
ok(!btn.hasClass('disabled'), 'btn does not have disabled class')
|
||||
start()
|
||||
}, 0)
|
||||
}, 0)
|
||||
|
||||
})
|
||||
|
||||
test("should toggle active", function () {
|
||||
var btn = $('<button class="btn">mdo</button>')
|
||||
ok(!btn.hasClass('active'), 'btn does not have active class')
|
||||
btn.button('toggle')
|
||||
ok(btn.hasClass('active'), 'btn has class active')
|
||||
})
|
||||
|
||||
test("should toggle active when btn children are clicked", function () {
|
||||
var btn = $('<button class="btn" data-toggle="button">mdo</button>')
|
||||
, inner = $('<i></i>')
|
||||
btn
|
||||
.append(inner)
|
||||
.appendTo($('#qunit-fixture'))
|
||||
ok(!btn.hasClass('active'), 'btn does not have active class')
|
||||
inner.click()
|
||||
ok(btn.hasClass('active'), 'btn has class active')
|
||||
})
|
||||
|
||||
test("should toggle active when btn children are clicked within btn-group", function () {
|
||||
var btngroup = $('<div class="btn-group" data-toggle="buttons"></div>')
|
||||
, btn = $('<button class="btn">fat</button>')
|
||||
, inner = $('<i></i>')
|
||||
btngroup
|
||||
.append(btn.append(inner))
|
||||
.appendTo($('#qunit-fixture'))
|
||||
ok(!btn.hasClass('active'), 'btn does not have active class')
|
||||
inner.click()
|
||||
ok(btn.hasClass('active'), 'btn has class active')
|
||||
})
|
||||
|
||||
test("should check for closest matching toggle", function () {
|
||||
var group = '<div class="btn-group" data-toggle="buttons">' +
|
||||
'<label class="btn btn-primary active">' +
|
||||
'<input type="radio" name="options" id="option1" checked="true"> Option 1' +
|
||||
'</label>' +
|
||||
'<label class="btn btn-primary">' +
|
||||
'<input type="radio" name="options" id="option2"> Option 2' +
|
||||
'</label>' +
|
||||
'<label class="btn btn-primary">' +
|
||||
'<input type="radio" name="options" id="option3"> Option 3' +
|
||||
'</label>' +
|
||||
'</div>'
|
||||
|
||||
group = $(group)
|
||||
|
||||
var btn1 = $(group.children()[0])
|
||||
var btn2 = $(group.children()[1])
|
||||
var btn3 = $(group.children()[2])
|
||||
|
||||
group.appendTo($('#qunit-fixture'))
|
||||
|
||||
ok(btn1.hasClass('active'), 'btn1 has active class')
|
||||
ok(btn1.find('input').prop('checked'), 'btn1 is checked')
|
||||
ok(!btn2.hasClass('active'), 'btn2 does not have active class')
|
||||
ok(!btn2.find('input').prop('checked'), 'btn2 is not checked')
|
||||
btn2.find('input').click()
|
||||
ok(!btn1.hasClass('active'), 'btn1 does not have active class')
|
||||
ok(!btn1.find('input').prop('checked'), 'btn1 is checked')
|
||||
ok(btn2.hasClass('active'), 'btn2 has active class')
|
||||
ok(btn2.find('input').prop('checked'), 'btn2 is checked')
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,87 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("carousel")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var carousel = $.fn.carousel.noConflict()
|
||||
ok(!$.fn.carousel, 'carousel was set back to undefined (org value)')
|
||||
$.fn.carousel = carousel
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
ok($(document.body).carousel, 'carousel method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
ok($(document.body).carousel()[0] == document.body, 'document.body returned')
|
||||
})
|
||||
|
||||
test("should not fire sliden when slide is prevented", function () {
|
||||
$.support.transition = false
|
||||
stop()
|
||||
$('<div class="carousel"/>')
|
||||
.on('slide.bs.carousel', function (e) {
|
||||
e.preventDefault();
|
||||
ok(true);
|
||||
start();
|
||||
})
|
||||
.on('slid.bs.carousel', function () {
|
||||
ok(false);
|
||||
})
|
||||
.carousel('next')
|
||||
})
|
||||
|
||||
test("should fire slide event with direction", function () {
|
||||
var template = '<div id="myCarousel" class="carousel slide"><div class="carousel-inner"><div class="item active"><img alt=""><div class="carousel-caption"><h4>{{_i}}First Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div><div class="item"><img alt=""><div class="carousel-caption"><h4>{{_i}}Second Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div><div class="item"><img alt=""><div class="carousel-caption"><h4>{{_i}}Third Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div></div><a class="left carousel-control" href="#myCarousel" data-slide="prev">‹</a><a class="right carousel-control" href="#myCarousel" data-slide="next">›</a></div>'
|
||||
$.support.transition = false
|
||||
stop()
|
||||
$(template).on('slide.bs.carousel', function (e) {
|
||||
e.preventDefault()
|
||||
ok(e.direction)
|
||||
ok(e.direction === 'right' || e.direction === 'left')
|
||||
start()
|
||||
}).carousel('next')
|
||||
})
|
||||
|
||||
test("should fire slide event with relatedTarget", function () {
|
||||
var template = '<div id="myCarousel" class="carousel slide"><div class="carousel-inner"><div class="item active"><img alt=""><div class="carousel-caption"><h4>{{_i}}First Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div><div class="item"><img alt=""><div class="carousel-caption"><h4>{{_i}}Second Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div><div class="item"><img alt=""><div class="carousel-caption"><h4>{{_i}}Third Thumbnail label{{/i}}</h4><p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p></div></div></div><a class="left carousel-control" href="#myCarousel" data-slide="prev">‹</a><a class="right carousel-control" href="#myCarousel" data-slide="next">›</a></div>'
|
||||
$.support.transition = false
|
||||
stop()
|
||||
$(template)
|
||||
.on('slide.bs.carousel', function (e) {
|
||||
e.preventDefault();
|
||||
ok(e.relatedTarget);
|
||||
ok($(e.relatedTarget).hasClass('item'));
|
||||
start();
|
||||
})
|
||||
.carousel('next')
|
||||
})
|
||||
|
||||
test("should set interval from data attribute", 4, function () {
|
||||
var template = $('<div id="myCarousel" class="carousel slide"> <div class="carousel-inner"> <div class="item active"> <img alt=""> <div class="carousel-caption"> <h4>{{_i}}First Thumbnail label{{/i}}</h4> <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p> </div> </div> <div class="item"> <img alt=""> <div class="carousel-caption"> <h4>{{_i}}Second Thumbnail label{{/i}}</h4> <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p> </div> </div> <div class="item"> <img alt=""> <div class="carousel-caption"> <h4>{{_i}}Third Thumbnail label{{/i}}</h4> <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.</p> </div> </div> </div> <a class="left carousel-control" href="#myCarousel" data-slide="prev">‹</a> <a class="right carousel-control" href="#myCarousel" data-slide="next">›</a> </div>');
|
||||
template.attr("data-interval", 1814);
|
||||
|
||||
template.appendTo("body");
|
||||
$('[data-slide]').first().click();
|
||||
ok($('#myCarousel').data('bs.carousel').options.interval == 1814);
|
||||
$('#myCarousel').remove();
|
||||
|
||||
template.appendTo("body").attr("data-modal", "foobar");
|
||||
$('[data-slide]').first().click();
|
||||
ok($('#myCarousel').data('bs.carousel').options.interval == 1814, "even if there is an data-modal attribute set");
|
||||
$('#myCarousel').remove();
|
||||
|
||||
template.appendTo("body");
|
||||
$('[data-slide]').first().click();
|
||||
$('#myCarousel').attr('data-interval', 1860);
|
||||
$('[data-slide]').first().click();
|
||||
ok($('#myCarousel').data('bs.carousel').options.interval == 1814, "attributes should be read only on intitialization");
|
||||
$('#myCarousel').remove();
|
||||
|
||||
template.attr("data-interval", false);
|
||||
template.appendTo("body");
|
||||
$('#myCarousel').carousel(1);
|
||||
ok($('#myCarousel').data('bs.carousel').options.interval === false, "data attribute has higher priority than default options");
|
||||
$('#myCarousel').remove();
|
||||
})
|
||||
})
|
||||
@@ -1,164 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("collapse")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var collapse = $.fn.collapse.noConflict()
|
||||
ok(!$.fn.collapse, 'collapse was set back to undefined (org value)')
|
||||
$.fn.collapse = collapse
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
ok($(document.body).collapse, 'collapse method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
ok($(document.body).collapse()[0] == document.body, 'document.body returned')
|
||||
})
|
||||
|
||||
test("should show a collapsed element", function () {
|
||||
var el = $('<div class="collapse"></div>').collapse('show')
|
||||
ok(el.hasClass('in'), 'has class in')
|
||||
ok(/height/.test(el.attr('style')), 'has height set')
|
||||
})
|
||||
|
||||
test("should hide a collapsed element", function () {
|
||||
var el = $('<div class="collapse"></div>').collapse('hide')
|
||||
ok(!el.hasClass('in'), 'does not have class in')
|
||||
ok(/height/.test(el.attr('style')), 'has height set')
|
||||
})
|
||||
|
||||
test("should not fire shown when show is prevented", function () {
|
||||
$.support.transition = false
|
||||
stop()
|
||||
$('<div class="collapse"/>')
|
||||
.on('show.bs.collapse', function (e) {
|
||||
e.preventDefault();
|
||||
ok(true);
|
||||
start();
|
||||
})
|
||||
.on('shown.bs.collapse', function () {
|
||||
ok(false);
|
||||
})
|
||||
.collapse('show')
|
||||
})
|
||||
|
||||
test("should reset style to auto after finishing opening collapse", function () {
|
||||
$.support.transition = false
|
||||
stop()
|
||||
$('<div class="collapse" style="height: 0px"/>')
|
||||
.on('show.bs.collapse', function () {
|
||||
ok(this.style.height == '0px')
|
||||
})
|
||||
.on('shown.bs.collapse', function () {
|
||||
ok(this.style.height == 'auto')
|
||||
start()
|
||||
})
|
||||
.collapse('show')
|
||||
})
|
||||
|
||||
test("should add active class to target when collapse shown", function () {
|
||||
$.support.transition = false
|
||||
stop()
|
||||
|
||||
var target = $('<a data-toggle="collapse" href="#test1"></a>')
|
||||
.appendTo($('#qunit-fixture'))
|
||||
|
||||
var collapsible = $('<div id="test1"></div>')
|
||||
.appendTo($('#qunit-fixture'))
|
||||
.on('show.bs.collapse', function () {
|
||||
ok(!target.hasClass('collapsed'))
|
||||
start()
|
||||
})
|
||||
|
||||
target.click()
|
||||
})
|
||||
|
||||
test("should remove active class to target when collapse hidden", function () {
|
||||
$.support.transition = false
|
||||
stop()
|
||||
|
||||
var target = $('<a data-toggle="collapse" href="#test1"></a>')
|
||||
.appendTo($('#qunit-fixture'))
|
||||
|
||||
var collapsible = $('<div id="test1" class="in"></div>')
|
||||
.appendTo($('#qunit-fixture'))
|
||||
.on('hide.bs.collapse', function () {
|
||||
ok(target.hasClass('collapsed'))
|
||||
start()
|
||||
})
|
||||
|
||||
target.click()
|
||||
})
|
||||
|
||||
test("should remove active class from inactive accordion targets", function () {
|
||||
$.support.transition = false
|
||||
stop()
|
||||
|
||||
var accordion = $('<div id="accordion"><div class="accordion-group"></div><div class="accordion-group"></div><div class="accordion-group"></div></div>')
|
||||
.appendTo($('#qunit-fixture'))
|
||||
|
||||
var target1 = $('<a data-toggle="collapse" href="#body1" data-parent="#accordion"></a>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(0))
|
||||
|
||||
var collapsible1 = $('<div id="body1" class="in"></div>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(0))
|
||||
|
||||
var target2 = $('<a class="collapsed" data-toggle="collapse" href="#body2" data-parent="#accordion"></a>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(1))
|
||||
|
||||
var collapsible2 = $('<div id="body2"></div>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(1))
|
||||
|
||||
var target3 = $('<a class="collapsed" data-toggle="collapse" href="#body3" data-parent="#accordion"></a>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(2))
|
||||
|
||||
var collapsible3 = $('<div id="body3"></div>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(2))
|
||||
.on('show.bs.collapse', function () {
|
||||
ok(target1.hasClass('collapsed'))
|
||||
ok(target2.hasClass('collapsed'))
|
||||
ok(!target3.hasClass('collapsed'))
|
||||
|
||||
start()
|
||||
})
|
||||
|
||||
target3.click()
|
||||
})
|
||||
|
||||
test("should allow dots in data-parent", function () {
|
||||
$.support.transition = false
|
||||
stop()
|
||||
|
||||
var accordion = $('<div class="accordion"><div class="accordion-group"></div><div class="accordion-group"></div><div class="accordion-group"></div></div>')
|
||||
.appendTo($('#qunit-fixture'))
|
||||
|
||||
var target1 = $('<a data-toggle="collapse" href="#body1" data-parent=".accordion"></a>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(0))
|
||||
|
||||
var collapsible1 = $('<div id="body1" class="in"></div>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(0))
|
||||
|
||||
var target2 = $('<a class="collapsed" data-toggle="collapse" href="#body2" data-parent=".accordion"></a>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(1))
|
||||
|
||||
var collapsible2 = $('<div id="body2"></div>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(1))
|
||||
|
||||
var target3 = $('<a class="collapsed" data-toggle="collapse" href="#body3" data-parent=".accordion"></a>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(2))
|
||||
|
||||
var collapsible3 = $('<div id="body3"></div>')
|
||||
.appendTo(accordion.find('.accordion-group').eq(2))
|
||||
.on('show.bs.collapse', function () {
|
||||
ok(target1.hasClass('collapsed'))
|
||||
ok(target2.hasClass('collapsed'))
|
||||
ok(!target3.hasClass('collapsed'))
|
||||
|
||||
start()
|
||||
})
|
||||
|
||||
target3.click()
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,219 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("dropdowns")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var dropdown = $.fn.dropdown.noConflict()
|
||||
ok(!$.fn.dropdown, 'dropdown was set back to undefined (org value)')
|
||||
$.fn.dropdown = dropdown
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
ok($(document.body).dropdown, 'dropdown method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
var el = $("<div />")
|
||||
ok(el.dropdown()[0] === el[0], 'same element returned')
|
||||
})
|
||||
|
||||
test("should not open dropdown if target is disabled", function () {
|
||||
var dropdownHTML = '<ul class="tabs">'
|
||||
+ '<li class="dropdown">'
|
||||
+ '<button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>'
|
||||
+ '<ul class="dropdown-menu">'
|
||||
+ '<li><a href="#">Secondary link</a></li>'
|
||||
+ '<li><a href="#">Something else here</a></li>'
|
||||
+ '<li class="divider"></li>'
|
||||
+ '<li><a href="#">Another link</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</li>'
|
||||
+ '</ul>'
|
||||
, dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').dropdown().click()
|
||||
|
||||
ok(!dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
|
||||
})
|
||||
|
||||
test("should not open dropdown if target is disabled", function () {
|
||||
var dropdownHTML = '<ul class="tabs">'
|
||||
+ '<li class="dropdown">'
|
||||
+ '<button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>'
|
||||
+ '<ul class="dropdown-menu">'
|
||||
+ '<li><a href="#">Secondary link</a></li>'
|
||||
+ '<li><a href="#">Something else here</a></li>'
|
||||
+ '<li class="divider"></li>'
|
||||
+ '<li><a href="#">Another link</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</li>'
|
||||
+ '</ul>'
|
||||
, dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').dropdown().click()
|
||||
|
||||
ok(!dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
|
||||
})
|
||||
|
||||
test("should add class open to menu if clicked", function () {
|
||||
var dropdownHTML = '<ul class="tabs">'
|
||||
+ '<li class="dropdown">'
|
||||
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
|
||||
+ '<ul class="dropdown-menu">'
|
||||
+ '<li><a href="#">Secondary link</a></li>'
|
||||
+ '<li><a href="#">Something else here</a></li>'
|
||||
+ '<li class="divider"></li>'
|
||||
+ '<li><a href="#">Another link</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</li>'
|
||||
+ '</ul>'
|
||||
, dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').dropdown().click()
|
||||
|
||||
ok(dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
|
||||
})
|
||||
|
||||
test("should test if element has a # before assuming it's a selector", function () {
|
||||
var dropdownHTML = '<ul class="tabs">'
|
||||
+ '<li class="dropdown">'
|
||||
+ '<a href="/foo/" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
|
||||
+ '<ul class="dropdown-menu">'
|
||||
+ '<li><a href="#">Secondary link</a></li>'
|
||||
+ '<li><a href="#">Something else here</a></li>'
|
||||
+ '<li class="divider"></li>'
|
||||
+ '<li><a href="#">Another link</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</li>'
|
||||
+ '</ul>'
|
||||
, dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').dropdown().click()
|
||||
|
||||
ok(dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
|
||||
})
|
||||
|
||||
|
||||
test("should remove open class if body clicked", function () {
|
||||
var dropdownHTML = '<ul class="tabs">'
|
||||
+ '<li class="dropdown">'
|
||||
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
|
||||
+ '<ul class="dropdown-menu">'
|
||||
+ '<li><a href="#">Secondary link</a></li>'
|
||||
+ '<li><a href="#">Something else here</a></li>'
|
||||
+ '<li class="divider"></li>'
|
||||
+ '<li><a href="#">Another link</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</li>'
|
||||
+ '</ul>'
|
||||
, dropdown = $(dropdownHTML)
|
||||
.appendTo('#qunit-fixture')
|
||||
.find('[data-toggle="dropdown"]')
|
||||
.dropdown()
|
||||
.click()
|
||||
|
||||
ok(dropdown.parent('.dropdown').hasClass('open'), 'open class added on click')
|
||||
$('body').click()
|
||||
ok(!dropdown.parent('.dropdown').hasClass('open'), 'open class removed')
|
||||
dropdown.remove()
|
||||
})
|
||||
|
||||
test("should remove open class if body clicked, with multiple drop downs", function () {
|
||||
var dropdownHTML =
|
||||
'<ul class="nav">'
|
||||
+ ' <li><a href="#menu1">Menu 1</a></li>'
|
||||
+ ' <li class="dropdown" id="testmenu">'
|
||||
+ ' <a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu <b class="caret"></b></a>'
|
||||
+ ' <ul class="dropdown-menu" role="menu">'
|
||||
+ ' <li><a href="#sub1">Submenu 1</a></li>'
|
||||
+ ' </ul>'
|
||||
+ ' </li>'
|
||||
+ '</ul>'
|
||||
+ '<div class="btn-group">'
|
||||
+ ' <button class="btn">Actions</button>'
|
||||
+ ' <button class="btn dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>'
|
||||
+ ' <ul class="dropdown-menu">'
|
||||
+ ' <li><a href="#">Action 1</a></li>'
|
||||
+ ' </ul>'
|
||||
+ '</div>'
|
||||
, dropdowns = $(dropdownHTML).appendTo('#qunit-fixture').find('[data-toggle="dropdown"]')
|
||||
, first = dropdowns.first()
|
||||
, last = dropdowns.last()
|
||||
|
||||
ok(dropdowns.length == 2, "Should be two dropdowns")
|
||||
|
||||
first.click()
|
||||
ok(first.parents('.open').length == 1, 'open class added on click')
|
||||
ok($('#qunit-fixture .open').length == 1, 'only one object is open')
|
||||
$('body').click()
|
||||
ok($("#qunit-fixture .open").length === 0, 'open class removed')
|
||||
|
||||
last.click()
|
||||
ok(last.parent('.open').length == 1, 'open class added on click')
|
||||
ok($('#qunit-fixture .open').length == 1, 'only one object is open')
|
||||
$('body').click()
|
||||
ok($("#qunit-fixture .open").length === 0, 'open class removed')
|
||||
|
||||
$("#qunit-fixture").html("")
|
||||
})
|
||||
|
||||
test("should fire show and hide event", function () {
|
||||
var dropdownHTML = '<ul class="tabs">'
|
||||
+ '<li class="dropdown">'
|
||||
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
|
||||
+ '<ul class="dropdown-menu">'
|
||||
+ '<li><a href="#">Secondary link</a></li>'
|
||||
+ '<li><a href="#">Something else here</a></li>'
|
||||
+ '<li class="divider"></li>'
|
||||
+ '<li><a href="#">Another link</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</li>'
|
||||
+ '</ul>'
|
||||
, dropdown = $(dropdownHTML)
|
||||
.appendTo('#qunit-fixture')
|
||||
.find('[data-toggle="dropdown"]')
|
||||
.dropdown()
|
||||
|
||||
stop()
|
||||
|
||||
dropdown
|
||||
.parent('.dropdown')
|
||||
.bind('show.bs.dropdown', function () {
|
||||
ok(true, 'show was called')
|
||||
})
|
||||
.bind('hide.bs.dropdown', function () {
|
||||
ok(true, 'hide was called')
|
||||
start()
|
||||
})
|
||||
|
||||
dropdown.click()
|
||||
$(document.body).click()
|
||||
})
|
||||
|
||||
|
||||
test("should fire shown and hiden event", function () {
|
||||
var dropdownHTML = '<ul class="tabs">'
|
||||
+ '<li class="dropdown">'
|
||||
+ '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>'
|
||||
+ '<ul class="dropdown-menu">'
|
||||
+ '<li><a href="#">Secondary link</a></li>'
|
||||
+ '<li><a href="#">Something else here</a></li>'
|
||||
+ '<li class="divider"></li>'
|
||||
+ '<li><a href="#">Another link</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</li>'
|
||||
+ '</ul>'
|
||||
, dropdown = $(dropdownHTML)
|
||||
.appendTo('#qunit-fixture')
|
||||
.find('[data-toggle="dropdown"]')
|
||||
.dropdown()
|
||||
|
||||
stop()
|
||||
|
||||
dropdown
|
||||
.parent('.dropdown')
|
||||
.bind('shown.bs.dropdown', function () {
|
||||
ok(true, 'show was called')
|
||||
})
|
||||
.bind('hidden.bs.dropdown', function () {
|
||||
ok(true, 'hide was called')
|
||||
start()
|
||||
})
|
||||
|
||||
dropdown.click()
|
||||
$(document.body).click()
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,196 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("modal")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var modal = $.fn.modal.noConflict()
|
||||
ok(!$.fn.modal, 'modal was set back to undefined (org value)')
|
||||
$.fn.modal = modal
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
var div = $("<div id='modal-test'></div>")
|
||||
ok(div.modal, 'modal method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
var div = $("<div id='modal-test'></div>")
|
||||
ok(div.modal() == div, 'document.body returned')
|
||||
$('#modal-test').remove()
|
||||
})
|
||||
|
||||
test("should expose defaults var for settings", function () {
|
||||
ok($.fn.modal.Constructor.DEFAULTS, 'default object exposed')
|
||||
})
|
||||
|
||||
test("should insert into dom when show method is called", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
$("<div id='modal-test'></div>")
|
||||
.on("shown.bs.modal", function () {
|
||||
ok($('#modal-test').length, 'modal inserted into dom')
|
||||
$(this).remove()
|
||||
start()
|
||||
})
|
||||
.modal("show")
|
||||
})
|
||||
|
||||
test("should fire show event", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
$("<div id='modal-test'></div>")
|
||||
.on("show.bs.modal", function () {
|
||||
ok(true, "show was called")
|
||||
})
|
||||
.on("shown.bs.modal", function () {
|
||||
$(this).remove()
|
||||
start()
|
||||
})
|
||||
.modal("show")
|
||||
})
|
||||
|
||||
test("should not fire shown when default prevented", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
$("<div id='modal-test'></div>")
|
||||
.on("show.bs.modal", function (e) {
|
||||
e.preventDefault()
|
||||
ok(true, "show was called")
|
||||
start()
|
||||
})
|
||||
.on("shown.bs.modal", function () {
|
||||
ok(false, "shown was called")
|
||||
})
|
||||
.modal("show")
|
||||
})
|
||||
|
||||
test("should hide modal when hide is called", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
|
||||
$("<div id='modal-test'></div>")
|
||||
.on("shown.bs.modal", function () {
|
||||
ok($('#modal-test').is(":visible"), 'modal visible')
|
||||
ok($('#modal-test').length, 'modal inserted into dom')
|
||||
$(this).modal("hide")
|
||||
})
|
||||
.on("hidden.bs.modal", function() {
|
||||
ok(!$('#modal-test').is(":visible"), 'modal hidden')
|
||||
$('#modal-test').remove()
|
||||
start()
|
||||
})
|
||||
.modal("show")
|
||||
})
|
||||
|
||||
test("should toggle when toggle is called", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
var div = $("<div id='modal-test'></div>")
|
||||
div
|
||||
.on("shown.bs.modal", function () {
|
||||
ok($('#modal-test').is(":visible"), 'modal visible')
|
||||
ok($('#modal-test').length, 'modal inserted into dom')
|
||||
div.modal("toggle")
|
||||
})
|
||||
.on("hidden.bs.modal", function() {
|
||||
ok(!$('#modal-test').is(":visible"), 'modal hidden')
|
||||
div.remove()
|
||||
start()
|
||||
})
|
||||
.modal("toggle")
|
||||
})
|
||||
|
||||
test("should remove from dom when click [data-dismiss=modal]", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
var div = $("<div id='modal-test'><span class='close' data-dismiss='modal'></span></div>")
|
||||
div
|
||||
.on("shown.bs.modal", function () {
|
||||
ok($('#modal-test').is(":visible"), 'modal visible')
|
||||
ok($('#modal-test').length, 'modal inserted into dom')
|
||||
div.find('.close').click()
|
||||
})
|
||||
.on("hidden.bs.modal", function() {
|
||||
ok(!$('#modal-test').is(":visible"), 'modal hidden')
|
||||
div.remove()
|
||||
start()
|
||||
})
|
||||
.modal("toggle")
|
||||
})
|
||||
|
||||
test("should allow modal close with 'backdrop:false'", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
var div = $("<div>", { id: 'modal-test', "data-backdrop": false })
|
||||
div
|
||||
.on("shown.bs.modal", function () {
|
||||
ok($('#modal-test').is(":visible"), 'modal visible')
|
||||
div.modal("hide")
|
||||
})
|
||||
.on("hidden.bs.modal", function() {
|
||||
ok(!$('#modal-test').is(":visible"), 'modal hidden')
|
||||
div.remove()
|
||||
start()
|
||||
})
|
||||
.modal("show")
|
||||
})
|
||||
|
||||
test("should close modal when clicking outside of modal-content", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
var div = $("<div id='modal-test'><div class='contents'></div></div>")
|
||||
div
|
||||
.bind("shown.bs.modal", function () {
|
||||
ok($('#modal-test').length, 'modal insterted into dom')
|
||||
$('.contents').click()
|
||||
ok($('#modal-test').is(":visible"), 'modal visible')
|
||||
$('#modal-test').click()
|
||||
})
|
||||
.bind("hidden.bs.modal", function() {
|
||||
ok(!$('#modal-test').is(":visible"), 'modal hidden')
|
||||
div.remove()
|
||||
start()
|
||||
})
|
||||
.modal("show")
|
||||
})
|
||||
|
||||
test("should trigger hide event once when clicking outside of modal-content", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
var div = $("<div id='modal-test'><div class='contents'></div></div>")
|
||||
var triggered
|
||||
div
|
||||
.bind("shown.bs.modal", function () {
|
||||
triggered = 0
|
||||
$('#modal-test').click()
|
||||
})
|
||||
.one("hidden.bs.modal", function() {
|
||||
div.modal("show")
|
||||
})
|
||||
.bind("hide.bs.modal", function () {
|
||||
triggered += 1
|
||||
ok(triggered === 1, 'modal hide triggered once')
|
||||
start()
|
||||
})
|
||||
.modal("show")
|
||||
})
|
||||
|
||||
test("should close reopened modal with [data-dismiss=modal] click", function () {
|
||||
stop()
|
||||
$.support.transition = false
|
||||
var div = $("<div id='modal-test'><div class='contents'><div id='close' data-dismiss='modal'></div></div></div>")
|
||||
div
|
||||
.bind("shown.bs.modal", function () {
|
||||
$('#close').click()
|
||||
ok(!$('#modal-test').is(":visible"), 'modal hidden')
|
||||
})
|
||||
.one("hidden.bs.modal", function() {
|
||||
div.one('hidden.bs.modal', function () {
|
||||
start()
|
||||
}).modal("show")
|
||||
})
|
||||
.modal("show")
|
||||
|
||||
div.remove()
|
||||
})
|
||||
})
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* grunt-contrib-qunit
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2013 "Cowboy" Ben Alman, contributors
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
/*global QUnit:true, alert:true*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Don't re-order tests.
|
||||
QUnit.config.reorder = false
|
||||
// Run tests serially, not in parallel.
|
||||
QUnit.config.autorun = false
|
||||
|
||||
// Send messages to the parent PhantomJS process via alert! Good times!!
|
||||
function sendMessage() {
|
||||
var args = [].slice.call(arguments)
|
||||
alert(JSON.stringify(args))
|
||||
}
|
||||
|
||||
// These methods connect QUnit to PhantomJS.
|
||||
QUnit.log = function(obj) {
|
||||
// What is this I don’t even
|
||||
if (obj.message === '[object Object], undefined:undefined') { return }
|
||||
// Parse some stuff before sending it.
|
||||
var actual = QUnit.jsDump.parse(obj.actual)
|
||||
var expected = QUnit.jsDump.parse(obj.expected)
|
||||
// Send it.
|
||||
sendMessage('qunit.log', obj.result, actual, expected, obj.message, obj.source)
|
||||
}
|
||||
|
||||
QUnit.testStart = function(obj) {
|
||||
sendMessage('qunit.testStart', obj.name)
|
||||
}
|
||||
|
||||
QUnit.testDone = function(obj) {
|
||||
sendMessage('qunit.testDone', obj.name, obj.failed, obj.passed, obj.total)
|
||||
}
|
||||
|
||||
QUnit.moduleStart = function(obj) {
|
||||
sendMessage('qunit.moduleStart', obj.name)
|
||||
}
|
||||
|
||||
QUnit.begin = function () {
|
||||
sendMessage('qunit.begin')
|
||||
console.log("Starting test suite")
|
||||
console.log("================================================\n")
|
||||
}
|
||||
|
||||
QUnit.moduleDone = function (opts) {
|
||||
if (opts.failed === 0) {
|
||||
console.log("\r\u2714 All tests passed in '" + opts.name + "' module")
|
||||
} else {
|
||||
console.log("\u2716 " + opts.failed + " tests failed in '" + opts.name + "' module")
|
||||
}
|
||||
sendMessage('qunit.moduleDone', opts.name, opts.failed, opts.passed, opts.total)
|
||||
}
|
||||
|
||||
QUnit.done = function (opts) {
|
||||
console.log("\n================================================")
|
||||
console.log("Tests completed in " + opts.runtime + " milliseconds")
|
||||
console.log(opts.passed + " tests of " + opts.total + " passed, " + opts.failed + " failed.")
|
||||
sendMessage('qunit.done', opts.failed, opts.passed, opts.total, opts.runtime)
|
||||
}
|
||||
|
||||
}())
|
||||
@@ -1,133 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("popover")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var popover = $.fn.popover.noConflict()
|
||||
ok(!$.fn.popover, 'popover was set back to undefined (org value)')
|
||||
$.fn.popover = popover
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
var div = $('<div></div>')
|
||||
ok(div.popover, 'popover method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
var div = $('<div></div>')
|
||||
ok(div.popover() == div, 'document.body returned')
|
||||
})
|
||||
|
||||
test("should render popover element", function () {
|
||||
$.support.transition = false
|
||||
var popover = $('<a href="#" title="mdo" data-content="http://twitter.com/mdo">@mdo</a>')
|
||||
.appendTo('#qunit-fixture')
|
||||
.popover('show')
|
||||
|
||||
ok($('.popover').length, 'popover was inserted')
|
||||
popover.popover('hide')
|
||||
ok(!$(".popover").length, 'popover removed')
|
||||
})
|
||||
|
||||
test("should store popover instance in popover data object", function () {
|
||||
$.support.transition = false
|
||||
var popover = $('<a href="#" title="mdo" data-content="http://twitter.com/mdo">@mdo</a>')
|
||||
.popover()
|
||||
|
||||
ok(!!popover.data('bs.popover'), 'popover instance exists')
|
||||
})
|
||||
|
||||
test("should get title and content from options", function () {
|
||||
$.support.transition = false
|
||||
var popover = $('<a href="#">@fat</a>')
|
||||
.appendTo('#qunit-fixture')
|
||||
.popover({
|
||||
title: function () {
|
||||
return '@fat'
|
||||
}
|
||||
, content: function () {
|
||||
return 'loves writing tests (╯°□°)╯︵ ┻━┻'
|
||||
}
|
||||
})
|
||||
|
||||
popover.popover('show')
|
||||
|
||||
ok($('.popover').length, 'popover was inserted')
|
||||
equal($('.popover .popover-title').text(), '@fat', 'title correctly inserted')
|
||||
equal($('.popover .popover-content').text(), 'loves writing tests (╯°□°)╯︵ ┻━┻', 'content correctly inserted')
|
||||
|
||||
popover.popover('hide')
|
||||
ok(!$('.popover').length, 'popover was removed')
|
||||
$('#qunit-fixture').empty()
|
||||
})
|
||||
|
||||
test("should get title and content from attributes", function () {
|
||||
$.support.transition = false
|
||||
var popover = $('<a href="#" title="@mdo" data-content="loves data attributes (づ。◕‿‿◕。)づ ︵ ┻━┻" >@mdo</a>')
|
||||
.appendTo('#qunit-fixture')
|
||||
.popover()
|
||||
.popover('show')
|
||||
|
||||
ok($('.popover').length, 'popover was inserted')
|
||||
equal($('.popover .popover-title').text(), '@mdo', 'title correctly inserted')
|
||||
equal($('.popover .popover-content').text(), "loves data attributes (づ。◕‿‿◕。)づ ︵ ┻━┻", 'content correctly inserted')
|
||||
|
||||
popover.popover('hide')
|
||||
ok(!$('.popover').length, 'popover was removed')
|
||||
$('#qunit-fixture').empty()
|
||||
})
|
||||
|
||||
|
||||
test("should get title and content from attributes #2", function () {
|
||||
$.support.transition = false
|
||||
var popover = $('<a href="#" title="@mdo" data-content="loves data attributes (づ。◕‿‿◕。)づ ︵ ┻━┻" >@mdo</a>')
|
||||
.appendTo('#qunit-fixture')
|
||||
.popover({
|
||||
title: 'ignored title option',
|
||||
content: 'ignored content option'
|
||||
})
|
||||
.popover('show')
|
||||
|
||||
ok($('.popover').length, 'popover was inserted')
|
||||
equal($('.popover .popover-title').text(), '@mdo', 'title correctly inserted')
|
||||
equal($('.popover .popover-content').text(), "loves data attributes (づ。◕‿‿◕。)づ ︵ ┻━┻", 'content correctly inserted')
|
||||
|
||||
popover.popover('hide')
|
||||
ok(!$('.popover').length, 'popover was removed')
|
||||
$('#qunit-fixture').empty()
|
||||
})
|
||||
|
||||
test("should respect custom classes", function() {
|
||||
$.support.transition = false
|
||||
var popover = $('<a href="#">@fat</a>')
|
||||
.appendTo('#qunit-fixture')
|
||||
.popover({
|
||||
title: 'Test'
|
||||
, content: 'Test'
|
||||
, template: '<div class="popover foobar"><div class="arrow"></div><div class="inner"><h3 class="title"></h3><div class="content"><p></p></div></div></div>'
|
||||
})
|
||||
|
||||
popover.popover('show')
|
||||
|
||||
ok($('.popover').length, 'popover was inserted')
|
||||
ok($('.popover').hasClass('foobar'), 'custom class is present')
|
||||
|
||||
popover.popover('hide')
|
||||
ok(!$('.popover').length, 'popover was removed')
|
||||
$('#qunit-fixture').empty()
|
||||
})
|
||||
|
||||
test("should destroy popover", function () {
|
||||
var popover = $('<div/>').popover({trigger: 'hover'}).on('click.foo', function(){})
|
||||
ok(popover.data('bs.popover'), 'popover has data')
|
||||
ok($._data(popover[0], 'events').mouseover && $._data(popover[0], 'events').mouseout, 'popover has hover event')
|
||||
ok($._data(popover[0], 'events').click[0].namespace == 'foo', 'popover has extra click.foo event')
|
||||
popover.popover('show')
|
||||
popover.popover('destroy')
|
||||
ok(!popover.hasClass('in'), 'popover is hidden')
|
||||
ok(!popover.data('popover'), 'popover does not have data')
|
||||
ok($._data(popover[0],'events').click[0].namespace == 'foo', 'popover still has click.foo')
|
||||
ok(!$._data(popover[0], 'events').mouseover && !$._data(popover[0], 'events').mouseout, 'popover does not have any events')
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("scrollspy")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var scrollspy = $.fn.scrollspy.noConflict()
|
||||
ok(!$.fn.scrollspy, 'scrollspy was set back to undefined (org value)')
|
||||
$.fn.scrollspy = scrollspy
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
ok($(document.body).scrollspy, 'scrollspy method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
ok($(document.body).scrollspy()[0] == document.body, 'document.body returned')
|
||||
})
|
||||
|
||||
test("should switch active class on scroll", function () {
|
||||
var sectionHTML = '<div id="masthead"></div>'
|
||||
, $section = $(sectionHTML).append('#qunit-fixture')
|
||||
, topbarHTML ='<div class="topbar">'
|
||||
+ '<div class="topbar-inner">'
|
||||
+ '<div class="container">'
|
||||
+ '<h3><a href="#">Bootstrap</a></h3>'
|
||||
+ '<ul class="nav">'
|
||||
+ '<li><a href="#masthead">Overview</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
, $topbar = $(topbarHTML).scrollspy()
|
||||
|
||||
ok($topbar.find('.active', true))
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,86 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
module("tabs")
|
||||
|
||||
test("should provide no conflict", function () {
|
||||
var tab = $.fn.tab.noConflict()
|
||||
ok(!$.fn.tab, 'tab was set back to undefined (org value)')
|
||||
$.fn.tab = tab
|
||||
})
|
||||
|
||||
test("should be defined on jquery object", function () {
|
||||
ok($(document.body).tab, 'tabs method is defined')
|
||||
})
|
||||
|
||||
test("should return element", function () {
|
||||
ok($(document.body).tab()[0] == document.body, 'document.body returned')
|
||||
})
|
||||
|
||||
test("should activate element by tab id", function () {
|
||||
var tabsHTML =
|
||||
'<ul class="tabs">'
|
||||
+ '<li><a href="#home">Home</a></li>'
|
||||
+ '<li><a href="#profile">Profile</a></li>'
|
||||
+ '</ul>'
|
||||
|
||||
$('<ul><li id="home"></li><li id="profile"></li></ul>').appendTo("#qunit-fixture")
|
||||
|
||||
$(tabsHTML).find('li:last a').tab('show')
|
||||
equal($("#qunit-fixture").find('.active').attr('id'), "profile")
|
||||
|
||||
$(tabsHTML).find('li:first a').tab('show')
|
||||
equal($("#qunit-fixture").find('.active').attr('id'), "home")
|
||||
})
|
||||
|
||||
test("should activate element by tab id", function () {
|
||||
var pillsHTML =
|
||||
'<ul class="pills">'
|
||||
+ '<li><a href="#home">Home</a></li>'
|
||||
+ '<li><a href="#profile">Profile</a></li>'
|
||||
+ '</ul>'
|
||||
|
||||
$('<ul><li id="home"></li><li id="profile"></li></ul>').appendTo("#qunit-fixture")
|
||||
|
||||
$(pillsHTML).find('li:last a').tab('show')
|
||||
equal($("#qunit-fixture").find('.active').attr('id'), "profile")
|
||||
|
||||
$(pillsHTML).find('li:first a').tab('show')
|
||||
equal($("#qunit-fixture").find('.active').attr('id'), "home")
|
||||
})
|
||||
|
||||
|
||||
test("should not fire closed when close is prevented", function () {
|
||||
$.support.transition = false
|
||||
stop();
|
||||
$('<div class="tab"/>')
|
||||
.on('show.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
ok(true);
|
||||
start();
|
||||
})
|
||||
.on('shown.bs.tab', function () {
|
||||
ok(false);
|
||||
})
|
||||
.tab('show')
|
||||
})
|
||||
|
||||
test("show and shown events should reference correct relatedTarget", function () {
|
||||
var dropHTML =
|
||||
'<ul class="drop">'
|
||||
+ '<li class="dropdown"><a data-toggle="dropdown" href="#">1</a>'
|
||||
+ '<ul class="dropdown-menu">'
|
||||
+ '<li><a href="#1-1" data-toggle="tab">1-1</a></li>'
|
||||
+ '<li><a href="#1-2" data-toggle="tab">1-2</a></li>'
|
||||
+ '</ul>'
|
||||
+ '</li>'
|
||||
+ '</ul>'
|
||||
|
||||
$(dropHTML).find('ul>li:first a').tab('show').end()
|
||||
.find('ul>li:last a').on('show', function(event){
|
||||
equal(event.relatedTarget.hash, "#1-1")
|
||||
}).on('shown', function(event){
|
||||
equal(event.relatedTarget.hash, "#1-1")
|
||||
}).tab('show')
|
||||
})
|
||||
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user