mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-18 12:29:41 +00:00
Compare commits
76 Commits
bugfix-dat
...
aa51ce7861
| Author | SHA1 | Date | |
|---|---|---|---|
| aa51ce7861 | |||
| cdc8635e2f | |||
| 5d07a1853d | |||
| 1eed448828 | |||
| e2e83e2cd8 | |||
| 534e1000d4 | |||
| 84119f6685 | |||
| e50cf8d06c | |||
| a3970cf553 | |||
| 4b8639faf4 | |||
| 6a4620a2e5 | |||
| cc233e0223 | |||
| 2351201e13 | |||
| 874b8ef37a | |||
| 6a0b00dc76 | |||
| 1c31608951 | |||
| 37c0890a4b | |||
| c772744a4b | |||
| 5319c2f6e3 | |||
| aa525372a2 | |||
| 2774690b59 | |||
| 81733d32ba | |||
| e6527db6f7 | |||
| d109ee231f | |||
| cde46cc8e4 | |||
| dddf0dc42a | |||
| 19c5f282d2 | |||
| d5d2e9167c | |||
| 3b3ba0b87e | |||
| 1fbe59dfb0 | |||
| 65c5307072 | |||
| 638816535e | |||
| 8346d705ba | |||
| 18eed1a654 | |||
| 5174a442bc | |||
| 5f1fd59dd2 | |||
| 3be2a9f4b5 | |||
| 883ef4ed8b | |||
| 2195a60438 | |||
| 12c4b63947 | |||
| 39ab9df836 | |||
| a99e4b1d1c | |||
| 18a091f8ea | |||
| d64d0f54d4 | |||
| 8f54897b69 | |||
| 97830596b5 | |||
| 68f401097d | |||
| 8c5b2f426d | |||
| e3fc05772e | |||
| e38205b9e7 | |||
| f2b9642772 | |||
| 08939a0e1f | |||
| 15b7f9c7c1 | |||
| c67f7e1e54 | |||
| 4f268b3168 | |||
| 0e7723828a | |||
| c46a2c53f3 | |||
| b3617a74bf | |||
| 5806cbc72d | |||
| ab2ff9f146 | |||
| fd5575a818 | |||
| 402a1dd7f0 | |||
| be7688aa75 | |||
| a472e414f7 | |||
| 618c02aa9c | |||
| 3056b6ef71 | |||
| 548c960996 | |||
| 2cc43dcdb7 | |||
| 6db25fb56b | |||
| 8ad629a47e | |||
|
|
2414eb9724 | ||
| 3414204209 | |||
|
099a184f2e
|
|||
|
|
0e67da82e2 | ||
| 02e8e8aaf7 | |||
|
|
4acd9156d0 |
@@ -1,32 +1,16 @@
|
||||
---
|
||||
engines:
|
||||
version: 2
|
||||
plugins:
|
||||
csslint:
|
||||
enabled: true
|
||||
duplication:
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
- ruby
|
||||
- javascript
|
||||
- python
|
||||
- php
|
||||
eslint:
|
||||
enabled: true
|
||||
fixme:
|
||||
enabled: true
|
||||
radon:
|
||||
enabled: true
|
||||
rubocop:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- "**.css"
|
||||
- "**.inc"
|
||||
- "**.js"
|
||||
- "**.jsx"
|
||||
- "**.module"
|
||||
- "**.php"
|
||||
- "**.py"
|
||||
- "**.rb"
|
||||
exclude_paths:
|
||||
- config/
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
[run]
|
||||
source =
|
||||
./
|
||||
|
||||
omit =
|
||||
*/migrations/*
|
||||
plugins = django_coverage_plugin
|
||||
omit = *migrations*, *tests*
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
--exclude-exts=.min.css
|
||||
--ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
|
||||
@@ -1 +0,0 @@
|
||||
**/*{.,-}min.js
|
||||
213
.eslintrc
213
.eslintrc
@@ -1,213 +0,0 @@
|
||||
ecmaFeatures:
|
||||
modules: true
|
||||
jsx: true
|
||||
|
||||
env:
|
||||
amd: true
|
||||
browser: true
|
||||
es6: true
|
||||
jquery: true
|
||||
node: true
|
||||
|
||||
# http://eslint.org/docs/rules/
|
||||
rules:
|
||||
# Possible Errors
|
||||
comma-dangle: [2, never]
|
||||
no-cond-assign: 2
|
||||
no-console: 0
|
||||
no-constant-condition: 2
|
||||
no-control-regex: 2
|
||||
no-debugger: 2
|
||||
no-dupe-args: 2
|
||||
no-dupe-keys: 2
|
||||
no-duplicate-case: 2
|
||||
no-empty: 2
|
||||
no-empty-character-class: 2
|
||||
no-ex-assign: 2
|
||||
no-extra-boolean-cast: 2
|
||||
no-extra-parens: 0
|
||||
no-extra-semi: 2
|
||||
no-func-assign: 2
|
||||
no-inner-declarations: [2, functions]
|
||||
no-invalid-regexp: 2
|
||||
no-irregular-whitespace: 2
|
||||
no-negated-in-lhs: 2
|
||||
no-obj-calls: 2
|
||||
no-regex-spaces: 2
|
||||
no-sparse-arrays: 2
|
||||
no-unexpected-multiline: 2
|
||||
no-unreachable: 2
|
||||
use-isnan: 2
|
||||
valid-jsdoc: 0
|
||||
valid-typeof: 2
|
||||
|
||||
# Best Practices
|
||||
accessor-pairs: 2
|
||||
block-scoped-var: 0
|
||||
complexity: [2, 6]
|
||||
consistent-return: 0
|
||||
curly: 0
|
||||
default-case: 0
|
||||
dot-location: 0
|
||||
dot-notation: 0
|
||||
eqeqeq: 2
|
||||
guard-for-in: 2
|
||||
no-alert: 2
|
||||
no-caller: 2
|
||||
no-case-declarations: 2
|
||||
no-div-regex: 2
|
||||
no-else-return: 0
|
||||
no-empty-label: 2
|
||||
no-empty-pattern: 2
|
||||
no-eq-null: 2
|
||||
no-eval: 2
|
||||
no-extend-native: 2
|
||||
no-extra-bind: 2
|
||||
no-fallthrough: 2
|
||||
no-floating-decimal: 0
|
||||
no-implicit-coercion: 0
|
||||
no-implied-eval: 2
|
||||
no-invalid-this: 0
|
||||
no-iterator: 2
|
||||
no-labels: 0
|
||||
no-lone-blocks: 2
|
||||
no-loop-func: 2
|
||||
no-magic-number: 0
|
||||
no-multi-spaces: 0
|
||||
no-multi-str: 0
|
||||
no-native-reassign: 2
|
||||
no-new-func: 2
|
||||
no-new-wrappers: 2
|
||||
no-new: 2
|
||||
no-octal-escape: 2
|
||||
no-octal: 2
|
||||
no-proto: 2
|
||||
no-redeclare: 2
|
||||
no-return-assign: 2
|
||||
no-script-url: 2
|
||||
no-self-compare: 2
|
||||
no-sequences: 0
|
||||
no-throw-literal: 0
|
||||
no-unused-expressions: 2
|
||||
no-useless-call: 2
|
||||
no-useless-concat: 2
|
||||
no-void: 2
|
||||
no-warning-comments: 0
|
||||
no-with: 2
|
||||
radix: 2
|
||||
vars-on-top: 0
|
||||
wrap-iife: 2
|
||||
yoda: 0
|
||||
|
||||
# Strict
|
||||
strict: 0
|
||||
|
||||
# Variables
|
||||
init-declarations: 0
|
||||
no-catch-shadow: 2
|
||||
no-delete-var: 2
|
||||
no-label-var: 2
|
||||
no-shadow-restricted-names: 2
|
||||
no-shadow: 0
|
||||
no-undef-init: 2
|
||||
no-undef: 0
|
||||
no-undefined: 0
|
||||
no-unused-vars: 0
|
||||
no-use-before-define: 0
|
||||
|
||||
# Node.js and CommonJS
|
||||
callback-return: 2
|
||||
global-require: 2
|
||||
handle-callback-err: 2
|
||||
no-mixed-requires: 0
|
||||
no-new-require: 0
|
||||
no-path-concat: 2
|
||||
no-process-exit: 2
|
||||
no-restricted-modules: 0
|
||||
no-sync: 0
|
||||
|
||||
# Stylistic Issues
|
||||
array-bracket-spacing: 0
|
||||
block-spacing: 0
|
||||
brace-style: 0
|
||||
camelcase: 0
|
||||
comma-spacing: 0
|
||||
comma-style: 0
|
||||
computed-property-spacing: 0
|
||||
consistent-this: 0
|
||||
eol-last: 0
|
||||
func-names: 0
|
||||
func-style: 0
|
||||
id-length: 0
|
||||
id-match: 0
|
||||
indent: 0
|
||||
jsx-quotes: 0
|
||||
key-spacing: 0
|
||||
linebreak-style: 0
|
||||
lines-around-comment: 0
|
||||
max-depth: 0
|
||||
max-len: 0
|
||||
max-nested-callbacks: 0
|
||||
max-params: 0
|
||||
max-statements: [2, 30]
|
||||
new-cap: 0
|
||||
new-parens: 0
|
||||
newline-after-var: 0
|
||||
no-array-constructor: 0
|
||||
no-bitwise: 0
|
||||
no-continue: 0
|
||||
no-inline-comments: 0
|
||||
no-lonely-if: 0
|
||||
no-mixed-spaces-and-tabs: 0
|
||||
no-multiple-empty-lines: 0
|
||||
no-negated-condition: 0
|
||||
no-nested-ternary: 0
|
||||
no-new-object: 0
|
||||
no-plusplus: 0
|
||||
no-restricted-syntax: 0
|
||||
no-spaced-func: 0
|
||||
no-ternary: 0
|
||||
no-trailing-spaces: 0
|
||||
no-underscore-dangle: 0
|
||||
no-unneeded-ternary: 0
|
||||
object-curly-spacing: 0
|
||||
one-var: 0
|
||||
operator-assignment: 0
|
||||
operator-linebreak: 0
|
||||
padded-blocks: 0
|
||||
quote-props: 0
|
||||
quotes: 0
|
||||
require-jsdoc: 0
|
||||
semi-spacing: 0
|
||||
semi: 0
|
||||
sort-vars: 0
|
||||
space-after-keywords: 0
|
||||
space-before-blocks: 0
|
||||
space-before-function-paren: 0
|
||||
space-before-keywords: 0
|
||||
space-in-parens: 0
|
||||
space-infix-ops: 0
|
||||
space-return-throw-case: 0
|
||||
space-unary-ops: 0
|
||||
spaced-comment: 0
|
||||
wrap-regex: 0
|
||||
|
||||
# ECMAScript 6
|
||||
arrow-body-style: 0
|
||||
arrow-parens: 0
|
||||
arrow-spacing: 0
|
||||
constructor-super: 0
|
||||
generator-star-spacing: 0
|
||||
no-arrow-condition: 0
|
||||
no-class-assign: 0
|
||||
no-const-assign: 0
|
||||
no-dupe-class-members: 0
|
||||
no-this-before-super: 0
|
||||
no-var: 0
|
||||
object-shorthand: 0
|
||||
prefer-arrow-callback: 0
|
||||
prefer-const: 0
|
||||
prefer-reflect: 0
|
||||
prefer-spread: 0
|
||||
prefer-template: 0
|
||||
require-yield: 0
|
||||
63
.github/workflows/django.yml
vendored
Normal file
63
.github/workflows/django.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Django CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
browser: ['chrome', 'firefox']
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: bahmutov/npm-install@v1
|
||||
- run: node node_modules/gulp/bin/gulp build
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Cache python deps
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ env.pythonLocation }}-${{ hashFiles('requirements.txt') }}
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pycodestyle coverage coveralls django_coverage_plugin pytest-cov
|
||||
pip install --upgrade --upgrade-strategy eager -r requirements.txt
|
||||
python manage.py collectstatic --noinput
|
||||
- name: Cache gulp output
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: static/
|
||||
key: static-${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
|
||||
- name: Basic Checks
|
||||
run: |
|
||||
pycodestyle . --exclude=migrations,node_modules
|
||||
python manage.py check
|
||||
python manage.py makemigrations --check --dry-run
|
||||
- name: Run Tests
|
||||
uses: paambaati/codeclimate-action@v2.7.5
|
||||
env:
|
||||
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
||||
with:
|
||||
coverageCommand: coverage run -m pytest -n 8
|
||||
coverageLocations: |
|
||||
${{github.workspace}}/.coverage:coverage.py
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: failure-screenshots ${{ matrix.test-group }}
|
||||
path: screenshots/
|
||||
retention-days: 5
|
||||
- name: Coveralls
|
||||
run: coveralls --service=github
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -68,19 +68,9 @@ target/
|
||||
|
||||
## Directory-based project format:
|
||||
.idea/
|
||||
# if you remove the above rule, at least ignore the following:
|
||||
|
||||
# User-specific stuff:
|
||||
# .idea/workspace.xml
|
||||
# .idea/tasks.xml
|
||||
# .idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
# .idea/dataSources.ids
|
||||
# .idea/dataSources.xml
|
||||
# .idea/sqlDataSources.xml
|
||||
# .idea/dynamic.xml
|
||||
# .idea/uiDesigner.xml
|
||||
#Built dependencies
|
||||
pipeline/built_assets
|
||||
|
||||
# Gradle:
|
||||
# .idea/gradle.xml
|
||||
@@ -109,5 +99,4 @@ com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
.vscode/
|
||||
/package-lock.json
|
||||
screenshots/
|
||||
screenshots/
|
||||
|
||||
1
.idea/.name
generated
1
.idea/.name
generated
@@ -1 +0,0 @@
|
||||
PyRIGS
|
||||
5
.idea/encodings.xml
generated
5
.idea/encodings.xml
generated
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
</project>
|
||||
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/pyrigs.iml" filepath="$PROJECT_DIR$/.idea/pyrigs.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/scopes/scope_settings.xml
generated
5
.idea/scopes/scope_settings.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<state>
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</state>
|
||||
</component>
|
||||
7
.idea/vcs.xml
generated
7
.idea/vcs.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
1156
.rubocop.yml
1156
.rubocop.yml
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,3 @@
|
||||
*.sqlite3
|
||||
*.scss
|
||||
*.md
|
||||
*.rb
|
||||
Vagrantfile
|
||||
config/vagrant/*
|
||||
config/vagrant.yml
|
||||
**/tests
|
||||
|
||||
37
.travis.yml
37
.travis.yml
@@ -1,37 +0,0 @@
|
||||
language: python
|
||||
python:
|
||||
"3.8"
|
||||
cache: pip
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
|
||||
before_install:
|
||||
- export LANGUAGE=en_GB.UTF-8
|
||||
|
||||
install:
|
||||
- |
|
||||
latest=$(wget -qO- https://chromedriver.storage.googleapis.com/LATEST_RELEASE)
|
||||
wget https://chromedriver.storage.googleapis.com/$latest/chromedriver_linux64.zip
|
||||
- unzip chromedriver_linux64.zip
|
||||
- export PATH=$PATH:$(pwd)
|
||||
- chmod +x chromedriver
|
||||
- pip install -r requirements.txt
|
||||
- pip install coveralls codeclimate-test-reporter pycodestyle
|
||||
|
||||
before_script:
|
||||
- export PATH=$PATH:/usr/lib/chromium-browser/
|
||||
- python manage.py collectstatic --noinput
|
||||
|
||||
script:
|
||||
- pycodestyle . --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
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
|
||||
from RIGS import models
|
||||
|
||||
@@ -8,11 +8,13 @@ For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||
"""
|
||||
|
||||
import datetime
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
import raven
|
||||
import secrets
|
||||
import datetime
|
||||
|
||||
import raven
|
||||
from envparse import env
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
@@ -20,13 +22,15 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
# 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 = env('SECRET_KEY', default='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
|
||||
DEBUG = env('DEBUG', cast=bool, default=True)
|
||||
|
||||
STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False
|
||||
STAGING = env('STAGING', cast=bool, default=False)
|
||||
|
||||
CI = env('CI', cast=bool, default=False)
|
||||
|
||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
||||
|
||||
@@ -150,11 +154,29 @@ LOGGING = {
|
||||
}
|
||||
}
|
||||
|
||||
# Tests lock up SQLite otherwise
|
||||
if STAGING or CI:
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
|
||||
}
|
||||
}
|
||||
elif DEBUG:
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
|
||||
}
|
||||
}
|
||||
else:
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
|
||||
'LOCATION': 'cache_table',
|
||||
}
|
||||
}
|
||||
|
||||
RAVEN_CONFIG = {
|
||||
'dsn': os.environ.get('RAVEN_DSN'),
|
||||
# If you are using git, you can also automatically configure the
|
||||
# release based on the git info.
|
||||
# 'release': raven.fetch_git_sha(os.path.dirname(os.path.dirname(__file__))),
|
||||
'dsn': env('RAVEN_DSN', default=""),
|
||||
}
|
||||
|
||||
# User system
|
||||
@@ -167,10 +189,8 @@ LOGOUT_URL = '/user/logout/'
|
||||
ACCOUNT_ACTIVATION_DAYS = 7
|
||||
|
||||
# reCAPTCHA settings
|
||||
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY',
|
||||
"6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
|
||||
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY',
|
||||
"6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
|
||||
RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY', default="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
|
||||
RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PUBLIC_KEY', default="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
|
||||
NOCAPTCHA = True
|
||||
|
||||
SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']
|
||||
@@ -179,13 +199,13 @@ SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']
|
||||
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', 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)))
|
||||
EMAIL_USE_SSL = bool(int(os.environ.get('EMAIL_USE_SSL', 0)))
|
||||
DEFAULT_FROM_EMAIL = os.environ.get('EMAIL_FROM')
|
||||
EMAIL_HOST = env('EMAIL_HOST')
|
||||
EMAIL_PORT = env('EMAIL_PORT', cast=int, default=25)
|
||||
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
|
||||
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
|
||||
EMAIL_USE_TLS = env('EMAIL_USE_TLS', cast=bool, default=False)
|
||||
EMAIL_USE_SSL = env('EMAIL_USE_SSL', cast=bool, default=False)
|
||||
DEFAULT_FROM_EMAIL = env('EMAIL_FROM')
|
||||
else:
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
@@ -216,6 +236,9 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
|
||||
STATIC_DIRS = (
|
||||
os.path.join(BASE_DIR, 'static/')
|
||||
)
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'pipeline/built_assets/'),
|
||||
]
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
@@ -244,10 +267,3 @@ 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)
|
||||
|
||||
IMGUR_UPLOAD_CLIENT_ID = os.environ.get('IMGUR_UPLOAD_CLIENT_ID', '')
|
||||
IMGUR_UPLOAD_CLIENT_SECRET = os.environ.get('IMGUR_UPLOAD_CLIENT_SECRET', '')
|
||||
|
||||
0
PyRIGS/tests/__init__.py
Normal file
0
PyRIGS/tests/__init__.py
Normal file
@@ -1,16 +1,17 @@
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.test import LiveServerTestCase
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
|
||||
from RIGS import models as rigsmodels
|
||||
from . import pages
|
||||
import os
|
||||
import pytz
|
||||
from datetime import date, time, datetime, timedelta
|
||||
from django.conf import settings
|
||||
import imgurpython
|
||||
import PyRIGS.settings
|
||||
import sys
|
||||
import pathlib
|
||||
import inspect
|
||||
from envparse import env
|
||||
|
||||
|
||||
def create_datetime(year, month, day, hour, min):
|
||||
@@ -18,25 +19,26 @@ def create_datetime(year, month, day, hour, min):
|
||||
return tz.localize(datetime(year, month, day, hour, min)).astimezone(pytz.utc)
|
||||
|
||||
|
||||
def create_browser():
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument("--window-size=1920,1080")
|
||||
# No caching, please and thank you
|
||||
options.add_argument("--aggressive-cache-discard")
|
||||
options.add_argument("--disk-cache-size=0")
|
||||
# God Save The Queen
|
||||
options.add_argument("--lang=en_GB")
|
||||
if os.environ.get('CI', False):
|
||||
def create_browser(browser):
|
||||
if browser == "firefox":
|
||||
options = webdriver.FirefoxOptions()
|
||||
options.headless = True
|
||||
driver = webdriver.Firefox(options=options)
|
||||
else:
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument("--window-size=1920,1080")
|
||||
options.add_argument("--headless")
|
||||
options.add_argument("--no-sandbox")
|
||||
driver = webdriver.Chrome(options=options)
|
||||
if settings.CI:
|
||||
options.add_argument("--no-sandbox")
|
||||
driver = webdriver.Chrome(options=options)
|
||||
return driver
|
||||
|
||||
|
||||
class BaseTest(LiveServerTestCase):
|
||||
def setUp(self):
|
||||
super().setUpClass()
|
||||
self.driver = create_browser()
|
||||
self.driver = create_browser(env('BROWSER', default="chrome"))
|
||||
self.wait = WebDriverWait(self.driver, 15)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
@@ -50,8 +52,8 @@ class AutoLoginTest(BaseTest):
|
||||
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")
|
||||
login_page = pages.LoginPage(self.driver, self.live_server_url).open()
|
||||
login_page.login("EventTest", "EventTestPassword")
|
||||
|
||||
|
||||
def screenshot_failure(func):
|
||||
@@ -64,20 +66,9 @@ def screenshot_failure(func):
|
||||
if not pathlib.Path("screenshots").is_dir():
|
||||
os.mkdir("screenshots")
|
||||
self.driver.save_screenshot(screenshot_file)
|
||||
|
||||
if settings.IMGUR_UPLOAD_CLIENT_ID != "":
|
||||
config = {
|
||||
'album': None,
|
||||
'name': screenshot_name,
|
||||
'title': screenshot_name,
|
||||
'description': ""
|
||||
}
|
||||
client = imgurpython.ImgurClient(settings.IMGUR_UPLOAD_CLIENT_ID, settings.IMGUR_UPLOAD_CLIENT_SECRET)
|
||||
image = client.upload_from_path(screenshot_file, config=config)
|
||||
print("Error in test {} is at url {}".format(screenshot_name, image['link']), file=sys.stderr)
|
||||
else:
|
||||
print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr)
|
||||
print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr)
|
||||
raise e
|
||||
|
||||
return wrapper_func
|
||||
|
||||
|
||||
@@ -88,15 +79,5 @@ def screenshot_failure_cls(cls):
|
||||
return cls
|
||||
|
||||
|
||||
# Checks if animation is done
|
||||
class animation_is_finished(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self, driver):
|
||||
numberAnimating = driver.execute_script('return $(":animated").length')
|
||||
finished = numberAnimating == 0
|
||||
if finished:
|
||||
import time
|
||||
time.sleep(0.1)
|
||||
return finished
|
||||
def assert_times_equal(first_time, second_time):
|
||||
assert first_time.replace(microsecond=0) == second_time.replace(microsecond=0)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from pypom import Page, Region
|
||||
from pypom import Page
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver import Chrome
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
|
||||
from PyRIGS.tests import regions
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class BasePage(Page):
|
||||
|
||||
class FormPage(BasePage):
|
||||
_errors_selector = (By.CLASS_NAME, "alert-danger")
|
||||
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
|
||||
|
||||
def remove_all_required(self):
|
||||
self.driver.execute_script(
|
||||
@@ -43,6 +44,7 @@ class FormPage(BasePage):
|
||||
submit = self.find_element(*self._submit_locator)
|
||||
ActionChains(self.driver).move_to_element(submit).perform()
|
||||
submit.click()
|
||||
self.wait.until(animation_is_finished())
|
||||
self.wait.until(lambda x: self.errors != previous_errors or self.success)
|
||||
|
||||
@property
|
||||
@@ -72,3 +74,13 @@ class LoginPage(BasePage):
|
||||
password_element.send_keys(password)
|
||||
|
||||
self.find_element(*self._submit_locator).click()
|
||||
|
||||
|
||||
class animation_is_finished():
|
||||
def __call__(self, driver):
|
||||
number_animating = driver.execute_script('return $(":animated").length')
|
||||
finished = number_animating == 0
|
||||
if finished:
|
||||
import time
|
||||
time.sleep(0.1)
|
||||
return finished
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from pypom import Region
|
||||
from django.utils import timezone
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support.select import Select
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from pypom import Region
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.support import expected_conditions
|
||||
from selenium.webdriver.support.select import Select
|
||||
|
||||
|
||||
def parse_bool_from_string(string):
|
||||
# Used to convert from attribute strings to boolean values, written after I found this:
|
||||
@@ -18,18 +18,22 @@ def parse_bool_from_string(string):
|
||||
else:
|
||||
return False
|
||||
|
||||
# 12-Hour vs 24-Hour Time. Affects widget display
|
||||
|
||||
|
||||
def get_time_format():
|
||||
# Default
|
||||
time_format = "%H:%M"
|
||||
# If system is 12hr
|
||||
if timezone.now().strftime("%p"):
|
||||
time_format = "%I:%M %p"
|
||||
time_format = "%H%M"
|
||||
if settings.CI: # The CI is American
|
||||
time_format = "%I%M%p"
|
||||
return time_format
|
||||
|
||||
|
||||
def get_date_format():
|
||||
date_format = "%d%m%Y"
|
||||
if settings.CI: # And try as I might I can't stop it being so
|
||||
date_format = "%m%d%Y"
|
||||
return date_format
|
||||
|
||||
|
||||
class BootstrapSelectElement(Region):
|
||||
_main_button_locator = (By.CSS_SELECTOR, 'button.dropdown-toggle')
|
||||
_option_box_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu')
|
||||
@@ -71,8 +75,7 @@ class BootstrapSelectElement(Region):
|
||||
self.open()
|
||||
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))
|
||||
self.wait.until(expected_conditions.invisibility_of_element_located(*self._status_locator))
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
@@ -126,6 +129,22 @@ class CheckBox(Region):
|
||||
self.toggle()
|
||||
|
||||
|
||||
class RadioSelect(Region): # Currently only works for yes/no radio selects
|
||||
def set_value(self, value):
|
||||
if value:
|
||||
value = "0"
|
||||
else:
|
||||
value = "1"
|
||||
self.find_element(By.XPATH, "//label[@for='{}_{}']".format(self.root.get_attribute("id"), value)).click()
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
try:
|
||||
return parse_bool_from_string(self.find_element(By.CSS_SELECTOR, '.custom-control-input:checked').get_attribute("value").lower())
|
||||
except NoSuchElementException:
|
||||
return None
|
||||
|
||||
|
||||
class DatePicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
@@ -133,13 +152,13 @@ class DatePicker(Region):
|
||||
|
||||
def set_value(self, value):
|
||||
self.root.clear()
|
||||
self.root.send_keys(value.strftime("%d%m%Y"))
|
||||
self.root.send_keys(value.strftime(get_date_format()))
|
||||
|
||||
|
||||
class TimePicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
return datetime.datetime.strptime(self.root.get_attribute("value"), get_time_format())
|
||||
return datetime.datetime.strptime(self.root.get_attribute("value"), "%H:%M")
|
||||
|
||||
def set_value(self, value):
|
||||
self.root.clear()
|
||||
@@ -149,12 +168,12 @@ class TimePicker(Region):
|
||||
class DateTimePicker(Region):
|
||||
@property
|
||||
def value(self):
|
||||
return datetime.datetime.strptime(self.root.get_attribute("value"), "%Y-%m-%d " + get_time_format())
|
||||
return datetime.datetime.strptime(self.root.get_attribute("value"), "%Y-%m-%d %H:%M")
|
||||
|
||||
def set_value(self, value):
|
||||
self.root.clear()
|
||||
|
||||
date = value.date().strftime("%d%m%Y")
|
||||
date = value.date().strftime(get_date_format())
|
||||
time = value.time().strftime(get_time_format())
|
||||
|
||||
self.root.send_keys(date)
|
||||
@@ -199,7 +218,7 @@ class ErrorPage(Region):
|
||||
|
||||
class Modal(Region):
|
||||
_submit_locator = (By.CSS_SELECTOR, '.btn-primary')
|
||||
_header_selector = (By.TAG_NAME, 'h3')
|
||||
_header_selector = (By.TAG_NAME, 'h4')
|
||||
|
||||
form_items = {
|
||||
'name': (TextBox, (By.ID, 'id_name'))
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
from django.urls import path
|
||||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.conf import settings
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.contrib.auth.views import LoginView
|
||||
from registration.backends.default.views import RegistrationView
|
||||
from PyRIGS.decorators import permission_required_with_403
|
||||
import RIGS
|
||||
import users
|
||||
from django.conf.urls import include
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.urls import path, re_path
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from PyRIGS import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', include('users.urls')),
|
||||
path('', include('versioning.urls')),
|
||||
path('', include('RIGS.urls')),
|
||||
path('assets/', include('assets.urls')),
|
||||
|
||||
path('', login_required(views.Index.as_view()), name='index'),
|
||||
|
||||
# API
|
||||
path('api/<str:model>/', login_required(views.SecureAPIRequest.as_view()),
|
||||
name="api_secure"),
|
||||
path('api/<str:model>/<int:pk>/', login_required(views.SecureAPIRequest.as_view()),
|
||||
name="api_secure"),
|
||||
|
||||
path('closemodal/', views.CloseModal.as_view(), name='closemodal'),
|
||||
path('search_help/', views.SearchHelp.as_view(), name='search_help'),
|
||||
|
||||
path('', include('users.urls')),
|
||||
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
|
||||
@@ -31,5 +34,6 @@ if settings.DEBUG:
|
||||
|
||||
import debug_toolbar
|
||||
urlpatterns = [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
re_path(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
|
||||
] + urlpatterns
|
||||
|
||||
117
PyRIGS/views.py
117
PyRIGS/views.py
@@ -1,24 +1,34 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.views import generic
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.core import serializers
|
||||
from django.conf import settings
|
||||
import datetime
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
import simplejson
|
||||
from django.contrib import messages
|
||||
import datetime
|
||||
import pytz
|
||||
import operator
|
||||
from registration.views import RegistrationView
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.core import serializers
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.views import generic
|
||||
|
||||
from RIGS import models, forms
|
||||
from RIGS import models
|
||||
from assets import models as asset_models
|
||||
from functools import reduce
|
||||
|
||||
|
||||
def is_ajax(request):
|
||||
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
|
||||
|
||||
# Displays the current rig count along with a few other bits and pieces
|
||||
|
||||
|
||||
class Index(generic.TemplateView):
|
||||
template_name = 'index.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(Index, self).get_context_data(**kwargs)
|
||||
context['rig_count'] = models.Event.objects.rig_count()
|
||||
return context
|
||||
|
||||
|
||||
class SecureAPIRequest(generic.View):
|
||||
@@ -136,9 +146,31 @@ class SecureAPIRequest(generic.View):
|
||||
return HttpResponse(model)
|
||||
|
||||
|
||||
class ModalURLMixin:
|
||||
def get_close_url(self, update, detail):
|
||||
if is_ajax(self.request):
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy(update, kwargs={'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
|
||||
messages.info(self.request, "modalobject[0]['update_url']='" + update_url + "'")
|
||||
else:
|
||||
url = reverse_lazy(detail, kwargs={
|
||||
'pk': self.object.pk,
|
||||
})
|
||||
return url
|
||||
|
||||
|
||||
class GenericListView(generic.ListView):
|
||||
template_name = 'generic_list.html'
|
||||
paginate_by = 20
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericListView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = self.model.__name__ + "s"
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
q = self.request.GET.get('q', "")
|
||||
|
||||
@@ -159,3 +191,54 @@ class GenericListView(generic.ListView):
|
||||
if orderBy != "":
|
||||
object_list = object_list.order_by(orderBy)
|
||||
return object_list
|
||||
|
||||
|
||||
class GenericDetailView(generic.DetailView):
|
||||
template_name = "generic_detail.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericDetailView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name)
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
|
||||
class GenericUpdateView(generic.UpdateView):
|
||||
template_name = "generic_form.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericUpdateView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Edit {}".format(self.model.__name__)
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
|
||||
class GenericCreateView(generic.CreateView):
|
||||
template_name = "generic_form.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericCreateView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Create {}".format(self.model.__name__)
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
|
||||
class SearchHelp(generic.TemplateView):
|
||||
template_name = 'search_help.html'
|
||||
|
||||
|
||||
"""
|
||||
Called from a modal window (e.g. when an item is submitted to an event/invoice).
|
||||
May optionally also include some javascript in a success message to cause a load of
|
||||
the new information onto the page.
|
||||
"""
|
||||
|
||||
|
||||
class CloseModal(generic.TemplateView):
|
||||
template_name = 'closemodal.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return {'messages': messages.get_messages(self.request)}
|
||||
|
||||
115
README.md
115
README.md
@@ -1,111 +1,18 @@
|
||||
# TEC PA & Lighting - PyRIGS #
|
||||
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
||||

|
||||
[](https://coveralls.io/github/nottinghamtec/PyRIGS)
|
||||
[](https://codeclimate.com/github/nottinghamtec/PyRIGS/maintainability)
|
||||
|
||||
Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.
|
||||
Welcome to TEC PA & Lighting's PyRIGS program. This is a reimplementation of the previous Rig Information Gathering System (RIGS) that was developed using Ruby on Rails. PyRIGS is our in house app for the centralisation of information on our events and now assets.
|
||||
|
||||
The purpose of this project is to make the system more compatible and easier to understand such that should future changes be needed they can be made without having to understand the intricacies of Rails.
|
||||
For setup information and other such helpful stuff check the [Wiki](https://github.com/nottinghamtec/PyRIGS/wiki)
|
||||
|
||||
### What is this repository for? ###
|
||||
When a significant feature is developed on a branch, raise a pull request and it can be reviewed before being put into production.
|
||||
|
||||
Most of the documents here assume a basic knowledge of how Python and Django work (hint, if I don't say something, Google it, you will find 10000's of answers). The documentation is purely to be specific to TEC's application of the framework.
|
||||
|
||||
### Editing ###
|
||||
It is recommended that you use the PyCharm IDE by JetBrains. Whilst other editors are available, this is the best for integration with Django as it can automatically manage all the pesky admin commands that frequently need running, as well as nice integration with git.
|
||||
|
||||
For the more experienced developer/somebody who doesn't want a full IDE and wants it to open in less than the age of the universe, I can strongly recommend [Sublime Text](http://www.sublimetext.com/). It has a bit of a steeper learning curve, and won't manage anything Django/git related out of the box, but once you get the hang of it is by far the fastest and most powerful editor I have used (for any type of project).
|
||||
|
||||
Please contact TJP for details on how to acquire these.
|
||||
|
||||
### Python Environment ###
|
||||
Whilst the Python version used is not critical to the running of the application, using the same version usually helps avoid a lot of issues. Orginally written with the C implementation of Python 2 (CPython 2, specifically the Python 2.7 standard), the application now runs in Python 3.
|
||||
|
||||
Once you have your Python distribution installed, go ahead an follow the steps to set up a virtualenv, which will isolate the project from the system environment.
|
||||
|
||||
#### PyCharm ####
|
||||
If you are using the prefered PyCharm IDE, then this should be quite easy.
|
||||
|
||||
1. Select "File/Settings" -> "Project Interpreter"
|
||||
2. Click the small cog in the top right
|
||||
3. Select "Create VirtualEnv"
|
||||
4. Enter a name and a location. This doesn't matter where, just make sure it makes sense and you remember it incase you need it later (I recommend calling it "pyrigs" in "~/.virtualenvs/pyrigs")
|
||||
5. Select the base interpreter to your Python 3 base interpreter (Python 2 will work, just be careful)
|
||||
6. Click OK, you *don't* want to inherit global packages or make it available to all projects.
|
||||
7. Open a file such as manage.py. PyCharm should winge that dependances aren't installed. This might take a while to register, but give it change. When it does, click the button to install them and let it do it's thing. If for some reason PyCharm should decide that it doesn't want to help you here, see below for the console instructions on how to do this manually.
|
||||
|
||||
To run the Django application follow these steps
|
||||
|
||||
1. Select "Run/Edit Configurations"
|
||||
2. Create a new "Django server", give it a sensible name for when you need it later.
|
||||
3. You might need to set the interpreter to be your virtualenv.
|
||||
4. Click "OK"
|
||||
5. Run the application
|
||||
|
||||
#### Console Based ####
|
||||
If you aren't using PyCharm, or want to use a console for some reason, this is really easy, there is even [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) to help things along. Simply run
|
||||
```
|
||||
virtualenv <dir>
|
||||
```
|
||||
Where dir is the directory you wish to create the virtualenv in.
|
||||
|
||||
Next activate the virtualenv.
|
||||
```
|
||||
Windows
|
||||
<virtualenv_dir>/Scripts/activate.bat
|
||||
|
||||
Unix
|
||||
source <virtualenv_dir>/bin/activate
|
||||
```
|
||||
Finally install the requirements using pip
|
||||
```
|
||||
cd <pyrigs project directory>
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
This might take a while, but be patient and you should then be ready to go.
|
||||
|
||||
To run the server under normal conditions when you are already in the virtualenv (see above)
|
||||
```
|
||||
python manage.py runserver
|
||||
```
|
||||
Please refer to Django documentation for a full list of options available here.
|
||||
|
||||
### Development using docker
|
||||
|
||||
```
|
||||
docker build . -t pyrigs
|
||||
docker run -it --rm -p=8000:8000 -v $(pwd):/app pyrigs
|
||||
```
|
||||
|
||||
### Sample Data ###
|
||||
Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty:
|
||||
```
|
||||
python manage.py flush
|
||||
```
|
||||
Then load the sample data using the command:
|
||||
```
|
||||
python manage.py generateSampleData
|
||||
```
|
||||
4 user accounts are created for convenience:
|
||||
|
||||
|Username |Password |
|
||||
|---------|---------|
|
||||
|superuser|superuser|
|
||||
|finance |finance |
|
||||
|keyholder|keyholder|
|
||||
|basic |basic |
|
||||
|
||||
### Testing ###
|
||||
Tests are contained in 3 files. `RIGS/test_models.py` contains tests for logic within the data models. `RIGS/test_unit.py` contains "Live server" tests, using raw web requests. `RIGS/test_integration.py` contains user interface tests which take control of a web browser. For automated Travis tests, we use [Sauce Labs](https://saucelabs.com). When debugging locally, ensure that you have the latest version of Google Chrome installed, then install [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) and ensure it is on the `PATH`.
|
||||
|
||||
You can run the entire test suite, or you can run specific sections individually. For example, in order of specificity:
|
||||
|
||||
```
|
||||
python manage.py test
|
||||
python manage.py test RIGS.test_models
|
||||
python manage.py test RIGS.test_models.EventTestCase
|
||||
python manage.py test RIGS.test_models.EventTestCase.test_current_events
|
||||
|
||||
```
|
||||
# Apps
|
||||
- PyRIGS: Base app, stores 'global' information
|
||||
- RIGS: Rigboard stuff - event calendar etc
|
||||
- assets: Database of our kit, testing data etc
|
||||
- versioning: Our custom logic built on top of django-reversion. Semi-modular.
|
||||
- users: Our custom logic for registration and profiles. Semi-modular.
|
||||
- training: SoonTM
|
||||
|
||||
[](https://forthebadge.com) [](https://forthebadge.com)
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
from django.contrib import admin
|
||||
from RIGS import models, forms
|
||||
from users import forms as user_forms
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from django.contrib.admin import helpers
|
||||
from django.template.response import TemplateResponse
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
from django.contrib.admin import helpers
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.forms import ModelForm
|
||||
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from reversion import revisions as reversion
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from RIGS import models
|
||||
from users import forms as user_forms
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(models.VatRate, VersionAdmin)
|
||||
admin.site.register(models.Event, VersionAdmin)
|
||||
admin.site.register(models.EventItem, VersionAdmin)
|
||||
admin.site.register(models.Invoice)
|
||||
admin.site.register(models.Payment)
|
||||
admin.site.register(models.Invoice, VersionAdmin)
|
||||
|
||||
|
||||
def approve_user(modeladmin, request, queryset):
|
||||
@@ -125,3 +123,13 @@ class VenueAdmin(AssociateAdmin):
|
||||
class OrganisationAdmin(AssociateAdmin):
|
||||
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
|
||||
merge_fields = ['name', 'phone', 'email', 'address', 'notes', 'union_account']
|
||||
|
||||
|
||||
@admin.register(models.RiskAssessment)
|
||||
class RiskAssessmentAdmin(VersionAdmin):
|
||||
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
||||
|
||||
|
||||
@admin.register(models.EventChecklist)
|
||||
class EventChecklistAdmin(VersionAdmin):
|
||||
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
import datetime
|
||||
import re
|
||||
|
||||
import reversion
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.urls import reverse_lazy
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import generic
|
||||
from django.db.models import Q
|
||||
from z3c.rml import rml2pdf
|
||||
from django.db.models import Q
|
||||
|
||||
from RIGS import models
|
||||
|
||||
from django import forms
|
||||
|
||||
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
||||
|
||||
|
||||
class InvoiceIndex(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'invoice_list_active.html'
|
||||
template_name = 'invoice_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceIndex, self).get_context_data(**kwargs)
|
||||
total = 0
|
||||
for i in context['object_list']:
|
||||
total += i.balance
|
||||
context['total'] = total
|
||||
context['count'] = len(list(context['object_list']))
|
||||
context['page_title'] = "Outstanding Invoices ({} Events, £{:.2f})".format(len(list(context['object_list'])), total)
|
||||
context['description'] = "Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger"
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -54,6 +53,11 @@ class InvoiceDetail(generic.DetailView):
|
||||
model = models.Invoice
|
||||
template_name = 'invoice_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceDetail, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Invoice {} ({})".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
|
||||
return context
|
||||
|
||||
|
||||
class InvoicePrint(generic.View):
|
||||
def get(self, request, pk):
|
||||
@@ -71,6 +75,7 @@ class InvoicePrint(generic.View):
|
||||
},
|
||||
'invoice': invoice,
|
||||
'current_user': request.user,
|
||||
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
|
||||
}
|
||||
|
||||
rml = template.render(context)
|
||||
@@ -79,11 +84,8 @@ class InvoicePrint(generic.View):
|
||||
|
||||
pdfData = buffer.read()
|
||||
|
||||
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = "filename=Invoice %05d - N%05d | %s.pdf" % (
|
||||
invoice.pk, invoice.event.pk, escapedEventName)
|
||||
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
|
||||
response.write(pdfData)
|
||||
return response
|
||||
|
||||
@@ -127,6 +129,12 @@ class InvoiceArchive(generic.ListView):
|
||||
template_name = 'invoice_list_archive.html'
|
||||
paginate_by = 25
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InvoiceArchive, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Invoice Archive"
|
||||
context['description'] = "This page displays all invoices: outstanding, paid, and void"
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
q = self.request.GET.get('q', "")
|
||||
|
||||
@@ -166,8 +174,7 @@ class InvoiceWaiting(generic.ListView):
|
||||
total = 0
|
||||
for obj in self.get_objects():
|
||||
total += obj.sum_total
|
||||
context['total'] = total
|
||||
context['count'] = len(self.get_objects())
|
||||
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(self.get_objects()), total)
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -192,7 +199,10 @@ class InvoiceWaiting(generic.ListView):
|
||||
|
||||
|
||||
class InvoiceEvent(generic.View):
|
||||
@transaction.atomic()
|
||||
@reversion.create_revision()
|
||||
def get(self, *args, **kwargs):
|
||||
reversion.set_user(self.request.user)
|
||||
epk = kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
invoice, created = models.Invoice.objects.get_or_create(event=event)
|
||||
@@ -201,6 +211,11 @@ class InvoiceEvent(generic.View):
|
||||
invoice.invoice_date = datetime.date.today()
|
||||
messages.success(self.request, 'Invoice created successfully')
|
||||
|
||||
if kwargs.get('void'):
|
||||
invoice.void = not invoice.void
|
||||
invoice.save()
|
||||
messages.warning(self.request, 'Invoice voided')
|
||||
|
||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk}))
|
||||
|
||||
|
||||
@@ -218,6 +233,13 @@ class PaymentCreate(generic.CreateView):
|
||||
initial.update({'invoice': invoice})
|
||||
return initial
|
||||
|
||||
@transaction.atomic()
|
||||
@reversion.create_revision()
|
||||
def form_valid(self, form, *args, **kwargs):
|
||||
reversion.add_to_revision(form.cleaned_data['invoice'])
|
||||
reversion.set_comment("Payment added")
|
||||
return super().form_valid(form, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
messages.info(self.request, "location.reload()")
|
||||
return reverse_lazy('closemodal')
|
||||
@@ -225,6 +247,14 @@ class PaymentCreate(generic.CreateView):
|
||||
|
||||
class PaymentDelete(generic.DeleteView):
|
||||
model = models.Payment
|
||||
template_name = 'payment_confirm_delete.html'
|
||||
|
||||
@transaction.atomic()
|
||||
@reversion.create_revision()
|
||||
def delete(self, *args, **kwargs):
|
||||
reversion.add_to_revision(self.get_object().invoice)
|
||||
reversion.set_comment("Payment removed")
|
||||
return super().delete(*args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.POST.get('next')
|
||||
|
||||
140
RIGS/forms.py
140
RIGS/forms.py
@@ -1,13 +1,11 @@
|
||||
from datetime import datetime
|
||||
|
||||
import simplejson
|
||||
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 django.utils import timezone
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from RIGS import models
|
||||
|
||||
@@ -18,8 +16,6 @@ forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'
|
||||
|
||||
|
||||
# Events Shit
|
||||
|
||||
|
||||
class EventForm(forms.ModelForm):
|
||||
datetime_input_formats = list(settings.DATETIME_INPUT_FORMATS)
|
||||
meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
|
||||
@@ -153,3 +149,129 @@ class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
|
||||
|
||||
class EventAuthorisationRequestForm(forms.Form):
|
||||
email = forms.EmailField(required=True, label='Authoriser Email')
|
||||
|
||||
|
||||
class EventRiskAssessmentForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EventRiskAssessmentForm, self).__init__(*args, **kwargs)
|
||||
for name, field in self.fields.items():
|
||||
if str(name) == 'supervisor_consulted':
|
||||
field.widget = forms.CheckboxInput()
|
||||
elif field.__class__ == forms.BooleanField:
|
||||
field.widget = forms.RadioSelect(choices=[
|
||||
(True, 'Yes'),
|
||||
(False, 'No')
|
||||
], attrs={'class': 'custom-control-input', 'required': 'true'})
|
||||
|
||||
def clean(self):
|
||||
# Check expected values
|
||||
unexpected_values = []
|
||||
for field, value in models.RiskAssessment.expected_values.items():
|
||||
if self.cleaned_data.get(field) != value:
|
||||
unexpected_values.append("<li>{}</li>".format(self._meta.model._meta.get_field(field).help_text))
|
||||
if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'):
|
||||
raise forms.ValidationError("Your answers to these questions: <ul>{}</ul> require consulting with a supervisor.".format(''.join([str(elem) for elem in unexpected_values])), code='unusual_answers')
|
||||
return super(EventRiskAssessmentForm, self).clean()
|
||||
|
||||
class Meta:
|
||||
model = models.RiskAssessment
|
||||
fields = '__all__'
|
||||
exclude = ['reviewed_at', 'reviewed_by']
|
||||
|
||||
|
||||
class EventChecklistForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EventChecklistForm, self).__init__(*args, **kwargs)
|
||||
self.fields['date'].widget.format = '%Y-%m-%d'
|
||||
for name, field in self.fields.items():
|
||||
if field.__class__ == forms.NullBooleanField:
|
||||
# Only display yes/no to user, the 'none' is only ever set in the background
|
||||
field.widget = forms.CheckboxInput()
|
||||
# Parsed from incoming form data by clean, then saved into models when the form is saved
|
||||
items = {}
|
||||
|
||||
related_models = {
|
||||
'venue': models.Venue,
|
||||
'power_mic': models.Profile,
|
||||
}
|
||||
|
||||
# Two possible formats
|
||||
def parsedatetime(self, date_string):
|
||||
try:
|
||||
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S'))
|
||||
except ValueError:
|
||||
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M'))
|
||||
|
||||
# There's probably a thousand better ways to do this, but this one is mine
|
||||
def clean(self):
|
||||
vehicles = {key: val for key, val in self.data.items()
|
||||
if key.startswith('vehicle')}
|
||||
for key in vehicles:
|
||||
pk = int(key.split('_')[1])
|
||||
driver_key = 'driver_' + str(pk)
|
||||
if(self.data[driver_key] == ''):
|
||||
raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
|
||||
else:
|
||||
try:
|
||||
item = models.EventChecklistVehicle.objects.get(pk=pk)
|
||||
except models.EventChecklistVehicle.DoesNotExist:
|
||||
item = models.EventChecklistVehicle()
|
||||
|
||||
item.vehicle = vehicles['vehicle_' + str(pk)]
|
||||
item.driver = models.Profile.objects.get(pk=self.data[driver_key])
|
||||
item.full_clean('checklist')
|
||||
|
||||
# item does not have a database pk yet as it isn't saved
|
||||
self.items['v' + str(pk)] = item
|
||||
|
||||
crewmembers = {key: val for key, val in self.data.items()
|
||||
if key.startswith('crewmember')}
|
||||
other_fields = ['start', 'role', 'end']
|
||||
for key in crewmembers:
|
||||
pk = int(key.split('_')[1])
|
||||
|
||||
for field in other_fields:
|
||||
value = self.data['{}_{}'.format(field, pk)]
|
||||
if value == '':
|
||||
raise forms.ValidationError('Add a {} to crewmember {}'.format(field, pk), code='{}_mismatch'.format(field))
|
||||
|
||||
try:
|
||||
item = models.EventChecklistCrew.objects.get(pk=pk)
|
||||
except models.EventChecklistCrew.DoesNotExist:
|
||||
item = models.EventChecklistCrew()
|
||||
|
||||
item.crewmember = models.Profile.objects.get(pk=self.data['crewmember_' + str(pk)])
|
||||
item.start = self.parsedatetime(self.data['start_' + str(pk)])
|
||||
item.role = self.data['role_' + str(pk)]
|
||||
item.end = self.parsedatetime(self.data['end_' + str(pk)])
|
||||
item.full_clean('checklist')
|
||||
|
||||
# item does not have a database pk yet as it isn't saved
|
||||
self.items['c' + str(pk)] = item
|
||||
|
||||
return super(EventChecklistForm, self).clean()
|
||||
|
||||
def save(self, commit=True):
|
||||
checklist = super(EventChecklistForm, self).save(commit=False)
|
||||
if (commit):
|
||||
# Remove all existing, to be recreated from the form
|
||||
checklist.vehicles.all().delete()
|
||||
checklist.crew.all().delete()
|
||||
checklist.save()
|
||||
|
||||
for key in self.items:
|
||||
item = self.items[key]
|
||||
reversion.add_to_revision(item)
|
||||
# finish and save new database items
|
||||
item.checklist = checklist
|
||||
item.full_clean()
|
||||
item.save()
|
||||
|
||||
self.items.clear()
|
||||
|
||||
return checklist
|
||||
|
||||
class Meta:
|
||||
model = models.EventChecklist
|
||||
fields = '__all__'
|
||||
exclude = ['reviewed_at', 'reviewed_by']
|
||||
|
||||
217
RIGS/hs.py
Normal file
217
RIGS/hs.py
Normal file
@@ -0,0 +1,217 @@
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.views import generic
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from RIGS import models, forms
|
||||
|
||||
|
||||
class EventRiskAssessmentCreate(generic.CreateView):
|
||||
model = models.RiskAssessment
|
||||
template_name = 'risk_assessment_form.html'
|
||||
form_class = forms.EventRiskAssessmentForm
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
epk = kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
|
||||
# Check if RA exists
|
||||
ra = models.RiskAssessment.objects.filter(event=event).first()
|
||||
|
||||
if ra is not None:
|
||||
return HttpResponseRedirect(reverse_lazy('ra_edit', kwargs={'pk': ra.pk}))
|
||||
|
||||
return super(EventRiskAssessmentCreate, self).get(self)
|
||||
|
||||
def get_form(self, **kwargs):
|
||||
form = super(EventRiskAssessmentCreate, self).get_form(**kwargs)
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
form.instance.event = event
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentCreate, self).get_context_data(**kwargs)
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
context['event'] = event
|
||||
context['page_title'] = 'Create Risk Assessment for Event {}'.format(event.display_id)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
class EventRiskAssessmentEdit(generic.UpdateView):
|
||||
model = models.RiskAssessment
|
||||
template_name = 'risk_assessment_form.html'
|
||||
form_class = forms.EventRiskAssessmentForm
|
||||
|
||||
def get_success_url(self):
|
||||
ra = self.get_object()
|
||||
ra.reviewed_by = None
|
||||
ra.reviewed_at = None
|
||||
ra.save()
|
||||
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentEdit, self).get_context_data(**kwargs)
|
||||
rpk = self.kwargs.get('pk')
|
||||
ra = models.RiskAssessment.objects.get(pk=rpk)
|
||||
context['event'] = ra.event
|
||||
context['edit'] = True
|
||||
context['page_title'] = 'Edit Risk Assessment for Event {}'.format(ra.event.display_id)
|
||||
return context
|
||||
|
||||
|
||||
class EventRiskAssessmentDetail(generic.DetailView):
|
||||
model = models.RiskAssessment
|
||||
template_name = 'risk_assessment_detail.html'
|
||||
|
||||
|
||||
class EventRiskAssessmentList(generic.ListView):
|
||||
paginate_by = 20
|
||||
model = models.RiskAssessment
|
||||
template_name = 'hs_object_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
|
||||
context['title'] = 'Risk Assessment'
|
||||
context['view'] = 'ra_detail'
|
||||
context['edit'] = 'ra_edit'
|
||||
context['review'] = 'ra_review'
|
||||
context['perm'] = 'perms.RIGS.review_riskassessment'
|
||||
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||
return context
|
||||
|
||||
|
||||
class EventRiskAssessmentReview(generic.View):
|
||||
def get(self, *args, **kwargs):
|
||||
rpk = kwargs.get('pk')
|
||||
ra = models.RiskAssessment.objects.get(pk=rpk)
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(self.request.user)
|
||||
ra.reviewed_by = self.request.user
|
||||
ra.reviewed_at = timezone.now()
|
||||
ra.save()
|
||||
return HttpResponseRedirect(reverse_lazy('ra_list'))
|
||||
|
||||
|
||||
class EventChecklistDetail(generic.DetailView):
|
||||
model = models.EventChecklist
|
||||
template_name = 'event_checklist_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistDetail, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Event Checklist for Event {} {}".format(self.object.event.display_id, self.object.event.name)
|
||||
return context
|
||||
|
||||
|
||||
class EventChecklistEdit(generic.UpdateView):
|
||||
model = models.EventChecklist
|
||||
template_name = 'event_checklist_form.html'
|
||||
form_class = forms.EventChecklistForm
|
||||
|
||||
def get_success_url(self):
|
||||
ec = self.get_object()
|
||||
ec.reviewed_by = None
|
||||
ec.reviewed_at = None
|
||||
ec.save()
|
||||
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistEdit, self).get_context_data(**kwargs)
|
||||
pk = self.kwargs.get('pk')
|
||||
ec = models.EventChecklist.objects.get(pk=pk)
|
||||
context['event'] = ec.event
|
||||
context['edit'] = True
|
||||
context['page_title'] = 'Edit Event Checklist for Event {}'.format(ec.event.display_id)
|
||||
form = context['form']
|
||||
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
||||
for field, model in form.related_models.items():
|
||||
value = form[field].value()
|
||||
if value is not None and value != '':
|
||||
context[field] = model.objects.get(pk=value)
|
||||
return context
|
||||
|
||||
|
||||
class EventChecklistCreate(generic.CreateView):
|
||||
model = models.EventChecklist
|
||||
template_name = 'event_checklist_form.html'
|
||||
form_class = forms.EventChecklistForm
|
||||
|
||||
# From both business logic and programming POVs, RAs must exist before ECs!
|
||||
def get(self, *args, **kwargs):
|
||||
epk = kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
|
||||
# Check if RA exists
|
||||
ra = models.RiskAssessment.objects.filter(event=event).first()
|
||||
|
||||
if ra is None:
|
||||
messages.error(self.request, 'A Risk Assessment must exist prior to creating any Event Checklists for {}! Please create one now.'.format(event))
|
||||
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
|
||||
|
||||
return super(EventChecklistCreate, self).get(self)
|
||||
|
||||
def get_form(self, **kwargs):
|
||||
form = super(EventChecklistCreate, self).get_form(**kwargs)
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
form.instance.event = event
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistCreate, self).get_context_data(**kwargs)
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
context['event'] = event
|
||||
context['page_title'] = 'Create Event Checklist for Event {}'.format(event.display_id)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
class EventChecklistList(generic.ListView):
|
||||
paginate_by = 20
|
||||
model = models.EventChecklist
|
||||
template_name = 'hs_object_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistList, self).get_context_data(**kwargs)
|
||||
context['title'] = 'Event Checklist'
|
||||
context['view'] = 'ec_detail'
|
||||
context['edit'] = 'ec_edit'
|
||||
context['review'] = 'ec_review'
|
||||
context['perm'] = 'perms.RIGS.review_eventchecklist'
|
||||
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||
return context
|
||||
|
||||
|
||||
class EventChecklistReview(generic.View):
|
||||
def get(self, *args, **kwargs):
|
||||
rpk = kwargs.get('pk')
|
||||
ec = models.EventChecklist.objects.get(pk=rpk)
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(self.request.user)
|
||||
ec.reviewed_by = self.request.user
|
||||
ec.reviewed_at = timezone.now()
|
||||
ec.save()
|
||||
return HttpResponseRedirect(reverse_lazy('ec_list'))
|
||||
|
||||
|
||||
class HSList(generic.ListView):
|
||||
paginate_by = 20
|
||||
model = models.Event
|
||||
template_name = 'hs_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return models.Event.objects.all().order_by('-start_date')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(HSList, self).get_context_data(**kwargs)
|
||||
context['page_title'] = 'H&S Overview'
|
||||
return context
|
||||
15
RIGS/ical.py
15
RIGS/ical.py
@@ -1,12 +1,11 @@
|
||||
from RIGS import models, forms
|
||||
from django_ical.views import ICalFeed
|
||||
from django.db.models import Q
|
||||
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
import datetime
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django_ical.views import ICalFeed
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
class CalendarICS(ICalFeed):
|
||||
@@ -102,7 +101,7 @@ class CalendarICS(ICalFeed):
|
||||
return item.earliest_time
|
||||
|
||||
def item_end_datetime(self, item):
|
||||
if type(item.latest_time) == datetime.date: # Ical end_datetime is non-inclusive, so add a day
|
||||
if isinstance(item.latest_time, datetime.date): # Ical end_datetime is non-inclusive, so add a day
|
||||
return item.latest_time + datetime.timedelta(days=1)
|
||||
|
||||
return item.latest_time
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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 django.contrib.auth.models import Group, Permission
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class Command(BaseCommand):
|
||||
|
||||
keyholder_group = None
|
||||
finance_group = None
|
||||
hs_group = None
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from django.conf import settings
|
||||
@@ -165,6 +166,7 @@ class Command(BaseCommand):
|
||||
def setupGroups(self):
|
||||
self.keyholder_group = Group.objects.create(name='Keyholders')
|
||||
self.finance_group = Group.objects.create(name='Finance')
|
||||
self.hs_group = Group.objects.create(name='H&S')
|
||||
|
||||
keyholderPerms = ["add_event", "change_event", "view_event",
|
||||
"add_eventitem", "change_eventitem", "delete_eventitem",
|
||||
@@ -172,10 +174,17 @@ class Command(BaseCommand):
|
||||
"add_person", "change_person", "view_person", "view_profile",
|
||||
"add_venue", "change_venue", "view_venue",
|
||||
"add_asset", "change_asset", "delete_asset",
|
||||
"asset_finance", "view_asset", "view_supplier", "asset_finance",
|
||||
"add_supplier"]
|
||||
"view_asset", "view_supplier", "change_supplier", "asset_finance",
|
||||
"add_supplier", "view_cabletype", "change_cabletype",
|
||||
"add_cabletype", "view_eventchecklist", "change_eventchecklist",
|
||||
"add_eventchecklist", "view_riskassessment", "change_riskassessment",
|
||||
"add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew",
|
||||
"delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle",
|
||||
"change_eventchecklistvehicle",
|
||||
"delete_eventchecklistvehicle", "view_eventchecklistvehicle", ]
|
||||
financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
|
||||
"add_payment", "change_payment", "delete_payment"]
|
||||
hsPerms = keyholderPerms + ["review_riskassessment", "review_eventchecklist"]
|
||||
|
||||
for permId in keyholderPerms:
|
||||
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
@@ -183,6 +192,9 @@ class Command(BaseCommand):
|
||||
for permId in financePerms:
|
||||
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
|
||||
for permId in hsPerms:
|
||||
self.hs_group.permissions.add(Permission.objects.get(codename=permId))
|
||||
|
||||
def setupGenericProfiles(self):
|
||||
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble",
|
||||
"Jack Harkness", "Mickey Smith", "Rose Tyler"]
|
||||
@@ -207,21 +219,29 @@ class Command(BaseCommand):
|
||||
|
||||
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
|
||||
initials="FU",
|
||||
email="financeuser@example.com", is_active=True)
|
||||
email="financeuser@example.com", is_active=True, is_approved=True)
|
||||
financeUser.groups.add(self.finance_group)
|
||||
financeUser.groups.add(self.keyholder_group)
|
||||
financeUser.set_password('finance')
|
||||
financeUser.save()
|
||||
|
||||
hsUser = models.Profile.objects.create(username="hs", first_name="HS", last_name="User",
|
||||
initials="HSU",
|
||||
email="hsuser@example.com", is_active=True, is_approved=True)
|
||||
hsUser.groups.add(self.hs_group)
|
||||
hsUser.groups.add(self.keyholder_group)
|
||||
hsUser.set_password('hs')
|
||||
hsUser.save()
|
||||
|
||||
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
|
||||
initials="KU",
|
||||
email="keyholderuser@example.com", is_active=True)
|
||||
email="keyholderuser@example.com", is_active=True, is_approved=True)
|
||||
keyholderUser.groups.add(self.keyholder_group)
|
||||
keyholderUser.set_password('keyholder')
|
||||
keyholderUser.save()
|
||||
|
||||
basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU",
|
||||
email="basicuser@example.com", is_active=True)
|
||||
email="basicuser@example.com", is_active=True, is_approved=True)
|
||||
basicUser.set_password('basic')
|
||||
basicUser.save()
|
||||
|
||||
|
||||
190
RIGS/migrations/0039_auto_20210123_1910.py
Normal file
190
RIGS/migrations/0039_auto_20210123_1910.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# Generated by Django 3.1.2 on 2021-01-23 19:10
|
||||
|
||||
import RIGS.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0038_auto_20200306_2000'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EventChecklist',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField()),
|
||||
('safe_parking', models.BooleanField(blank=True, help_text='Vehicles parked safely?<br><small>(does not obstruct venue access)</small>', null=True)),
|
||||
('safe_packing', models.BooleanField(blank=True, help_text='Equipment packed away safely?<br><small>(including flightcases)</small>', null=True)),
|
||||
('exits', models.BooleanField(blank=True, help_text='Emergency exits clear?', null=True)),
|
||||
('trip_hazard', models.BooleanField(blank=True, help_text='Appropriate barriers around kit and cabling secured?', null=True)),
|
||||
('warning_signs', models.BooleanField(blank=True, help_text='Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>')),
|
||||
('ear_plugs', models.BooleanField(blank=True, help_text='Ear plugs issued to crew where needed?', null=True)),
|
||||
('hs_location', models.CharField(blank=True, help_text='Location of Safety Bag/Box', max_length=255, null=True)),
|
||||
('extinguishers_location', models.CharField(blank=True, help_text='Location of fire extinguishers', max_length=255, null=True)),
|
||||
('rcds', models.BooleanField(blank=True, help_text='RCDs installed where needed and tested?', null=True)),
|
||||
('supply_test', models.BooleanField(blank=True, help_text='Electrical supplies tested?<br><small>(using socket tester)</small>', null=True)),
|
||||
('earthing', models.BooleanField(blank=True, help_text='Equipment appropriately earthed?<br><small>(truss, stage, generators etc)</small>', null=True)),
|
||||
('pat', models.BooleanField(blank=True, help_text='All equipment in PAT period?', null=True)),
|
||||
('source_rcd', models.BooleanField(blank=True, help_text='Source RCD protected?<br><small>(if cable is more than 3m long) </small>', null=True)),
|
||||
('labelling', models.BooleanField(blank=True, help_text='Appropriate and clear labelling on distribution and cabling?', null=True)),
|
||||
('fd_voltage_l1', models.IntegerField(blank=True, help_text='L1 - N', null=True, verbose_name='First Distro Voltage L1-N')),
|
||||
('fd_voltage_l2', models.IntegerField(blank=True, help_text='L2 - N', null=True, verbose_name='First Distro Voltage L2-N')),
|
||||
('fd_voltage_l3', models.IntegerField(blank=True, help_text='L3 - N', null=True, verbose_name='First Distro Voltage L3-N')),
|
||||
('fd_phase_rotation', models.BooleanField(blank=True, help_text='Phase Rotation<br><small>(if required)</small>', null=True, verbose_name='Phase Rotation')),
|
||||
('fd_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True, verbose_name='Earth Fault Loop Impedance')),
|
||||
('fd_pssc', models.IntegerField(blank=True, help_text='Prospective Short Circuit Current', null=True, verbose_name='PSCC')),
|
||||
('w1_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
|
||||
('w1_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
|
||||
('w1_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
|
||||
('w1_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
|
||||
('w2_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
|
||||
('w2_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
|
||||
('w2_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
|
||||
('w2_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
|
||||
('w3_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
|
||||
('w3_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
|
||||
('w3_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
|
||||
('w3_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
|
||||
('all_rcds_tested', models.BooleanField(blank=True, help_text='All circuit RCDs tested?<br><small>(using test button)</small>', null=True)),
|
||||
('public_sockets_tested', models.BooleanField(blank=True, help_text='Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>', null=True)),
|
||||
('reviewed_at', models.DateTimeField(null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['event'],
|
||||
'permissions': [('review_eventchecklist', 'Can review Event Checklists')],
|
||||
},
|
||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventChecklistCrew',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('role', models.CharField(max_length=255)),
|
||||
('start', models.DateTimeField()),
|
||||
('end', models.DateTimeField()),
|
||||
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')),
|
||||
],
|
||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventChecklistVehicle',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('vehicle', models.CharField(max_length=255)),
|
||||
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')),
|
||||
],
|
||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RiskAssessment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('nonstandard_equipment', models.BooleanField(help_text="Does the event require any hired in equipment or use of equipment that is not covered by <a href='https://nottinghamtec.sharepoint.com/:f:/g/HealthAndSafety/Eo4xED_DrqFFsfYIjKzMZIIB6Gm_ZfR-a8l84RnzxtBjrA?e=Bf0Haw'>TEC's standard risk assessments and method statements?</a>")),
|
||||
('nonstandard_use', models.BooleanField(help_text='Are TEC using their equipment in a way that is abnormal?<br><small>i.e. Not covered by TECs standard health and safety documentation</small>')),
|
||||
('contractors', models.BooleanField(help_text='Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>')),
|
||||
('other_companies', models.BooleanField(help_text='Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>')),
|
||||
('crew_fatigue', models.BooleanField(help_text='Is crew fatigue likely to be a risk at any point during this event?')),
|
||||
('general_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
|
||||
('big_power', models.BooleanField(help_text='Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?')),
|
||||
('outside', models.BooleanField(help_text='Is the event outdoors?')),
|
||||
('generators', models.BooleanField(help_text='Will generators be used?')),
|
||||
('other_companies_power', models.BooleanField(help_text='Will TEC be supplying power to any other companies?')),
|
||||
('nonstandard_equipment_power', models.BooleanField(help_text='Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?')),
|
||||
('multiple_electrical_environments', models.BooleanField(help_text='Will the electrical installation occupy more than one electrical environment?')),
|
||||
('power_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
|
||||
('power_plan', models.URLField(blank=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", null=True, validators=[RIGS.models.validate_url])),
|
||||
('noise_monitoring', models.BooleanField(help_text='Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?')),
|
||||
('sound_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
|
||||
('known_venue', models.BooleanField(help_text='Is this venue new to you (the MIC) or new to TEC?')),
|
||||
('safe_loading', models.BooleanField(help_text='Are there any issues preventing a safe load in or out? (e.g. sufficient lighting, flat, not in a crowded area etc.)')),
|
||||
('safe_storage', models.BooleanField(help_text='Are there any problems with safe and secure equipment storage?')),
|
||||
('area_outside_of_control', models.BooleanField(help_text="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?")),
|
||||
('barrier_required', models.BooleanField(help_text='Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?')),
|
||||
('nonstandard_emergency_procedure', models.BooleanField(help_text="Does the emergency procedure for the event differ from TEC's standard procedures?")),
|
||||
('special_structures', models.BooleanField(help_text='Does the event require use of winch stands, motors, MPT Towers, or staging?')),
|
||||
('suspended_structures', models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")),
|
||||
('persons_responsible_structures', models.TextField(blank=True, help_text='Who are the persons on site responsible for their use?', null=True)),
|
||||
('rigging_plan', models.URLField(blank=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", null=True, validators=[RIGS.models.validate_url])),
|
||||
('reviewed_at', models.DateTimeField(null=True)),
|
||||
('supervisor_consulted', models.BooleanField(null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['event'],
|
||||
'permissions': [('review_riskassessment', 'Can review Risk Assessments')],
|
||||
},
|
||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventcrew',
|
||||
name='event',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventcrew',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='RIGSVersion',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='event',
|
||||
name='risk_assessment_edit_url',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='first_name',
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EventCrew',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='riskassessment',
|
||||
name='event',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='RIGS.event'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='riskassessment',
|
||||
name='power_mic',
|
||||
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='power_mic', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='riskassessment',
|
||||
name='reviewed_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklistvehicle',
|
||||
name='driver',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklistcrew',
|
||||
name='crewmember',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crewed', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklist',
|
||||
name='event',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to='RIGS.event'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklist',
|
||||
name='power_mic',
|
||||
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC?', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklist',
|
||||
name='reviewed_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventchecklist',
|
||||
name='venue',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='RIGS.venue'),
|
||||
),
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
# Generated by Django 3.0.3 on 2020-03-18 00:24
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0038_auto_20200306_2000'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='EventCrew',
|
||||
),
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
# Generated by Django 3.0.3 on 2020-04-15 18:40
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0039_delete_eventcrew'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='RIGSVersion',
|
||||
),
|
||||
]
|
||||
345
RIGS/models.py
345
RIGS/models.py
@@ -1,23 +1,22 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import datetime
|
||||
import pytz
|
||||
import random
|
||||
import string
|
||||
from collections import Counter
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from reversion import revisions as reversion
|
||||
from reversion.models import Version
|
||||
import string
|
||||
|
||||
import random
|
||||
from collections import Counter
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
class Profile(AbstractUser):
|
||||
@@ -65,8 +64,15 @@ class Profile(AbstractUser):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
# TODO move to versioning - currently get import errors with that
|
||||
|
||||
|
||||
class RevisionMixin(object):
|
||||
@property
|
||||
def is_first_version(self):
|
||||
versions = Version.objects.get_for_object(self)
|
||||
return len(versions) == 1
|
||||
|
||||
@property
|
||||
def current_version(self):
|
||||
version = Version.objects.get_for_object(self).select_related('revision').first()
|
||||
@@ -189,6 +195,8 @@ class VatRate(models.Model, RevisionMixin):
|
||||
|
||||
objects = VatManager()
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
@property
|
||||
def as_percent(self):
|
||||
return self.rate * 100
|
||||
@@ -267,9 +275,7 @@ class EventManager(models.Manager):
|
||||
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Ends after
|
||||
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q(
|
||||
status=Event.CANCELLED)) | # Active dry hire
|
||||
(models.Q(dry_hire=True, checked_in_by__isnull=True, is_rig=True) & (
|
||||
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) # Active dry hire GT
|
||||
status=Event.CANCELLED)) # Active dry hire
|
||||
).count()
|
||||
return event_count
|
||||
|
||||
@@ -326,8 +332,12 @@ class Event(models.Model, RevisionMixin):
|
||||
auth_request_at = models.DateTimeField(null=True, blank=True)
|
||||
auth_request_to = models.EmailField(null=True, blank=True)
|
||||
|
||||
# Risk assessment info
|
||||
risk_assessment_edit_url = models.CharField(verbose_name="risk assessment", max_length=255, blank=True, null=True)
|
||||
@property
|
||||
def display_id(self):
|
||||
if self.is_rig:
|
||||
return str("N%05d" % self.pk)
|
||||
else:
|
||||
return self.pk
|
||||
|
||||
# Calculated values
|
||||
"""
|
||||
@@ -336,17 +346,6 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def sum_total(self):
|
||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||
# if connection.vendor == 'postgresql':
|
||||
# sql = "SELECT SUM(quantity * cost) AS sum_total FROM \"RIGS_eventitem\" WHERE event_id=%i" % self.id
|
||||
# else:
|
||||
# sql = "SELECT id, SUM(quantity * cost) AS sum_total FROM RIGS_eventitem WHERE event_id=%i" % self.id
|
||||
# total = self.items.raw(sql)[0]
|
||||
# if total.sum_total:
|
||||
# return total.sum_total
|
||||
# total = 0.0
|
||||
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
||||
# total += item.sum
|
||||
total = EventItem.objects.filter(event=self).aggregate(
|
||||
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
||||
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||
@@ -380,8 +379,8 @@ class Event(models.Model, RevisionMixin):
|
||||
return (self.status == self.BOOKED or self.status == self.CONFIRMED)
|
||||
|
||||
@property
|
||||
def authorised(self):
|
||||
return not self.internal and self.purchase_order or self.authorisation.amount == self.total
|
||||
def hs_done(self):
|
||||
return self.riskassessment is not None and len(self.checklists.all()) > 0
|
||||
|
||||
@property
|
||||
def has_start_time(self):
|
||||
@@ -445,7 +444,14 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def internal(self):
|
||||
return self.organisation and self.organisation.union_account
|
||||
return bool(self.organisation and self.organisation.union_account)
|
||||
|
||||
@property
|
||||
def authorised(self):
|
||||
if self.internal:
|
||||
return self.authorisation.amount == self.total
|
||||
else:
|
||||
return bool(self.purchase_order)
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
@@ -453,22 +459,26 @@ class Event(models.Model, RevisionMixin):
|
||||
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return str(self.pk) + ": " + self.name
|
||||
return "{}: {}".format(self.display_id, self.name)
|
||||
|
||||
def clean(self):
|
||||
errdict = {}
|
||||
if self.end_date and self.start_date > self.end_date:
|
||||
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
|
||||
errdict['end_date'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
|
||||
|
||||
startEndSameDay = not self.end_date or self.end_date == self.start_date
|
||||
hasStartAndEnd = self.has_start_time and self.has_end_time
|
||||
if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time:
|
||||
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
|
||||
errdict['end_time'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
|
||||
|
||||
if self.access_at is not None:
|
||||
if self.access_at.date() > self.start_date:
|
||||
raise ValidationError('Regardless of what some clients might think, access time cannot be after the event has started.')
|
||||
errdict['access_at'] = ['Regardless of what some clients might think, access time cannot be after the event has started.']
|
||||
elif self.start_time is not None and self.start_date == self.access_at.date() and self.access_at.time() > self.start_time:
|
||||
raise ValidationError('Regardless of what some clients might think, access time cannot be after the event has started.')
|
||||
errdict['access_at'] = ['Regardless of what some clients might think, access time cannot be after the event has started.']
|
||||
|
||||
if errdict != {}: # If there was an error when validation
|
||||
raise ValidationError(errdict)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Call :meth:`full_clean` before saving."""
|
||||
@@ -476,7 +486,8 @@ class Event(models.Model, RevisionMixin):
|
||||
super(Event, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class EventItem(models.Model):
|
||||
@reversion.register
|
||||
class EventItem(models.Model, RevisionMixin):
|
||||
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
@@ -484,6 +495,8 @@ class EventItem(models.Model):
|
||||
cost = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
order = models.IntegerField()
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
@property
|
||||
def total_cost(self):
|
||||
return self.cost * self.quantity
|
||||
@@ -494,6 +507,10 @@ class EventItem(models.Model):
|
||||
def __str__(self):
|
||||
return str(self.event.pk) + "." + str(self.order) + ": " + self.event.name + " | " + self.name
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str("item {}".format(self.name))
|
||||
|
||||
|
||||
@reversion.register
|
||||
class EventAuthorisation(models.Model, RevisionMixin):
|
||||
@@ -510,15 +527,17 @@ class EventAuthorisation(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')')
|
||||
return "{} (requested by {})".format(self.event.display_id, self.sent_by.initials)
|
||||
|
||||
|
||||
@reversion.register(follow=['payment_set'])
|
||||
class Invoice(models.Model):
|
||||
class Invoice(models.Model, RevisionMixin):
|
||||
event = models.OneToOneField('Event', on_delete=models.CASCADE)
|
||||
invoice_date = models.DateField(auto_now_add=True)
|
||||
void = models.BooleanField(default=False)
|
||||
|
||||
reversion_perm = 'RIGS.view_invoice'
|
||||
|
||||
@property
|
||||
def sum_total(self):
|
||||
return self.event.sum_total
|
||||
@@ -542,14 +561,26 @@ class Invoice(models.Model):
|
||||
def is_closed(self):
|
||||
return self.balance == 0 or self.void
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('invoice_detail', kwargs={'pk': self.pk})
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return "#{} for Event {}".format(self.display_id, "N%05d" % self.event.pk)
|
||||
|
||||
def __str__(self):
|
||||
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
||||
|
||||
@property
|
||||
def display_id(self):
|
||||
return "{:05d}".format(self.pk)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-invoice_date']
|
||||
|
||||
|
||||
class Payment(models.Model):
|
||||
@reversion.register
|
||||
class Payment(models.Model, RevisionMixin):
|
||||
CASH = 'C'
|
||||
INTERNAL = 'I'
|
||||
EXTERNAL = 'E'
|
||||
@@ -568,5 +599,239 @@ class Payment(models.Model):
|
||||
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
|
||||
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %d" % (self.get_method_display(), self.amount)
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str("payment of £{}".format(self.amount))
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
if not value:
|
||||
return # Required error is done the field
|
||||
obj = urlparse(value)
|
||||
if obj.hostname not in ('nottinghamtec.sharepoint.com'):
|
||||
raise ValidationError('URL must point to a location on the TEC Sharepoint')
|
||||
|
||||
|
||||
@reversion.register
|
||||
class RiskAssessment(models.Model, RevisionMixin):
|
||||
SMALL = (0, 'Small')
|
||||
MEDIUM = (1, 'Medium')
|
||||
LARGE = (2, 'Large')
|
||||
SIZES = (SMALL, MEDIUM, LARGE)
|
||||
|
||||
event = models.OneToOneField('Event', on_delete=models.CASCADE)
|
||||
# General
|
||||
nonstandard_equipment = models.BooleanField(help_text="Does the event require any hired in equipment or use of equipment that is not covered by <a href='https://nottinghamtec.sharepoint.com/:f:/g/HealthAndSafety/Eo4xED_DrqFFsfYIjKzMZIIB6Gm_ZfR-a8l84RnzxtBjrA?e=Bf0Haw'>"
|
||||
"TEC's standard risk assessments and method statements?</a>")
|
||||
nonstandard_use = models.BooleanField(help_text="Are TEC using their equipment in a way that is abnormal?<br><small>i.e. Not covered by TECs standard health and safety documentation</small>")
|
||||
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
|
||||
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
|
||||
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
|
||||
general_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||
|
||||
# Power
|
||||
# event_size = models.IntegerField(blank=True, null=True, choices=SIZES)
|
||||
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
|
||||
# If yes to the above two, you must answer...
|
||||
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
|
||||
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)")
|
||||
outside = models.BooleanField(help_text="Is the event outdoors?")
|
||||
generators = models.BooleanField(help_text="Will generators be used?")
|
||||
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
|
||||
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
|
||||
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
|
||||
power_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||
power_plan = models.URLField(blank=True, null=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
||||
|
||||
# Sound
|
||||
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
|
||||
sound_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||
|
||||
# Site
|
||||
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
|
||||
safe_loading = models.BooleanField(help_text="Are there any issues preventing a safe load in or out? (e.g. sufficient lighting, flat, not in a crowded area etc.)")
|
||||
safe_storage = models.BooleanField(help_text="Are there any problems with safe and secure equipment storage?")
|
||||
area_outside_of_control = models.BooleanField(help_text="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?")
|
||||
barrier_required = models.BooleanField(help_text="Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?")
|
||||
nonstandard_emergency_procedure = models.BooleanField(help_text="Does the emergency procedure for the event differ from TEC's standard procedures?")
|
||||
|
||||
# Structures
|
||||
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
|
||||
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
|
||||
persons_responsible_structures = models.TextField(blank=True, null=True, help_text="Who are the persons on site responsible for their use?")
|
||||
rigging_plan = models.URLField(blank=True, null=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
||||
|
||||
# Blimey that was a lot of options
|
||||
|
||||
reviewed_at = models.DateTimeField(null=True)
|
||||
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||
verbose_name="Reviewer", on_delete=models.CASCADE)
|
||||
|
||||
supervisor_consulted = models.BooleanField(null=True)
|
||||
|
||||
expected_values = {
|
||||
'nonstandard_equipment': False,
|
||||
'nonstandard_use': False,
|
||||
'contractors': False,
|
||||
'other_companies': False,
|
||||
'crew_fatigue': False,
|
||||
'big_power': False,
|
||||
'generators': False,
|
||||
'other_companies_power': False,
|
||||
'nonstandard_equipment_power': False,
|
||||
'multiple_electrical_environments': False,
|
||||
'noise_monitoring': False,
|
||||
'known_venue': False,
|
||||
'safe_loading': False,
|
||||
'safe_storage': False,
|
||||
'area_outside_of_control': False,
|
||||
'barrier_required': False,
|
||||
'nonstandard_emergency_procedure': False,
|
||||
'special_structures': False,
|
||||
'suspended_structures': False,
|
||||
}
|
||||
inverted_fields = {key: value for (key, value) in expected_values.items() if not value}.keys()
|
||||
|
||||
def clean(self):
|
||||
# Check for idiots
|
||||
if not self.outside and self.generators:
|
||||
raise forms.ValidationError("Engage brain, please. <strong>No generators indoors!(!)</strong>")
|
||||
|
||||
class Meta:
|
||||
ordering = ['event']
|
||||
permissions = [
|
||||
('review_riskassessment', 'Can review Risk Assessments')
|
||||
]
|
||||
|
||||
@property
|
||||
def event_size(self):
|
||||
# Confirm event size. Check all except generators, since generators entails outside
|
||||
if self.outside or self.other_companies_power or self.nonstandard_equipment_power or self.multiple_electrical_environments:
|
||||
return self.LARGE[0]
|
||||
elif self.big_power:
|
||||
return self.MEDIUM[0]
|
||||
else:
|
||||
return self.SMALL[0]
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str(self.event)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('ra_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "%i - %s" % (self.pk, self.event)
|
||||
|
||||
|
||||
@reversion.register(follow=['vehicles', 'crew'])
|
||||
class EventChecklist(models.Model, RevisionMixin):
|
||||
event = models.ForeignKey('Event', related_name='checklists', on_delete=models.CASCADE)
|
||||
|
||||
# General
|
||||
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='checklists',
|
||||
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC?")
|
||||
venue = models.ForeignKey('Venue', on_delete=models.CASCADE)
|
||||
date = models.DateField()
|
||||
|
||||
# Safety Checks
|
||||
safe_parking = models.BooleanField(blank=True, null=True, help_text="Vehicles parked safely?<br><small>(does not obstruct venue access)</small>")
|
||||
safe_packing = models.BooleanField(blank=True, null=True, help_text="Equipment packed away safely?<br><small>(including flightcases)</small>")
|
||||
exits = models.BooleanField(blank=True, null=True, help_text="Emergency exits clear?")
|
||||
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
|
||||
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
|
||||
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
|
||||
hs_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of Safety Bag/Box")
|
||||
extinguishers_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of fire extinguishers")
|
||||
|
||||
# Small Electrical Checks
|
||||
rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?")
|
||||
supply_test = models.BooleanField(blank=True, null=True, help_text="Electrical supplies tested?<br><small>(using socket tester)</small>")
|
||||
|
||||
# Shared electrical checks
|
||||
earthing = models.BooleanField(blank=True, null=True, help_text="Equipment appropriately earthed?<br><small>(truss, stage, generators etc)</small>")
|
||||
pat = models.BooleanField(blank=True, null=True, help_text="All equipment in PAT period?")
|
||||
|
||||
# Medium Electrical Checks
|
||||
source_rcd = models.BooleanField(blank=True, null=True, help_text="Source RCD protected?<br><small>(if cable is more than 3m long) </small>")
|
||||
labelling = models.BooleanField(blank=True, null=True, help_text="Appropriate and clear labelling on distribution and cabling?")
|
||||
# First Distro
|
||||
fd_voltage_l1 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L1-N", help_text="L1 - N")
|
||||
fd_voltage_l2 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L2-N", help_text="L2 - N")
|
||||
fd_voltage_l3 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L3-N", help_text="L3 - N")
|
||||
fd_phase_rotation = models.BooleanField(blank=True, null=True, verbose_name="Phase Rotation", help_text="Phase Rotation<br><small>(if required)</small>")
|
||||
fd_earth_fault = models.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
|
||||
# Worst case points
|
||||
w1_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
|
||||
w1_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||
w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
||||
w1_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
w2_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
|
||||
w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
||||
w2_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
w3_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
|
||||
w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
||||
w3_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
|
||||
all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>")
|
||||
public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>")
|
||||
|
||||
reviewed_at = models.DateTimeField(null=True)
|
||||
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||
verbose_name="Reviewer", on_delete=models.CASCADE)
|
||||
|
||||
inverted_fields = []
|
||||
|
||||
class Meta:
|
||||
ordering = ['event']
|
||||
permissions = [
|
||||
('review_eventchecklist', 'Can review Event Checklists')
|
||||
]
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str(self.event)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('ec_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "%i - %s" % (self.pk, self.event)
|
||||
|
||||
|
||||
@reversion.register
|
||||
class EventChecklistVehicle(models.Model, RevisionMixin):
|
||||
checklist = models.ForeignKey('EventChecklist', related_name='vehicles', blank=True, on_delete=models.CASCADE)
|
||||
vehicle = models.CharField(max_length=255)
|
||||
driver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='vehicles', on_delete=models.CASCADE)
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
def __str__(self):
|
||||
return "{} driven by {}".format(self.vehicle, str(self.driver))
|
||||
|
||||
|
||||
@reversion.register
|
||||
class EventChecklistCrew(models.Model, RevisionMixin):
|
||||
checklist = models.ForeignKey('EventChecklist', related_name='crew', blank=True, on_delete=models.CASCADE)
|
||||
crewmember = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='crewed', on_delete=models.CASCADE)
|
||||
role = models.CharField(max_length=255)
|
||||
start = models.DateTimeField()
|
||||
end = models.DateTimeField()
|
||||
|
||||
reversion_hide = True
|
||||
|
||||
def clean(self):
|
||||
if self.start > self.end:
|
||||
raise ValidationError('Unless you\'ve invented time travel, crew can\'t finish before they have started.')
|
||||
|
||||
def __str__(self):
|
||||
return "{} ({})".format(str(self.crewmember), self.role)
|
||||
|
||||
133
RIGS/rigboard.py
133
RIGS/rigboard.py
@@ -1,34 +1,33 @@
|
||||
from io import BytesIO
|
||||
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.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.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 copy
|
||||
import datetime
|
||||
import re
|
||||
import copy
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from io import BytesIO
|
||||
|
||||
import premailer
|
||||
import simplejson
|
||||
from PyPDF2 import PdfFileMerger, PdfFileReader
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.core import signing
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import generic
|
||||
from z3c.rml import rml2pdf
|
||||
|
||||
from PyRIGS import decorators
|
||||
from RIGS import models, forms
|
||||
|
||||
__author__ = 'ghost'
|
||||
|
||||
@@ -42,6 +41,7 @@ class RigboardIndex(generic.TemplateView):
|
||||
|
||||
# call out method to get current events
|
||||
context['events'] = models.Event.objects.current_events()
|
||||
context['page_title'] = "Rigboard"
|
||||
return context
|
||||
|
||||
|
||||
@@ -82,26 +82,6 @@ class EventEmbed(EventDetail):
|
||||
template_name = '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
|
||||
@@ -109,11 +89,12 @@ class EventCreate(generic.CreateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventCreate, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "New Event"
|
||||
context['edit'] = True
|
||||
context['currentVAT'] = models.VatRate.objects.current_rate()
|
||||
|
||||
form = context['form']
|
||||
if re.search(r'"-\d+"', form['items_json'].value()):
|
||||
if hasattr(form, 'items_json') and re.search(r'"-\d+"', form['items_json'].value()):
|
||||
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
|
||||
|
||||
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
||||
@@ -134,6 +115,7 @@ class EventUpdate(generic.UpdateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventUpdate, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Event {}".format(self.object.display_id)
|
||||
context['edit'] = True
|
||||
|
||||
form = context['form']
|
||||
@@ -147,7 +129,7 @@ class EventUpdate(generic.UpdateView):
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
if not hasattr(context, 'duplicate'):
|
||||
if hasattr(context, 'duplicate') and not context['duplicate']:
|
||||
# If this event has already been emailed to a client, show a warning
|
||||
if self.object.auth_request_at is not None:
|
||||
messages.info(self.request,
|
||||
@@ -155,7 +137,7 @@ class EventUpdate(generic.UpdateView):
|
||||
|
||||
if hasattr(self.object, 'authorised'):
|
||||
messages.warning(self.request,
|
||||
'This event has already been authorised by client, any changes to price will require reauthorisation.')
|
||||
'This event has already been authorised by the client, any changes to the price will require reauthorisation.')
|
||||
return super(EventUpdate, self).render_to_response(context, **response_kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
@@ -168,6 +150,7 @@ class EventDuplicate(EventUpdate):
|
||||
new = copy.copy(old) # Make a copy of the object in memory
|
||||
new.based_on = old # Make the new event based on the old event
|
||||
new.purchase_order = None # Remove old PO
|
||||
new.status = new.PROVISIONAL # Return status to provisional
|
||||
|
||||
# Clear checked in by if it's a dry hire
|
||||
if new.dry_hire is True:
|
||||
@@ -188,6 +171,7 @@ class EventDuplicate(EventUpdate):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventDuplicate, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Duplicate of Event {}".format(self.object.display_id)
|
||||
context["duplicate"] = True
|
||||
return context
|
||||
|
||||
@@ -209,6 +193,7 @@ class EventPrint(generic.View):
|
||||
},
|
||||
'quote': True,
|
||||
'current_user': request.user,
|
||||
'filename': 'Event {} {} {}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
|
||||
}
|
||||
|
||||
rml = template.render(context)
|
||||
@@ -223,10 +208,7 @@ class EventPrint(generic.View):
|
||||
merger.write(merged)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
|
||||
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||
|
||||
response['Content-Disposition'] = "filename=N%05d | %s.pdf" % (object.pk, escapedEventName)
|
||||
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
|
||||
response.write(merged.getvalue())
|
||||
return response
|
||||
|
||||
@@ -242,6 +224,8 @@ class EventArchive(generic.ListView):
|
||||
|
||||
context['start'] = self.request.GET.get('start', None)
|
||||
context['end'] = self.request.GET.get('end', datetime.date.today().strftime('%Y-%m-%d'))
|
||||
context['statuses'] = models.Event.EVENT_STATUS_CHOICES
|
||||
context['page_title'] = 'Event Archive'
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -281,6 +265,11 @@ class EventArchive(generic.ListView):
|
||||
|
||||
filter &= qfilter
|
||||
|
||||
status = self.request.GET.getlist('status', "")
|
||||
|
||||
if len(status) > 0:
|
||||
filter &= Q(status__in=status)
|
||||
|
||||
qs = self.model.objects.filter(filter).order_by('-start_date')
|
||||
|
||||
# Preselect related for efficiency
|
||||
@@ -302,7 +291,7 @@ class EventAuthorise(generic.UpdateView):
|
||||
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))
|
||||
'You will also receive email confirmation to %s.' % self.object.email)
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
@property
|
||||
@@ -318,8 +307,10 @@ class EventAuthorise(generic.UpdateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventAuthorise, self).get_context_data(**kwargs)
|
||||
context['event'] = self.event
|
||||
|
||||
context['tos_url'] = settings.TERMS_OF_HIRE_URL
|
||||
context['page_title'] = "{}: {}".format(self.event.display_id, self.event.name)
|
||||
if self.event.dry_hire:
|
||||
context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>'
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@@ -382,7 +373,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
||||
email = form.cleaned_data['email']
|
||||
event = self.object
|
||||
event.auth_request_by = self.request.user
|
||||
event.auth_request_at = datetime.datetime.now()
|
||||
event.auth_request_at = timezone.now()
|
||||
event.auth_request_to = email
|
||||
event.save()
|
||||
|
||||
@@ -437,27 +428,3 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
||||
})
|
||||
context['to_name'] = self.request.GET.get('to_name', None)
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class LogRiskAssessment(generic.View):
|
||||
http_method_names = ["post"]
|
||||
|
||||
def post(self, request, **kwargs):
|
||||
data = request.POST
|
||||
shared_secret = data.get("secret")
|
||||
edit_url = data.get("editUrl")
|
||||
rig_number = data.get("rigNum")
|
||||
if shared_secret is None or edit_url is None or rig_number is None:
|
||||
return HttpResponse(status=422)
|
||||
|
||||
if shared_secret != settings.RISK_ASSESSMENT_SECRET:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
rig_number = int(re.sub("[^0-9]", "", rig_number))
|
||||
|
||||
event = get_object_or_404(models.Event, pk=rig_number)
|
||||
event.risk_assessment_edit_url = edit_url
|
||||
event.save()
|
||||
|
||||
return HttpResponse(status=200)
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import datetime
|
||||
import re
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
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.cache import cache
|
||||
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||
from django.db.models.signals import post_save
|
||||
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 registration.signals import user_activated
|
||||
from reversion import revisions as reversion
|
||||
from z3c.rml import rml2pdf
|
||||
|
||||
from RIGS import models
|
||||
@@ -138,3 +139,11 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
|
||||
|
||||
|
||||
user_activated.connect(send_admin_awaiting_approval_email)
|
||||
|
||||
|
||||
def update_cache(sender, instance, created, **kwargs):
|
||||
cache.clear()
|
||||
|
||||
|
||||
for model in reversion.get_registered_models():
|
||||
post_save.connect(update_cache, sender=model)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/*!
|
||||
* Ajax Bootstrap Select
|
||||
*
|
||||
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
|
||||
*
|
||||
* @version 1.4.5
|
||||
* @author Adam Heim - https://github.com/truckingsim
|
||||
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||
* @copyright 2019 Adam Heim
|
||||
* @license Released under the MIT license.
|
||||
*
|
||||
* Contributors:
|
||||
* Mark Carver - https://github.com/markcarver
|
||||
*
|
||||
* Last build: 2019-04-23 12:18:56 PM EDT
|
||||
*/
|
||||
.bootstrap-select .status {
|
||||
background: #f0f0f0;
|
||||
clear: both;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-bottom: -5px;
|
||||
padding: 10px 20px; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztFQWVFO0FBQ0Y7RUFDRSxtQkFBbUI7RUFDbkIsV0FBVztFQUNYLFdBQVc7RUFDWCxlQUFlO0VBQ2Ysa0JBQWtCO0VBQ2xCLGdCQUFnQjtFQUNoQixjQUFjO0VBQ2QsbUJBQW1CO0VBQ25CLGtCQUFrQixFQUFBIiwiZmlsZSI6ImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIvKiFcbiAqIEFqYXggQm9vdHN0cmFwIFNlbGVjdFxuICpcbiAqIEV4dGVuZHMgZXhpc3RpbmcgW0Jvb3RzdHJhcCBTZWxlY3RdIGltcGxlbWVudGF0aW9ucyBieSBhZGRpbmcgdGhlIGFiaWxpdHkgdG8gc2VhcmNoIHZpYSBBSkFYIHJlcXVlc3RzIGFzIHlvdSB0eXBlLiBPcmlnaW5hbGx5IGZvciBDUk9TQ09OLlxuICpcbiAqIEB2ZXJzaW9uIDEuNC41XG4gKiBAYXV0aG9yIEFkYW0gSGVpbSAtIGh0dHBzOi8vZ2l0aHViLmNvbS90cnVja2luZ3NpbVxuICogQGxpbmsgaHR0cHM6Ly9naXRodWIuY29tL3RydWNraW5nc2ltL0FqYXgtQm9vdHN0cmFwLVNlbGVjdFxuICogQGNvcHlyaWdodCAyMDE5IEFkYW0gSGVpbVxuICogQGxpY2Vuc2UgUmVsZWFzZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlLlxuICpcbiAqIENvbnRyaWJ1dG9yczpcbiAqICAgTWFyayBDYXJ2ZXIgLSBodHRwczovL2dpdGh1Yi5jb20vbWFya2NhcnZlclxuICpcbiAqIExhc3QgYnVpbGQ6IDIwMTktMDQtMjMgMTI6MTg6NTYgUE0gRURUXG4gKi9cbi5ib290c3RyYXAtc2VsZWN0IC5zdGF0dXMge1xuICBiYWNrZ3JvdW5kOiAjZjBmMGYwO1xuICBjbGVhcjogYm90aDtcbiAgY29sb3I6ICM5OTk7XG4gIGZvbnQtc2l6ZTogMTFweDtcbiAgZm9udC1zdHlsZTogaXRhbGljO1xuICBmb250LXdlaWdodDogNTAwO1xuICBsaW5lLWhlaWdodDogMTtcbiAgbWFyZ2luLWJvdHRvbTogLTVweDtcbiAgcGFkZGluZzogMTBweCAyMHB4O1xufVxuIl19 */
|
||||
28
RIGS/static/css/ajax-bootstrap-select.min.css
vendored
28
RIGS/static/css/ajax-bootstrap-select.min.css
vendored
@@ -1,28 +0,0 @@
|
||||
/*!
|
||||
* Ajax Bootstrap Select
|
||||
*
|
||||
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
|
||||
*
|
||||
* @version 1.4.5
|
||||
* @author Adam Heim - https://github.com/truckingsim
|
||||
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||
* @copyright 2019 Adam Heim
|
||||
* @license Released under the MIT license.
|
||||
*
|
||||
* Contributors:
|
||||
* Mark Carver - https://github.com/markcarver
|
||||
*
|
||||
* Last build: 2019-04-23 12:18:56 PM EDT
|
||||
*/
|
||||
.bootstrap-select .status {
|
||||
background: #f0f0f0;
|
||||
clear: both;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-bottom: -5px;
|
||||
padding: 10px 20px; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5taW4uY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7RUFlRTtBQUFDO0VBQTBCLG1CQUFrQjtFQUFDLFdBQVU7RUFBQyxXQUFVO0VBQUMsZUFBYztFQUFDLGtCQUFpQjtFQUFDLGdCQUFlO0VBQUMsY0FBYTtFQUFDLG1CQUFrQjtFQUFDLGtCQUFpQixFQUFBIiwiZmlsZSI6ImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5taW4uY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLyohXG4gKiBBamF4IEJvb3RzdHJhcCBTZWxlY3RcbiAqXG4gKiBFeHRlbmRzIGV4aXN0aW5nIFtCb290c3RyYXAgU2VsZWN0XSBpbXBsZW1lbnRhdGlvbnMgYnkgYWRkaW5nIHRoZSBhYmlsaXR5IHRvIHNlYXJjaCB2aWEgQUpBWCByZXF1ZXN0cyBhcyB5b3UgdHlwZS4gT3JpZ2luYWxseSBmb3IgQ1JPU0NPTi5cbiAqXG4gKiBAdmVyc2lvbiAxLjQuNVxuICogQGF1dGhvciBBZGFtIEhlaW0gLSBodHRwczovL2dpdGh1Yi5jb20vdHJ1Y2tpbmdzaW1cbiAqIEBsaW5rIGh0dHBzOi8vZ2l0aHViLmNvbS90cnVja2luZ3NpbS9BamF4LUJvb3RzdHJhcC1TZWxlY3RcbiAqIEBjb3B5cmlnaHQgMjAxOSBBZGFtIEhlaW1cbiAqIEBsaWNlbnNlIFJlbGVhc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZS5cbiAqXG4gKiBDb250cmlidXRvcnM6XG4gKiAgIE1hcmsgQ2FydmVyIC0gaHR0cHM6Ly9naXRodWIuY29tL21hcmtjYXJ2ZXJcbiAqXG4gKiBMYXN0IGJ1aWxkOiAyMDE5LTA0LTIzIDEyOjE4OjU2IFBNIEVEVFxuICovLmJvb3RzdHJhcC1zZWxlY3QgLnN0YXR1c3tiYWNrZ3JvdW5kOiNmMGYwZjA7Y2xlYXI6Ym90aDtjb2xvcjojOTk5O2ZvbnQtc2l6ZToxMXB4O2ZvbnQtc3R5bGU6aXRhbGljO2ZvbnQtd2VpZ2h0OjUwMDtsaW5lLWhlaWdodDoxO21hcmdpbi1ib3R0b206LTVweDtwYWRkaW5nOjEwcHggMjBweH0iXX0= */
|
||||
@@ -1,23 +0,0 @@
|
||||
.autocomplete {
|
||||
background: white;
|
||||
z-index: 1000;
|
||||
font: 14px/22px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(50, 50, 50, 0.6); }
|
||||
|
||||
.autocomplete * {
|
||||
font: inherit; }
|
||||
|
||||
.autocomplete > div {
|
||||
padding: 0 4px; }
|
||||
|
||||
.autocomplete .group {
|
||||
background: #eee; }
|
||||
|
||||
.autocomplete > div:hover:not(.group),
|
||||
.autocomplete > div.selected {
|
||||
background: #81ca91;
|
||||
cursor: pointer; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImF1dG9jb21wbGV0ZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0E7RUFDSSxpQkFBaUI7RUFDakIsYUFBYTtFQUNiLDRHQUE0RztFQUM1RyxjQUFjO0VBQ2Qsc0JBQXNCO0VBQ3RCLHVDQUF1QyxFQUFBOztBQUczQztFQUNJLGFBQWEsRUFBQTs7QUFHakI7RUFDSSxjQUFjLEVBQUE7O0FBR2xCO0VBQ0ksZ0JBQWdCLEVBQUE7O0FBR3BCOztFQUVJLG1CQUFtQjtFQUNuQixlQUFlLEVBQUEiLCJmaWxlIjoiYXV0b2NvbXBsZXRlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIlxyXG4uYXV0b2NvbXBsZXRlIHtcclxuICAgIGJhY2tncm91bmQ6IHdoaXRlO1xyXG4gICAgei1pbmRleDogMTAwMDtcclxuICAgIGZvbnQ6IDE0cHgvMjJweCBcIi1hcHBsZS1zeXN0ZW1cIiwgQmxpbmtNYWNTeXN0ZW1Gb250LCBcIlNlZ29lIFVJXCIsIFJvYm90bywgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBBcmlhbCwgc2Fucy1zZXJpZjtcclxuICAgIG92ZXJmbG93OiBhdXRvO1xyXG4gICAgYm94LXNpemluZzogYm9yZGVyLWJveDtcclxuICAgIGJvcmRlcjogMXB4IHNvbGlkIHJnYmEoNTAsIDUwLCA1MCwgMC42KTtcclxufVxyXG5cclxuLmF1dG9jb21wbGV0ZSAqIHtcclxuICAgIGZvbnQ6IGluaGVyaXQ7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgPiBkaXYge1xyXG4gICAgcGFkZGluZzogMCA0cHg7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgLmdyb3VwIHtcclxuICAgIGJhY2tncm91bmQ6ICNlZWU7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgPiBkaXY6aG92ZXI6bm90KC5ncm91cCksXHJcbi5hdXRvY29tcGxldGUgPiBkaXYuc2VsZWN0ZWQge1xyXG4gICAgYmFja2dyb3VuZDogIzgxY2E5MTtcclxuICAgIGN1cnNvcjogcG9pbnRlcjtcclxufVxyXG5cclxuIl19 */
|
||||
248
RIGS/static/css/bootstrap-datetimepicker.min.css
vendored
248
RIGS/static/css/bootstrap-datetimepicker.min.css
vendored
File diff suppressed because one or more lines are too long
455
RIGS/static/css/bootstrap-select.css
vendored
455
RIGS/static/css/bootstrap-select.css
vendored
File diff suppressed because one or more lines are too long
@@ -1,39 +0,0 @@
|
||||
body {
|
||||
margin: 0px; }
|
||||
|
||||
.main-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse; }
|
||||
|
||||
.client-header {
|
||||
background-image: url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");
|
||||
background-color: #222;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 100%;
|
||||
margin-bottom: 28px; }
|
||||
.client-header .logos {
|
||||
width: 100%;
|
||||
max-width: 640px; }
|
||||
.client-header img {
|
||||
height: 110px; }
|
||||
|
||||
.content-container {
|
||||
width: 100%; }
|
||||
.content-container .content {
|
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
padding: 10px;
|
||||
text-align: left; }
|
||||
.content-container .content .button-container {
|
||||
width: 100%; }
|
||||
.content-container .content .button-container .button {
|
||||
padding: 6px 12px;
|
||||
background-color: #357ebf;
|
||||
border-radius: 4px; }
|
||||
.content-container .content .button-container .button a {
|
||||
color: #fff;
|
||||
text-decoration: none; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImVtYWlsLnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7RUFDRSxXQUFXLEVBQUE7O0FBR2I7RUFDRSxXQUFXO0VBQ1gseUJBQXlCLEVBQUE7O0FBSTNCO0VBQ0UsMkVBQTJFO0VBQzNFLHNCQUFzQjtFQUN0Qiw0QkFBNEI7RUFDNUIsMkJBQTJCO0VBRTNCLFdBQVc7RUFFWCxtQkFBbUIsRUFBQTtFQVJyQjtJQVdJLFdBQVc7SUFDWCxnQkFBZ0IsRUFBQTtFQVpwQjtJQWdCSSxhQUFhLEVBQUE7O0FBSWpCO0VBQ0UsV0FBVyxFQUFBO0VBRGI7SUFJSSx3RUFBd0U7SUFFeEUsV0FBVztJQUNYLGdCQUFnQjtJQUNoQixhQUFhO0lBQ2IsZ0JBQWdCLEVBQUE7SUFUcEI7TUFZTSxXQUFXLEVBQUE7TUFaakI7UUFlUSxpQkFBaUI7UUFDakIseUJBaERjO1FBaURkLGtCQUFrQixFQUFBO1FBakIxQjtVQW9CVSxXQUFXO1VBQ1gscUJBQXFCLEVBQUEiLCJmaWxlIjoiZW1haWwuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiJGJ1dHRvbl9jb2xvcjogIzM1N2ViZjtcblxuYm9keXtcbiAgbWFyZ2luOiAwcHg7XG59XG5cbi5tYWluLXRhYmxle1xuICB3aWR0aDogMTAwJTtcbiAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTtcblxufVxuXG4uY2xpZW50LWhlYWRlciB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybChcImh0dHBzOi8vd3d3Lm5vdHRpbmdoYW10ZWMuY28udWsvaW1ncy93b2YyMDE0LTEuanBnXCIpO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMjIyO1xuICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0O1xuICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiBjZW50ZXI7XG5cbiAgd2lkdGg6IDEwMCU7XG5cbiAgbWFyZ2luLWJvdHRvbTogMjhweDtcblxuICAubG9nb3N7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgbWF4LXdpZHRoOiA2NDBweDtcbiAgfVxuXG4gIGltZyB7XG4gICAgaGVpZ2h0OiAxMTBweDtcbiAgfVxufVxuXG4uY29udGVudC1jb250YWluZXJ7XG4gIHdpZHRoOiAxMDAlO1xuXG4gIC5jb250ZW50IHtcbiAgICBmb250LWZhbWlseTogXCJPcGVuIFNhbnNcIiwgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmO1xuXG4gICAgd2lkdGg6IDEwMCU7XG4gICAgbWF4LXdpZHRoOiA2MDBweDtcbiAgICBwYWRkaW5nOiAxMHB4O1xuICAgIHRleHQtYWxpZ246IGxlZnQ7XG5cbiAgICAuYnV0dG9uLWNvbnRhaW5lcntcbiAgICAgIHdpZHRoOiAxMDAlO1xuXG4gICAgICAuYnV0dG9uIHtcbiAgICAgICAgcGFkZGluZzogNnB4IDEycHg7XG4gICAgICAgIGJhY2tncm91bmQtY29sb3I6ICRidXR0b25fY29sb3I7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDRweDtcblxuICAgICAgICBhIHtcbiAgICAgICAgICBjb2xvcjogI2ZmZjtcbiAgICAgICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gICAgICAgIH1cblxuICAgICAgfVxuXG4gICAgfVxuXG4gIH1cbn1cblxuIl19 */
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJpZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6W119 */
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap alert.js v4.4.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("jquery"),require("./util.js")):"function"==typeof define&&define.amd?define(["jquery","./util.js"],t):(e=e||self).Alert=t(e.jQuery,e.Util)}(this,(function(e,t){"use strict";function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}e=e&&e.hasOwnProperty("default")?e.default:e,t=t&&t.hasOwnProperty("default")?t.default:t;var r=e.fn.alert,o={CLOSE:"close.bs.alert",CLOSED:"closed.bs.alert",CLICK_DATA_API:"click.bs.alert.data-api"},i="alert",l="fade",s="show",a=function(){function r(e){this._element=e}var a,u,f,c=r.prototype;return c.close=function(e){var t=this._element;e&&(t=this._getRootElement(e)),this._triggerCloseEvent(t).isDefaultPrevented()||this._removeElement(t)},c.dispose=function(){e.removeData(this._element,"bs.alert"),this._element=null},c._getRootElement=function(n){var r=t.getSelectorFromElement(n),o=!1;return r&&(o=document.querySelector(r)),o||(o=e(n).closest("."+i)[0]),o},c._triggerCloseEvent=function(t){var n=e.Event(o.CLOSE);return e(t).trigger(n),n},c._removeElement=function(n){var r=this;if(e(n).removeClass(s),e(n).hasClass(l)){var o=t.getTransitionDurationFromElement(n);e(n).one(t.TRANSITION_END,(function(e){return r._destroyElement(n,e)})).emulateTransitionEnd(o)}else this._destroyElement(n)},c._destroyElement=function(t){e(t).detach().trigger(o.CLOSED).remove()},r._jQueryInterface=function(t){return this.each((function(){var n=e(this),o=n.data("bs.alert");o||(o=new r(this),n.data("bs.alert",o)),"close"===t&&o[t](this)}))},r._handleDismiss=function(e){return function(t){t&&t.preventDefault(),e.close(this)}},a=r,f=[{key:"VERSION",get:function(){return"4.4.1"}}],(u=null)&&n(a.prototype,u),f&&n(a,f),r}();return e(document).on(o.CLICK_DATA_API,'[data-dismiss="alert"]',a._handleDismiss(new a)),e.fn.alert=a._jQueryInterface,e.fn.alert.Constructor=a,e.fn.alert.noConflict=function(){return e.fn.alert=r,a._jQueryInterface},a}));
|
||||
File diff suppressed because one or more lines are too long
1
RIGS/static/js/asteroids.min.js
vendored
1
RIGS/static/js/asteroids.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
$(document).ready((function(){function e(e){targetObject=$("#"+e.attr("id")+"-update"),update_url=$("option:selected",e).data("update_url"),""==update_url?targetObject.attr("disabled",!0):(targetObject.attr("href",update_url),targetObject.attr("disabled",!1))}clearSelectionLabel="(no selection)",$(".selectpicker").each((function(){var t={ajax:{url:$(this).data("sourceurl"),type:"GET",dataType:"json",data:{term:"{{{q}}}"}},locale:{emptyTitle:""},clearOnEmpty:!1,preprocessData:function(e){var t,a=e.length,l=[];if(l.push({text:clearSelectionLabel,value:"",data:{update_url:"",subtext:""}}),a)for(t=0;t<a;t++)l.push($.extend(!0,e[t],{text:e[t].label,value:e[t].pk,data:{update_url:e[t].update,subtext:""}}));return l}};$(this).prepend($("<option></option>").attr("value","").text(clearSelectionLabel).data("update_url","")),$(this).selectpicker().ajaxSelectPicker(t),$(this).change((function(){e($(this))})),e($(this))})),$("#modal").on("hide.bs.modal",(function(e){null!=modaltarget&&""!=modalobject&&function(e,t,a,l){e.find("option").remove(),e.append($("<option></option>").attr("value",t).text(a).data("update_url",l)),e.selectpicker("render"),e.selectpicker("refresh"),e.selectpicker("val",t),e.change()}($(modaltarget),modalobject[0].pk,modalobject[0].fields.name,modalobject[0].update_url)}))}));
|
||||
1
RIGS/static/js/bootstrap-datetimepicker.js
vendored
1
RIGS/static/js/bootstrap-datetimepicker.js
vendored
File diff suppressed because one or more lines are too long
7
RIGS/static/js/bootstrap-select.js
vendored
7
RIGS/static/js/bootstrap-select.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
function setupItemTable(t){objectitems=JSON.parse(t),$.each(objectitems,(function(t,e){objectitems[t]=JSON.parse(e)})),newitem=-1}function nl2br(t,e){return(t+"").replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g,"$1"+(e||void 0===e?"<br />":"<br>")+"$2")}function escapeHtml(t){return $("<div/>").text(t).html()}function updatePrices(){var t=0;for(var e in objectitems){var i=objectitems[e].fields,a=i.cost*i.quantity;$("#item-"+e+" .sub-total").html(parseFloat(a).toFixed(2)).data("subtotal",a),t+=Number(a)}$("#sumtotal").text(parseFloat(t).toFixed(2));var o=t*Number($("#vat-rate").data("rate"));$("#vat").text(parseFloat(o).toFixed(2)),$("#total").text(parseFloat(t+o).toFixed(2))}$("#item-table").on("click",".item-delete",(function(){delete objectitems[$(this).data("pk")],$("#item-"+$(this).data("pk")).remove(),updatePrices()})),$("#item-table").on("click",".item-add",(function(){$("#item-form").data("pk",newitem),$("#item_name").val(""),$("#item_description").val(""),$("#item_quantity").val(""),$("#item_cost").val(""),$($(this).data("target")).modal("show")})),$("#item-table").on("click",".item-edit",(function(){var t=$(this).data("pk");$("#item-form").data("pk",t);var e=objectitems[t].fields;$("#item_name").val(e.name),$("#item_description").val(e.description),$("#item_quantity").val(e.quantity),$("#item_cost").val(e.cost),$($(this).data("target")).modal("show")})),$("body").on("submit","#item-form",(function(t){t.preventDefault();var e,i=$(this).data("pk");if($("#itemModal").modal("hide"),i==newitem--){(e=new Object).name=$("#item_name").val(),e.description=$("#item_description").val(),e.cost=$("#item_cost").val(),e.quantity=$("#item_quantity").val();var a=0;for(item in objectitems)a++;e.order=a,objectitems[i]=new Object,objectitems[i].fields=e,$("#new-item-row").clone().attr("id","item-"+i).data("pk",i).appendTo("#item-table-body"),$("#item-"+i+" .item-delete, #item-"+i+" .item-edit").data("pk",i)}else(e=objectitems[i].fields).name=$("#item_name").val(),e.description=$("#item_description").val(),e.cost=$("#item_cost").val(),e.quantity=$("#item_quantity").val(),objectitems[i].fields=e;$row=$("#item-"+i),$row.find(".name").html(escapeHtml(e.name)),$row.find(".description").html(nl2br(escapeHtml(e.description))),$row.find(".cost").html(parseFloat(e.cost).toFixed(2)),$row.find(".quantity").html(e.quantity),updatePrices()})),$("body").on("submit",".itemised_form",(function(t){$("#id_items_json").val(JSON.stringify(objectitems))}));var fixHelper=function(t,e){return e.children().each((function(){$(this).width($(this).width())})),e};$("#item-table tbody").sortable({helper:fixHelper,update:function(t,e){info=$(this).sortable("toArray"),itemorder=new Array,$.each(info,(function(t,e){pk=$("#"+e).data("pk"),objectitems[pk].fields.order=t}))}});
|
||||
186
RIGS/static/js/jquery-ui.js
vendored
186
RIGS/static/js/jquery-ui.js
vendored
File diff suppressed because one or more lines are too long
25
RIGS/static/js/jquery.js
vendored
25
RIGS/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
var Konami=function(t){var e={addEvent:function(t,e,n,o){t.addEventListener?t.addEventListener(e,n,!1):t.attachEvent&&(t["e"+e+n]=n,t[e+n]=function(){t["e"+e+n](window.event,o)},t.attachEvent("on"+e,t[e+n]))},removeEvent:function(t,e,n){t.removeEventListener?t.removeEventListener(e,n):t.attachEvent&&t.detachEvent(e)},input:"",pattern:"38384040373937396665",keydownHandler:function(t,n){if(n&&(e=n),e.input+=t?t.keyCode:event.keyCode,e.input.length>e.pattern.length&&(e.input=e.input.substr(e.input.length-e.pattern.length)),e.input===e.pattern)return e.code(e._currentLink),e.input="",t.preventDefault(),!1},load:function(t){this._currentLink=t,this.addEvent(document,"keydown",this.keydownHandler,this),this.iphone.load(t)},unload:function(){this.removeEvent(document,"keydown",this.keydownHandler),this.iphone.unload()},code:function(t){window.location=t},iphone:{start_x:0,start_y:0,stop_x:0,stop_y:0,tap:!1,capture:!1,orig_keys:"",keys:["UP","UP","DOWN","DOWN","LEFT","RIGHT","LEFT","RIGHT","TAP","TAP"],input:[],code:function(t){e.code(t)},touchmoveHandler:function(t){if(1===t.touches.length&&!0===e.iphone.capture){var n=t.touches[0];e.iphone.stop_x=n.pageX,e.iphone.stop_y=n.pageY,e.iphone.tap=!1,e.iphone.capture=!1,e.iphone.check_direction()}},touchendHandler:function(){if(e.iphone.input.push(e.iphone.check_direction()),e.iphone.input.length>e.iphone.keys.length&&e.iphone.input.shift(),e.iphone.input.length===e.iphone.keys.length){for(var t=!0,n=0;n<e.iphone.keys.length;n++)e.iphone.input[n]!==e.iphone.keys[n]&&(t=!1);t&&e.iphone.code(e._currentLink)}},touchstartHandler:function(t){e.iphone.start_x=t.changedTouches[0].pageX,e.iphone.start_y=t.changedTouches[0].pageY,e.iphone.tap=!0,e.iphone.capture=!0},load:function(t){this.orig_keys=this.keys,e.addEvent(document,"touchmove",this.touchmoveHandler),e.addEvent(document,"touchend",this.touchendHandler,!1),e.addEvent(document,"touchstart",this.touchstartHandler)},unload:function(){e.removeEvent(document,"touchmove",this.touchmoveHandler),e.removeEvent(document,"touchend",this.touchendHandler),e.removeEvent(document,"touchstart",this.touchstartHandler)},check_direction:function(){return x_magnitude=Math.abs(this.start_x-this.stop_x),y_magnitude=Math.abs(this.start_y-this.stop_y),x=this.start_x-this.stop_x<0?"RIGHT":"LEFT",y=this.start_y-this.stop_y<0?"DOWN":"UP",result=x_magnitude>y_magnitude?x:y,result=!0===this.tap?"TAP":result,result}}};return"string"==typeof t&&e.load(t),"function"==typeof t&&(e.code=t,e.load()),e};"undefined"!=typeof module&&void 0!==module.exports?module.exports=Konami:"function"==typeof define&&define.amd?define([],(function(){return Konami})):window.Konami=Konami;
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap popover.js v4.4.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("jquery"),require("./tooltip.js")):"function"==typeof define&&define.amd?define(["jquery","./tooltip.js"],t):(e=e||self).Popover=t(e.jQuery,e.Tooltip)}(this,(function(e,t){"use strict";function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}e=e&&e.hasOwnProperty("default")?e.default:e,t=t&&t.hasOwnProperty("default")?t.default:t;var u="popover",s=".bs.popover",c=e.fn[u],p=new RegExp("(^|\\s)bs-popover\\S+","g"),f=i({},t.Default,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'}),a=i({},t.DefaultType,{content:"(string|element|function)"}),l="fade",h="show",y=".popover-header",d=".popover-body",v={HIDE:"hide"+s,HIDDEN:"hidden"+s,SHOW:"show"+s,SHOWN:"shown"+s,INSERTED:"inserted"+s,CLICK:"click"+s,FOCUSIN:"focusin"+s,FOCUSOUT:"focusout"+s,MOUSEENTER:"mouseenter"+s,MOUSELEAVE:"mouseleave"+s},g=function(t){var r,o;function i(){return t.apply(this,arguments)||this}o=t,(r=i).prototype=Object.create(o.prototype),r.prototype.constructor=r,r.__proto__=o;var c,g,b,O=i.prototype;return O.isWithContent=function(){return this.getTitle()||this._getContent()},O.addAttachmentClass=function(t){e(this.getTipElement()).addClass("bs-popover-"+t)},O.getTipElement=function(){return this.tip=this.tip||e(this.config.template)[0],this.tip},O.setContent=function(){var t=e(this.getTipElement());this.setElementContent(t.find(y),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(t.find(d),n),t.removeClass(l+" "+h)},O._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},O._cleanTipClass=function(){var t=e(this.getTipElement()),n=t.attr("class").match(p);null!==n&&n.length>0&&t.removeClass(n.join(""))},i._jQueryInterface=function(t){return this.each((function(){var n=e(this).data("bs.popover"),r="object"==typeof t?t:null;if((n||!/dispose|hide/.test(t))&&(n||(n=new i(this,r),e(this).data("bs.popover",n)),"string"==typeof t)){if(void 0===n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},c=i,b=[{key:"VERSION",get:function(){return"4.4.1"}},{key:"Default",get:function(){return f}},{key:"NAME",get:function(){return u}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return v}},{key:"EVENT_KEY",get:function(){return s}},{key:"DefaultType",get:function(){return a}}],(g=null)&&n(c.prototype,g),b&&n(c,b),i}(t);return e.fn[u]=g._jQueryInterface,e.fn[u].Constructor=g,e.fn[u].noConflict=function(){return e.fn[u]=c,g._jQueryInterface},g}));
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap util.js v4.4.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):(t=t||self).Util=e(t.jQuery)}(this,(function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;function e(e){var r=this,o=!1;return t(this).one(n.TRANSITION_END,(function(){o=!0})),setTimeout((function(){o||n.triggerTransitionEnd(r)}),e),this}var n={TRANSITION_END:"bsTransitionEnd",getUID:function(t){do{t+=~~(1e6*Math.random())}while(document.getElementById(t));return t},getSelectorFromElement:function(t){var e=t.getAttribute("data-target");if(!e||"#"===e){var n=t.getAttribute("href");e=n&&"#"!==n?n.trim():""}try{return document.querySelector(e)?e:null}catch(t){return null}},getTransitionDurationFromElement:function(e){if(!e)return 0;var n=t(e).css("transition-duration"),r=t(e).css("transition-delay"),o=parseFloat(n),i=parseFloat(r);return o||i?(n=n.split(",")[0],r=r.split(",")[0],1e3*(parseFloat(n)+parseFloat(r))):0},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(e){t(e).trigger("transitionend")},supportsTransitionEnd:function(){return Boolean("transitionend")},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,r){for(var o in r)if(Object.prototype.hasOwnProperty.call(r,o)){var i=r[o],a=e[o],u=a&&n.isElement(a)?"element":(s=a,{}.toString.call(s).match(/\s([a-z]+)/i)[1].toLowerCase());if(!new RegExp(i).test(u))throw new Error(t.toUpperCase()+': Option "'+o+'" provided type "'+u+'" but expected type "'+i+'".')}var s},findShadowRoot:function(t){if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){var e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?n.findShadowRoot(t.parentNode):null},jQueryDetection:function(){if(void 0===t)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1===e[0]&&9===e[1]&&e[2]<1||e[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};return n.jQueryDetection(),t.fn.emulateTransitionEnd=e,t.event.special[n.TRANSITION_END]={bindType:"transitionend",delegateType:"transitionend",handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}},n}));
|
||||
@@ -1,11 +0,0 @@
|
||||
$font-family-sans-serif: "Open Sans", sans-serif;
|
||||
//Make it look less primary school
|
||||
$font-size-base: 0.875rem;
|
||||
$theme-colors: (
|
||||
"yellow": #ffd351,
|
||||
"success": #3b7743,
|
||||
"warning": #D3963B,
|
||||
"danger": #A94447,
|
||||
"info": #B8FAFF,
|
||||
"primary": #4CB8F1
|
||||
);
|
||||
@@ -30,6 +30,18 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% if perms.RIGS.view_riskassessment %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownHS" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
H&S
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownHS">
|
||||
<a class="dropdown-item" href="{% url 'hs_list' %}"><span class="fas fa-eye"></span> Overview</a>
|
||||
<a class="dropdown-item" href="{% url 'ra_list' %}"><span class="fas fa-file-medical"></span> Risk Assessments</a>
|
||||
<a class="dropdown-item" href="{% url 'ec_list' %}"><span class="fas fa-tasks"></span> Event Checklists</a>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.RIGS.view_invoice %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownInvoices" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@@ -53,51 +65,14 @@
|
||||
{% if perms.RIGS.view_venue %}
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'venue_list' %}">Venues</a></li>
|
||||
{% endif %}
|
||||
<form id="searchForm" class="form-inline flex-nowrap mx-3" role="form" method="GET">
|
||||
<div class="input-group input-group-sm flex-nowrap">
|
||||
<div class="input-group-prepend">
|
||||
<input id="id_search_input" type="search" name="q" class="form-control form-control-sm" placeholder="Search..." />
|
||||
</div>
|
||||
<select id="search-options" class="custom-select form-control">
|
||||
<option selected data-action="{% url 'event_archive' %}" href="#">Events</option>
|
||||
<option data-action="{% url 'person_list' %}" href="#">People</option>
|
||||
<option data-action="{% url 'organisation_list' %}" href="#">Organisations</option>
|
||||
<option data-action="{% url 'venue_list' %}" href="#">Venues</option>
|
||||
{% if perms.RIGS.view_invoice %}
|
||||
<option data-action="{% url 'invoice_archive' %}" href="#">Invoices</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary form-control form-control-sm btn-sm">Search</button>
|
||||
<a href="{% url 'search_help' %}" class="nav-link modal-href btn-sm"><span class="fas fa-question-circle"></span></a>
|
||||
</form>
|
||||
{% endif %}
|
||||
<li class="nav-item dropdown" id="user">
|
||||
{% if user.is_authenticated %}
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Hi {{ user.first_name }}
|
||||
</a>
|
||||
<ul class="dropdown-menu p-3" id="userdropdown">
|
||||
<li class="media">
|
||||
<a href="{% url 'profile_detail' %}">
|
||||
<img src="{{ request.user.profile_picture }}" class="media-object"/>
|
||||
<div class="media-body">
|
||||
<b>{{ request.user.first_name }} {{ request.user.last_name }}</b>
|
||||
<p class="muted">{{ request.user.email }}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'logout' %}" class="btn btn-primary align"><i class="fas fa-sign-out-alt"></i> Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<a class="nav-link" href="{% url 'login' %}">
|
||||
Login
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block titleelements_right %}
|
||||
{% include 'partials/search.html' %}
|
||||
{% include 'partials/navbar_user.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||
<script src="{% static 'js/popover.js' %}"></script>
|
||||
@@ -106,17 +81,4 @@
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
})
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#search-options option').click(function(){
|
||||
$('#searchForm').attr('action', $(this).data('action')).submit();
|
||||
});
|
||||
$('#id_search_input').keypress(function (e) {
|
||||
if (e.which == 13) {
|
||||
$('#searchForm').attr('action', $('#search-options option').first().data('action')).submit();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,77 +1,60 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block title %}Calendar{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<link href="{% static "css/fullcalendar.css" %}" rel='stylesheet' />
|
||||
<link href="{% static "css/fullcalendar.print.css" %}" rel='stylesheet' media='print' />
|
||||
<link href="{% static 'css/main.min.css' %}" rel='stylesheet' />
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'js/moment.js' %}"></script>
|
||||
<script src="{% static 'js/fullcalendar.js' %}"></script>
|
||||
<script src="{% static 'js/main.min.js' %}"></script>
|
||||
<script>
|
||||
function getUrlVars() {
|
||||
var vars = {};
|
||||
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
|
||||
vars[key] = value;
|
||||
});
|
||||
return vars;
|
||||
viewToUrl = {
|
||||
'timeGridWeek':'week',
|
||||
'timeGridDay':'day',
|
||||
'dayGridMonth':'month'
|
||||
}
|
||||
viewFromUrl = {
|
||||
'week':'timeGridWeek',
|
||||
'day':'timeGridDay',
|
||||
'month':'dayGridMonth'
|
||||
}
|
||||
var calendar; //Need to access it from jquery ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
viewToUrl = {
|
||||
'agendaWeek':'week',
|
||||
'agendaDay':'day',
|
||||
'month':'month'
|
||||
}
|
||||
viewFromUrl = {
|
||||
'week':'agendaWeek',
|
||||
'day':'agendaDay',
|
||||
'month':'month'
|
||||
}
|
||||
|
||||
$('#calendar').fullCalendar({
|
||||
editable: false,
|
||||
eventLimit: true, // allow "more" link when too many events
|
||||
firstDay: 1,
|
||||
aspectRatio: 1.5,
|
||||
timeFormat: 'HH:mm',
|
||||
views: {
|
||||
basic: {
|
||||
// options apply to basicWeek and basicDay views
|
||||
},
|
||||
agenda: {
|
||||
// options apply to agendaWeek and agendaDay views
|
||||
},
|
||||
week: {
|
||||
columnFormat:'ddd D/M'
|
||||
},
|
||||
day: {
|
||||
// options apply to basicDay and agendaDay views
|
||||
}
|
||||
},
|
||||
header:false,
|
||||
|
||||
events: function(start_moment, end_moment, timezone, callback) {
|
||||
|
||||
calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
themeSystem: 'bootstrap',
|
||||
//defaultView: 'dayGridMonth', This is now default
|
||||
aspectRatio: 1.5,
|
||||
eventTimeFormat: {
|
||||
'hour': '2-digit',
|
||||
'minute': '2-digit',
|
||||
'hour12': false
|
||||
},
|
||||
//nowIndicator: true,
|
||||
//firstDay: 1,
|
||||
headerToolbar: false,
|
||||
editable: false,
|
||||
dayMaxEventRows: true, // allow "more" link when too many events
|
||||
events: function(fetchInfo, successCallback, failureCallback) {
|
||||
$.ajax({
|
||||
url: '/api/event',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
start: moment(start_moment).format("YYYY-MM-DD[T]HH:mm:ss"),
|
||||
end: moment(end_moment).format("YYYY-MM-DD[T]HH:mm:ss")
|
||||
start: moment(fetchInfo.startStr).format("YYYY-MM-DD[T]HH:mm:ss"),
|
||||
end: moment(fetchInfo.endStr).format("YYYY-MM-DD[T]HH:mm:ss")
|
||||
},
|
||||
success: function(doc) {
|
||||
var events = [];
|
||||
colours = {'Provisional': '#f0ad4e',
|
||||
'Confirmed': '#5cb85c' ,
|
||||
'Booked': '#5cb85c' ,
|
||||
colours = {
|
||||
'Provisional': '#FFE89B',
|
||||
'Confirmed': '#3AB54A' ,
|
||||
'Booked': '#3AB54A' ,
|
||||
'Cancelled': 'grey' ,
|
||||
'non-rig': '#5bc0de'
|
||||
'non-rig': '#25AAE2'
|
||||
};
|
||||
$(doc).each(function() {
|
||||
end = $(this).attr('latest')
|
||||
@@ -87,100 +70,94 @@
|
||||
'url': $(this).attr('url')
|
||||
}
|
||||
|
||||
if($(this).attr('is_rig')==true || $(this).attr('status') == "Cancelled"){
|
||||
if($(this).attr('is_rig')===true || $(this).attr('status') === "Cancelled"){
|
||||
thisEvent['color'] = colours[$(this).attr('status')];
|
||||
}else{
|
||||
thisEvent['color'] = colours['non-rig'];
|
||||
}
|
||||
|
||||
events.push(thisEvent);
|
||||
});
|
||||
callback(events);
|
||||
successCallback(events);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
viewRender: function(view, element){
|
||||
datesSet: function(info) {
|
||||
var view = info.view;
|
||||
// Set the title of the view
|
||||
$('#calendar-header').text(view.title);
|
||||
|
||||
// Enable/Disable "Today" button as required
|
||||
if(moment().isBetween(view.intervalStart, view.intervalEnd)){
|
||||
let $today = $('#today-button');
|
||||
if(moment().isBetween(view.currentStart, view.currentEnd)){
|
||||
//Today is within the current view
|
||||
$('#today-button').prop('disabled', true);
|
||||
$today.prop('disabled', true);
|
||||
}else{
|
||||
$('#today-button').prop('disabled', false);
|
||||
$today.prop('disabled', false);
|
||||
}
|
||||
|
||||
// Set active view select button
|
||||
switch(view.name){
|
||||
case 'month':
|
||||
$('#month-button').addClass('active');
|
||||
$('#week-button').removeClass('active');
|
||||
$('#day-button').removeClass('active');
|
||||
let $month = $('#month-button');
|
||||
let $week = $('#week-button');
|
||||
let $day = $('#day-button');
|
||||
switch(view.type){
|
||||
case 'dayGridMonth':
|
||||
$month.addClass('active');
|
||||
$week.removeClass('active');
|
||||
$day.removeClass('active');
|
||||
break;
|
||||
|
||||
case 'agendaWeek':
|
||||
$('#month-button').removeClass('active');
|
||||
$('#week-button').addClass('active');
|
||||
$('#day-button').removeClass('active');
|
||||
case 'timeGridWeek':
|
||||
$month.removeClass('active');
|
||||
$week.addClass('active');
|
||||
$day.removeClass('active');
|
||||
break;
|
||||
|
||||
case 'agendaDay':
|
||||
$('#month-button').removeClass('active');
|
||||
$('#week-button').removeClass('active');
|
||||
$('#day-button').addClass('active');
|
||||
case 'timeGridDay':
|
||||
$month.removeClass('active');
|
||||
$week.removeClass('active');
|
||||
$day.addClass('active');
|
||||
break;
|
||||
}
|
||||
history.replaceState(null,null,'{% url 'web_calendar' %}'+viewToUrl[view.name]+'/'+view.intervalStart.format('YYYY-MM-DD')+'/');
|
||||
history.replaceState(null,null,"{% url 'web_calendar' %}"+viewToUrl[view.type]+'/'+moment(view.currentStart).format('YYYY-MM-DD')+'/');
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
$(document).ready(function() {
|
||||
// set some button listeners
|
||||
|
||||
$('#next-button').click(function(){ $('#calendar').fullCalendar('next') });
|
||||
$('#prev-button').click(function(){ $('#calendar').fullCalendar('prev') });
|
||||
$('#today-button').click(function(){ $('#calendar').fullCalendar('today') });
|
||||
|
||||
$('#month-button').click(function(){ $('#calendar').fullCalendar('changeView','month') });
|
||||
$('#week-button').click(function(){ $('#calendar').fullCalendar('changeView','agendaWeek') });
|
||||
$('#day-button').click(function(){ $('#calendar').fullCalendar('changeView','agendaDay') });
|
||||
|
||||
$('#next-button').click(function(){ calendar.next(); });
|
||||
$('#prev-button').click(function(){ calendar.prev(); });
|
||||
$('#today-button').click(function(){ calendar.today(); });
|
||||
$('#month-button').click(function(){ calendar.changeView('dayGridMonth'); });
|
||||
$('#week-button').click(function(){ calendar.changeView('timeGridWeek'); });
|
||||
$('#day-button').click(function(){ calendar.changeView('timeGridDay'); });
|
||||
$('#go-to-date-input').change(function(){
|
||||
if( moment($('#go-to-date-input').val()).isValid() ){
|
||||
if(moment($('#go-to-date-input').val()).isValid()){
|
||||
$('#go-to-date-button').prop('disabled', false);
|
||||
}else{
|
||||
} else{
|
||||
$('#go-to-date-button').prop('disabled', true);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$('#go-to-date-button').click(function(){
|
||||
day = moment($('#go-to-date-input').val()) ;
|
||||
day = moment($('#go-to-date-input').val());
|
||||
if(day.isValid()){
|
||||
$('#calendar').fullCalendar( 'gotoDate', day);
|
||||
}else{
|
||||
calendar.gotoDate(day.format("YYYY-MM-DD"));
|
||||
} else{
|
||||
alert('Invalid Date');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
{% if view and date %}
|
||||
// Go to the initial settings, if they're valid
|
||||
view = viewFromUrl['{{view}}'];
|
||||
$('#calendar').fullCalendar( 'changeView', view);
|
||||
|
||||
calendar.changeView(view);
|
||||
day = moment('{{date}}');
|
||||
if(day.isValid()){
|
||||
$('#calendar').fullCalendar( 'gotoDate', day);
|
||||
}else{
|
||||
calendar.gotoDate(day.format("YYYY-MM-DD"));
|
||||
} else{
|
||||
console.log('Supplied date is invalid - using default')
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
<div class="row my-3">
|
||||
<div class="col-sm-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Contact Details</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-5">Person</dt>
|
||||
<dd class="col-sm-7">
|
||||
{% if event.person %}
|
||||
{{ event.person.name }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-5">Email</dt>
|
||||
<dd class="col-sm-7">
|
||||
<span class="overflow-ellipsis">{{ event.person.email }}</span>
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-5">Phone Number</dt>
|
||||
<dd class="col-sm-7">{{ event.person.phone }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{% if event.organisation %}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Organisation Details</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-5">Organisation</dt>
|
||||
<dd class="col-sm-7">
|
||||
{{ event.organisation.name }}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-5">Phone Number</dt>
|
||||
<dd class="col-sm-7">{{ object.organisation.phone }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="card border-info">
|
||||
<div class="card-header">Event Info</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-5">Event Venue</dt>
|
||||
<dd class="col-sm-7">
|
||||
{% if object.venue %}
|
||||
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
|
||||
{{ object.venue }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-5">Status</dt>
|
||||
<dd class="col-sm-7">{{ event.get_status_display }}</dd>
|
||||
|
||||
<dd class="col-sm-12"> </dd>
|
||||
|
||||
<dt class="col-sm-5">Access From</dt>
|
||||
<dd class="col-sm-7">{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
|
||||
|
||||
<dt class="col-sm-5">Event Starts</dt>
|
||||
<dd class="col-sm-7">{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
|
||||
|
||||
<dt class="col-sm-5">Event Ends</dt>
|
||||
<dd class="col-sm-7">{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
|
||||
|
||||
<dd class="col-sm-12"> </dd>
|
||||
|
||||
<dt class="col-sm-5">Event Description</dt>
|
||||
<dd class="col-sm-12">{{ event.description|linebreaksbr }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,48 +1,56 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
{% load paginator from filters %}
|
||||
{% load get_list from filters %}
|
||||
{% load button from filters %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Event Archive{% endblock %}
|
||||
{% block css %}
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload_js %}
|
||||
<script src="{% static 'js/bootstrap-select.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h2>Event Archive</h2>
|
||||
</div>
|
||||
<div class="col-sm-12 py-2">
|
||||
<form class="form-inline">
|
||||
<div class="input-group mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Start</span>
|
||||
</div>
|
||||
<input type="date" name="start" id="start" value="{{ start|default_if_none:"" }}" placeholder="Start" class="form-control" />
|
||||
<div class="row">
|
||||
<div class="col-sm-12 py-2">
|
||||
<form class="form-inline" method="GET">
|
||||
<div class="input-group mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Start</span>
|
||||
</div>
|
||||
<div class="input-group mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">End</span>
|
||||
</div>
|
||||
<input type="date" name="end" id="end" value="{{ end|default_if_none:"" }}" placeholder="End" class="form-control" />
|
||||
<input type="date" name="start" id="start" value="{{ start|default_if_none:'' }}" placeholder="Start" class="form-control" />
|
||||
</div>
|
||||
<div class="input-group mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">End</span>
|
||||
</div>
|
||||
<div class="input-group mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Keyword</span>
|
||||
</div>
|
||||
<input type="search" name="q" placeholder="Keyword" value="{{ request.GET.q }}" class="form-control" />
|
||||
<input type="date" name="end" id="end" value="{{ end|default_if_none:'' }}" placeholder="End" class="form-control" />
|
||||
</div>
|
||||
<div class="input-group mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Keyword</span>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary" value="Search"/>
|
||||
</form>
|
||||
</div>
|
||||
<input type="search" name="q" placeholder="Keyword" value="{{ request.GET.q }}" class="form-control" />
|
||||
</div>
|
||||
<select class="selectpicker pr-3" multiple data-actions-box="true" data-none-selected-text="Status" data-actions-box="true" id="status" name="status">
|
||||
{% for status in statuses %}
|
||||
<option value="{{status.0}}" {% if status.0|safe in request.GET|get_list:'status' %}selected=""{% endif %}>{{status.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% button 'search' %}
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% with object_list as events %}
|
||||
{% include 'event_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% with object_list as events %}
|
||||
{% include 'partials/event_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% paginator %}
|
||||
|
||||
{% if is_paginated %}
|
||||
<div class="row justify-content-center">
|
||||
{% paginator %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
247
RIGS/templates/event_checklist_detail.html
Normal file
247
RIGS/templates/event_checklist_detail.html
Normal file
@@ -0,0 +1,247 @@
|
||||
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
||||
{% load help_text from filters %}
|
||||
{% load profile_by_index from filters %}
|
||||
{% load yesnoi from filters %}
|
||||
{% load button from filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-12 text-right my-3">
|
||||
{% button 'edit' url='ec_edit' pk=object.pk %}
|
||||
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
|
||||
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">General</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-6">Date</dt>
|
||||
<dd class="col-6">
|
||||
{{ object.date }}
|
||||
</dd>
|
||||
<dt class="col-6">Venue</dt>
|
||||
<dd class="col-6">
|
||||
{% if object.venue %}
|
||||
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
|
||||
{{ object.venue }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt class="col-6">{{ object|help_text:'power_mic' }}</dt>
|
||||
<dd class="col-6">
|
||||
<a href="{% url 'profile_detail' object.power_mic.pk %}">{{ object.power_mic.name }}</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>List vehicles and their drivers</p>
|
||||
<ul>
|
||||
{% for i in object.vehicles.all %}
|
||||
<li>{{i}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Safety Checks</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-10">{{ object|help_text:'safe_parking'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.safe_parking|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'safe_packing'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.safe_packing|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'exits'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.exits|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'trip_hazard'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.trip_hazard|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'warning_signs'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.warning_signs|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'ear_plugs'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.ear_plugs|yesnoi }}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Crew Record</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Crewmember</th>
|
||||
<th scope="col">Start Time</th>
|
||||
<th scope="col">Role</th>
|
||||
<th scope="col">End Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="crewmemberst">
|
||||
{% for crew in object.crew.all %}
|
||||
<tr>
|
||||
<td>{{crew.crewmember}}</td>
|
||||
<td>{{crew.start}}</td>
|
||||
<td>{{crew.role}}</td>
|
||||
<td>{{crew.end}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Power {% include 'partials/event_size.html' with object=object.event.riskassessment %}</div>
|
||||
{% if object.event.riskassessment.event_size != 2 %}
|
||||
<div class="card-body">
|
||||
{% if object.event.riskassessment.event_size == 1 %}
|
||||
<dl class="row">
|
||||
<dt class="col-10">{{ object|help_text:'source_rcd'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.source_rcd|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'labelling'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.labelling|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'earthing'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.earthing|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'pat'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.pat|yesnoi }}
|
||||
</dd>
|
||||
</dl>
|
||||
<hr>
|
||||
<p>Tests at first distro</p>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="text-center">Test</th>
|
||||
<th scope="col" colspan="3" class="text-center">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row" rowspan="2">Voltage<br><small>(cube meter)</small></th>
|
||||
<th>{{ object|help_text:'fd_voltage_l1' }}</th>
|
||||
<th>{{ object|help_text:'fd_voltage_l2' }}</th>
|
||||
<th>{{ object|help_text:'fd_voltage_l3' }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ object.fd_voltage_l1 }}</td>
|
||||
<td>{{ object.fd_voltage_l2 }}</td>
|
||||
<td>{{ object.fd_voltage_l3 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ object|help_text:'fd_phase_rotation'|safe }}</th>
|
||||
<td colspan="3">{{ object.fd_phase_rotation|yesnoi }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ object|help_text:'fd_earth_fault'|safe}}</th>
|
||||
<td colspan="3">{{ object.fd_earth_fault }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ object|help_text:'fd_pssc'}}</th>
|
||||
<td colspan="3">{{ object.fd_pssc }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
<p>Tests at 'Worst Case' points (at least 1 point required)</p>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="text-center">Test</th>
|
||||
<th scope="col" class="text-center">Point 1</th>
|
||||
<th scope="col" class="text-center">Point 2</th>
|
||||
<th scope="col" class="text-center">Point 3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">{{ object|help_text:'w1_description'|safe}}</th>
|
||||
<td>{{ object.w1_description }}</td>
|
||||
<td>{{ object.w2_description|default:'' }}</td>
|
||||
<td>{{ object.w3_description|default:'' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ object|help_text:'w1_polarity'|safe}}</th>
|
||||
<td>{{ object.w1_polarity|yesnoi }}</td>
|
||||
<td>{{ object.w2_polarity|default:''|yesnoi }}</td>
|
||||
<td>{{ object.w3_polarity|default:''|yesnoi }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ object|help_text:'w1_voltage'|safe}}</th>
|
||||
<td>{{ object.w1_voltage }}</td>
|
||||
<td>{{ object.w2_voltage|default:'' }}</td>
|
||||
<td>{{ object.w3_voltage|default:'' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ object|help_text:'w1_earth_fault'|safe}}</th>
|
||||
<td>{{ object.w1_earth_fault }}</td>
|
||||
<td>{{ object.w2_earth_fault|default:'' }}</td>
|
||||
<td>{{ object.w3_earth_fault|default:'' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
<dl class="row">
|
||||
<dt class="col-10">{{ object|help_text:'all_rcds_tested'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.all_rcds_tested|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'public_sockets_tested'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.public_sockets_tested|yesnoi }}
|
||||
</dd>
|
||||
</dl>
|
||||
<hr>
|
||||
{% include 'partials/ec_power_info.html' %}
|
||||
{% else %}
|
||||
<dl class="row">
|
||||
<dt class="col-10">{{ object|help_text:'rcds'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.rcds|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'supply_test'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.supply_test|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'earthing'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.earthing|yesnoi }}
|
||||
</dd>
|
||||
<dt class="col-10">{{ object|help_text:'pat'|safe }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.pat|yesnoi }}
|
||||
</dd>
|
||||
</dl>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
{% button 'edit' url='ec_edit' pk=object.pk %}
|
||||
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
|
||||
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
{% include 'partials/last_edited.html' with target="eventchecklist_history" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
367
RIGS/templates/event_checklist_form.html
Normal file
367
RIGS/templates/event_checklist_form.html
Normal file
@@ -0,0 +1,367 @@
|
||||
{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
{% load help_text from filters %}
|
||||
{% load profile_by_index from filters %}
|
||||
{% load button from filters %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
|
||||
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload_js %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/bootstrap-select.js' %}"></script>
|
||||
<script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/jquery-ui.js' %}"></script><!--TODO optimise-->
|
||||
<script src="{% static 'js/interaction.js' %}"></script>
|
||||
<script src="{% static 'js/modal.js' %}"></script>
|
||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||
|
||||
<script src="{% static 'js/autocompleter.js' %}"></script>
|
||||
|
||||
{% include 'partials/datetime-fix.html' %}
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('button[data-action=add]').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
var target = $($(this).attr('data-target'));
|
||||
var newID = Number(target.attr('data-pk'));
|
||||
var newRow = $($(this).attr('data-clone'))
|
||||
.clone().attr('style', "")
|
||||
.attr('id', function(i, val){
|
||||
return val.split("_")[0] + '_' + newID;
|
||||
})
|
||||
.appendTo(target);
|
||||
newRow.find('select,input').attr('name', function(i, val){
|
||||
return val.split("_")[0] + '_' + newID;
|
||||
})//Disabled is to prevent the hidden row being sent to the form
|
||||
.removeAttr('disabled');
|
||||
newRow.find('button[data-action=delete]').attr('data-id', newID);
|
||||
newRow.find('select').addClass('selectpicker');
|
||||
newRow.find('.selectpicker').selectpicker('refresh');
|
||||
$(".selectpicker").each(function(){initPicker($(this))});
|
||||
initDatetime();
|
||||
$(target).attr('data-pk', newID - 1);
|
||||
});
|
||||
$(document).on('click', 'button[data-action=delete]', function(event) {
|
||||
event.preventDefault();
|
||||
$(this).closest('tr').remove();
|
||||
});
|
||||
//Somewhat rudimentary way of ensuring people fill in completely (if it hits the database validation the whole table row disappears when the page reloads...)
|
||||
//the not is to avoid adding it to some of bootstrap-selects extra crap
|
||||
$('#vehiclest,#crewmemberst').on('change', 'select,input', function () {
|
||||
$(this).closest('tr').find("select,input").not(':input[type=search]').attr('required', 'true');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-12">
|
||||
{% include 'form_errors.html' %}
|
||||
{% if edit %}
|
||||
<form role="form" method="POST" action="{% url 'ec_edit' pk=object.pk %}">
|
||||
{% else %}
|
||||
<form role="form" method="POST" action="{% url 'event_ec' pk=event.pk %}">
|
||||
{% endif %}
|
||||
<input type="hidden" name="{{ form.event.name }}" id="{{ form.event.id_for_label }}"
|
||||
value="{{event.pk}}"/>
|
||||
{% csrf_token %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">Event Information</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-4">Event Date</dt>
|
||||
<dd class="col-8">{{ event.start_date}}{%if event.end_date %}-{{ event.end_date}}{%endif%}</dd>
|
||||
<dt class="col-4">Event Name</dt>
|
||||
<dd class="col-8">{{ event.name }}</dd>
|
||||
<dt class="col-4">Client</dt>
|
||||
<dd class="col-8">{{ event.person }}</dd>
|
||||
<dt class="col-4">Event Size</dt>
|
||||
<dd class="col-8">{% include 'partials/event_size.html' with object=event.riskassessment %}</dd>
|
||||
</dl>
|
||||
<div class="form-group form-row">
|
||||
<label for="{{ form.date.id_for_label }}"
|
||||
class="col-4 col-form-label">{{ form.date.label }}</label>
|
||||
{% if not form.date.value %}
|
||||
{% render_field form.date class+="form-control col-8" value=event.start_date %}
|
||||
{% else %}
|
||||
{% render_field form.date class+="form-control col-8" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group form-row" id="{{ form.venue.id_for_label }}-group">
|
||||
<label for="{{ form.venue.id_for_label }}"
|
||||
class="col-4 col-form-label">{{ form.venue.label }}</label>
|
||||
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker col-8" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
|
||||
{% if venue %}
|
||||
<option value="{{venue.pk}}" selected="selected">{{ venue.name }}</option>
|
||||
{% elif event.venue %}
|
||||
<option value="{{event.venue.pk}}" selected="selected">{{ event.venue.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group form-row" id="{{ form.power_mic.id_for_label }}-group">
|
||||
<label for="{{ form.power_mic.id_for_label }}"
|
||||
class="col-4 col-form-label">{{ form.power_mic.help_text }}</label>
|
||||
<select id="{{ form.power_mic.id_for_label }}" name="{{ form.power_mic.name }}" class="form-control selectpicker col-8" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required="true">
|
||||
{% if power_mic %}
|
||||
<option value="{{power_mic.pk}}" selected="selected">{{ power_mic.name }}</option>
|
||||
{% elif event.riskassessment.power_mic %}
|
||||
<option value="{{event.riskassessment.power_mic.pk}}" selected="selected">{{ event.riskassessment.power_mic.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<p class="pt-3 font-weight-bold">List vehicles and their drivers</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Vehicle</th>
|
||||
<th scope="col">Driver</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vehiclest" data-pk="-1">
|
||||
<tr id="vehicles_new" style="display: none;">
|
||||
<td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td>
|
||||
<td><select class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" name="driver_new" disabled="true"></select></td>
|
||||
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button</td>
|
||||
</tr>
|
||||
{% for i in object.vehicles.all %}
|
||||
<tr id="vehicles_{{i.pk}}">
|
||||
<td><input name="vehicle_{{i.pk}}" type="text" class="form-control" value="{{ i.vehicle }}"/></td>
|
||||
<td>
|
||||
<select name="driver_{{i.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
{% if i.driver != '' %}
|
||||
<option value="{{i.driver.pk}}" selected="selected">{{ i.driver.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</td>
|
||||
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-id='{{i.pk}}' data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<button type="button" class="btn btn-secondary" id="vehicle-add" data-action='add' data-target='#vehiclest' data-clone='#vehicles_new'><span class="fas fa-plus"></span> Add Vehicle</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-3">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">Safety Checks</div>
|
||||
<div class="card-body">
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.safe_parking %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.safe_packing %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.exits %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.trip_hazard %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.warning_signs %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.ear_plugs %}
|
||||
<div class="row pt-3">
|
||||
<label class="col-5" for="{{ form.hs_location.id_for_label }}">{{ form.hs_location.help_text }}</label>
|
||||
{% render_field form.hs_location class+="form-control col-7 col-md-4" %}
|
||||
</div>
|
||||
<div class="row pt-1">
|
||||
<label class="col-5" for="{{ form.extinguishers_location.id_for_label }}">{{ form.extinguishers_location.help_text }}</label>
|
||||
{% render_field form.extinguishers_location class+="form-control col-7 col-md-4" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-3">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">Crew Record</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Person</th>
|
||||
<th scope="col">Start Time</th>
|
||||
<th scope="col">Role</th>
|
||||
<th scope="col">End Time</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="crewmemberst" data-pk="-1">
|
||||
<tr id="crew_new" style="display: none;">
|
||||
<td>
|
||||
<select name="crewmember_new" class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" disabled="true"></select>
|
||||
</td>
|
||||
<td style="min-width: 15ch"><input name="start_new" type="datetime-local" class="form-control" value="{{ i.start }}" disabled="true"/></td>
|
||||
<td style="min-width: 15ch"><input name="role_new" type="text" class="form-control" value="{{ i.role }}" disabled="true"/></td>
|
||||
<td style="min-width: 15ch"><input name="end_new" type="datetime-local" class="form-control" value="{{ i.end }}" disabled="true" /></td>
|
||||
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-id='{{crew.pk}}' data-action='delete' data-target='#crewmember'><span class="fas fa-times"></span></button</td>
|
||||
</tr>
|
||||
{% for crew in object.crew.all %}
|
||||
<tr id="crew_{{crew.pk}}">
|
||||
<td>
|
||||
<select name="crewmember_{{crew.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
{% if crew.crewmember != '' %}
|
||||
<option value="{{crew.crewmember.pk}}" selected="selected">{{ crew.crewmember.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</td>
|
||||
<td><input name="start_{{crew.pk}}" type="datetime-local" class="form-control" value="{{ crew.start|date:'Y-m-d' }}T{{ crew.start|date:'H:i:s' }}"/></td>
|
||||
<td><input name="role_{{crew.pk}}" type="text" class="form-control" value="{{ crew.role }}"/></td>
|
||||
<td><input name="end_{{crew.pk}}" type="datetime-local" class="form-control" value="{{ crew.end|date:'Y-m-d' }}T{{ crew.end|date:'H:i:s' }}"/></td>
|
||||
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-id='{{crew.pk}}' data-action='delete' data-target='#crewmember'><span class="fas fa-times"></span></button</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="text-right">
|
||||
<button type="button" class="btn btn-secondary" data-action='add' data-target='#crewmemberst' data-clone='#crew_new'><span class="fas fa-plus"></span> Add Crewmember</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if event.riskassessment.event_size == 0 %}
|
||||
<div class="row my-3" id="size-0">
|
||||
<div class="col-12">
|
||||
<div class="card border-success">
|
||||
<div class="card-header">Electrical Checks <small>for ‘Small’ TEC Events <6kVA (aprox. 26A)</small></div>
|
||||
<div class="card-body">
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.rcds %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.supply_test %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.earthing %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.pat %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% elif event.riskassessment.event_size == 1 %}
|
||||
<div class="row my-3" id="size-1">
|
||||
<div class="col-12">
|
||||
<div class="card border-warning">
|
||||
<div class="card-header">Electrical Checks <small>for ‘Medium’ TEC Events </small></div>
|
||||
<div class="card-body">
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.source_rcd %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.labelling %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.earthing %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.pat %}
|
||||
<hr>
|
||||
<p>Tests at first distro</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="text-center">Test</th>
|
||||
<th scope="col" colspan="3" class="text-center">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row" rowspan="2">Voltage<br><small>(cube meter)</small></th>
|
||||
<th class="text-center">{{ form.fd_voltage_l1.help_text }}</th>
|
||||
<th class="text-center">{{ form.fd_voltage_l2.help_text }}</th>
|
||||
<th class="text-center">{{ form.fd_voltage_l3.help_text }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% render_field form.fd_voltage_l1 class+="form-control" style="min-width: 5rem;" %}</td>
|
||||
<td>{% render_field form.fd_voltage_l2 class+="form-control" style="min-width: 5rem;" %}</td>
|
||||
<td>{% render_field form.fd_voltage_l3 class+="form-control" style="min-width: 5rem;" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{form.fd_phase_rotation.help_text|safe}}</th>
|
||||
<td colspan="3">{% include 'partials/checklist_checkbox.html' with formitem=form.fd_phase_rotation %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{form.fd_earth_fault.help_text|safe}}</th>
|
||||
<td colspan="3">{% render_field form.fd_earth_fault class+="form-control" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{form.fd_pssc.help_text|safe}}</th>
|
||||
<td colspan="3">{% render_field form.fd_pssc class+="form-control" %}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
<p>Tests at 'Worst Case' points (at least 1 point required)</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="text-center">Test</th>
|
||||
<th scope="col" class="text-center">Point 1</th>
|
||||
<th scope="col" class="text-center">Point 2</th>
|
||||
<th scope="col" class="text-center">Point 3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">{{form.w1_description.help_text|safe}}</th>
|
||||
<td>{% render_field form.w1_description class+="form-control" style="min-width: 5rem;" %}</td>
|
||||
<td>{% render_field form.w2_description class+="form-control" style="min-width: 5rem;" %}</td>
|
||||
<td>{% render_field form.w3_description class+="form-control" style="min-width: 5rem;" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{form.w1_polarity.help_text|safe}}</th>
|
||||
<td>{% render_field form.w1_polarity %}</td>
|
||||
<td>{% render_field form.w2_polarity %}</td>
|
||||
<td>{% render_field form.w3_polarity %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{form.w1_voltage.help_text|safe}}</th>
|
||||
<td>{% render_field form.w1_voltage class+="form-control" %}</td>
|
||||
<td>{% render_field form.w2_voltage class+="form-control" %}</td>
|
||||
<td>{% render_field form.w3_voltage class+="form-control" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{form.w1_earth_fault.help_text|safe}}</th>
|
||||
<td>{% render_field form.w1_earth_fault class+="form-control" %}</td>
|
||||
<td>{% render_field form.w2_earth_fault class+="form-control" %}</td>
|
||||
<td>{% render_field form.w3_earth_fault class+="form-control" %}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.all_rcds_tested %}
|
||||
{% include 'partials/checklist_checkbox.html' with formitem=form.public_sockets_tested %}
|
||||
{% include 'partials/ec_power_info.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row my-3" id="size-2">
|
||||
<div class="col-12">
|
||||
<div class="card border-danger">
|
||||
<div class="card-header">Electrical Checks <small>for ‘Large’ TEC Events</small></div>
|
||||
<div class="card-body">
|
||||
<p>Outside the scope of this assessment. <strong>I really hope you checked with a supervisor...</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm-12 text-right">
|
||||
{% button 'submit' %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,7 @@
|
||||
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
||||
{% load linkornone from filters %}
|
||||
{% load namewithnotes from filters %}
|
||||
|
||||
{% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -18,30 +21,28 @@
|
||||
{% endif %}
|
||||
{% if object.is_rig and perms.RIGS.view_event %}
|
||||
{# only need contact details for a rig #}
|
||||
<div class="col-sm">
|
||||
<div class="col-md-6">
|
||||
{% if event.person %}
|
||||
<div class="card card-default mb-3">
|
||||
<div class="card-header">Contact Details</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-6">Person</dt>
|
||||
<dd class="col-sm-6"
|
||||
<dd class="col-sm-6">
|
||||
{% if object.person %}
|
||||
<a href="{% url 'person_detail' object.person.pk %}" class="modal-href">
|
||||
{{ object.person }}
|
||||
{{ object.person|namewithnotes:'person_detail' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt class="col-sm-6">Email</dt>
|
||||
<dd class="col-sm-6">
|
||||
<a href="mailto:{{object.person.email}}" target="_blank">
|
||||
<span class="overflow-ellipsis">{{ object.person.email }}</span>
|
||||
</a>
|
||||
</dd>
|
||||
<dd class="col-sm-6">{{ object.person.email|linkornone:'mailto' }}</dd>
|
||||
<dt class="col-sm-6">Phone Number</dt>
|
||||
<dd class="col-sm-6"><a href="tel:{{object.person.phone}}">{{ object.person.phone }}</a></dd>
|
||||
<dd class="col-sm-6">{{ object.person.phone|linkornone:'tel' }}</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if event.organisation %}
|
||||
<div class="card card-default">
|
||||
<div class="card-header">Organisation</div>
|
||||
@@ -51,16 +52,14 @@
|
||||
<dd class="col-sm-6">
|
||||
{% if object.organisation %}
|
||||
<a href="{% url 'organisation_detail' object.organisation.pk %}" class="modal-href">
|
||||
{{ object.organisation }}
|
||||
{{ object.organisation|namewithnotes:'organisation_detail' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt class="col-sm-6">Email</dt>
|
||||
<dd class="col-sm-6">{{ object.organisation.email|linkornone:'mailto' }}</dd>
|
||||
<dt class="col-sm-6">Phone Number</dt>
|
||||
<dd class="col-sm-6">
|
||||
<a href="tel:{{object.person.phone}}">
|
||||
{{ object.organisation.phone }}
|
||||
</a>
|
||||
</dd>
|
||||
<dd class="col-sm-6">{{ object.organisation.phone|linkornone:'tel' }}</a></dd>
|
||||
<dt class="col-sm-6">Has SU Account</dt>
|
||||
<dd class="col-sm-6">{{ event.organisation.union_account|yesno|capfirst }}</dd>
|
||||
</dl>
|
||||
@@ -69,13 +68,20 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm">
|
||||
<div class="col-md-6">
|
||||
{% include 'partials/event_details.html' %}
|
||||
</div>
|
||||
{% if event.is_rig and event.internal and perms.RIGS.view_event %}
|
||||
<div class="col-sm-12 py-3">
|
||||
{ include 'partials/auth_details.html' %}
|
||||
<div>
|
||||
{% if not event.dry_hire %}
|
||||
<div class="col {% if event.is_rig %}py-3{%endif %}">
|
||||
{% include 'partials/hs_details.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if event.is_rig %}
|
||||
{% if event.is_rig and event.internal and perms.RIGS.view_event %}
|
||||
<div class="col-md-8 py-3">
|
||||
{% include 'partials/auth_details.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not request.is_ajax and perms.RIGS.view_event %}
|
||||
<div class="col-sm-12 text-right">
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
{% load button from filters %}
|
||||
<div class="btn-group py-3">
|
||||
<a href="{% url 'event_update' event.pk %}" class="btn btn-secondary"><span
|
||||
class="fas fa-edit"></span> <span
|
||||
class="hidden-xs">Edit</span></a>
|
||||
{% button 'edit' 'event_update' event.pk %}
|
||||
{% if event.is_rig %}
|
||||
{% if not event.dry_hire %}
|
||||
<a href="{% url 'event_ra' event.pk %}" class="btn
|
||||
{% if event.risk_assessment_edit_url %}
|
||||
btn-success
|
||||
{% else %}
|
||||
btn-warning
|
||||
{% endif %}
|
||||
"><i class="fas fa-paperclip"></i> <span
|
||||
class="hidden-xs">RA</span></a>
|
||||
{% endif %}
|
||||
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-primary"><i
|
||||
class="fas fa-print"></i> <span
|
||||
class="hidden-xs">Print</span></a>
|
||||
{% button 'print' 'event_print' event.pk %}
|
||||
{% endif %}
|
||||
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-secondary" title="Duplicate Rig"><span
|
||||
class="fas fa-copy"></span> <span
|
||||
class="hidden-xs">Duplicate</span></a>
|
||||
{% button 'duplicate' 'event_duplicate' event.pk %}
|
||||
{% if event.is_rig %}
|
||||
{% if event.internal %}
|
||||
<a class="btn item-add modal-href event-authorise-request
|
||||
@@ -29,11 +14,13 @@
|
||||
btn-warning
|
||||
{% elif event.auth_request_to %}
|
||||
btn-info
|
||||
{% else %}
|
||||
btn-secondary
|
||||
{% endif %}
|
||||
"
|
||||
href="{% url 'event_authorise_request' object.pk %}">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
<span class="hidden-xs">
|
||||
<span class="d-none d-sm-inline">
|
||||
{% if event.authorised %}
|
||||
Authorised
|
||||
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
|
||||
@@ -58,7 +45,7 @@
|
||||
{% endif %}
|
||||
" title="Invoice Rig"><span
|
||||
class="fas fa-pound-sign"></span>
|
||||
<span class="hidden-xs">Invoice</span></a>
|
||||
<span class="d-none d-sm-inline">Invoice</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<span class="pull-right">
|
||||
{% if object.mic %}
|
||||
<div class="text-center">
|
||||
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo img-rounded"/>
|
||||
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo rounded"/>
|
||||
</div>
|
||||
{% elif object.is_rig %}
|
||||
<span class="fas fa-exclamation-sign"></span>
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
{% load multiply from filters %}
|
||||
|
||||
{% block title %}
|
||||
{% if object.pk %}
|
||||
Event {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
|
||||
{% else %}New Event{% endif %}
|
||||
{% endblock %}
|
||||
{% load button from filters %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
|
||||
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
|
||||
<link rel="stylesheet" href="{% static 'css/flatpickr.css' %}"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload_js %}
|
||||
@@ -31,92 +27,40 @@
|
||||
|
||||
<script src="{% static 'js/autocompleter.js' %}"></script>
|
||||
|
||||
{% include 'partials/datetime-fix.html' %}
|
||||
|
||||
<script>
|
||||
function setTime23Hours() {
|
||||
$('#{{ form.end_time.id_for_label }}').val('23:00');
|
||||
}
|
||||
|
||||
function setTime02Hours() {
|
||||
var id_start = "{{ form.start_date.id_for_label }}";
|
||||
var id_end_date = "{{ form.end_date.id_for_label }}";
|
||||
var id_end_time = "{{ form.end_time.id_for_label }}";
|
||||
if ($('#'+id_start).val() == $('#'+id_end_date).val()) {
|
||||
var end_date = new Date($('#'+id_end_date).val());
|
||||
end_date.setDate(end_date.getDate() + 1);
|
||||
$('#'+id_end_date).val(end_date.getISOString());
|
||||
}
|
||||
$('#'+id_end_time).val('02:00');
|
||||
}
|
||||
|
||||
const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches;
|
||||
$(document).ready(function () {
|
||||
dur = matches ? 0 : 500;
|
||||
{% if not object.pk and not form.errors %}
|
||||
|
||||
$('.form-hws').slideUp(function () {
|
||||
$('.form-is_rig').slideUp();
|
||||
$('.form-hws').slideUp(dur, function () {
|
||||
$('.form-is_rig').slideUp(dur);
|
||||
});
|
||||
|
||||
{% elif not object.pk and form.errors %}
|
||||
if ($('#{{form.is_rig.auto_id}}').attr('checked') != 'checked') {
|
||||
if ($('#{{form.is_rig.auto_id}}').attr('checked') !== 'checked') {
|
||||
$('.form-is_rig').hide();
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if not object.pk %}
|
||||
$('#is_rig-selector button').on('click', function () {
|
||||
$('.form-non_rig').slideDown();
|
||||
if ($(this).data('is_rig') == 1) {
|
||||
$('.form-non_rig').slideDown(dur);
|
||||
if ($(this).data('is_rig') === 1) {
|
||||
$('#{{form.is_rig.auto_id}}').prop('checked', true);
|
||||
if ($('.form-non_rig').is(':hidden')) {
|
||||
$('.form-is_rig').show();
|
||||
} else {
|
||||
$('.form-is_rig').slideDown();
|
||||
$('.form-is_rig').slideDown(dur);
|
||||
}
|
||||
$('.form-hws, .form-hws .form-is_rig').css('overflow', 'visible');
|
||||
} else {
|
||||
|
||||
$('#{{form.is_rig.auto_id}}').prop('checked', false);
|
||||
$('.form-is_rig').slideUp();
|
||||
$('.form-is_rig').slideUp(dur);
|
||||
}
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
function supportsDate() {
|
||||
//return false; //for development
|
||||
var input = document.createElement('input');
|
||||
input.setAttribute('type','date');
|
||||
var notADateValue = 'not-a-date';
|
||||
input.setAttribute('value', notADateValue);
|
||||
return !(input.value === notADateValue);
|
||||
}
|
||||
if(supportsDate()){
|
||||
//Good, we'll use the browser implementation
|
||||
}else{
|
||||
//Rubbish browser - do JQuery backup
|
||||
$('<link>')
|
||||
.appendTo('head')
|
||||
.attr({type : 'text/css', rel : 'stylesheet'})
|
||||
.attr('href', '{% static "css/bootstrap-datetimepicker.min.css" %}');
|
||||
$.when(
|
||||
$.getScript( "{% static "js/moment.js" %}" ),
|
||||
$.getScript( "{% static "js/bootstrap-datetimepicker.js" %}" ),
|
||||
$.Deferred(function( deferred ){
|
||||
$( deferred.resolve );
|
||||
})
|
||||
).done(function(){
|
||||
$('input[type=date]').attr('type','text').datetimepicker({
|
||||
format: 'YYYY-MM-DD',
|
||||
});
|
||||
$('input[type=time]').attr('type','text').datetimepicker({
|
||||
format: 'HH:mm',
|
||||
});
|
||||
$('input[type=datetime-local]').attr('type','text').datetimepicker({
|
||||
format: 'YYYY-MM-DD[T]HH:mm',
|
||||
sideBySide: true,
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
setupItemTable($("#{{ form.items_json.id_for_label }}").val());
|
||||
});
|
||||
@@ -124,59 +68,285 @@
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
})
|
||||
</script>
|
||||
<noscript>
|
||||
<style>
|
||||
.form-hws {
|
||||
display: inherit !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'item_modal.html' %}
|
||||
<form class="form-horizontal itemised_form" role="form" method="POST">
|
||||
<form class=" itemised_form" role="form" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h2>
|
||||
{% if duplicate %}
|
||||
Duplicate of Event N{{ object.pk|stringformat:"05d" }}
|
||||
{% elif object.pk %}
|
||||
Event N{{ object.pk|stringformat:"05d" }}
|
||||
{% else %}
|
||||
New Event
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="col-12">
|
||||
{% include 'form_errors.html' %}
|
||||
</div>
|
||||
{% include 'form_errors.html' %}
|
||||
{% render_field form.is_rig style="display: none" %}
|
||||
<input type="hidden" name="{{ form.items_json.name }}" id="{{ form.items_json.id_for_label }}"
|
||||
value="{{ form.items_json.value }}"/>
|
||||
|
||||
{# New rig buttons #}
|
||||
{% if not object.pk %}
|
||||
<div class="col-sm-12">
|
||||
<div class="card row align-items-center">
|
||||
<div class="card-body" id="is_rig-selector">
|
||||
<span data-toggle="tooltip"
|
||||
title="Anything that involves TEC kit, crew, or otherwise us providing a service to anyone.">
|
||||
<button type="button" class="btn btn-primary" data-is_rig="1">Rig</button>
|
||||
</span>
|
||||
<span data-toggle="tooltip"
|
||||
title="Things that aren't service-based, like training, meetings and site visits.">
|
||||
<button type="button" class="btn btn-info" data-is_rig="0">Non-Rig</button>
|
||||
</span>
|
||||
<span data-toggle="tooltip" title="Coming soon...">
|
||||
<button type="button" class="btn btn-warning" data-is_rig="-1">Subhire</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="card text-center" id="is_rig-selector">
|
||||
<div class="card-body">
|
||||
<span data-toggle="tooltip"
|
||||
title="Anything that involves TEC kit, crew, or otherwise us providing a service to anyone.">
|
||||
<button type="button" class="btn btn-primary w-25" data-is_rig="1">Rig</button>
|
||||
</span>
|
||||
<span data-toggle="tooltip"
|
||||
title="Things that aren't service-based, like training, meetings and site visits.">
|
||||
<button type="button" class="btn btn-info w-25" data-is_rig="0">Non-Rig</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Contact details #}
|
||||
{% include 'partials/contact_details_form.html' %}
|
||||
<div class="col-md-6 mt-3">
|
||||
<div class="card form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %} mb-3" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
|
||||
<div class="card-header">Contact Details</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group" data-toggle="tooltip" title="The main contact for the event, can be left blank if purely an organisation">
|
||||
<label for="{{ form.person.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.person.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='person' %}">
|
||||
{% if person %}
|
||||
<option value="{{form.person.value}}" selected="selected" data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'person_create' %}" class="btn btn-success modal-href"
|
||||
data-target="#{{ form.person.id_for_label }}">
|
||||
<span class="fas fa-plus"></span>
|
||||
</a>
|
||||
<a {% if form.person.value %}href="{% url 'person_update' form.person.value %}"{% endif %} class="btn btn-warning modal-href" id="{{ form.person.id_for_label }}-update" data-target="#{{ form.person.id_for_label }}">
|
||||
<span class="fas fa-user-edit"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-toggle="tooltip" title="The client organisation, leave blank if client is an individual">
|
||||
<label for="{{ form.organisation.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.organisation.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||
<select id="{{ form.organisation.id_for_label }}" name="{{ form.organisation.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='organisation' %}" >
|
||||
{% if organisation %}
|
||||
<option value="{{form.organisation.value}}" selected="selected" data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'organisation_create' %}" class="btn btn-success modal-href"
|
||||
data-target="#{{ form.organisation.id_for_label }}">
|
||||
<span class="fas fa-plus"></span>
|
||||
</a>
|
||||
<a {% if form.organisation.value %}href="{% url 'organisation_update' form.organisation.value %}"{% endif %} class="btn btn-warning modal-href" id="{{ form.organisation.id_for_label }}-update" data-target="#{{ form.organisation.id_for_label }}">
|
||||
<span class="fas fa-edit"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card form-hws form-non_rig mb-3" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
|
||||
<div class="card-header">Event Description</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group" data-toggle="tooltip" title="A short description of the event, shown on rigboard and on paperwork">
|
||||
<label for="{{ form.description.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.description.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.description class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Event details #}
|
||||
{% include 'partials/event_details_form.html' %}
|
||||
<div class="col-md-6 my-3">
|
||||
<div class="card card-default form-hws form-non_rig" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
|
||||
<div class="card-header">Event Details</div>
|
||||
<div class="card-body">
|
||||
<div id="form-hws">
|
||||
<div class="form-group" data-toggle="tooltip" title="Name of the event, displays on rigboard and on paperwork">
|
||||
<label for="{{ form.name.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.name.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.name class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-toggle="tooltip" title="The venue for the rig, leave blank if unknown (e.g. for a dry hire)">
|
||||
<label for="{{ form.venue.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.venue.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
|
||||
{% if venue %}
|
||||
<option value="{{form.venue.value}}" selected="selected" data-update_url="{% url 'venue_update' form.venue.value %}">{{ venue }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'venue_create' %}" class="btn btn-success modal-href"
|
||||
data-target="#{{ form.venue.id_for_label }}">
|
||||
<span class="fas fa-plus"></span>
|
||||
</a>
|
||||
<a href="{% if object.venue %}{% url 'venue_update' object.venue.pk %}{% endif %}" class="btn btn-warning modal-href" id="{{ form.venue.id_for_label }}-update" data-target="#{{ form.venue.id_for_label }}">
|
||||
<span class="fas fa-edit"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.start_date.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.start_date.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="Start date for event, required">
|
||||
{% render_field form.start_date class+="form-control" %}
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="Start time of event, can be left blank">
|
||||
{% render_field form.start_time class+="form-control" step="60" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.end_date.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.end_date.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="End date of event, leave blank if unknown or same as start date">
|
||||
{% render_field form.end_date class+="form-control" %}
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="End time of event, leave blank if unknown">
|
||||
{% render_field form.end_time class+="form-control" step="60" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Rig only information #}
|
||||
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
|
||||
<div class="form-group" data-toggle="tooltip" title="The date/time at which TEC have access to the venue">
|
||||
<label for="{{ form.access_at.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.access_at.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.access_at class+="form-control" step="60" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-toggle="tooltip" title="The date/time at which crew should meet for this event">
|
||||
<label for="{{ form.meet_at.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.meet_at.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.meet_at class+="form-control" step="60" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-4 col-sm-8">
|
||||
<div class="checkbox">
|
||||
<label data-toggle="tooltip" title="Mark this event as a dry-hire, so it needs to be checked in at the end">
|
||||
{% render_field form.dry_hire %}{{ form.dry_hire.label }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Status is needed on all events types and it looks good here in the form #}
|
||||
<div class="form-group" data-toggle="tooltip" title="The current status of the event. Only mark as booked once paperwork is received">
|
||||
<label for="{{ form.status.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.status.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.status class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
|
||||
<div class="form-group" data-toggle="tooltip" title="The Member in Charge of this event">
|
||||
<label for="{{ form.mic.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.mic.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<select id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
{% if mic %}
|
||||
<option value="{{form.mic.value}}" selected="selected" >{{ mic.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object.dry_hire %}
|
||||
<div class="form-group" data-toggle="tooltip" title="The person who checked-in this dry hire">
|
||||
<label for="{{ form.checked_in_by.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.checked_in_by.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<select id="{{ form.checked_in_by.id_for_label }}" name="{{ form.checked_in_by.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
{% if checked_in_by %}
|
||||
<option value="{{form.checked_in_by.value}}" selected="selected" >{{ checked_in_by.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group" data-toggle="tooltip" title="The student ID of the client who collected the dry-hire">
|
||||
<label for="{{ form.collector.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.collector.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.collector class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)">
|
||||
<label for="{{ form.purchase_order.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.purchase_order.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.purchase_order class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Notes and item shit #}
|
||||
<div class="col-sm-12">
|
||||
<div class="card card-default form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
|
||||
<div class="card card-default form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
|
||||
<div class="card-body">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group" data-toggle="tooltip" title="Notes on the event. This is only visible to keyholders, and is not displayed on the paperwork">
|
||||
@@ -188,12 +358,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 text-right form-hws form-non_rig my-3">
|
||||
<div class="btn-group">
|
||||
<button type="submit" class="btn btn-primary" title="Save"><i
|
||||
class="fas fa-save"></i> Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-12 text-right form-hws form-non_rig my-3" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
|
||||
{% button 'submit' %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE document SYSTEM "rml.dtd">
|
||||
|
||||
<document filename="Event {{ object.id }} - {{ object.name }} - {{ object.start_date }}.pdf">
|
||||
<document filename="{{filename}}">
|
||||
<docinit>
|
||||
<registerTTFont faceName="OpenSans" fileName="{{ fonts.opensans.regular }}"/>
|
||||
<registerTTFont faceName="OpenSans-Bold" fileName="{{ fonts.opensans.bold }}"/>
|
||||
@@ -97,8 +97,6 @@
|
||||
<drawString x="265" y="746">info@nottinghamtec.co.uk</drawString>
|
||||
<drawString x="137" y="732">Phone: (0115) 846 8720</drawString>
|
||||
|
||||
|
||||
|
||||
<setFont name="OpenSans" size="10" />
|
||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||
<setFont name="OpenSans" size="7" />
|
||||
|
||||
@@ -314,7 +314,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>24 Hour Emergency Contacts: 07825 065681 and 07825 065678</td>
|
||||
<td>General Enquires and 24 Hour Emergency Contact: 0115 84 68720</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
<div class="d-none d-md-block">
|
||||
<div class="table-responsive">
|
||||
<table class="table mb-0">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Event Date</th>
|
||||
<th scope="col">Event Details</th>
|
||||
<th scope="col">MIC</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in events %}
|
||||
<tr {% include 'partials/event_table_colour.html' %} id="event_row">
|
||||
<!---Number-->
|
||||
<th scope="row" id="event_number">{{ event.pk }}</th>
|
||||
<!--Dates-->
|
||||
<td id="event_dates">
|
||||
<div><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></div>
|
||||
{% if event.end_date and event.end_date != event.start_date %}
|
||||
<div><strong>{{ event.end_date|date:"D d/m/Y" }}</strong></div>
|
||||
{% endif %}
|
||||
<!---Times-->
|
||||
{% if not event.cancelled %}
|
||||
<dl class="dl-horizontal">
|
||||
{% if event.meet_at %}
|
||||
<dt>Crew meet</dt>
|
||||
<dd>{{ event.meet_at|date:"H:i" }}<br/>{{ event.meet_at|date:"(Y-m-d)" }}</dd>
|
||||
{% endif %}
|
||||
{% if event.has_start_time %}
|
||||
<dt>Event starts</dt>
|
||||
<dd>
|
||||
{{ event.start_time|date:"H:i" }}<br/>
|
||||
{{ event.start_date|date:"(Y-m-d)" }}<br/>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if event.has_end_time%}{% if event.start_date != event.end_date or event.start_time != event.end_time %}
|
||||
<dt>Event ends</dt>
|
||||
<dd>
|
||||
{{ event.end_time|date:"H:i" }}<br/>
|
||||
{{ event.end_date|date:"(Y-m-d)" }}
|
||||
</dd>
|
||||
{% endif %}{% endif %}
|
||||
</dl>
|
||||
{% endif %}
|
||||
</td>
|
||||
<!---Details-->
|
||||
<td id="event_details">
|
||||
<h4>
|
||||
<a href="{% url 'event_detail' event.pk %}">
|
||||
{{ event.name }}
|
||||
</a>
|
||||
{% if event.venue %}
|
||||
<small>at {{ event.venue }}</small>
|
||||
{% endif %}
|
||||
{% if event.dry_hire %}
|
||||
<span class="badge badge-secondary">Dry Hire</span>
|
||||
{% endif %}
|
||||
</h4>
|
||||
{% if event.is_rig and not event.cancelled %}
|
||||
<h5>
|
||||
{{ event.person.name }}
|
||||
{% if event.organisation %}
|
||||
for {{ event.organisation.name }}
|
||||
{% endif %}
|
||||
</h5>
|
||||
{% endif %}
|
||||
{% if not event.cancelled and event.description %}
|
||||
<p>{{ event.description|linebreaksbr }}</p>
|
||||
{% endif %}
|
||||
{% include 'partials/event_status.html' %}
|
||||
</td>
|
||||
<!---MIC-->
|
||||
<td id="event_mic">
|
||||
{% if event.mic %}
|
||||
<div class="media">
|
||||
{% if perms.RIGS.view_profile %}
|
||||
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
|
||||
{% endif %}
|
||||
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo mr-3"/>
|
||||
{% if perms.RIGS.view_profile %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="media-body">
|
||||
<p>{{ event.mic.initials }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% elif event.is_rig %}
|
||||
<span class="fas fa-exclamation"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr class="bg-warning">
|
||||
<td colspan="6">No events found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-xs-block d-sm-block d-md-none">
|
||||
{% include 'event_table_mobile.html' %}
|
||||
</div>
|
||||
@@ -1,41 +0,0 @@
|
||||
<div class="card">
|
||||
{% for event in events %}
|
||||
<div class="card-header {% if event.cancelled %}
|
||||
text-muted bg-secondary
|
||||
{% elif event.authorised and event.risk_assessment_edit_url and event.mic %}
|
||||
bg-success
|
||||
{% elif not event.is_rig %}
|
||||
bg-info
|
||||
{% else %}
|
||||
bg-warning
|
||||
{% endif %}
|
||||
">
|
||||
<a href="{% url 'event_detail' event.pk %}">{{ event.pk }} | {{ event.name }}</a>
|
||||
{% if event.dry_hire %}
|
||||
<span class="badge badge-pill badge-secondary">Dry Hire</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% include 'partials/event_status.html' %}
|
||||
<h6 class="pt-2"><strong>{{ event.start_date|date:"D d/m/Y" }}</strong>
|
||||
{% if event.end_date and event.end_date != event.start_date %}
|
||||
<strong>– {{ event.end_date|date:"D d/m/Y" }}</strong></h4>
|
||||
{% else %}
|
||||
</h6>
|
||||
{% endif %}
|
||||
<ul class="list-group list-group-flush pb-3">
|
||||
<li class="list-group-item">Venue: {{ event.venue }}</li>
|
||||
<li class="list-group-item">Client: {{ event.person }}</li>
|
||||
<li class="list-group-item">Organisation: {{ event.organisation }}</li>
|
||||
</ul>
|
||||
{% if not event.cancelled and event.description %}
|
||||
<p>{{ event.description|linebreaksbr }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer">MIC: {% if event.mic %}<p>{{ event.mic.initials }}</p>{% elif event.is_rig %}<i class="fas fa-exclamation"></i>{% endif %}</div>
|
||||
{% empty %}
|
||||
<div class="card-body bg-warning">
|
||||
<p>No events found</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
37
RIGS/templates/eventauthorisation.html
Normal file
37
RIGS/templates/eventauthorisation.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends 'base_client.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
$('form').on('submit', function () {
|
||||
$('#loading-modal').modal({
|
||||
backdrop: 'static',
|
||||
show: true
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row my-3">
|
||||
{% include 'partials/client_eventdetails.html' %}
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12">
|
||||
<div class="card">
|
||||
{% with object=event auth=True %}
|
||||
{% include 'item_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block authorisation %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -9,13 +9,7 @@
|
||||
by <b>{{ object.name }}</b> as of <b>{{ object.event.last_edited_at }}</b>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
|
||||
Your event is now fully booked and payment will be processed by the finance department automatically.
|
||||
{% else %}{# external #}
|
||||
Your event is now fully booked and our finance department will be contact to arrange payment.
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>Your event is now fully booked and payment will be processed by the finance department automatically.</p>
|
||||
|
||||
<p>TEC PA & Lighting</p>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,10 +2,6 @@ Hi {{ to_name|default_if_none:"there" }},
|
||||
|
||||
Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.event.last_edited_at}}.
|
||||
|
||||
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
|
||||
Your event is now fully booked and payment will be processed by the finance department automatically.
|
||||
{% else %}{# external #}
|
||||
Your event is now fully booked and our finance department will be contact to arrange payment.
|
||||
{% endif %}
|
||||
|
||||
TEC PA & Lighting
|
||||
|
||||
@@ -1,130 +1,99 @@
|
||||
{% extends 'base_client.html' %}
|
||||
{% extends 'eventauthorisation.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
$('form').on('submit', function () {
|
||||
$('#loading-modal').modal({
|
||||
backdrop: 'static',
|
||||
show: true
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }}
|
||||
{% endblock %}
|
||||
{% block authorisation %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="card border-primary">
|
||||
<div class="card-header" id="eventauth">Event Authorisation</div>
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header my-3">
|
||||
<h1>
|
||||
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %}
|
||||
| {{ event.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form class=" itemised_form" role="form" method="POST" action="#eventauth">
|
||||
{% csrf_token %}
|
||||
{% include 'form_errors.html' %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<p>
|
||||
I agree that I am authorised to approve this event. I agree that I am the
|
||||
<strong>President/Treasurer or account holder</strong> of the hirer, or that I
|
||||
have the written permission of the
|
||||
<strong>President/Treasurer or account holder</strong> of the hirer stating that
|
||||
I can authorise this event.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% include 'client_eventdetails.html' %}
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="col-sm-12 form-group form-row" data-toggle="tooltip"
|
||||
title="Your name as the person authorising the event.">
|
||||
<label for="{{ form.name.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.name.label }}</label>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% with object=event %}
|
||||
{% include 'item_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="card border-primary">
|
||||
<div class="card-header" id="eventauth">Event Authorisation</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form class="form-horizontal itemised_form" role="form" method="POST" action="#eventauth">
|
||||
{% csrf_token %}
|
||||
{% include 'form_errors.html' %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<p>
|
||||
I agree that I am authorised to approve this event. I agree that I am the
|
||||
<strong>President/Treasurer or account holder</strong> of the hirer, or that I
|
||||
have the written permission of the
|
||||
<strong>President/Treasurer or account holder</strong> of the hirer stating that
|
||||
I can authorise this event.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||
title="Your name as the person authorising the event.">
|
||||
<label for="{{ form.name.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.name.label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.name class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||
title="Your Student ID or Staff username as the person authorising the event.">
|
||||
<label for="{{ form.uni_id.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.uni_id.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.uni_id class+="form-control" %}
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.name class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||
title="The Students' Union account code you wish this event to be charged to.">
|
||||
<label for="{{ form.account_code.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.account_code.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.account_code class+="form-control" %}
|
||||
</div>
|
||||
<div class="col-sm-12 form-group form-row" data-toggle="tooltip"
|
||||
title="Your Student ID or Staff username as the person authorising the event.">
|
||||
<label for="{{ form.uni_id.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.uni_id.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.uni_id class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 form-group" data-toggle="tooltip"
|
||||
title="The full amount chargable for this event as displayed above, including VAT.">
|
||||
<label for="{{ form.amount.id_for_label }}"
|
||||
class="col-sm-4 control-label">{{ form.amount.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.amount class+="form-control" %}
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="col-sm-12 form-group form-row" data-toggle="tooltip"
|
||||
title="The Students' Union account code you wish this event to be charged to.">
|
||||
<label for="{{ form.account_code.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.account_code.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.account_code class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 col-md-8 form-group">
|
||||
<div class="col-sm-offset-4 col-sm-8 col-md-offset-3" data-toggle="tooltip"
|
||||
title="In order to book an event you must agree to the TEC Terms of Hire.">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
{% render_field form.tos %} I have read and agree to the TEC
|
||||
<a href="{{ tos_url }}">Terms of Hire</a>. E&OE.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-4 text-right">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary btn-lg" type="submit">Authorise</button>
|
||||
<div class="col-sm-12 form-group form-row" data-toggle="tooltip"
|
||||
title="The full amount chargable for this event as displayed above, including VAT.">
|
||||
<label for="{{ form.amount.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.amount.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">£</span>
|
||||
</div>
|
||||
{% render_field form.amount class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 col-md-8 form-group">
|
||||
<div class="col-sm-offset-4 col-sm-8 col-md-offset-3" data-toggle="tooltip"
|
||||
title="In order to book an event you must agree to the TEC Terms of Hire.">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
{% render_field form.tos %} I have read and agree to the TEC
|
||||
<a href="{{ tos_url }}">Terms of Hire</a>. E&OE.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary btn-lg" type="submit">Authorise</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,64 +1,42 @@
|
||||
{% extends 'base_client.html' %}
|
||||
{% extends 'eventauthorisation.html' %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block title %}
|
||||
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }}
|
||||
{% block js %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1>
|
||||
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %}
|
||||
| {{ event.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% block authorisation %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-header">Event Authorisation</div>
|
||||
|
||||
{% include 'client_eventdetails.html' %}
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<dl class="dl row">
|
||||
<dt class="col-6">Name</dt>
|
||||
<dd class="col-6">{{ object.name }}</dd>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% with object=event %}
|
||||
{% include 'item_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
<dt class="col-6">Email</dt>
|
||||
<dd class="col-6">{{ object.email }}</dd>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="card card-default">
|
||||
<div class="card-header">Event Authorisation</div>
|
||||
<dt class="col-6">University ID</dt>
|
||||
<dd class="col-6">{{ object.uni_id }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Name</dt>
|
||||
<dd>{{ object.name }}</dd>
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<dl class="dl row">
|
||||
<dt class="col-6">Account code</dt>
|
||||
<dd class="col-6">{{ object.account_code }}</dd>
|
||||
|
||||
<dt>Email</dt>
|
||||
<dd>{{ object.email }}</dd>
|
||||
|
||||
{% if internal %}
|
||||
<dt>University ID</dt>
|
||||
<dd>{{ object.uni_id }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Account code</dt>
|
||||
<dd>{{ object.account_code }}</dd>
|
||||
|
||||
<dt>Authorised amount</dt>
|
||||
<dd>£ {{ object.amount|floatformat:2 }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<dt class="col-6">Authorised amount</dt>
|
||||
<dd class="col-6">£ {{ object.amount|floatformat:2 }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
50
RIGS/templates/hs_list.html
Normal file
50
RIGS/templates/hs_list.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
{% load paginator from filters %}
|
||||
{% load button from filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="table-responsive">
|
||||
<table class="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Event</th>
|
||||
<th scope="col">Dates</th>
|
||||
<th scope="col">RA</th>
|
||||
<th scope="col">Checklists</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in object_list %}
|
||||
<tr id="event_row">
|
||||
<th scope="row" id="event_number"><a href="{% url 'event_detail' event.pk %}">{{ event }}</a></th>
|
||||
<!--Dates-->
|
||||
<td id="event_dates">
|
||||
<span><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></span>
|
||||
{% if event.end_date and event.end_date != event.start_date %}
|
||||
<br><span><strong>{{ event.end_date|date:"D d/m/Y" }}</strong></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% include 'partials/hs_status.html' with event=event object=event.riskassessment view='ra_detail' edit='ra_edit' create='event_ra' review='ra_review' perm=perms.RIGS.review_riskassessment %}</td>
|
||||
<td>
|
||||
{% for checklist in event.checklists.all %}
|
||||
{% include 'partials/hs_status.html' with event=event object=checklist view='ec_detail' edit='ec_edit' create='event_ec' review='ec_review' perm=perms.RIGS.review_eventchecklist %}
|
||||
<br>
|
||||
{% endfor %}
|
||||
<a href="{% url 'event_ec' event.pk %}" class="btn btn-info"><span class="fas fa-paperclip"></span> <span
|
||||
class="d-none d-sm-inline">Create</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr class="bg-warning text-dark">
|
||||
<td colspan="6">No events found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if is_paginated %}
|
||||
<div class="row justify-content-center">
|
||||
{% paginator %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
59
RIGS/templates/hs_object_list.html
Normal file
59
RIGS/templates/hs_object_list.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
{% load paginator from filters %}
|
||||
{% load help_text from filters %}
|
||||
{% load verbose_name from filters %}
|
||||
{% load get_field from filters %}
|
||||
|
||||
{% block title %}{{ title }} List{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2>{{title}} List</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="table-responsive">
|
||||
<table class="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Event</th>
|
||||
{# mmm hax #}
|
||||
{% if object_list.0 != None %}
|
||||
{% for field in fields %}
|
||||
<th scope="col">{{ object_list.0|verbose_name:field|title }}</th>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for object in object_list %}
|
||||
<tr class="{% if object.reviewed_by %}table-success{%endif%}">
|
||||
{# General #}
|
||||
<th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a></th>
|
||||
{% for field in fields %}
|
||||
<td>{{ object|get_field:field }}</td>
|
||||
{% endfor %}
|
||||
{# Buttons #}
|
||||
<td>
|
||||
{% include 'partials/hs_status.html' %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr class="bg-warning">
|
||||
<td colspan="6">Nothing found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if is_paginated %}
|
||||
<div class="row justify-content-center">
|
||||
{% paginator %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,35 +1,20 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
|
||||
{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %}
|
||||
{% block title %}Delete Invoice {{ object.invoice.pk }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-offset-2 col-sm-8">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h2>Delete invoice {{ object.pk }}</h2>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h2>Delete Invoice {{ object.pk }}</h2>
|
||||
|
||||
<p>Are you sure you wish to delete invoice {{ object.pk }}?</p>
|
||||
<p>Are you sure you wish to delete invoice {{ object.pk }}?</p>
|
||||
|
||||
<p class="text-center"><strong>This action cannot be undone!</strong></p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<form action="{{ action_link }}" method="post">{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{% url 'invoice_list' %}"/>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<input type="submit" value="Yes" class="btn btn-danger col-sm-1"/>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<form action="{{ action_link }}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{% url 'invoice_list' %}"/>
|
||||
<input type="submit" value="Yes" class="btn btn-danger col-sm-1"/>
|
||||
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-success col-sm-1">No</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
|
||||
{% block title %}Invoice {{ object.pk }}{% endblock %}
|
||||
{% load button from filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<h2>Invoice {{ object.pk }} ({{ object.invoice_date|date:"d/m/Y"}})</h2>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-end py-3">
|
||||
<div class="col-sm-4 text-right">
|
||||
<div class="btn-group btn-page">
|
||||
<a href="{% url 'invoice_delete' object.pk %}" class="btn btn-danger" title="Delete Invoice">
|
||||
<span class="fas fa-times"></span> <span
|
||||
class="hidden-xs">Delete</span>
|
||||
</a>
|
||||
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-warning" title="Void Invoice">
|
||||
<span class="fas fa-ban"></span> <span
|
||||
class="hidden-xs">Void</span>
|
||||
</a>
|
||||
<a href="{% url 'invoice_print' object.pk %}" target="_blank" title="Print Invoice" class="btn btn-primary"><span
|
||||
class="fas fa-print"></span> <span
|
||||
class="hidden-xs">Print</span></a>
|
||||
<a href="{% url 'invoice_delete' object.pk %}" class="btn btn-danger" title="Delete Invoice">
|
||||
<span class="fas fa-times"></span> <span
|
||||
class="d-none d-sm-inline">Delete</span>
|
||||
</a>
|
||||
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-warning" title="Void Invoice">
|
||||
<span class="fas fa-ban"></span> <span
|
||||
class="d-none d-sm-inline">Void</span>
|
||||
</a>
|
||||
{% button 'print' url='invoice_print' pk=object.pk %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,13 +46,13 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="row py-4">
|
||||
<div class="col-sm-6">
|
||||
<div class="card card-default">
|
||||
<div class="card-body">
|
||||
<div class="pull-right">
|
||||
<div class="text-right py-3">
|
||||
<a href="{% url 'payment_create' %}?invoice={{ object.pk }}"
|
||||
class="btn btn-default modal-href"
|
||||
class="btn btn-success modal-href"
|
||||
data-target="#{{ form.person.id_for_label }}">
|
||||
<span class="fas fa-plus"></span> Add
|
||||
</a>
|
||||
@@ -96,14 +89,14 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="card card-default">
|
||||
<div class="card-body">
|
||||
{% with object.event as object %}
|
||||
{% include 'item_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="card">
|
||||
{% with object.event as object %}
|
||||
{% include 'item_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
{% include 'partials/last_edited.html' with target="invoice_history" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
{% load paginator from filters %}
|
||||
{% load button from filters %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Invoices{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h2>{% block heading %}Invoices{% endblock %}</h2>
|
||||
{% block description %}{% endblock %}
|
||||
{{ description }}
|
||||
{% block search %}{% endblock %}
|
||||
<div class="table-responsive col-sm-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Invoice #</th>
|
||||
<th scope="col">Event</th>
|
||||
@@ -25,7 +23,7 @@
|
||||
<tbody>
|
||||
{% for invoice in invoice_list %}
|
||||
<tr class="table-{% if invoice.is_closed %}success{% else %}warning{% endif %}">
|
||||
<th scope="row">{{ invoice.pk }}<br>
|
||||
<th scope="row">{{ invoice.display_id }}<br>
|
||||
<span class="text-muted">{% if invoice.void %}(VOID){% elif invoice.is_closed %}(PAID){% else %}(O/S){% endif %}</span></th>
|
||||
<td><a href="{% url 'event_detail' invoice.event.pk %}">N{{ invoice.event.pk|stringformat:"05d" }}</a>: {{ invoice.event.name }} <br>
|
||||
<span class="text-muted">{{ invoice.event.get_status_display }}{% if not invoice.event.mic %}, No MIC{% endif %}
|
||||
@@ -51,9 +49,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'invoice_detail' invoice.pk %}" class="btn btn-primary">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
{% button 'view' url='invoice_detail' pk=invoice.pk %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{% extends 'invoice_list.html' %}
|
||||
|
||||
{% block title %}
|
||||
Outstanding Invoices
|
||||
{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
Outstanding Invoices ({{ count }} Events, £{{ total|floatformat:2 }})
|
||||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
<p>Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger</p>
|
||||
{% endblock %}
|
||||
@@ -1,24 +1,7 @@
|
||||
{% extends 'invoice_list.html' %}
|
||||
|
||||
{% block title %}
|
||||
Invoice Archive
|
||||
{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
All Invoices
|
||||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
<p>This page displays all invoices: outstanding, paid, and void</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block search %}
|
||||
<div class="col-sm-3 col-sm-offset-9">
|
||||
<form class="form form-horizontal col-sm-12">
|
||||
<div class="form-group">
|
||||
<input type="search" name="q" placeholder="Search" value="{{ request.GET.q }}"
|
||||
class="form-control"/>
|
||||
</div>
|
||||
</form>
|
||||
<div class="py-3">
|
||||
{% include 'partials/list_search.html' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
{% load paginator from filters %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Events for Invoice{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static "js/tooltip.js" %}"></script>
|
||||
<script>
|
||||
@@ -15,11 +13,10 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h2>Events for Invoice ({{count}} Events, £{{ total|floatformat:2 }})</h2>
|
||||
<p>These events have happened, but paperwork has not yet been sent to treasury</p>
|
||||
<div class="table-responsive col-sm-12">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Event #</th>
|
||||
<th scope="col">Start Date</th>
|
||||
@@ -32,8 +29,8 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in object_list %}
|
||||
<tr {% include 'partials/event_table_colour.html' %}>
|
||||
<th scope="row"><a href="{% url 'event_detail' event.pk %}">N{{ event.pk|stringformat:"05d" }}</a><br>
|
||||
<tr class="{{event.status_color}}">
|
||||
<th scope="row"><a href="{% url 'event_detail' event.pk %}">{{ event.display_id }}</a><br>
|
||||
<span class="text-muted">{{ event.get_status_display }}</span></th>
|
||||
<td>{{ event.start_date }}</td>
|
||||
<td>
|
||||
@@ -69,12 +66,15 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'invoice_event' event.pk %}"
|
||||
class="btn btn-primary"
|
||||
data-toggle="tooltip"
|
||||
title="'Invoice' this event - click this when paperwork has been sent to treasury">
|
||||
<i class="fas fa-pound-sign"></i> Paperwork Sent
|
||||
</a>
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'invoice_event' event.pk %}"
|
||||
class="btn btn-primary">
|
||||
<span class="fas fa-pound-sign"></span> Create Invoice
|
||||
</a>
|
||||
<a href="{% url 'invoice_event_void' event.pk %}"
|
||||
class="btn btn-warning">& Void
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -3,18 +3,21 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>{{ object.name|default:"New Event" }}</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="item-form">
|
||||
<div class="modal-body">
|
||||
<div class="form-group form-row">
|
||||
<label for="item_name" class="col-sm-2 control-label">Item Name</label>
|
||||
<label for="item_name" class="col-sm-2 col-form-label">Item Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" placeholder="Item Name" class="form-control" required maxlength="255"
|
||||
id="item_name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-row">
|
||||
<label for="item_description" class="col-sm-2 control-label">Description</label>
|
||||
<label for="item_description" class="col-sm-2 col-form-label">Description</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea type="text" placeholder="Description" class="form-control"
|
||||
id="item_description" rows="8"></textarea>
|
||||
@@ -23,7 +26,7 @@
|
||||
<div class="form-row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group form-row">
|
||||
<label for="item_quantity" class="col-sm-4 control-label">Quantity</label>
|
||||
<label for="item_quantity" class="col-sm-4 col-form-label">Quantity</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" placeholder="Quantity" class="form-control" required
|
||||
min="1" id="item_quantity"/>
|
||||
@@ -32,7 +35,7 @@
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group form-row">
|
||||
<label for="item_cost" class="col-sm-4 control-label">Cost</label>
|
||||
<label for="item_cost" class="col-sm-4 col-form-label">Cost</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
{% if edit %}
|
||||
<td class="vert-align text-right">
|
||||
<div class="btn-group" role="group" aria-label="Action buttons">
|
||||
<button type="button" class="item-edit btn btn-xs btn-warning"
|
||||
<button type="button" class="item-edit btn btn-sm btn-warning"
|
||||
data-pk="{{item.pk}}"
|
||||
data-toggle="modal" data-target="#itemModal">
|
||||
<span class="fas fa-edit"></span>
|
||||
</button>
|
||||
<button type="button" class="item-delete btn btn-xs btn-danger"
|
||||
<button type="button" class="item-delete btn btn-sm btn-danger"
|
||||
data-pk="{{item.pk}}">
|
||||
<span class="fas fa-times-circle"></span>
|
||||
</button>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{% endif %}
|
||||
{% if edit %}
|
||||
<th scope="col" class="text-right align-self-start">
|
||||
<button type="button" class="btn btn-success btn-xs item-add"
|
||||
<button type="button" class="btn btn-success btn-sm item-add"
|
||||
data-toggle="modal"
|
||||
data-target="#itemModal">
|
||||
<i class="fas fa-plus"></i> Add Item
|
||||
@@ -26,7 +26,7 @@
|
||||
{% include 'item_row.html' %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% if perms.RIGS.view_event %}
|
||||
{% if auth or perms.RIGS.view_event %}
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td rowspan="3" colspan="2"></td>
|
||||
@@ -65,12 +65,12 @@
|
||||
{% if edit %}
|
||||
<td class="vert-align text-right">
|
||||
<div class="btn-group" role="group" aria-label="Action buttons">
|
||||
<button type="button" class="item-edit btn btn-xs btn-warning"
|
||||
<button type="button" class="item-edit btn btn-sm btn-warning"
|
||||
data-pk="{{item.pk}}"
|
||||
data-toggle="modal" data-target="#itemModal">
|
||||
<span class="fas fa-edit"></span>
|
||||
</button>
|
||||
<button type="button" class="item-delete btn btn-xs btn-danger"
|
||||
<button type="button" class="item-delete btn btn-sm btn-danger"
|
||||
data-pk="{{item.pk}}">
|
||||
<span class="fas fa-times-circle"></span>
|
||||
</button>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user