Compare commits
252 Commits
feature/bu
...
requires-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9deb8ebb14 | ||
| 4a4d4a5cf3 | |||
| ae151ed45e | |||
|
|
116c497590 | ||
| f6f3149036 | |||
|
|
81e7bf6d46 | ||
| 79f97bb05f | |||
|
6ba87b0a5a
|
|||
|
1e03b5107e
|
|||
|
39dbdd7ce4
|
|||
| 1a953073be | |||
| 630011aff7 | |||
|
|
e0c6a56263 | ||
| 87d460c799 | |||
| 295397b32d | |||
| 10add5ab33 | |||
| 7e3e8f37e2 | |||
| 3a25b85e95 | |||
| 16b950c3b2 | |||
| f616017423 | |||
| 1480ae17fa | |||
| 4ad12ab40a | |||
| 13205770f1 | |||
| 6bb0c88c72 | |||
| 82a30ca77d | |||
|
|
97c0dffbd3 | ||
|
|
3b28eafc82 | ||
| ca8253894a | |||
| 01a87e0e0b | |||
|
|
7c876348d7 | ||
|
ddc23ce4e5
|
|||
|
602ccc15ea
|
|||
|
|
b77615b9b9 | ||
|
|
228d72b7b2 | ||
|
62541194ee
|
|||
|
0d8fd99d92
|
|||
|
9d51a82f31
|
|||
|
c059227d5d
|
|||
|
2c334196d5
|
|||
| 4f036af85a | |||
| 5210afc772 | |||
|
|
4da8040351 | ||
|
|
1a49bb50e5 | ||
|
|
86b349f60e | ||
|
|
35997aa882 | ||
|
|
faa4573f6d | ||
|
|
7babaee44c | ||
|
|
c0fe176495 | ||
|
|
b269069b6a | ||
|
|
7e06b5a162 | ||
|
|
a18bb07d78 | ||
|
|
bd28d2054e | ||
|
|
14836f135c | ||
|
|
5f8a77586a | ||
|
|
e5b7fdbae1 | ||
|
|
f1c8dca8c4 | ||
|
|
843b76d8ea | ||
|
|
e81af9e479 | ||
|
|
efab8c8cef | ||
|
|
3f7531e157 | ||
|
|
988f3dced4 | ||
|
|
bd3240c2bc | ||
|
|
7fbbe2871f | ||
|
|
109ece508b | ||
|
|
971144c2e6 | ||
|
|
a3c6edda0b | ||
|
|
71bb4696b8 | ||
|
|
2a3ed0b763 | ||
|
|
632fa56353 | ||
|
|
f3020fc783 | ||
|
|
9fdf5e674e | ||
|
|
cfe03a8628 | ||
|
|
eccf224b63 | ||
|
|
0b2c86ebb5 | ||
|
|
f616857131 | ||
|
|
60fb90a50c | ||
|
|
66f024e961 | ||
|
|
06daacf611 | ||
|
|
c74bc945b6 | ||
|
|
3c605d2976 | ||
|
|
9720066fd7 | ||
|
|
b157e3b187 | ||
|
|
19030fdf2f | ||
|
|
42450b5a22 | ||
|
|
ce11df9bbc | ||
|
|
82e664c5e0 | ||
|
|
8098b33698 | ||
|
|
9c2603557c | ||
|
|
f4209f21dc | ||
|
|
1e3c021a76 | ||
|
|
d8f9256252 | ||
|
|
014bab6c1f | ||
|
|
8872084cab | ||
|
|
76ceb15000 | ||
|
|
7dff951f28 | ||
|
|
05feb7df1c | ||
|
|
ad3b38d222 | ||
|
|
a26e65073c | ||
|
|
7f3d628d01 | ||
|
|
abdf785723 | ||
|
|
85edba03a2 | ||
|
|
d61b21df0a | ||
|
|
7e68dfb851 | ||
|
|
69cbac31e7 | ||
|
|
ce4e0f5630 | ||
|
|
c99c5d573f | ||
|
|
221ef739b9 | ||
|
|
aa98039c35 | ||
|
|
7ef923b89f | ||
|
|
9ac86a6ad0 | ||
|
|
c0c143a166 | ||
|
|
0abfa2fd0c | ||
|
|
3333b29f24 | ||
|
|
880509d611 | ||
|
|
387f5b0d8e | ||
|
|
27b12d6bf4 | ||
|
|
c511f2f528 | ||
|
|
a61165f301 | ||
|
|
98245939fe | ||
|
|
b203832f79 | ||
|
|
c6846b85c7 | ||
|
|
0c15718d31 | ||
|
|
0f1db22452 | ||
|
|
e3970929e4 | ||
|
|
a64bf8e16f | ||
|
|
cf8edc8a1c | ||
|
|
ca9e309b1b | ||
|
|
0ccb669019 | ||
|
|
92acd17e64 | ||
|
|
ff83bcc14a | ||
|
|
f17e0e199c | ||
|
|
7b3b05677c | ||
|
|
403549f449 | ||
|
|
aee4ccb5a2 | ||
|
|
f8c2551eeb | ||
|
|
1bab3464b7 | ||
|
|
2b863b91b3 | ||
|
|
5c4181a5cc | ||
|
|
f9389e3996 | ||
|
|
131ff3e612 | ||
|
|
7dd7378e6c | ||
|
|
4d0da2fdc1 | ||
|
|
ddbc0699ee | ||
|
|
5f14001d01 | ||
|
|
91f1e6d290 | ||
|
|
00d46274f1 | ||
|
|
0aec836b1a | ||
|
|
ee930aa86a | ||
|
|
a88f4d0cb2 | ||
|
|
50c997b568 | ||
|
|
3035320e82 | ||
|
|
98182143ec | ||
|
|
bffbaeb4c6 | ||
|
|
8f1c640bea | ||
|
|
827a2cdd25 | ||
|
|
3202981fbe | ||
|
|
f0edd5020e | ||
|
|
cf1c7ae725 | ||
|
|
1ac637bfd7 | ||
|
|
37933650d3 | ||
|
|
1266dd419c | ||
|
|
74747e8700 | ||
|
|
34ffd62436 | ||
|
|
22c520e841 | ||
|
|
3fa9191150 | ||
|
|
ae4f05a661 | ||
|
|
c178a293a1 | ||
|
|
aebaa16311 | ||
|
|
175807f664 | ||
|
|
abb56af222 | ||
|
|
48139b29cf | ||
|
|
7ec09fb774 | ||
|
|
e37d1c663b | ||
|
|
bdd7f02fe2 | ||
|
|
b4ea818992 | ||
|
|
7cb9e97ecb | ||
|
|
7fdafd854e | ||
|
|
d85ebb63a1 | ||
|
|
c6b7bbc219 | ||
|
|
4d316c7a4a | ||
|
|
75a3059c88 | ||
|
|
b6e4c0ed14 | ||
|
|
0a45b047a2 | ||
|
|
4e79f00551 | ||
|
|
b4ab29393e | ||
|
|
703fb8561a | ||
|
|
f3c020b613 | ||
|
|
4b87b0a196 | ||
|
|
7cc715cedc | ||
|
|
4b032944ac | ||
|
|
cb23fd183e | ||
|
|
fbc039c274 | ||
|
|
b3156dbb0d | ||
|
|
fdce2fa53d | ||
|
|
55d24e96cb | ||
|
|
36d258253f | ||
|
|
865bb131a5 | ||
|
|
eb1e8935f4 | ||
|
|
f8aaf9f36e | ||
|
|
7a4f5ba8bf | ||
|
|
16a993123b | ||
|
|
e547e3e858 | ||
|
|
ea26823fec | ||
|
|
1f4e53ad27 | ||
|
|
374c31e8b4 | ||
|
|
0d726b2b60 | ||
|
|
38a8ac1eb4 | ||
|
|
872e5e72f3 | ||
|
|
d916c1ca19 | ||
|
|
9b1cc965c7 | ||
|
|
83028418fe | ||
|
|
7f680dcffb | ||
|
|
e573088c5e | ||
|
|
7ac9eef7a2 | ||
|
|
286e4314f5 | ||
|
|
6b05938953 | ||
|
|
602ba1d051 | ||
|
|
1710c3f01f | ||
|
|
f57ac3acb1 | ||
|
|
331dab20f7 | ||
|
|
d9076a4f5f | ||
|
|
56d4e438b6 | ||
|
|
430862b24d | ||
|
|
e12367bde7 | ||
|
|
c0f4884242 | ||
|
|
36638e4df6 | ||
|
|
a0440e1587 | ||
|
|
6e78f16c33 | ||
|
|
82b6f1cbf8 | ||
|
|
5be3842aea | ||
|
|
067e03b757 | ||
|
|
391d9ef28f | ||
|
|
5d17d642ec | ||
|
|
22119a3d08 | ||
|
|
306c11bb2f | ||
|
|
3fa9795cde | ||
|
|
7fd0c50146 | ||
|
|
97b11eabbd | ||
|
|
3b2aa02ae5 | ||
|
|
cf11e8235f | ||
|
|
1670b190c2 | ||
|
|
e65e97b1a3 | ||
|
|
c2787d54b0 | ||
|
|
8cfda69717 | ||
|
|
463c4d147c | ||
|
|
6da688cc9e | ||
|
|
c1d164bd73 | ||
|
|
d43e4b2465 | ||
|
|
98ee9bb0db | ||
|
|
cd2aed00d7 | ||
|
|
0ee37b1cd3 | ||
|
|
486c66b198 |
4
.gitignore
vendored
@@ -25,7 +25,6 @@ var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
node_modules/
|
||||
|
||||
# Continer extras
|
||||
.vagrant
|
||||
@@ -45,6 +44,7 @@ htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
.pytest_cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
@@ -54,6 +54,7 @@ coverage.xml
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
db.sqlite3
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
@@ -107,3 +108,4 @@ atlassian-ide-plugin.xml
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
.vscode/
|
||||
31
.travis.yml
@@ -1,25 +1,32 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
"6"
|
||||
language: python
|
||||
python:
|
||||
"2.7"
|
||||
"3.6"
|
||||
cache: pip
|
||||
|
||||
before_install:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
|
||||
addons:
|
||||
chrome: stable
|
||||
|
||||
install:
|
||||
- pip install --user -r requirements.txt
|
||||
- pip install --user coveralls codeclimate-test-reporter
|
||||
- npm install
|
||||
- npm run build
|
||||
- wget https://chromedriver.storage.googleapis.com/2.36/chromedriver_linux64.zip
|
||||
- unzip chromedriver_linux64.zip
|
||||
- export PATH=$PATH:$(pwd)
|
||||
- chmod +x chromedriver
|
||||
- pip install -r requirements.txt
|
||||
- pip install coveralls codeclimate-test-reporter pep8
|
||||
|
||||
before_script:
|
||||
- export PATH=$PATH:/usr/lib/chromium-browser/
|
||||
- python manage.py collectstatic --noinput
|
||||
|
||||
script:
|
||||
- coverage run manage.py test RIGS
|
||||
- pep8 . --exclude=migrations,importer*
|
||||
- python manage.py check
|
||||
- python manage.py makemigrations --check --dry-run
|
||||
- coverage run manage.py test --verbosity=2
|
||||
|
||||
after_success:
|
||||
- coveralls
|
||||
- codeclimate-test-reporter
|
||||
|
||||
notifications:
|
||||
webhooks: https://fathomless-fjord-24024.herokuapp.com/notify
|
||||
|
||||
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM python:3.6
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ADD . /app
|
||||
|
||||
RUN pip install -r requirements.txt && \
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||
1
Procfile
@@ -1 +1,2 @@
|
||||
release: python manage.py migrate
|
||||
web: gunicorn PyRIGS.wsgi --log-file -
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
|
||||
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['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
|
||||
|
||||
|
||||
def has_oembed(oembed_view, login_url=None):
|
||||
if not login_url:
|
||||
from django.conf import settings
|
||||
login_url = settings.LOGIN_URL
|
||||
|
||||
def _dec(view_func):
|
||||
def _checklogin(request, *args, **kwargs):
|
||||
if request.user.is_authenticated:
|
||||
return view_func(request, *args, **kwargs)
|
||||
else:
|
||||
if oembed_view is not 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
|
||||
|
||||
|
||||
def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
|
||||
"""
|
||||
Decorator for views that checks that the user passes the given test.
|
||||
@@ -24,17 +51,13 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
|
||||
def _checklogin(request, *args, **kwargs):
|
||||
if test_func(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
elif not request.user.is_authenticated():
|
||||
elif not request.user.is_authenticated:
|
||||
if oembed_view is not None:
|
||||
extra_context = {}
|
||||
extra_context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs))
|
||||
extra_context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path())
|
||||
resp = render_to_response('login_redirect.html', extra_context, context_instance=RequestContext(request))
|
||||
return resp
|
||||
return get_oembed(login_url, request, oembed_view, kwargs)
|
||||
else:
|
||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
||||
else:
|
||||
resp = render_to_response('403.html', context_instance=RequestContext(request))
|
||||
resp = render(request, '403.html')
|
||||
resp.status_code = 403
|
||||
return resp
|
||||
_checklogin.__doc__ = view_func.__doc__
|
||||
@@ -62,7 +85,7 @@ def api_key_required(function):
|
||||
userid = kwargs.get('api_pk')
|
||||
key = kwargs.get('api_key')
|
||||
|
||||
error_resp = render_to_response('403.html', context_instance=RequestContext(request))
|
||||
error_resp = render(request, '403.html')
|
||||
error_resp.status_code = 403
|
||||
|
||||
if key is None:
|
||||
@@ -79,3 +102,17 @@ def api_key_required(function):
|
||||
return error_resp
|
||||
return function(request, *args, **kwargs)
|
||||
return wrap
|
||||
|
||||
|
||||
def nottinghamtec_address_required(function):
|
||||
"""
|
||||
Checks that the current user has an email address ending @nottinghamtec.co.uk
|
||||
"""
|
||||
def wrap(request, *args, **kwargs):
|
||||
# Fail if current user's email address isn't @nottinghamtec.co.uk
|
||||
if not request.user.email.endswith('@nottinghamtec.co.uk'):
|
||||
error_resp = render(request, 'RIGS/eventauthorisation_request_error.html')
|
||||
return error_resp
|
||||
|
||||
return function(request, *args, **kwargs)
|
||||
return wrap
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
DATETIME_FORMAT = ('d/m/Y H:i')
|
||||
DATE_FORMAT = ('d/m/Y')
|
||||
|
||||
@@ -10,36 +10,44 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
import raven
|
||||
import secrets
|
||||
import datetime
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get('SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e'
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get(
|
||||
'SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = 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
|
||||
|
||||
TEMPLATE_DEBUG = True
|
||||
STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False
|
||||
|
||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
||||
|
||||
if STAGING:
|
||||
ALLOWED_HOSTS.append('.herokuapp.com')
|
||||
|
||||
if DEBUG:
|
||||
ALLOWED_HOSTS.append('localhost')
|
||||
ALLOWED_HOSTS.append('example.com')
|
||||
ALLOWED_HOSTS.append('127.0.0.1')
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
if not DEBUG:
|
||||
SECURE_SSL_REDIRECT = True # Redirect all http requests to https
|
||||
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
|
||||
ADMINS = (
|
||||
('Tom Price', 'tomtom5152@gmail.com')
|
||||
)
|
||||
|
||||
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'))
|
||||
|
||||
# Application definition
|
||||
|
||||
@@ -51,6 +59,7 @@ INSTALLED_APPS = (
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'RIGS',
|
||||
'assets',
|
||||
|
||||
'debug_toolbar',
|
||||
'registration',
|
||||
@@ -60,15 +69,15 @@ INSTALLED_APPS = (
|
||||
'raven.contrib.django.raven_compat',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
MIDDLEWARE = (
|
||||
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
'reversion.middleware.RevisionMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
@@ -77,7 +86,6 @@ ROOT_URLCONF = 'PyRIGS.urls'
|
||||
|
||||
WSGI_APPLICATION = 'PyRIGS.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
DATABASES = {
|
||||
@@ -89,6 +97,7 @@ DATABASES = {
|
||||
|
||||
if not DEBUG:
|
||||
import dj_database_url
|
||||
|
||||
DATABASES['default'] = dj_database_url.config()
|
||||
|
||||
# Logging
|
||||
@@ -139,8 +148,6 @@ LOGGING = {
|
||||
}
|
||||
}
|
||||
|
||||
import raven
|
||||
|
||||
RAVEN_CONFIG = {
|
||||
'dsn': os.environ.get('RAVEN_DSN'),
|
||||
# If you are using git, you can also automatically configure the
|
||||
@@ -152,14 +159,14 @@ RAVEN_CONFIG = {
|
||||
AUTH_USER_MODEL = 'RIGS.Profile'
|
||||
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
LOGIN_URL = '/user/login'
|
||||
LOGOUT_URL = '/user/logout'
|
||||
LOGIN_URL = '/user/login/'
|
||||
LOGOUT_URL = '/user/logout/'
|
||||
|
||||
ACCOUNT_ACTIVATION_DAYS = 7
|
||||
|
||||
# reCAPTCHA settings
|
||||
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY', None)
|
||||
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY', None)
|
||||
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
|
||||
|
||||
# Email
|
||||
@@ -167,7 +174,7 @@ EMAILER_TEST = False
|
||||
if not DEBUG or EMAILER_TEST:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = os.environ.get('EMAIL_HOST')
|
||||
EMAIL_PORT = int(os.environ.get('EMAIL_PORT'))
|
||||
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25))
|
||||
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
|
||||
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
|
||||
EMAIL_USE_TLS = bool(int(os.environ.get('EMAIL_USE_TLS', 0)))
|
||||
@@ -176,6 +183,8 @@ if not DEBUG or EMAILER_TEST:
|
||||
else:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
EMAIL_COOLDOWN = datetime.timedelta(minutes=15)
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.7/topics/i18n/
|
||||
|
||||
@@ -193,31 +202,43 @@ USE_TZ = True
|
||||
|
||||
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.core.context_processors.debug",
|
||||
"django.core.context_processors.i18n",
|
||||
"django.core.context_processors.media",
|
||||
"django.core.context_processors.static",
|
||||
"django.core.context_processors.tz",
|
||||
"django.core.context_processors.request",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
)
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# 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/')
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(BASE_DIR, 'dist/'),
|
||||
STATIC_DIRS = (
|
||||
os.path.join(BASE_DIR, 'static/')
|
||||
)
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, 'templates'),
|
||||
)
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.i18n",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"django.template.context_processors.tz",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
'debug': DEBUG
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
USE_GRAVATAR = True
|
||||
|
||||
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
||||
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
|
||||
RISK_ASSESSMENT_URL = os.environ.get('RISK_ASSESSMENT_URL') if os.environ.get(
|
||||
'RISK_ASSESSMENT_URL') else "http://example.com"
|
||||
RISK_ASSESSMENT_SECRET = os.environ.get('RISK_ASSESSMENT_SECRET') if os.environ.get(
|
||||
'RISK_ASSESSMENT_SECRET') else secrets.token_hex(15)
|
||||
|
||||
36
PyRIGS/tests/base.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from django.test import LiveServerTestCase
|
||||
from selenium import webdriver
|
||||
from RIGS import models as rigsmodels
|
||||
from . import pages
|
||||
import os
|
||||
|
||||
|
||||
def create_browser():
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument("--window-size=1920,1080")
|
||||
if os.environ.get('CI', False):
|
||||
options.add_argument("--headless")
|
||||
options.add_argument("--no-sandbox")
|
||||
driver = webdriver.Chrome(chrome_options=options)
|
||||
return driver
|
||||
|
||||
|
||||
class BaseTest(LiveServerTestCase):
|
||||
def setUp(self):
|
||||
super().setUpClass()
|
||||
self.driver = create_browser()
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
self.driver.quit()
|
||||
|
||||
|
||||
class AutoLoginTest(BaseTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.profile = rigsmodels.Profile(
|
||||
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
||||
self.profile.set_password("EventTestPassword")
|
||||
self.profile.save()
|
||||
loginPage = pages.LoginPage(self.driver, self.live_server_url).open()
|
||||
loginPage.login("EventTest", "EventTestPassword")
|
||||
86
PyRIGS/tests/pages.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from pypom import Page, Region
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver import Chrome
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
|
||||
|
||||
class BasePage(Page):
|
||||
form_items = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
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
|
||||
|
||||
|
||||
class FormPage(BasePage):
|
||||
_errors_selector = (By.CLASS_NAME, "alert-danger")
|
||||
|
||||
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\")});")
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
try:
|
||||
error_page = self.ErrorPage(self, self.find_element(*self._errors_selector))
|
||||
return error_page.errors
|
||||
except NoSuchElementException:
|
||||
return None
|
||||
|
||||
class ErrorPage(Region):
|
||||
_error_item_selector = (By.CSS_SELECTOR, "dl>span")
|
||||
|
||||
class ErrorItem(Region):
|
||||
_field_selector = (By.CSS_SELECTOR, "dt")
|
||||
_error_selector = (By.CSS_SELECTOR, "dd>ul>li")
|
||||
|
||||
@property
|
||||
def field_name(self):
|
||||
return self.find_element(*self._field_selector).text
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
return [x.text for x in self.find_elements(*self._error_selector)]
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
error_items = [self.ErrorItem(self, x) for x in self.find_elements(*self._error_item_selector)]
|
||||
errors = {}
|
||||
for error in error_items:
|
||||
errors[error.field_name] = error.errors
|
||||
return errors
|
||||
|
||||
|
||||
class LoginPage(BasePage):
|
||||
URL_TEMPLATE = '/user/login'
|
||||
|
||||
_username_locator = (By.ID, 'id_username')
|
||||
_password_locator = (By.ID, 'id_password')
|
||||
_submit_locator = (By.ID, 'id_submit')
|
||||
_error_locator = (By.CSS_SELECTOR, '.errorlist>li')
|
||||
|
||||
def login(self, username, password):
|
||||
username_element = self.find_element(*self._username_locator)
|
||||
username_element.clear()
|
||||
username_element.send_keys(username)
|
||||
|
||||
password_element = self.find_element(*self._password_locator)
|
||||
password_element.clear()
|
||||
password_element.send_keys(password)
|
||||
|
||||
self.find_element(*self._submit_locator).click()
|
||||
133
PyRIGS/tests/regions.py
Normal file
@@ -0,0 +1,133 @@
|
||||
from pypom import Region
|
||||
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
|
||||
import datetime
|
||||
|
||||
|
||||
def parse_bool_from_string(string):
|
||||
# Used to convert from attribute strings to boolean values, written after I found this:
|
||||
# >>> bool("false")
|
||||
# True
|
||||
if string == "true":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
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]')
|
||||
_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')
|
||||
_status_locator = (By.CLASS_NAME, 'status')
|
||||
|
||||
@property
|
||||
def is_open(self):
|
||||
return parse_bool_from_string(self.find_element(*self._main_button_locator).get_attribute("aria-expanded"))
|
||||
|
||||
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))
|
||||
else:
|
||||
self.wait.until(expected_conditions.visibility_of_element_located(option_box))
|
||||
|
||||
def open(self):
|
||||
if not self.is_open:
|
||||
self.toggle()
|
||||
|
||||
def close(self):
|
||||
if self.is_open:
|
||||
self.toggle()
|
||||
|
||||
def select_all(self):
|
||||
self.find_element(*self._select_all_locator).click()
|
||||
|
||||
def deselect_all(self):
|
||||
self.find_element(*self._deselect_all_locator).click()
|
||||
|
||||
def search(self, query):
|
||||
search_box = self.find_element(*self._search_locator)
|
||||
search_box.clear()
|
||||
search_box.send_keys(query)
|
||||
status_text = self.find_element(*self._status_locator)
|
||||
self.wait.until(expected_conditions.invisibility_of_element_located(self._status_locator))
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
options = list(self.find_elements(*self._option_locator))
|
||||
return [self.BootstrapSelectOption(self, i) for i in options]
|
||||
|
||||
def set_option(self, name, selected):
|
||||
options = list((x for x in self.options if x.name == name))
|
||||
assert len(options) == 1
|
||||
options[0].set_selected(selected)
|
||||
|
||||
class BootstrapSelectOption(Region):
|
||||
_text_locator = (By.CLASS_NAME, 'text')
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
return parse_bool_from_string(self.root.get_attribute("aria-selected"))
|
||||
|
||||
def toggle(self):
|
||||
self.root.click()
|
||||
|
||||
def set_selected(self, selected):
|
||||
if self.selected != selected:
|
||||
self.toggle()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.find_element(*self._text_locator).text
|
||||
|
||||
|
||||
class TextBox(Region):
|
||||
@property
|
||||
def value(self):
|
||||
return self.root.get_attribute("value")
|
||||
|
||||
def set_value(self, value):
|
||||
self.root.clear()
|
||||
self.root.send_keys(value)
|
||||
|
||||
|
||||
class CheckBox(Region):
|
||||
def toggle(self):
|
||||
self.root.click()
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return parse_bool_from_string(self.root.get_attribute("checked"))
|
||||
|
||||
def set_value(self, value):
|
||||
if value != self.value:
|
||||
self.toggle()
|
||||
|
||||
|
||||
class DatePicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
return datetime.datetime.strptime(self.root.get_attribute("value"), "%Y-%m-%d")
|
||||
|
||||
def set_value(self, value):
|
||||
self.root.clear()
|
||||
self.root.send_keys(value.strftime("%d%m%Y"))
|
||||
|
||||
|
||||
class SingleSelectPicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
picker = Select(self.root)
|
||||
return picker.first_selected_option.text
|
||||
|
||||
def set_value(self, value):
|
||||
picker = Select(self.root)
|
||||
picker.select_by_visible_text(value)
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.conf import settings
|
||||
@@ -6,19 +6,25 @@ from registration.backends.default.views import RegistrationView
|
||||
import RIGS
|
||||
from RIGS import regbackend
|
||||
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = [
|
||||
# Examples:
|
||||
# url(r'^$', 'PyRIGS.views.home', name='home'),
|
||||
# url(r'^blog/', include('blog.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"),
|
||||
url('^user/', include('django.contrib.auth.urls')),
|
||||
url('^user/', include('registration.backends.default.urls')),
|
||||
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
url(r'^admin/', admin.site.urls),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
||||
import debug_toolbar
|
||||
urlpatterns = [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
] + urlpatterns
|
||||
|
||||
@@ -9,7 +9,7 @@ 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
|
||||
from dj_static import Cling
|
||||
from django.core.wsgi import get_wsgi_application # noqa
|
||||
from dj_static import Cling # noqa
|
||||
|
||||
application = Cling(get_wsgi_application())
|
||||
|
||||
34
README.md
@@ -1,18 +1,13 @@
|
||||
# TEC PA & Lighting - PyRIGS #
|
||||
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
||||
[](https://coveralls.io/github/nottinghamtec/PyRIGS?branch=develop)
|
||||
[](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.
|
||||
|
||||
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.
|
||||
|
||||
At this stage the project is very early on, and the main focus has been on getting a working system that can be tested and put into use ASAP due to the imminent failure of the existing system. Because of this, the documentation is still quite weak, but this should be fixed as time goes on.
|
||||
|
||||
This document is intended to get you up and running, but if don't care about what I have to say, just clone the sodding repository and have a poke around with what's in it, but for GODS SAKE DO NOT PUSH WITHOUT TESTING.
|
||||
|
||||
### What is this repository for? ###
|
||||
For the rapid development of the application for medium term deployment, the main branch is being used.
|
||||
Once the application is deployed in a production environment, other branches should be used to properly stage edits and pushes of new features. When a significant feature is developed on a branch, raise a pull request and it can be reviewed before being put into production.
|
||||
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.
|
||||
|
||||
@@ -24,7 +19,7 @@ For the more experienced developer/somebody who doesn't want a full IDE and want
|
||||
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. Mainly the C implementation of Python 2 (CPython 2) has been used (specifically the Python 2.7 standard). Most of the application has been written with Python 3 in mind however, and should run without issue. Some level of testing on Python 3 has been done, but there is no guarantee it will work (for more information on this please see [[Python Version]] on the wiki)
|
||||
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.
|
||||
|
||||
@@ -75,6 +70,13 @@ 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:
|
||||
```
|
||||
@@ -93,5 +95,17 @@ python manage.py generateSampleData
|
||||
|keyholder|keyholder|
|
||||
|basic |basic |
|
||||
|
||||
### Committing, pushing and testing ###
|
||||
Feel free to commit as you wish, on your own branch. On my branch (master for development) do not commit code that you either know doesn't work or don't know works. If you must commit this code, please make sure you say in the commit message that it isn't working, and if you can why it isn't working. If and only if you absolutely must push, then please don't leave it as the HEAD for too long, it's not much to ask but when you are done just make sure you haven't broken the HEAD for the next person.
|
||||
### 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
|
||||
|
||||
```
|
||||
|
||||
[](https://forthebadge.com) [](https://forthebadge.com)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
default_app_config = 'RIGS.apps.RIGSAppConfig'
|
||||
|
||||
@@ -2,7 +2,7 @@ from django.contrib import admin
|
||||
from RIGS import models, forms
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import reversion
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from django.contrib.admin import helpers
|
||||
from django.template.response import TemplateResponse
|
||||
@@ -12,21 +12,32 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import Count
|
||||
from django.forms import ModelForm
|
||||
|
||||
from reversion import revisions as reversion
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(models.VatRate, reversion.VersionAdmin)
|
||||
admin.site.register(models.Event, reversion.VersionAdmin)
|
||||
admin.site.register(models.EventItem, reversion.VersionAdmin)
|
||||
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)
|
||||
|
||||
|
||||
def approve_user(modeladmin, request, queryset):
|
||||
queryset.update(is_approved=True)
|
||||
|
||||
|
||||
approve_user.short_description = "Approve selected users"
|
||||
|
||||
|
||||
@admin.register(models.Profile)
|
||||
class ProfileAdmin(UserAdmin):
|
||||
# Don't know how to add 'is_approved' whilst preserving the default list...
|
||||
list_filter = ('is_approved', 'is_active', 'is_staff', 'is_superuser', 'groups')
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
(_('Personal info'), {
|
||||
'fields': ('first_name', 'last_name', 'email', 'initials', 'phone')}),
|
||||
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
|
||||
(_('Permissions'), {'fields': ('is_approved', 'is_active', 'is_staff', 'is_superuser',
|
||||
'groups', 'user_permissions')}),
|
||||
(_('Important dates'), {
|
||||
'fields': ('last_login', 'date_joined')}),
|
||||
@@ -39,9 +50,10 @@ class ProfileAdmin(UserAdmin):
|
||||
)
|
||||
form = forms.ProfileChangeForm
|
||||
add_form = forms.ProfileCreationForm
|
||||
actions = [approve_user]
|
||||
|
||||
|
||||
class AssociateAdmin(reversion.VersionAdmin):
|
||||
class AssociateAdmin(VersionAdmin):
|
||||
list_display = ('id', 'name', 'number_of_events')
|
||||
search_fields = ['id', 'name']
|
||||
list_display_links = ['id', 'name']
|
||||
@@ -93,8 +105,7 @@ class AssociateAdmin(reversion.VersionAdmin):
|
||||
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
|
||||
'forms': forms
|
||||
}
|
||||
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context,
|
||||
current_app=self.admin_site.name)
|
||||
return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context)
|
||||
|
||||
|
||||
@admin.register(models.Person)
|
||||
|
||||
8
RIGS/apps.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RIGSAppConfig(AppConfig):
|
||||
name = 'RIGS'
|
||||
|
||||
def ready(self):
|
||||
import RIGS.signals
|
||||
@@ -1,9 +1,8 @@
|
||||
import cStringIO as StringIO
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.urls import reverse_lazy
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
@@ -15,6 +14,9 @@ from z3c.rml import rml2pdf
|
||||
|
||||
from RIGS import models
|
||||
|
||||
from django import forms
|
||||
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
||||
|
||||
|
||||
class InvoiceIndex(generic.ListView):
|
||||
model = models.Invoice
|
||||
@@ -55,8 +57,8 @@ class InvoicePrint(generic.View):
|
||||
invoice = get_object_or_404(models.Invoice, pk=pk)
|
||||
object = invoice.event
|
||||
template = get_template('RIGS/event_print.xml')
|
||||
copies = ('TEC', 'Client')
|
||||
context = RequestContext(request, {
|
||||
|
||||
context = {
|
||||
'object': object,
|
||||
'fonts': {
|
||||
'opensans': {
|
||||
@@ -66,10 +68,9 @@ class InvoicePrint(generic.View):
|
||||
},
|
||||
'invoice': invoice,
|
||||
'current_user': request.user,
|
||||
})
|
||||
}
|
||||
|
||||
rml = template.render(context)
|
||||
buffer = StringIO.StringIO()
|
||||
|
||||
buffer = rml2pdf.parseString(rml)
|
||||
|
||||
@@ -94,6 +95,7 @@ class InvoiceVoid(generic.View):
|
||||
return HttpResponseRedirect(reverse_lazy('invoice_list'))
|
||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk}))
|
||||
|
||||
|
||||
class InvoiceDelete(generic.DeleteView):
|
||||
model = models.Invoice
|
||||
|
||||
@@ -114,6 +116,7 @@ class InvoiceDelete(generic.DeleteView):
|
||||
def get_success_url(self):
|
||||
return self.request.POST.get('next')
|
||||
|
||||
|
||||
class InvoiceArchive(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'RIGS/invoice_list_archive.html'
|
||||
@@ -143,8 +146,8 @@ class InvoiceWaiting(generic.ListView):
|
||||
(
|
||||
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
|
||||
Q(end_date__lte=datetime.date.today()) # Has end date, finishes before
|
||||
) & Q(invoice__isnull=True) # Has not already been invoiced
|
||||
& Q(is_rig=True) # Is a rig (not non-rig)
|
||||
) & Q(invoice__isnull=True) & # Has not already been invoiced
|
||||
Q(is_rig=True) # Is a rig (not non-rig)
|
||||
|
||||
).order_by('start_date') \
|
||||
.select_related('person',
|
||||
@@ -175,7 +178,7 @@ class PaymentCreate(generic.CreateView):
|
||||
def get_initial(self):
|
||||
initial = super(generic.CreateView, self).get_initial()
|
||||
invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None))
|
||||
if invoicepk == None:
|
||||
if invoicepk is None:
|
||||
raise Http404()
|
||||
invoice = get_object_or_404(models.Invoice, pk=invoicepk)
|
||||
initial.update({'invoice': invoice})
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
__author__ = 'Ghost'
|
||||
from django import forms
|
||||
from django.utils import formats
|
||||
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 registration.forms import RegistrationFormUniqueEmail
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from captcha.fields import ReCaptchaField
|
||||
import simplejson
|
||||
|
||||
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', 'phone')
|
||||
fields = ('username', 'email', 'first_name', 'last_name', 'initials')
|
||||
|
||||
def clean_initials(self):
|
||||
"""
|
||||
@@ -28,7 +35,21 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail):
|
||||
return self.cleaned_data['initials']
|
||||
|
||||
|
||||
# Login form
|
||||
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')
|
||||
|
||||
@@ -45,7 +66,7 @@ class ProfileChangeForm(UserChangeForm):
|
||||
|
||||
# Events Shit
|
||||
class EventForm(forms.ModelForm):
|
||||
datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + settings.DATETIME_INPUT_FORMATS
|
||||
datetime_input_formats = formats.get_format_lazy("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)
|
||||
|
||||
@@ -118,6 +139,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')
|
||||
return super(EventForm, self).clean()
|
||||
|
||||
def save(self, commit=True):
|
||||
m = super(EventForm, self).save(commit=False)
|
||||
|
||||
@@ -140,4 +166,32 @@ class EventForm(forms.ModelForm):
|
||||
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
|
||||
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
|
||||
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
|
||||
'collector', 'purchase_order']
|
||||
'purchase_order', 'collector']
|
||||
|
||||
|
||||
class BaseClientEventAuthorisationForm(forms.ModelForm):
|
||||
tos = forms.BooleanField(required=True, label="Terms of hire")
|
||||
name = forms.CharField(label="Your Name")
|
||||
|
||||
def clean(self):
|
||||
if self.cleaned_data.get('amount') != self.instance.event.total:
|
||||
self.add_error('amount', 'The amount authorised must equal the total for the event (inc VAT).')
|
||||
return super(BaseClientEventAuthorisationForm, self).clean()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
|
||||
def __init__(self, **kwargs):
|
||||
super(InternalClientEventAuthorisationForm, self).__init__(**kwargs)
|
||||
self.fields['uni_id'].required = True
|
||||
self.fields['account_code'].required = True
|
||||
|
||||
class Meta:
|
||||
model = models.EventAuthorisation
|
||||
fields = ('tos', 'name', 'amount', 'uni_id', 'account_code')
|
||||
|
||||
|
||||
class EventAuthorisationRequestForm(forms.Form):
|
||||
email = forms.EmailField(required=True, label='Authoriser Email')
|
||||
|
||||
11
RIGS/ical.py
@@ -1,11 +1,13 @@
|
||||
from RIGS import models, forms
|
||||
from django_ical.views import ICalFeed
|
||||
from django.db.models import Q
|
||||
from django.core.urlresolvers import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
import datetime, pytz
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
|
||||
class CalendarICS(ICalFeed):
|
||||
"""
|
||||
@@ -97,7 +99,7 @@ class CalendarICS(ICalFeed):
|
||||
return item.earliest_time
|
||||
|
||||
def item_end_datetime(self, item):
|
||||
if type(item.latest_time) is datetime.date: # Ical end_datetime is non-inclusive, so add a day
|
||||
if type(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
|
||||
@@ -119,7 +121,6 @@ class CalendarICS(ICalFeed):
|
||||
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'
|
||||
@@ -136,7 +137,7 @@ class CalendarICS(ICalFeed):
|
||||
# if item.notes: // Need to add proper keyholder checks before this gets put back
|
||||
# desc += 'Notes:\n'+item.notes+'\n\n'
|
||||
|
||||
base_url = "http://rigs.nottinghamtec.co.uk"
|
||||
base_url = "https://rigs.nottinghamtec.co.uk"
|
||||
desc += 'URL = ' + base_url + str(item.get_absolute_url())
|
||||
|
||||
return desc
|
||||
|
||||
@@ -1,248 +1,11 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.db import transaction
|
||||
import reversion
|
||||
from django.core.management import call_command
|
||||
|
||||
import datetime
|
||||
import random
|
||||
|
||||
from RIGS import models
|
||||
class Command(BaseCommand):
|
||||
help = 'Adds sample data to use for testing'
|
||||
can_import_settings = True
|
||||
|
||||
people = []
|
||||
organisations = []
|
||||
venues = []
|
||||
profiles = []
|
||||
|
||||
keyholder_group = None
|
||||
finance_group = None
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from django.conf import settings
|
||||
|
||||
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
|
||||
|
||||
with transaction.atomic():
|
||||
models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1')
|
||||
|
||||
self.setupGenericProfiles()
|
||||
|
||||
self.setupPeople()
|
||||
self.setupOrganisations()
|
||||
self.setupVenues()
|
||||
|
||||
self.setupGroups()
|
||||
|
||||
self.setupEvents()
|
||||
|
||||
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","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"]
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
|
||||
newPerson = models.Person.objects.create(name=name)
|
||||
if i % 3 == 0:
|
||||
newPerson.email = "address@person.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newPerson.phone = "01234 567894"
|
||||
|
||||
newPerson.save()
|
||||
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","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"]
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
newOrganisation = models.Organisation.objects.create(name=name)
|
||||
if i % 2 == 0:
|
||||
newOrganisation.has_su_account = True
|
||||
|
||||
if i % 3 == 0:
|
||||
newOrganisation.email = "address@organisation.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newOrganisation.phone = "01234 567894"
|
||||
|
||||
newOrganisation.save()
|
||||
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","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"]
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
newVenue = models.Venue.objects.create(name=name)
|
||||
if i % 2 == 0:
|
||||
newVenue.three_phase_available = True
|
||||
|
||||
if i % 3 == 0:
|
||||
newVenue.email = "address@venue.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newVenue.phone = "01234 567894"
|
||||
|
||||
newVenue.save()
|
||||
self.venues.append(newVenue)
|
||||
|
||||
def setupGroups(self):
|
||||
self.keyholder_group = Group.objects.create(name='Keyholders')
|
||||
self.finance_group = Group.objects.create(name='Finance')
|
||||
|
||||
keyholderPerms = ["add_event","change_event","view_event","add_eventitem","change_eventitem","delete_eventitem","add_organisation","change_organisation","view_organisation","add_person","change_person","view_person","view_profile","add_venue","change_venue","view_venue"]
|
||||
financePerms = ["change_event","view_event","add_eventitem","change_eventitem","add_invoice","change_invoice","view_invoice","add_organisation","change_organisation","view_organisation","add_payment","change_payment","delete_payment","add_person","change_person","view_person"]
|
||||
|
||||
for permId in keyholderPerms:
|
||||
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
|
||||
for permId in financePerms:
|
||||
self.finance_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"]
|
||||
for i, name in enumerate(names):
|
||||
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:
|
||||
newProfile.phone = "01234 567894"
|
||||
|
||||
newProfile.save()
|
||||
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.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.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)
|
||||
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)
|
||||
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!"]
|
||||
|
||||
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
|
||||
|
||||
for i in range(150): # Let's add 100 events
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
|
||||
name = names[i%len(names)]
|
||||
|
||||
startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
|
||||
dayDelta = dayDelta + random.randint(0,3)
|
||||
|
||||
newEvent = models.Event.objects.create(name=name, start_date=startDate)
|
||||
|
||||
if random.randint(0,2) > 1: # 1 in 3 have a start time
|
||||
newEvent.start_time = datetime.time(random.randint(15,20))
|
||||
if random.randint(0,2) > 1: # of those, 1 in 3 have an end time on the same day
|
||||
newEvent.end_time = datetime.time(random.randint(21,23))
|
||||
elif random.randint(0,1)>0: # half of the others finish early the next day
|
||||
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1)
|
||||
newEvent.end_time = datetime.time(random.randint(0,5))
|
||||
elif random.randint(0,2)>1: # 1 in 3 of the others finish a few days ahead
|
||||
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1,4))
|
||||
|
||||
|
||||
if random.randint(0,6) > 0: # 5 in 6 have MIC
|
||||
newEvent.mic = random.choice(self.profiles)
|
||||
|
||||
if random.randint(0,6) > 0: # 5 in 6 have organisation
|
||||
newEvent.organisation = random.choice(self.organisations)
|
||||
|
||||
if random.randint(0,6) > 0: # 5 in 6 have person
|
||||
newEvent.person = random.choice(self.people)
|
||||
|
||||
if random.randint(0,6) > 0: # 5 in 6 have venue
|
||||
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.dry_hire = (random.randint(0,7)==0) # 1 in 7 are dry hire
|
||||
|
||||
if random.randint(0,1) > 0: # 1 in 2 have description
|
||||
newEvent.description = random.choice(descriptions)
|
||||
|
||||
if random.randint(0,1) > 0: # 1 in 2 have notes
|
||||
newEvent.notes = random.choice(notes)
|
||||
|
||||
newEvent.save()
|
||||
|
||||
# Now add some items
|
||||
for j in range(random.randint(1,5)):
|
||||
itemData = itemOptions[random.randint(0,len(itemOptions)-1)]
|
||||
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
||||
newItem.save()
|
||||
|
||||
while newEvent.sum_total < 0:
|
||||
itemData = itemOptions[random.randint(0,len(itemOptions)-1)]
|
||||
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
||||
newItem.save()
|
||||
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
if newEvent.start_date < datetime.date.today(): # think about adding an invoice
|
||||
if random.randint(0,2) > 0: # 2 in 3 have had paperwork sent to treasury
|
||||
newInvoice = models.Invoice.objects.create(event=newEvent)
|
||||
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())
|
||||
call_command('generateSampleRIGSData')
|
||||
call_command('generateSampleAssetsData')
|
||||
|
||||
260
RIGS/management/commands/generateSampleRIGSData.py
Normal file
@@ -0,0 +1,260 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.db import transaction
|
||||
from reversion import revisions as reversion
|
||||
|
||||
import datetime
|
||||
import random
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Adds sample data to use for testing'
|
||||
can_import_settings = True
|
||||
|
||||
people = []
|
||||
organisations = []
|
||||
venues = []
|
||||
profiles = []
|
||||
|
||||
keyholder_group = None
|
||||
finance_group = None
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from django.conf import settings
|
||||
|
||||
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
|
||||
|
||||
with transaction.atomic():
|
||||
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||
|
||||
self.setupGenericProfiles()
|
||||
|
||||
self.setupPeople()
|
||||
self.setupOrganisations()
|
||||
self.setupVenues()
|
||||
|
||||
self.setupGroups()
|
||||
|
||||
self.setupEvents()
|
||||
|
||||
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
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
|
||||
newPerson = models.Person.objects.create(name=name)
|
||||
if i % 3 == 0:
|
||||
newPerson.email = "address@person.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newPerson.phone = "01234 567894"
|
||||
|
||||
newPerson.save()
|
||||
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
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
newOrganisation = models.Organisation.objects.create(name=name)
|
||||
if i % 2 == 0:
|
||||
newOrganisation.has_su_account = True
|
||||
|
||||
if i % 3 == 0:
|
||||
newOrganisation.email = "address@organisation.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newOrganisation.phone = "01234 567894"
|
||||
|
||||
newOrganisation.save()
|
||||
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
|
||||
for i, name in enumerate(names):
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
newVenue = models.Venue.objects.create(name=name)
|
||||
if i % 2 == 0:
|
||||
newVenue.three_phase_available = True
|
||||
|
||||
if i % 3 == 0:
|
||||
newVenue.email = "address@venue.com"
|
||||
|
||||
if i % 5 == 0:
|
||||
newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||
|
||||
if i % 7 == 0:
|
||||
newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||
|
||||
if i % 9 == 0:
|
||||
newVenue.phone = "01234 567894"
|
||||
|
||||
newVenue.save()
|
||||
self.venues.append(newVenue)
|
||||
|
||||
def setupGroups(self):
|
||||
self.keyholder_group = Group.objects.create(name='Keyholders')
|
||||
self.finance_group = Group.objects.create(name='Finance')
|
||||
|
||||
keyholderPerms = ["add_event", "change_event", "view_event",
|
||||
"add_eventitem", "change_eventitem", "delete_eventitem",
|
||||
"add_organisation", "change_organisation", "view_organisation",
|
||||
"add_person", "change_person", "view_person", "view_profile",
|
||||
"add_venue", "change_venue", "view_venue",
|
||||
"add_asset", "change_asset", "delete_asset",
|
||||
"asset_finance", "view_asset", "view_supplier", "asset_finance",
|
||||
"add_supplier"]
|
||||
financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
|
||||
"add_payment", "change_payment", "delete_payment"]
|
||||
|
||||
for permId in keyholderPerms:
|
||||
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
|
||||
for permId in financePerms:
|
||||
self.finance_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"]
|
||||
for i, name in enumerate(names):
|
||||
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:
|
||||
newProfile.phone = "01234 567894"
|
||||
|
||||
newProfile.save()
|
||||
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.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.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)
|
||||
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)
|
||||
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!"]
|
||||
|
||||
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
|
||||
|
||||
for i in range(150): # Let's add 100 events
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
|
||||
name = names[i % len(names)]
|
||||
|
||||
startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
|
||||
dayDelta = dayDelta + random.randint(0, 3)
|
||||
|
||||
newEvent = models.Event.objects.create(name=name, start_date=startDate)
|
||||
|
||||
if random.randint(0, 2) > 1: # 1 in 3 have a start time
|
||||
newEvent.start_time = datetime.time(random.randint(15, 20))
|
||||
if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day
|
||||
newEvent.end_time = datetime.time(random.randint(21, 23))
|
||||
elif random.randint(0, 1) > 0: # half of the others finish early the next day
|
||||
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1)
|
||||
newEvent.end_time = datetime.time(random.randint(0, 5))
|
||||
elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead
|
||||
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4))
|
||||
|
||||
if random.randint(0, 6) > 0: # 5 in 6 have MIC
|
||||
newEvent.mic = random.choice(self.profiles)
|
||||
|
||||
if random.randint(0, 6) > 0: # 5 in 6 have organisation
|
||||
newEvent.organisation = random.choice(self.organisations)
|
||||
|
||||
if random.randint(0, 6) > 0: # 5 in 6 have person
|
||||
newEvent.person = random.choice(self.people)
|
||||
|
||||
if random.randint(0, 6) > 0: # 5 in 6 have venue
|
||||
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.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
|
||||
|
||||
if random.randint(0, 1) > 0: # 1 in 2 have description
|
||||
newEvent.description = random.choice(descriptions)
|
||||
|
||||
if random.randint(0, 1) > 0: # 1 in 2 have notes
|
||||
newEvent.notes = random.choice(notes)
|
||||
|
||||
newEvent.save()
|
||||
|
||||
# Now add some items
|
||||
for j in range(random.randint(1, 5)):
|
||||
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
|
||||
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
||||
newItem.save()
|
||||
|
||||
while newEvent.sum_total < 0:
|
||||
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
|
||||
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
||||
newItem.save()
|
||||
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(random.choice(self.profiles))
|
||||
if newEvent.start_date < datetime.date.today(): # think about adding an invoice
|
||||
if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury
|
||||
newInvoice = models.Invoice.objects.create(event=newEvent)
|
||||
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())
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.core.validators
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('postedAt', models.DateTimeField(auto_now=True)),
|
||||
('message', models.TextField()),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
import RIGS.models
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
import RIGS.models
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
@@ -33,11 +33,11 @@ class Migration(migrations.Migration):
|
||||
('payment_method', models.CharField(blank=True, null=True, max_length=255)),
|
||||
('payment_received', models.CharField(blank=True, null=True, max_length=255)),
|
||||
('purchase_order', models.CharField(blank=True, null=True, max_length=255)),
|
||||
('based_on', models.ForeignKey(to='RIGS.Event', related_name='future_events')),
|
||||
('checked_in_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in')),
|
||||
('mic', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic')),
|
||||
('organisation', models.ForeignKey(to='RIGS.Organisation')),
|
||||
('person', models.ForeignKey(to='RIGS.Person')),
|
||||
('based_on', models.ForeignKey(to='RIGS.Event', related_name='future_events', on_delete=models.CASCADE)),
|
||||
('checked_in_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', on_delete=models.CASCADE)),
|
||||
('mic', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', on_delete=models.CASCADE)),
|
||||
('organisation', models.ForeignKey(to='RIGS.Organisation', on_delete=models.CASCADE)),
|
||||
('person', models.ForeignKey(to='RIGS.Person', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
@@ -52,7 +52,7 @@ class Migration(migrations.Migration):
|
||||
('quantity', models.IntegerField()),
|
||||
('cost', models.DecimalField(max_digits=10, decimal_places=2)),
|
||||
('order', models.IntegerField()),
|
||||
('event', models.ForeignKey(to='RIGS.Event', related_name='item')),
|
||||
('event', models.ForeignKey(to='RIGS.Event', related_name='item', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
@@ -75,7 +75,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='venue',
|
||||
field=models.ForeignKey(to='RIGS.Venue'),
|
||||
field=models.ForeignKey(to='RIGS.Venue', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
@@ -14,26 +14,26 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='based_on',
|
||||
field=models.ForeignKey(to='RIGS.Event', related_name='future_events', blank=True, null=True),
|
||||
field=models.ForeignKey(to='RIGS.Event', related_name='future_events', blank=True, null=True, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='checked_in_by',
|
||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True,
|
||||
null=True),
|
||||
null=True, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='mic',
|
||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True),
|
||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='organisation',
|
||||
field=models.ForeignKey(to='RIGS.Organisation', blank=True, null=True),
|
||||
field=models.ForeignKey(to='RIGS.Organisation', blank=True, null=True, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
@@ -19,8 +19,8 @@ class Migration(migrations.Migration):
|
||||
('run', models.BooleanField(default=False)),
|
||||
('derig', models.BooleanField(default=False)),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('event', models.ForeignKey(related_name='crew', to='RIGS.Event')),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||
('event', models.ForeignKey(related_name='crew', to='RIGS.Event', on_delete=models.CASCADE)),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='eventitem',
|
||||
name='event',
|
||||
field=models.ForeignKey(related_name='items', to='RIGS.Event'),
|
||||
field=models.ForeignKey(related_name='items', to='RIGS.Event', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
@@ -14,7 +14,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='person',
|
||||
field=models.ForeignKey(blank=True, null=True, to='RIGS.Person'),
|
||||
field=models.ForeignKey(blank=True, null=True, to='RIGS.Person', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
@@ -14,13 +14,13 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='venue',
|
||||
field=models.ForeignKey(blank=True, to='RIGS.Venue', null=True),
|
||||
field=models.ForeignKey(blank=True, to='RIGS.Venue', null=True, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventitem',
|
||||
name='event',
|
||||
field=models.ForeignKey(related_name='items', blank=True, to='RIGS.Event'),
|
||||
field=models.ForeignKey(related_name='items', blank=True, to='RIGS.Event', on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('invoice_date', models.DateField(auto_now_add=True)),
|
||||
('void', models.BooleanField()),
|
||||
('event', models.OneToOneField(to='RIGS.Event')),
|
||||
('event', models.OneToOneField(to='RIGS.Event', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
@@ -31,7 +31,7 @@ class Migration(migrations.Migration):
|
||||
('date', models.DateField()),
|
||||
('amount', models.DecimalField(help_text=b'Please use ex. VAT', max_digits=10, decimal_places=2)),
|
||||
('method', models.CharField(max_length=2, choices=[(b'C', b'Cash'), (b'I', b'Internal'), (b'E', b'External'), (b'SU', b'SU Core'), (b'M', b'Members')])),
|
||||
('invoice', models.ForeignKey(to='RIGS.Invoice')),
|
||||
('invoice', models.ForeignKey(to='RIGS.Invoice', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
@@ -40,7 +40,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='mic',
|
||||
field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True),
|
||||
field=models.ForeignKey(related_name='event_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.core.validators
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.db.models.deletion
|
||||
|
||||
26
RIGS/migrations/0025_auto_20160331_1302.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2016-03-31 12:02
|
||||
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0024_auto_20160229_2042'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='username',
|
||||
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vatrate',
|
||||
name='start_at',
|
||||
field=models.DateField(),
|
||||
),
|
||||
]
|
||||
27
RIGS/migrations/0025_eventauthorisation.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0024_auto_20160229_2042'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EventAuthorisation',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('uni_id', models.CharField(max_length=10, null=True, verbose_name=b'University ID', blank=True)),
|
||||
('account_code', models.CharField(max_length=50, null=True, blank=True)),
|
||||
('amount', models.DecimalField(verbose_name=b'authorisation amount', max_digits=10, decimal_places=2)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('event', models.ForeignKey(related_name='authroisations', to='RIGS.Event', on_delete=models.CASCADE)),
|
||||
],
|
||||
),
|
||||
]
|
||||
21
RIGS/migrations/0026_auto_20170510_1846.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.1 on 2017-05-10 17:46
|
||||
|
||||
|
||||
import django.contrib.auth.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0025_auto_20160331_1302'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='username',
|
||||
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'),
|
||||
),
|
||||
]
|
||||
18
RIGS/migrations/0026_remove_eventauthorisation_created_at.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0025_eventauthorisation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='eventauthorisation',
|
||||
name='created_at',
|
||||
),
|
||||
]
|
||||
19
RIGS/migrations/0027_eventauthorisation_event_singular.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0026_remove_eventauthorisation_created_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='eventauthorisation',
|
||||
name='event',
|
||||
field=models.OneToOneField(related_name='authorisation', to='RIGS.Event', on_delete=models.CASCADE),
|
||||
),
|
||||
]
|
||||
21
RIGS/migrations/0029_eventauthorisation_sent_by.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0027_eventauthorisation_event_singular'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='eventauthorisation',
|
||||
name='sent_by',
|
||||
field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
30
RIGS/migrations/0030_auth_request_sending.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0029_eventauthorisation_sent_by'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='auth_request_at',
|
||||
field=models.DateTimeField(null=True, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='auth_request_by',
|
||||
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='auth_request_to',
|
||||
field=models.EmailField(max_length=254, null=True, blank=True),
|
||||
),
|
||||
]
|
||||
16
RIGS/migrations/0031_merge_20170512_2102.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.1 on 2017-05-12 20:02
|
||||
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0030_auth_request_sending'),
|
||||
('RIGS', '0026_auto_20170510_1846'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
69
RIGS/migrations/0032_auto_20170904_2355.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-04 22:55
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
import django.contrib.auth.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('reversion', '0001_squashed_0004_auto_20160611_1202'),
|
||||
('RIGS', '0031_merge_20170512_2102'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RIGSVersion',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'indexes': [],
|
||||
'proxy': True,
|
||||
},
|
||||
bases=('reversion.version',),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='collector',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='collected by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='mic',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='event_mic', to=settings.AUTH_USER_MODEL, verbose_name='MIC'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='purchase_order',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='PO'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventauthorisation',
|
||||
name='amount',
|
||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='authorisation amount'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventauthorisation',
|
||||
name='uni_id',
|
||||
field=models.CharField(blank=True, max_length=10, null=True, verbose_name='University ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='amount',
|
||||
field=models.DecimalField(decimal_places=2, help_text='Please use ex. VAT', max_digits=10),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='method',
|
||||
field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('SU', 'SU Core'), ('T', 'TEC Adjustment')], max_length=2, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='username',
|
||||
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
|
||||
),
|
||||
]
|
||||
18
RIGS/migrations/0033_auto_20180325_0016.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.0.3 on 2018-03-25 00:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0032_auto_20170904_2355'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='last_name',
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
|
||||
),
|
||||
]
|
||||
18
RIGS/migrations/0034_event_risk_assessment_edit_url.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.0.5 on 2019-07-28 21:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0033_auto_20180325_0016'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='risk_assessment_edit_url',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
18
RIGS/migrations/0035_auto_20191124_1319.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.0.13 on 2019-11-24 13:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0034_event_risk_assessment_edit_url'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='risk_assessment_edit_url',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='risk assessment'),
|
||||
),
|
||||
]
|
||||
23
RIGS/migrations/0036_profile_is_approved.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 2.0.13 on 2020-01-10 14:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0035_auto_20191124_1319'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='is_approved',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='last_emailed',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
19
RIGS/migrations/0037_approve_legacy.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.0.13 on 2020-01-11 18:29
|
||||
# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved
|
||||
from django.db import migrations
|
||||
|
||||
def approve_legacy(apps, schema_editor):
|
||||
Profile = apps.get_model('RIGS', 'Profile')
|
||||
for person in Profile.objects.all():
|
||||
person.is_approved = True
|
||||
person.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0036_profile_is_approved'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(approve_legacy)
|
||||
]
|
||||
152
RIGS/models.py
@@ -1,19 +1,24 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import datetime
|
||||
import pytz
|
||||
import random
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from reversion import revisions as reversion
|
||||
from reversion.models import Version
|
||||
import string
|
||||
|
||||
import random
|
||||
from collections import Counter
|
||||
from decimal import Decimal
|
||||
|
||||
import reversion
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.functional import cached_property
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
# Create your models here.
|
||||
@@ -22,19 +27,21 @@ 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...
|
||||
|
||||
@classmethod
|
||||
def make_api_key(cls):
|
||||
size = 20
|
||||
chars = string.ascii_letters + string.digits
|
||||
new_api_key = ''.join(random.choice(chars) for x in range(size))
|
||||
return new_api_key;
|
||||
return new_api_key
|
||||
|
||||
@property
|
||||
def profile_picture(self):
|
||||
url = ""
|
||||
if settings.USE_GRAVATAR or settings.USE_GRAVATAR is None:
|
||||
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email).hexdigest() + "?d=wavatar&s=500"
|
||||
url = "https://www.gravatar.com/avatar/" + hashlib.md5(self.email.encode('utf-8')).hexdigest() + "?d=wavatar&s=500"
|
||||
return url
|
||||
|
||||
@property
|
||||
@@ -48,6 +55,14 @@ class Profile(AbstractUser):
|
||||
def latest_events(self):
|
||||
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||
|
||||
@classmethod
|
||||
def admins(cls):
|
||||
return Profile.objects.filter(email__in=[y for x in settings.ADMINS for y in x])
|
||||
|
||||
@classmethod
|
||||
def users_awaiting_approval_count(cls):
|
||||
return Profile.objects.filter(models.Q(is_approved=False)).count()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@@ -58,32 +73,31 @@ class Profile(AbstractUser):
|
||||
|
||||
|
||||
class RevisionMixin(object):
|
||||
@property
|
||||
def current_version(self):
|
||||
version = Version.objects.get_for_object(self).select_related('revision').first()
|
||||
return version
|
||||
|
||||
@property
|
||||
def last_edited_at(self):
|
||||
versions = reversion.get_for_object(self)
|
||||
if versions:
|
||||
version = reversion.get_for_object(self)[0]
|
||||
return version.revision.date_created
|
||||
else:
|
||||
version = self.current_version
|
||||
if version is None:
|
||||
return None
|
||||
return version.revision.date_created
|
||||
|
||||
@property
|
||||
def last_edited_by(self):
|
||||
versions = reversion.get_for_object(self)
|
||||
if versions:
|
||||
version = reversion.get_for_object(self)[0]
|
||||
return version.revision.user
|
||||
else:
|
||||
version = self.current_version
|
||||
if version is None:
|
||||
return None
|
||||
return version.revision.user
|
||||
|
||||
@property
|
||||
def current_version_id(self):
|
||||
versions = reversion.get_for_object(self)
|
||||
if versions:
|
||||
version = reversion.get_for_object(self)[0]
|
||||
return "V{0} | R{1}".format(version.pk, version.revision.pk)
|
||||
else:
|
||||
version = self.current_version
|
||||
if version is None:
|
||||
return None
|
||||
return "V{0} | R{1}".format(version.pk, version.revision.pk)
|
||||
|
||||
|
||||
@reversion.register
|
||||
@@ -175,7 +189,7 @@ class Organisation(models.Model, RevisionMixin):
|
||||
|
||||
class VatManager(models.Manager):
|
||||
def current_rate(self):
|
||||
return self.find_rate(datetime.datetime.now())
|
||||
return self.find_rate(timezone.now())
|
||||
|
||||
def find_rate(self, date):
|
||||
# return self.filter(startAt__lte=date).latest()
|
||||
@@ -190,7 +204,7 @@ class VatManager(models.Manager):
|
||||
@reversion.register
|
||||
@python_2_unicode_compatible
|
||||
class VatRate(models.Model, RevisionMixin):
|
||||
start_at = models.DateTimeField()
|
||||
start_at = models.DateField()
|
||||
rate = models.DecimalField(max_digits=6, decimal_places=6)
|
||||
comment = models.CharField(max_length=255)
|
||||
|
||||
@@ -241,18 +255,12 @@ class Venue(models.Model, RevisionMixin):
|
||||
class EventManager(models.Manager):
|
||||
def current_events(self):
|
||||
events = self.filter(
|
||||
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Starts after with no end
|
||||
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~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=datetime.date.today()) # Canceled but not started
|
||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
||||
'organisation',
|
||||
'venue', 'mic')
|
||||
(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')
|
||||
return events
|
||||
|
||||
def events_in_bounds(self, start, end):
|
||||
@@ -275,12 +283,12 @@ class EventManager(models.Manager):
|
||||
|
||||
def rig_count(self):
|
||||
event_count = self.filter(
|
||||
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False,
|
||||
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False,
|
||||
is_rig=True) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Starts after with no end
|
||||
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False, is_rig=True) & ~models.Q(
|
||||
(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=datetime.date.today(), is_rig=True) & ~models.Q(
|
||||
(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
|
||||
@@ -304,9 +312,9 @@ class Event(models.Model, RevisionMixin):
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
person = models.ForeignKey('Person', null=True, blank=True)
|
||||
organisation = models.ForeignKey('Organisation', blank=True, null=True)
|
||||
venue = models.ForeignKey('Venue', blank=True, null=True)
|
||||
person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE)
|
||||
organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE)
|
||||
venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
||||
@@ -325,9 +333,9 @@ 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)
|
||||
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")
|
||||
verbose_name="MIC", on_delete=models.CASCADE)
|
||||
|
||||
# Monies
|
||||
payment_method = models.CharField(max_length=255, blank=True, null=True)
|
||||
@@ -335,6 +343,14 @@ class Event(models.Model, RevisionMixin):
|
||||
purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO')
|
||||
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
|
||||
|
||||
# Authorisation request details
|
||||
auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
|
||||
auth_request_at = models.DateTimeField(null=True, blank=True)
|
||||
auth_request_to = models.EmailField(null=True, blank=True)
|
||||
|
||||
# Risk assessment info
|
||||
risk_assessment_edit_url = models.CharField(verbose_name="risk assessment", max_length=255, blank=True, null=True)
|
||||
|
||||
# Calculated values
|
||||
"""
|
||||
EX Vat
|
||||
@@ -367,7 +383,7 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def vat(self):
|
||||
return self.sum_total * self.vat_rate.rate
|
||||
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01'))
|
||||
|
||||
"""
|
||||
Inc VAT
|
||||
@@ -375,7 +391,7 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
return self.sum_total + self.vat
|
||||
return Decimal(self.sum_total + self.vat).quantize(Decimal('.01'))
|
||||
|
||||
@property
|
||||
def cancelled(self):
|
||||
@@ -385,6 +401,10 @@ class Event(models.Model, RevisionMixin):
|
||||
def confirmed(self):
|
||||
return (self.status == self.BOOKED or self.status == self.CONFIRMED)
|
||||
|
||||
@property
|
||||
def authorised(self):
|
||||
return not self.internal and self.purchase_order or self.authorisation.amount == self.total
|
||||
|
||||
@property
|
||||
def has_start_time(self):
|
||||
return self.start_time is not None
|
||||
@@ -445,13 +465,17 @@ class Event(models.Model, RevisionMixin):
|
||||
else:
|
||||
return endDate
|
||||
|
||||
@property
|
||||
def internal(self):
|
||||
return self.organisation and self.organisation.union_account
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self.pk) + ": " + self.name
|
||||
return str(self.pk) + ": " + self.name
|
||||
|
||||
def clean(self):
|
||||
if self.end_date and self.start_date > self.end_date:
|
||||
@@ -474,7 +498,7 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
|
||||
class EventItem(models.Model):
|
||||
event = models.ForeignKey('Event', related_name='items', blank=True)
|
||||
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)
|
||||
quantity = models.IntegerField()
|
||||
@@ -493,17 +517,35 @@ class EventItem(models.Model):
|
||||
|
||||
|
||||
class EventCrew(models.Model):
|
||||
event = models.ForeignKey('Event', related_name='crew')
|
||||
user = models.ForeignKey(settings.AUTH_USER_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)
|
||||
|
||||
|
||||
@reversion.register
|
||||
class EventAuthorisation(models.Model, RevisionMixin):
|
||||
event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE)
|
||||
email = models.EmailField()
|
||||
name = models.CharField(max_length=255)
|
||||
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
|
||||
account_code = models.CharField(max_length=50, blank=True, null=True)
|
||||
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
|
||||
sent_by = models.ForeignKey('RIGS.Profile', on_delete=models.CASCADE)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('event_detail', kwargs={'pk': self.event.pk})
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Invoice(models.Model):
|
||||
event = models.OneToOneField('Event')
|
||||
event = models.OneToOneField('Event', on_delete=models.CASCADE)
|
||||
invoice_date = models.DateField(auto_now_add=True)
|
||||
void = models.BooleanField(default=False)
|
||||
|
||||
@@ -555,7 +597,7 @@ class Payment(models.Model):
|
||||
(ADJUSTMENT, 'TEC Adjustment'),
|
||||
)
|
||||
|
||||
invoice = models.ForeignKey('Invoice')
|
||||
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
|
||||
date = models.DateField()
|
||||
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)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
from RIGS.models import Profile
|
||||
from RIGS.forms import ProfileRegistrationFormUniqueEmail
|
||||
from registration.signals import user_registered
|
||||
|
||||
|
||||
def user_created(sender, user, request, **kwargs):
|
||||
form = ProfileRegistrationFormUniqueEmail(request.POST)
|
||||
user.first_name = form.data['first_name']
|
||||
user.last_name = form.data['last_name']
|
||||
user.initials = form.data['initials']
|
||||
user.phone = form.data['phone']
|
||||
# user.phone = form.data['phone']
|
||||
user.save()
|
||||
|
||||
from registration.signals import user_registered
|
||||
|
||||
user_registered.connect(user_created)
|
||||
262
RIGS/rigboard.py
@@ -1,23 +1,31 @@
|
||||
import os
|
||||
import cStringIO as StringIO
|
||||
from io import BytesIO
|
||||
import urllib2
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||
from django.views import generic
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.urls import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from django.core import signing
|
||||
from django.http import HttpResponse
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
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 z3c.rml import rml2pdf
|
||||
from PyPDF2 import PdfFileMerger, PdfFileReader
|
||||
import simplejson
|
||||
import premailer
|
||||
|
||||
from RIGS import models, forms
|
||||
from PyRIGS import decorators
|
||||
import datetime
|
||||
import re
|
||||
import copy
|
||||
@@ -36,6 +44,7 @@ class RigboardIndex(generic.TemplateView):
|
||||
context['events'] = models.Event.objects.current_events()
|
||||
return context
|
||||
|
||||
|
||||
class WebCalendar(generic.TemplateView):
|
||||
template_name = 'RIGS/calendar.html'
|
||||
|
||||
@@ -45,6 +54,7 @@ class WebCalendar(generic.TemplateView):
|
||||
context['date'] = kwargs.get('date', '')
|
||||
return context
|
||||
|
||||
|
||||
class EventDetail(generic.DetailView):
|
||||
model = models.Event
|
||||
|
||||
@@ -53,7 +63,6 @@ class EventOembed(generic.View):
|
||||
model = models.Event
|
||||
|
||||
def get(self, request, pk=None):
|
||||
|
||||
embed_url = reverse('event_embed', args=[pk])
|
||||
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
||||
|
||||
@@ -72,6 +81,25 @@ 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)
|
||||
|
||||
|
||||
class EventCreate(generic.CreateView):
|
||||
model = models.Event
|
||||
form_class = forms.EventForm
|
||||
@@ -85,9 +113,8 @@ class EventCreate(generic.CreateView):
|
||||
if re.search('"-\d+"', form['items_json'].value()):
|
||||
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
|
||||
|
||||
|
||||
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
||||
for field, model in form.related_models.iteritems():
|
||||
for field, model in form.related_models.items():
|
||||
value = form[field].value()
|
||||
if value is not None and value != '':
|
||||
context[field] = model.objects.get(pk=value)
|
||||
@@ -108,23 +135,45 @@ class EventUpdate(generic.UpdateView):
|
||||
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.iteritems():
|
||||
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
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
if not hasattr(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.')
|
||||
|
||||
if hasattr(self.object, 'authorised'):
|
||||
messages.warning(self.request, 'This event has already been authorised by client, any changes to price will require reauthorisation.')
|
||||
return super(EventUpdate, self).render_to_response(context, **response_kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('event_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
class EventDuplicate(EventUpdate):
|
||||
def get_object(self, queryset=None):
|
||||
old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating)
|
||||
new = copy.copy(old) # Make a copy of the object in memory
|
||||
new.based_on = old # Make the new event based on the old event
|
||||
new.purchase_order = None
|
||||
new.purchase_order = None # Remove old PO
|
||||
|
||||
if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
|
||||
# Clear checked in by if it's a dry hire
|
||||
if new.dry_hire is True:
|
||||
new.checked_in_by = None
|
||||
|
||||
# Remove all the authorisation information from the new event
|
||||
new.auth_request_to = None
|
||||
new.auth_request_by = None
|
||||
new.auth_request_at = None
|
||||
|
||||
if self.request.method in (
|
||||
'POST', 'PUT'): # This only happens on save (otherwise items won't display in editor)
|
||||
new.pk = None # This means a new event will be created on save, and all items will be re-created
|
||||
else:
|
||||
messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.')
|
||||
@@ -136,17 +185,15 @@ class EventDuplicate(EventUpdate):
|
||||
context["duplicate"] = True
|
||||
return context
|
||||
|
||||
|
||||
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')
|
||||
copies = ('TEC', 'Client')
|
||||
|
||||
merger = PdfFileMerger()
|
||||
|
||||
for copy in copies:
|
||||
|
||||
context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this
|
||||
context = {
|
||||
'object': object,
|
||||
'fonts': {
|
||||
'opensans': {
|
||||
@@ -154,23 +201,18 @@ class EventPrint(generic.View):
|
||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||
}
|
||||
},
|
||||
'copy':copy,
|
||||
'quote': True,
|
||||
'current_user': request.user,
|
||||
})
|
||||
|
||||
# context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3
|
||||
}
|
||||
|
||||
rml = template.render(context)
|
||||
buffer = StringIO.StringIO()
|
||||
|
||||
buffer = rml2pdf.parseString(rml)
|
||||
|
||||
merger.append(PdfFileReader(buffer))
|
||||
|
||||
buffer.close()
|
||||
|
||||
terms = urllib2.urlopen(settings.TERMS_OF_HIRE_URL)
|
||||
merger.append(StringIO.StringIO(terms.read()))
|
||||
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
|
||||
merger.append(BytesIO(terms.read()))
|
||||
|
||||
merged = BytesIO()
|
||||
merger.write(merged)
|
||||
@@ -183,6 +225,7 @@ class EventPrint(generic.View):
|
||||
response.write(merged.getvalue())
|
||||
return response
|
||||
|
||||
|
||||
class EventArchive(generic.ArchiveIndexView):
|
||||
model = models.Event
|
||||
date_field = "start_date"
|
||||
@@ -219,3 +262,174 @@ class EventArchive(generic.ArchiveIndexView):
|
||||
messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.")
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class EventAuthorise(generic.UpdateView):
|
||||
template_name = 'RIGS/eventauthorisation_form.html'
|
||||
success_template = 'RIGS/eventauthorisation_success.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
|
||||
self.template_name = self.success_template
|
||||
messages.add_message(self.request, messages.SUCCESS,
|
||||
'Success! Your event has been authorised. ' +
|
||||
'You will also receive email confirmation to %s.' % (self.object.email))
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
@property
|
||||
def event(self):
|
||||
return models.Event.objects.select_related('organisation', 'person', 'venue').get(pk=self.kwargs['pk'])
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return getattr(self.event, 'authorisation', None)
|
||||
|
||||
def get_form_class(self):
|
||||
return forms.InternalClientEventAuthorisationForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventAuthorise, self).get_context_data(**kwargs)
|
||||
context['event'] = self.event
|
||||
|
||||
context['tos_url'] = settings.TERMS_OF_HIRE_URL
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if self.get_object() is not None and self.get_object().pk is not None:
|
||||
if self.event.authorised:
|
||||
messages.add_message(self.request, messages.WARNING,
|
||||
"This event has already been authorised. "
|
||||
"Reauthorising is not necessary at this time.")
|
||||
else:
|
||||
messages.add_message(self.request, messages.WARNING,
|
||||
"This event has already been authorised, but the amount has changed. " +
|
||||
"Please check the amount and reauthorise.")
|
||||
return super(EventAuthorise, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, **kwargs):
|
||||
form = super(EventAuthorise, self).get_form(**kwargs)
|
||||
form.instance.event = self.event
|
||||
form.instance.email = self.request.email
|
||||
form.instance.sent_by = self.request.sent_by
|
||||
return form
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
# Verify our signature matches up and all is well with the integrity of the URL
|
||||
try:
|
||||
data = signing.loads(kwargs.get('hmac'))
|
||||
assert int(kwargs.get('pk')) == int(data.get('pk'))
|
||||
request.email = data['email']
|
||||
request.sent_by = models.Profile.objects.get(pk=data['sent_by'])
|
||||
except (signing.BadSignature, AssertionError, KeyError, models.Profile.DoesNotExist):
|
||||
raise SuspiciousOperation(
|
||||
"This URL is invalid. Please ask your TEC contact for a new URL")
|
||||
return super(EventAuthorise, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin):
|
||||
model = models.Event
|
||||
form_class = forms.EventAuthorisationRequestForm
|
||||
template_name = 'RIGS/eventauthorisation_request.html'
|
||||
|
||||
@method_decorator(decorators.nottinghamtec_address_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(EventAuthorisationRequest, self).dispatch(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def object(self):
|
||||
return self.get_object()
|
||||
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
url = reverse_lazy('closemodal')
|
||||
messages.info(self.request, "location.reload()")
|
||||
else:
|
||||
url = reverse_lazy('event_detail', kwargs={
|
||||
'pk': self.object.pk,
|
||||
})
|
||||
messages.add_message(self.request, messages.SUCCESS, "Authorisation request successfully sent.")
|
||||
return url
|
||||
|
||||
def form_valid(self, form):
|
||||
email = form.cleaned_data['email']
|
||||
event = self.object
|
||||
event.auth_request_by = self.request.user
|
||||
event.auth_request_at = datetime.datetime.now()
|
||||
event.auth_request_to = email
|
||||
event.save()
|
||||
|
||||
context = {
|
||||
'object': self.object,
|
||||
'request': self.request,
|
||||
'hmac': signing.dumps({
|
||||
'pk': self.object.pk,
|
||||
'email': email,
|
||||
'sent_by': self.request.user.pk,
|
||||
}),
|
||||
}
|
||||
if event.person is not None and email == event.person.email:
|
||||
context['to_name'] = event.person.name
|
||||
elif event.organisation is not None and email == event.organisation.email:
|
||||
context['to_name'] = event.organisation.name
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
"N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name),
|
||||
get_template("RIGS/eventauthorisation_client_request.txt").render(context),
|
||||
to=[email],
|
||||
reply_to=[self.request.user.email],
|
||||
)
|
||||
css = staticfiles_storage.path('css/email.css')
|
||||
html = premailer.Premailer(get_template("RIGS/eventauthorisation_client_request.html").render(context),
|
||||
external_styles=css).transform()
|
||||
msg.attach_alternative(html, 'text/html')
|
||||
|
||||
msg.send()
|
||||
|
||||
return super(EventAuthorisationRequest, self).form_valid(form)
|
||||
|
||||
|
||||
class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
||||
template_name = "RIGS/eventauthorisation_client_request.html"
|
||||
model = models.Event
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
css = staticfiles_storage.path('css/email.css')
|
||||
response = super(EventAuthoriseRequestEmailPreview, self).render_to_response(context, **response_kwargs)
|
||||
assert isinstance(response, HttpResponse)
|
||||
response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventAuthoriseRequestEmailPreview, self).get_context_data(**kwargs)
|
||||
context['hmac'] = signing.dumps({
|
||||
'pk': self.object.pk,
|
||||
'email': self.request.GET.get('email', 'hello@world.test'),
|
||||
'sent_by': self.request.user.pk,
|
||||
})
|
||||
context['to_name'] = self.request.GET.get('to_name', None)
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class LogRiskAssessment(generic.View):
|
||||
http_method_names = ["post"]
|
||||
|
||||
def post(self, request, **kwargs):
|
||||
data = request.POST
|
||||
shared_secret = data.get("secret")
|
||||
edit_url = data.get("editUrl")
|
||||
rig_number = data.get("rigNum")
|
||||
if shared_secret is None or edit_url is None or rig_number is None:
|
||||
return HttpResponse(status=422)
|
||||
|
||||
if shared_secret != settings.RISK_ASSESSMENT_SECRET:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
rig_number = int(re.sub("[^0-9]", "", rig_number))
|
||||
|
||||
event = get_object_or_404(models.Event, pk=rig_number)
|
||||
event.risk_assessment_edit_url = edit_url
|
||||
event.save()
|
||||
|
||||
return HttpResponse(status=200)
|
||||
|
||||
140
RIGS/signals.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import datetime
|
||||
import re
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
from io import BytesIO
|
||||
|
||||
from django.db.models.signals import post_save
|
||||
from PyPDF2 import PdfFileReader, PdfFileMerger
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from registration.signals import user_activated
|
||||
from premailer import Premailer
|
||||
from z3c.rml import rml2pdf
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
def send_eventauthorisation_success_email(instance):
|
||||
# Generate PDF first to prevent context conflicts
|
||||
context = {
|
||||
'object': instance.event,
|
||||
'fonts': {
|
||||
'opensans': {
|
||||
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||
}
|
||||
},
|
||||
'receipt': True,
|
||||
'current_user': False,
|
||||
}
|
||||
|
||||
template = get_template('RIGS/event_print.xml')
|
||||
merger = PdfFileMerger()
|
||||
|
||||
rml = template.render(context)
|
||||
|
||||
buffer = rml2pdf.parseString(rml)
|
||||
merger.append(PdfFileReader(buffer))
|
||||
buffer.close()
|
||||
|
||||
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
|
||||
merger.append(BytesIO(terms.read()))
|
||||
|
||||
merged = BytesIO()
|
||||
merger.write(merged)
|
||||
|
||||
# Produce email content
|
||||
context = {
|
||||
'object': instance,
|
||||
}
|
||||
|
||||
if instance.event.person is not None and instance.email == instance.event.person.email:
|
||||
context['to_name'] = instance.event.person.name
|
||||
elif instance.event.organisation is not None and instance.email == instance.event.organisation.email:
|
||||
context['to_name'] = instance.event.organisation.name
|
||||
|
||||
subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name)
|
||||
|
||||
client_email = EmailMultiAlternatives(
|
||||
subject,
|
||||
get_template("RIGS/eventauthorisation_client_success.txt").render(context),
|
||||
to=[instance.email],
|
||||
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
||||
)
|
||||
|
||||
css = staticfiles_storage.path('css/email.css')
|
||||
html = Premailer(get_template("RIGS/eventauthorisation_client_success.html").render(context),
|
||||
external_styles=css).transform()
|
||||
client_email.attach_alternative(html, 'text/html')
|
||||
|
||||
escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', instance.event.name)
|
||||
|
||||
client_email.attach('N%05d - %s - CONFIRMATION.pdf' % (instance.event.pk, escapedEventName),
|
||||
merged.getvalue(),
|
||||
'application/pdf'
|
||||
)
|
||||
|
||||
if instance.event.mic:
|
||||
mic_email_address = instance.event.mic.email
|
||||
else:
|
||||
mic_email_address = settings.AUTHORISATION_NOTIFICATION_ADDRESS
|
||||
|
||||
mic_email = EmailMessage(
|
||||
subject,
|
||||
get_template("RIGS/eventauthorisation_mic_success.txt").render(context),
|
||||
to=[mic_email_address]
|
||||
)
|
||||
|
||||
# Now we have both emails successfully generated, send them out
|
||||
client_email.send(fail_silently=True)
|
||||
mic_email.send(fail_silently=True)
|
||||
|
||||
# Set event to booked now that it's authorised
|
||||
instance.event.status = models.Event.BOOKED
|
||||
instance.event.save()
|
||||
|
||||
|
||||
def on_revision_commit(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
send_eventauthorisation_success_email(instance)
|
||||
|
||||
|
||||
post_save.connect(on_revision_commit, sender=models.EventAuthorisation)
|
||||
|
||||
|
||||
def send_admin_awaiting_approval_email(user, request, **kwargs):
|
||||
# Bit more controlled than just emailing all superusers
|
||||
for admin in models.Profile.admins():
|
||||
# Check we've ever emailed them before and if so, if cooldown has passed.
|
||||
if admin.last_emailed is None or admin.last_emailed + settings.EMAIL_COOLDOWN <= timezone.now():
|
||||
context = {
|
||||
'request': request,
|
||||
'link_suffix': reverse("admin:RIGS_profile_changelist") + '?is_approved__exact=0',
|
||||
'number_of_users': models.Profile.users_awaiting_approval_count(),
|
||||
'to_name': admin.first_name
|
||||
}
|
||||
|
||||
email = EmailMultiAlternatives(
|
||||
"%s new users awaiting approval on RIGS" % (context['number_of_users']),
|
||||
get_template("RIGS/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),
|
||||
external_styles=css).transform()
|
||||
email.attach_alternative(html, 'text/html')
|
||||
email.send()
|
||||
|
||||
# Update last sent
|
||||
admin.last_emailed = timezone.now()
|
||||
admin.save()
|
||||
|
||||
|
||||
user_activated.connect(send_admin_awaiting_approval_email)
|
||||
6
RIGS/static/css/bootstrap-select.min.css
vendored
Normal file → Executable file
1
RIGS/static/css/email.css
Normal file
@@ -0,0 +1 @@
|
||||
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}
|
||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.2 KiB |
162
RIGS/static/js/affix.js
Executable file
@@ -0,0 +1,162 @@
|
||||
/* ========================================================================
|
||||
* 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);
|
||||
80
RIGS/static/js/ajax-bootstrap-select.js
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.1
|
||||
* @author Adam Heim - https://github.com/truckingsim
|
||||
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||
* @copyright 2015 Adam Heim
|
||||
* @copyright 2017 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: 2017-07-21 1:08:54 PM GMT-0400
|
||||
*/
|
||||
!(function ($, window) {
|
||||
|
||||
@@ -186,10 +186,25 @@ var AjaxBootstrapSelect = function (element, options) {
|
||||
if (dataKeys.length) {
|
||||
// Object containing the data attribute options.
|
||||
var dataOptions = {};
|
||||
var flattenedOptions = ['locale'];
|
||||
for (i = 0, l = dataKeys.length; i < l; i++) {
|
||||
var name = dataKeys[i].replace(/^abs([A-Z])/, matchToLowerCase).replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
var keys = name.split('-');
|
||||
|
||||
// Certain options should be flattened to a single object
|
||||
// and not fully expanded (such as Locale).
|
||||
if (keys[0] && keys.length > 1 && flattenedOptions.indexOf(keys[0]) !== -1) {
|
||||
var newKeys = [keys.shift()];
|
||||
var property = '';
|
||||
// Combine the remaining keys as a single property.
|
||||
for (var ii = 0; ii < keys.length; ii++) {
|
||||
property += (ii === 0 ? keys[ii] : keys[ii].charAt(0).toUpperCase() + keys[ii].slice(1));
|
||||
}
|
||||
newKeys.push(property);
|
||||
keys = newKeys;
|
||||
}
|
||||
this.log(this.LOG_DEBUG, 'Processing data attribute "data-abs-' + name + '":', data[dataKeys[i]]);
|
||||
expandObject(name.split('-'), data[dataKeys[i]], dataOptions);
|
||||
expandObject(keys, data[dataKeys[i]], dataOptions);
|
||||
}
|
||||
this.options = $.extend(true, {}, this.options, dataOptions);
|
||||
this.log(this.LOG_DEBUG, 'Merged in the data attribute options: ', dataOptions, this.options);
|
||||
@@ -316,6 +331,12 @@ AjaxBootstrapSelect.prototype.init = function () {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't process if below minimum query length
|
||||
if (query.length < plugin.options.minLength) {
|
||||
plugin.list.setStatus(plugin.t('statusTooShort'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear out any existing timer.
|
||||
clearTimeout(requestDelayTimer);
|
||||
|
||||
@@ -573,8 +594,25 @@ var AjaxBootstrapSelectList = function (plugin) {
|
||||
this.title = null;
|
||||
this.selectedTextFormat = plugin.selectpicker.options.selectedTextFormat;
|
||||
|
||||
// Save initial options
|
||||
var initial_options = [];
|
||||
plugin.$element.find('option').each(function() {
|
||||
var $option = $(this);
|
||||
var value = $option.attr('value');
|
||||
initial_options.push({
|
||||
value: value,
|
||||
text: $option.text(),
|
||||
'class': $option.attr('class') || '',
|
||||
data: $option.data() || {},
|
||||
preserved: plugin.options.preserveSelected,
|
||||
selected: !!$option.attr('selected')
|
||||
});
|
||||
});
|
||||
this.cacheSet(/*query=*/'', initial_options);
|
||||
|
||||
// Preserve selected options.
|
||||
if (plugin.options.preserveSelected) {
|
||||
that.selected = initial_options;
|
||||
plugin.$element.on('change.abs.preserveSelected', function (e) {
|
||||
var $selected = plugin.$element.find(':selected');
|
||||
that.selected = [];
|
||||
@@ -644,7 +682,7 @@ AjaxBootstrapSelectList.prototype.build = function (data) {
|
||||
}
|
||||
|
||||
// Set various properties.
|
||||
$option.val(item.value).text(item.text);
|
||||
$option.val(item.value).text(item.text).attr('title', item.text);
|
||||
if (item['class'].length) {
|
||||
$option.attr('class', item['class']);
|
||||
}
|
||||
@@ -732,7 +770,13 @@ AjaxBootstrapSelectList.prototype.refresh = function (triggerChange) {
|
||||
if (!this.plugin.$element.find('option').length && emptyTitle && emptyTitle.length) {
|
||||
this.setTitle(emptyTitle);
|
||||
}
|
||||
else if (this.title) {
|
||||
else if (
|
||||
this.title ||
|
||||
(
|
||||
this.selectedTextFormat !== 'static' &&
|
||||
this.selectedTextFormat !== this.plugin.selectpicker.options.selectedTextFormat
|
||||
)
|
||||
) {
|
||||
this.restoreTitle();
|
||||
}
|
||||
this.plugin.selectpicker.refresh();
|
||||
@@ -1224,6 +1268,14 @@ $.fn.ajaxSelectPicker.defaults = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @member $.fn.ajaxSelectPicker.defaults
|
||||
* @cfg {Number} minLength = 0
|
||||
* @markdown
|
||||
* Invoke a request for empty search values.
|
||||
*/
|
||||
minLength: 0,
|
||||
|
||||
/**
|
||||
* @member $.fn.ajaxSelectPicker.defaults
|
||||
* @cfg {String} ajaxSearchUrl
|
||||
@@ -1296,8 +1348,7 @@ $.fn.ajaxSelectPicker.defaults = {
|
||||
* 39: "right",
|
||||
* 38: "up",
|
||||
* 40: "down",
|
||||
* 91: "meta",
|
||||
* 229: "unknown"
|
||||
* 91: "meta"
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@@ -1311,8 +1362,7 @@ $.fn.ajaxSelectPicker.defaults = {
|
||||
39: "right",
|
||||
38: "up",
|
||||
40: "down",
|
||||
91: "meta",
|
||||
229: "unknown"
|
||||
91: "meta"
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1547,7 +1597,15 @@ $.fn.ajaxSelectPicker.locale['en-US'] = {
|
||||
* @markdown
|
||||
* The text to use in the status container when a request is being initiated.
|
||||
*/
|
||||
statusSearching: 'Searching...'
|
||||
statusSearching: 'Searching...',
|
||||
|
||||
/**
|
||||
* @member $.fn.ajaxSelectPicker.locale
|
||||
* @cfg {String} statusToShort = 'Please enter more characters'
|
||||
* @markdown
|
||||
* The text used in the status container when the request returns no results.
|
||||
*/
|
||||
statusTooShort: 'Please enter more characters'
|
||||
};
|
||||
$.fn.ajaxSelectPicker.locale.en = $.fn.ajaxSelectPicker.locale['en-US'];
|
||||
|
||||
|
||||
94
RIGS/static/js/alert.js
Executable file
@@ -0,0 +1,94 @@
|
||||
/* ========================================================================
|
||||
* 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);
|
||||
1443
RIGS/static/js/bootstrap-select.js
vendored
125
RIGS/static/js/button.js
Executable file
@@ -0,0 +1,125 @@
|
||||
/* ========================================================================
|
||||
* 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);
|
||||
237
RIGS/static/js/carousel.js
Executable file
@@ -0,0 +1,237 @@
|
||||
/* ========================================================================
|
||||
* 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);
|
||||
212
RIGS/static/js/collapse.js
Executable file
@@ -0,0 +1,212 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: collapse.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#collapse
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
/* jshint latedef: false */
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// COLLAPSE PUBLIC CLASS DEFINITION
|
||||
// ================================
|
||||
|
||||
var Collapse = function (element, options) {
|
||||
this.$element = $(element)
|
||||
this.options = $.extend({}, Collapse.DEFAULTS, options)
|
||||
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
|
||||
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
|
||||
this.transitioning = null
|
||||
|
||||
if (this.options.parent) {
|
||||
this.$parent = this.getParent()
|
||||
} else {
|
||||
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
|
||||
}
|
||||
|
||||
if (this.options.toggle) this.toggle()
|
||||
}
|
||||
|
||||
Collapse.VERSION = '3.3.7'
|
||||
|
||||
Collapse.TRANSITION_DURATION = 350
|
||||
|
||||
Collapse.DEFAULTS = {
|
||||
toggle: true
|
||||
}
|
||||
|
||||
Collapse.prototype.dimension = function () {
|
||||
var hasWidth = this.$element.hasClass('width')
|
||||
return hasWidth ? 'width' : 'height'
|
||||
}
|
||||
|
||||
Collapse.prototype.show = function () {
|
||||
if (this.transitioning || this.$element.hasClass('in')) return
|
||||
|
||||
var activesData
|
||||
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
|
||||
|
||||
if (actives && actives.length) {
|
||||
activesData = actives.data('bs.collapse')
|
||||
if (activesData && activesData.transitioning) return
|
||||
}
|
||||
|
||||
var startEvent = $.Event('show.bs.collapse')
|
||||
this.$element.trigger(startEvent)
|
||||
if (startEvent.isDefaultPrevented()) return
|
||||
|
||||
if (actives && actives.length) {
|
||||
Plugin.call(actives, 'hide')
|
||||
activesData || actives.data('bs.collapse', null)
|
||||
}
|
||||
|
||||
var dimension = this.dimension()
|
||||
|
||||
this.$element
|
||||
.removeClass('collapse')
|
||||
.addClass('collapsing')[dimension](0)
|
||||
.attr('aria-expanded', true)
|
||||
|
||||
this.$trigger
|
||||
.removeClass('collapsed')
|
||||
.attr('aria-expanded', true)
|
||||
|
||||
this.transitioning = 1
|
||||
|
||||
var complete = function () {
|
||||
this.$element
|
||||
.removeClass('collapsing')
|
||||
.addClass('collapse in')[dimension]('')
|
||||
this.transitioning = 0
|
||||
this.$element
|
||||
.trigger('shown.bs.collapse')
|
||||
}
|
||||
|
||||
if (!$.support.transition) return complete.call(this)
|
||||
|
||||
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
|
||||
|
||||
this.$element
|
||||
.one('bsTransitionEnd', $.proxy(complete, this))
|
||||
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
|
||||
}
|
||||
|
||||
Collapse.prototype.hide = function () {
|
||||
if (this.transitioning || !this.$element.hasClass('in')) return
|
||||
|
||||
var startEvent = $.Event('hide.bs.collapse')
|
||||
this.$element.trigger(startEvent)
|
||||
if (startEvent.isDefaultPrevented()) return
|
||||
|
||||
var dimension = this.dimension()
|
||||
|
||||
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
|
||||
|
||||
this.$element
|
||||
.addClass('collapsing')
|
||||
.removeClass('collapse in')
|
||||
.attr('aria-expanded', false)
|
||||
|
||||
this.$trigger
|
||||
.addClass('collapsed')
|
||||
.attr('aria-expanded', false)
|
||||
|
||||
this.transitioning = 1
|
||||
|
||||
var complete = function () {
|
||||
this.transitioning = 0
|
||||
this.$element
|
||||
.removeClass('collapsing')
|
||||
.addClass('collapse')
|
||||
.trigger('hidden.bs.collapse')
|
||||
}
|
||||
|
||||
if (!$.support.transition) return complete.call(this)
|
||||
|
||||
this.$element
|
||||
[dimension](0)
|
||||
.one('bsTransitionEnd', $.proxy(complete, this))
|
||||
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
|
||||
}
|
||||
|
||||
Collapse.prototype.toggle = function () {
|
||||
this[this.$element.hasClass('in') ? 'hide' : 'show']()
|
||||
}
|
||||
|
||||
Collapse.prototype.getParent = function () {
|
||||
return $(this.options.parent)
|
||||
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
|
||||
.each($.proxy(function (i, element) {
|
||||
var $element = $(element)
|
||||
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
|
||||
}, this))
|
||||
.end()
|
||||
}
|
||||
|
||||
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
|
||||
var isOpen = $element.hasClass('in')
|
||||
|
||||
$element.attr('aria-expanded', isOpen)
|
||||
$trigger
|
||||
.toggleClass('collapsed', !isOpen)
|
||||
.attr('aria-expanded', isOpen)
|
||||
}
|
||||
|
||||
function getTargetFromTrigger($trigger) {
|
||||
var href
|
||||
var target = $trigger.attr('data-target')
|
||||
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
|
||||
|
||||
return $(target)
|
||||
}
|
||||
|
||||
|
||||
// COLLAPSE PLUGIN DEFINITION
|
||||
// ==========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.collapse')
|
||||
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
|
||||
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
|
||||
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.collapse
|
||||
|
||||
$.fn.collapse = Plugin
|
||||
$.fn.collapse.Constructor = Collapse
|
||||
|
||||
|
||||
// COLLAPSE NO CONFLICT
|
||||
// ====================
|
||||
|
||||
$.fn.collapse.noConflict = function () {
|
||||
$.fn.collapse = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// COLLAPSE DATA-API
|
||||
// =================
|
||||
|
||||
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
|
||||
var $this = $(this)
|
||||
|
||||
if (!$this.attr('data-target')) e.preventDefault()
|
||||
|
||||
var $target = getTargetFromTrigger($this)
|
||||
var data = $target.data('bs.collapse')
|
||||
var option = data ? 'toggle' : $this.data()
|
||||
|
||||
Plugin.call($target, option)
|
||||
})
|
||||
|
||||
}(jQuery);
|
||||
165
RIGS/static/js/dropdown.js
Executable file
@@ -0,0 +1,165 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: dropdown.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#dropdowns
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// DROPDOWN CLASS DEFINITION
|
||||
// =========================
|
||||
|
||||
var backdrop = '.dropdown-backdrop'
|
||||
var toggle = '[data-toggle="dropdown"]'
|
||||
var Dropdown = function (element) {
|
||||
$(element).on('click.bs.dropdown', this.toggle)
|
||||
}
|
||||
|
||||
Dropdown.VERSION = '3.3.7'
|
||||
|
||||
function getParent($this) {
|
||||
var selector = $this.attr('data-target')
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
|
||||
}
|
||||
|
||||
var $parent = selector && $(selector)
|
||||
|
||||
return $parent && $parent.length ? $parent : $this.parent()
|
||||
}
|
||||
|
||||
function clearMenus(e) {
|
||||
if (e && e.which === 3) return
|
||||
$(backdrop).remove()
|
||||
$(toggle).each(function () {
|
||||
var $this = $(this)
|
||||
var $parent = getParent($this)
|
||||
var relatedTarget = { relatedTarget: this }
|
||||
|
||||
if (!$parent.hasClass('open')) return
|
||||
|
||||
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
|
||||
|
||||
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
|
||||
|
||||
if (e.isDefaultPrevented()) return
|
||||
|
||||
$this.attr('aria-expanded', 'false')
|
||||
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
|
||||
})
|
||||
}
|
||||
|
||||
Dropdown.prototype.toggle = function (e) {
|
||||
var $this = $(this)
|
||||
|
||||
if ($this.is('.disabled, :disabled')) return
|
||||
|
||||
var $parent = getParent($this)
|
||||
var isActive = $parent.hasClass('open')
|
||||
|
||||
clearMenus()
|
||||
|
||||
if (!isActive) {
|
||||
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
|
||||
// if mobile we use a backdrop because click events don't delegate
|
||||
$(document.createElement('div'))
|
||||
.addClass('dropdown-backdrop')
|
||||
.insertAfter($(this))
|
||||
.on('click', clearMenus)
|
||||
}
|
||||
|
||||
var relatedTarget = { relatedTarget: this }
|
||||
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
|
||||
|
||||
if (e.isDefaultPrevented()) return
|
||||
|
||||
$this
|
||||
.trigger('focus')
|
||||
.attr('aria-expanded', 'true')
|
||||
|
||||
$parent
|
||||
.toggleClass('open')
|
||||
.trigger($.Event('shown.bs.dropdown', relatedTarget))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Dropdown.prototype.keydown = function (e) {
|
||||
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
|
||||
|
||||
var $this = $(this)
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
if ($this.is('.disabled, :disabled')) return
|
||||
|
||||
var $parent = getParent($this)
|
||||
var isActive = $parent.hasClass('open')
|
||||
|
||||
if (!isActive && e.which != 27 || isActive && e.which == 27) {
|
||||
if (e.which == 27) $parent.find(toggle).trigger('focus')
|
||||
return $this.trigger('click')
|
||||
}
|
||||
|
||||
var desc = ' li:not(.disabled):visible a'
|
||||
var $items = $parent.find('.dropdown-menu' + desc)
|
||||
|
||||
if (!$items.length) return
|
||||
|
||||
var index = $items.index(e.target)
|
||||
|
||||
if (e.which == 38 && index > 0) index-- // up
|
||||
if (e.which == 40 && index < $items.length - 1) index++ // down
|
||||
if (!~index) index = 0
|
||||
|
||||
$items.eq(index).trigger('focus')
|
||||
}
|
||||
|
||||
|
||||
// DROPDOWN PLUGIN DEFINITION
|
||||
// ==========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.dropdown')
|
||||
|
||||
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
|
||||
if (typeof option == 'string') data[option].call($this)
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.dropdown
|
||||
|
||||
$.fn.dropdown = Plugin
|
||||
$.fn.dropdown.Constructor = Dropdown
|
||||
|
||||
|
||||
// DROPDOWN NO CONFLICT
|
||||
// ====================
|
||||
|
||||
$.fn.dropdown.noConflict = function () {
|
||||
$.fn.dropdown = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// APPLY TO STANDARD DROPDOWN ELEMENTS
|
||||
// ===================================
|
||||
|
||||
$(document)
|
||||
.on('click.bs.dropdown.data-api', clearMenus)
|
||||
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
|
||||
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
||||
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
|
||||
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
|
||||
|
||||
}(jQuery);
|
||||
@@ -47,7 +47,7 @@ $('#item-table').on('click', '.item-add', function () {
|
||||
$('#item_quantity').val('');
|
||||
$('#item_cost').val('');
|
||||
|
||||
$($(this).data('target'));
|
||||
$($(this).data('target')).modal('show');
|
||||
});
|
||||
|
||||
$('#item-table').on('click', '.item-edit', function () {
|
||||
@@ -62,7 +62,7 @@ $('#item-table').on('click', '.item-edit', function () {
|
||||
$('#item_quantity').val(fields.quantity);
|
||||
$('#item_cost').val(fields.cost);
|
||||
|
||||
$($(this).data('target'));
|
||||
$($(this).data('target')).modal('show');
|
||||
});
|
||||
|
||||
$('body').on('submit', '#item-form', function (e) {
|
||||
|
||||
339
RIGS/static/js/modal.js
Executable file
@@ -0,0 +1,339 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: modal.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#modals
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// MODAL CLASS DEFINITION
|
||||
// ======================
|
||||
|
||||
var Modal = function (element, options) {
|
||||
this.options = options
|
||||
this.$body = $(document.body)
|
||||
this.$element = $(element)
|
||||
this.$dialog = this.$element.find('.modal-dialog')
|
||||
this.$backdrop = null
|
||||
this.isShown = null
|
||||
this.originalBodyPad = null
|
||||
this.scrollbarWidth = 0
|
||||
this.ignoreBackdropClick = false
|
||||
|
||||
if (this.options.remote) {
|
||||
this.$element
|
||||
.find('.modal-content')
|
||||
.load(this.options.remote, $.proxy(function () {
|
||||
this.$element.trigger('loaded.bs.modal')
|
||||
}, this))
|
||||
}
|
||||
}
|
||||
|
||||
Modal.VERSION = '3.3.7'
|
||||
|
||||
Modal.TRANSITION_DURATION = 300
|
||||
Modal.BACKDROP_TRANSITION_DURATION = 150
|
||||
|
||||
Modal.DEFAULTS = {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
show: true
|
||||
}
|
||||
|
||||
Modal.prototype.toggle = function (_relatedTarget) {
|
||||
return this.isShown ? this.hide() : this.show(_relatedTarget)
|
||||
}
|
||||
|
||||
Modal.prototype.show = function (_relatedTarget) {
|
||||
var that = this
|
||||
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
||||
|
||||
this.$element.trigger(e)
|
||||
|
||||
if (this.isShown || e.isDefaultPrevented()) return
|
||||
|
||||
this.isShown = true
|
||||
|
||||
this.checkScrollbar()
|
||||
this.setScrollbar()
|
||||
this.$body.addClass('modal-open')
|
||||
|
||||
this.escape()
|
||||
this.resize()
|
||||
|
||||
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
|
||||
|
||||
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
|
||||
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
|
||||
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
|
||||
})
|
||||
})
|
||||
|
||||
this.backdrop(function () {
|
||||
var transition = $.support.transition && that.$element.hasClass('fade')
|
||||
|
||||
if (!that.$element.parent().length) {
|
||||
that.$element.appendTo(that.$body) // don't move modals dom position
|
||||
}
|
||||
|
||||
that.$element
|
||||
.show()
|
||||
.scrollTop(0)
|
||||
|
||||
that.adjustDialog()
|
||||
|
||||
if (transition) {
|
||||
that.$element[0].offsetWidth // force reflow
|
||||
}
|
||||
|
||||
that.$element.addClass('in')
|
||||
|
||||
that.enforceFocus()
|
||||
|
||||
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
|
||||
|
||||
transition ?
|
||||
that.$dialog // wait for modal to slide in
|
||||
.one('bsTransitionEnd', function () {
|
||||
that.$element.trigger('focus').trigger(e)
|
||||
})
|
||||
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
|
||||
that.$element.trigger('focus').trigger(e)
|
||||
})
|
||||
}
|
||||
|
||||
Modal.prototype.hide = function (e) {
|
||||
if (e) e.preventDefault()
|
||||
|
||||
e = $.Event('hide.bs.modal')
|
||||
|
||||
this.$element.trigger(e)
|
||||
|
||||
if (!this.isShown || e.isDefaultPrevented()) return
|
||||
|
||||
this.isShown = false
|
||||
|
||||
this.escape()
|
||||
this.resize()
|
||||
|
||||
$(document).off('focusin.bs.modal')
|
||||
|
||||
this.$element
|
||||
.removeClass('in')
|
||||
.off('click.dismiss.bs.modal')
|
||||
.off('mouseup.dismiss.bs.modal')
|
||||
|
||||
this.$dialog.off('mousedown.dismiss.bs.modal')
|
||||
|
||||
$.support.transition && this.$element.hasClass('fade') ?
|
||||
this.$element
|
||||
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
|
||||
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
|
||||
this.hideModal()
|
||||
}
|
||||
|
||||
Modal.prototype.enforceFocus = function () {
|
||||
$(document)
|
||||
.off('focusin.bs.modal') // guard against infinite focus loop
|
||||
.on('focusin.bs.modal', $.proxy(function (e) {
|
||||
if (document !== e.target &&
|
||||
this.$element[0] !== e.target &&
|
||||
!this.$element.has(e.target).length) {
|
||||
this.$element.trigger('focus')
|
||||
}
|
||||
}, this))
|
||||
}
|
||||
|
||||
Modal.prototype.escape = function () {
|
||||
if (this.isShown && this.options.keyboard) {
|
||||
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
|
||||
e.which == 27 && this.hide()
|
||||
}, this))
|
||||
} else if (!this.isShown) {
|
||||
this.$element.off('keydown.dismiss.bs.modal')
|
||||
}
|
||||
}
|
||||
|
||||
Modal.prototype.resize = function () {
|
||||
if (this.isShown) {
|
||||
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
|
||||
} else {
|
||||
$(window).off('resize.bs.modal')
|
||||
}
|
||||
}
|
||||
|
||||
Modal.prototype.hideModal = function () {
|
||||
var that = this
|
||||
this.$element.hide()
|
||||
this.backdrop(function () {
|
||||
that.$body.removeClass('modal-open')
|
||||
that.resetAdjustments()
|
||||
that.resetScrollbar()
|
||||
that.$element.trigger('hidden.bs.modal')
|
||||
})
|
||||
}
|
||||
|
||||
Modal.prototype.removeBackdrop = function () {
|
||||
this.$backdrop && this.$backdrop.remove()
|
||||
this.$backdrop = null
|
||||
}
|
||||
|
||||
Modal.prototype.backdrop = function (callback) {
|
||||
var that = this
|
||||
var animate = this.$element.hasClass('fade') ? 'fade' : ''
|
||||
|
||||
if (this.isShown && this.options.backdrop) {
|
||||
var doAnimate = $.support.transition && animate
|
||||
|
||||
this.$backdrop = $(document.createElement('div'))
|
||||
.addClass('modal-backdrop ' + animate)
|
||||
.appendTo(this.$body)
|
||||
|
||||
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
|
||||
if (this.ignoreBackdropClick) {
|
||||
this.ignoreBackdropClick = false
|
||||
return
|
||||
}
|
||||
if (e.target !== e.currentTarget) return
|
||||
this.options.backdrop == 'static'
|
||||
? this.$element[0].focus()
|
||||
: this.hide()
|
||||
}, this))
|
||||
|
||||
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
|
||||
|
||||
this.$backdrop.addClass('in')
|
||||
|
||||
if (!callback) return
|
||||
|
||||
doAnimate ?
|
||||
this.$backdrop
|
||||
.one('bsTransitionEnd', callback)
|
||||
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
|
||||
callback()
|
||||
|
||||
} else if (!this.isShown && this.$backdrop) {
|
||||
this.$backdrop.removeClass('in')
|
||||
|
||||
var callbackRemove = function () {
|
||||
that.removeBackdrop()
|
||||
callback && callback()
|
||||
}
|
||||
$.support.transition && this.$element.hasClass('fade') ?
|
||||
this.$backdrop
|
||||
.one('bsTransitionEnd', callbackRemove)
|
||||
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
|
||||
callbackRemove()
|
||||
|
||||
} else if (callback) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// these following methods are used to handle overflowing modals
|
||||
|
||||
Modal.prototype.handleUpdate = function () {
|
||||
this.adjustDialog()
|
||||
}
|
||||
|
||||
Modal.prototype.adjustDialog = function () {
|
||||
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
||||
|
||||
this.$element.css({
|
||||
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
||||
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
||||
})
|
||||
}
|
||||
|
||||
Modal.prototype.resetAdjustments = function () {
|
||||
this.$element.css({
|
||||
paddingLeft: '',
|
||||
paddingRight: ''
|
||||
})
|
||||
}
|
||||
|
||||
Modal.prototype.checkScrollbar = function () {
|
||||
var fullWindowWidth = window.innerWidth
|
||||
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
|
||||
var documentElementRect = document.documentElement.getBoundingClientRect()
|
||||
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
|
||||
}
|
||||
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
|
||||
this.scrollbarWidth = this.measureScrollbar()
|
||||
}
|
||||
|
||||
Modal.prototype.setScrollbar = function () {
|
||||
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
|
||||
this.originalBodyPad = document.body.style.paddingRight || ''
|
||||
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
|
||||
}
|
||||
|
||||
Modal.prototype.resetScrollbar = function () {
|
||||
this.$body.css('padding-right', this.originalBodyPad)
|
||||
}
|
||||
|
||||
Modal.prototype.measureScrollbar = function () { // thx walsh
|
||||
var scrollDiv = document.createElement('div')
|
||||
scrollDiv.className = 'modal-scrollbar-measure'
|
||||
this.$body.append(scrollDiv)
|
||||
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
|
||||
this.$body[0].removeChild(scrollDiv)
|
||||
return scrollbarWidth
|
||||
}
|
||||
|
||||
|
||||
// MODAL PLUGIN DEFINITION
|
||||
// =======================
|
||||
|
||||
function Plugin(option, _relatedTarget) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.modal')
|
||||
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
|
||||
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
||||
if (typeof option == 'string') data[option](_relatedTarget)
|
||||
else if (options.show) data.show(_relatedTarget)
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.modal
|
||||
|
||||
$.fn.modal = Plugin
|
||||
$.fn.modal.Constructor = Modal
|
||||
|
||||
|
||||
// MODAL NO CONFLICT
|
||||
// =================
|
||||
|
||||
$.fn.modal.noConflict = function () {
|
||||
$.fn.modal = old
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// MODAL DATA-API
|
||||
// ==============
|
||||
|
||||
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
|
||||
var $this = $(this)
|
||||
var href = $this.attr('href')
|
||||
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
|
||||
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
|
||||
|
||||
if ($this.is('a')) e.preventDefault()
|
||||
|
||||
$target.one('show.bs.modal', function (showEvent) {
|
||||
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
|
||||
$target.one('hidden.bs.modal', function () {
|
||||
$this.is(':visible') && $this.trigger('focus')
|
||||
})
|
||||
})
|
||||
Plugin.call($target, option, this)
|
||||
})
|
||||
|
||||
}(jQuery);
|
||||
108
RIGS/static/js/popover.js
Executable file
@@ -0,0 +1,108 @@
|
||||
/* ========================================================================
|
||||
* 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);
|
||||
172
RIGS/static/js/scrollspy.js
Executable file
@@ -0,0 +1,172 @@
|
||||
/* ========================================================================
|
||||
* 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);
|
||||
155
RIGS/static/js/tab.js
Executable file
@@ -0,0 +1,155 @@
|
||||
/* ========================================================================
|
||||
* 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);
|
||||
52
RIGS/static/js/tests/index.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!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>
|
||||
25
RIGS/static/js/tests/unit/affix.js
Normal file
@@ -0,0 +1,25 @@
|
||||
$(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')
|
||||
})
|
||||
|
||||
})
|
||||
62
RIGS/static/js/tests/unit/alert.js
Normal file
@@ -0,0 +1,62 @@
|
||||
$(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')
|
||||
})
|
||||
|
||||
})
|
||||
116
RIGS/static/js/tests/unit/button.js
Normal file
@@ -0,0 +1,116 @@
|
||||
$(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')
|
||||
})
|
||||
|
||||
})
|
||||
87
RIGS/static/js/tests/unit/carousel.js
Normal file
@@ -0,0 +1,87 @@
|
||||
$(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();
|
||||
})
|
||||
})
|
||||
164
RIGS/static/js/tests/unit/collapse.js
Normal file
@@ -0,0 +1,164 @@
|
||||
$(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()
|
||||
})
|
||||
|
||||
})
|
||||
219
RIGS/static/js/tests/unit/dropdown.js
Normal file
@@ -0,0 +1,219 @@
|
||||
$(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()
|
||||
})
|
||||
|
||||
})
|
||||
196
RIGS/static/js/tests/unit/modal.js
Normal file
@@ -0,0 +1,196 @@
|
||||
$(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()
|
||||
})
|
||||
})
|
||||
69
RIGS/static/js/tests/unit/phantom.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
|
||||
}())
|
||||
133
RIGS/static/js/tests/unit/popover.js
Normal file
@@ -0,0 +1,133 @@
|
||||
$(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')
|
||||
})
|
||||
|
||||
})
|
||||
37
RIGS/static/js/tests/unit/scrollspy.js
Normal file
@@ -0,0 +1,37 @@
|
||||
$(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))
|
||||
})
|
||||
|
||||
})
|
||||