mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-08 07:59:42 +00:00
Compare commits
160 Commits
8b0cd13159
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d16074e647 | ||
|
|
68b28a6df2 | ||
|
|
b022a0541e | ||
|
|
51deadc192 | ||
|
|
444f64ddc1 | ||
|
|
3f38ce77e0 | ||
|
|
39a2401ec9 | ||
|
|
032768d3b0 | ||
|
|
007571fab6 | ||
|
|
311189470d | ||
|
|
db387c768a | ||
|
|
3c4901f86a | ||
|
|
385a3bd979 | ||
|
|
ded8925ef5 | ||
|
|
0bb7418f17 | ||
|
|
30a053b813 | ||
|
|
bd5af7c390 | ||
|
|
22bba7e8af | ||
|
|
dcf19ff773 | ||
|
|
6e1ac4e203 | ||
|
|
899f958aa3 | ||
|
|
ee83fd2064 | ||
|
|
87dd32da6c | ||
|
|
25593f3e6c | ||
|
|
89d50fbd49 | ||
|
|
7313c905ea | ||
|
|
074d40ea03 | ||
|
|
1496a75826 | ||
|
|
1f22470bfd | ||
|
|
47babff6d4 | ||
|
|
ad8ffa69de | ||
|
|
99fdca6a2d | ||
|
|
245a92f8e6 | ||
|
|
89d7c5140e | ||
|
|
6b4a1ce6bf | ||
|
|
93762fe198 | ||
|
|
2559c92927 | ||
| 3afa4deb52 | |||
|
|
9f910af7eb | ||
|
|
6c32db3998 | ||
|
|
c2ef469d5d | ||
|
|
e5c7e24941 | ||
| be7b595edb | |||
| 6d53df0c8b | |||
| 732a6e5c1e | |||
|
|
c6823bb9ac | ||
|
|
ec000beee8 | ||
|
|
6c8eb380fd | ||
|
3123d3899c
|
|||
|
|
c93c04ec6e | ||
| 6bf8d56ce8 | |||
|
8246071b8c
|
|||
|
06fa1a3b1b
|
|||
|
70abfaf2ae
|
|||
|
02a5a94fbb
|
|||
|
|
ddce9752c6 | ||
|
|
5a16e06bed | ||
|
|
5160a61a62 | ||
|
|
607f282ef6 | ||
|
|
d71bb81edf | ||
|
|
f46915233e | ||
|
|
0c900d2447 | ||
|
953b691cc2
|
|||
|
17fa447861
|
|||
|
|
409125c8a3 | ||
|
|
03d996eb66 | ||
|
|
1e40916a94 | ||
|
|
eae3f762b7 | ||
|
|
7a387e8724 | ||
|
|
4f42219821 | ||
|
d7d2f93295
|
|||
| 2a2ce742b0 | |||
|
|
68d3605230 | ||
|
1fff150566
|
|||
|
240ff25c63
|
|||
|
e265ad58a6
|
|||
|
1a32ef424b
|
|||
|
|
44c92fc859 | ||
|
|
1af182eaa1 | ||
|
|
425a697743 | ||
|
|
b7011e368b | ||
| 7d2f8d2dc8 | |||
| 5a54092771 | |||
|
|
e8a8f2bf0d | ||
|
|
3984c17605 | ||
|
|
b2209eef4e | ||
|
e7c4f7f73d
|
|||
|
769d983e3d
|
|||
|
303005a32b
|
|||
|
c722773586
|
|||
|
|
0c2e677786 | ||
|
|
9719581977 | ||
|
|
b838dde30b | ||
|
387fa56442
|
|||
|
f35ce88acc
|
|||
|
ee5468fdd7
|
|||
|
1ce6ec3284
|
|||
|
677f352524
|
|||
|
8b3102b136
|
|||
|
e2b1dc1d05
|
|||
|
c9ba228bd2
|
|||
|
9f4cd41d23
|
|||
|
2049d0f76d
|
|||
|
29db3b5a0c
|
|||
|
53b09e47b8
|
|||
|
097e7c2481
|
|||
| 16874073e9 | |||
|
|
d03a4e115f | ||
| e1b87b412a | |||
| 54b44404ba | |||
|
|
26942b80dd | ||
|
|
888300490c | ||
| 9201f9d896 | |||
| 9fae129e26 | |||
|
|
8d45e260dd | ||
| 7d8dddb952 | |||
| 1104f10c91 | |||
| 3d5efba0af | |||
| 9a44aaf557 | |||
| 550eff83ee | |||
| bf3da5ae25 | |||
| 87aa87bc0f | |||
| 01a0b8f831 | |||
|
724762a1e8
|
|||
|
6ea5dc9698
|
|||
|
|
eb45db8950 | ||
|
b1a2859f1b
|
|||
|
dc71c2de62
|
|||
| 8863d86ed0 | |||
|
|
6550ed2318 | ||
|
|
c9759a6339 | ||
|
|
b637c4e452 | ||
|
|
8986b94b07 | ||
| 86c033ba97 | |||
| 52fd662340 | |||
| 9818ed995f | |||
| 5178614d71 | |||
| a7bf990666 | |||
|
a4a28a6130
|
|||
|
e3d8cf8978
|
|||
|
626779ef25
|
|||
| fa1dc31639 | |||
| d69543e309 | |||
|
|
a24e6d4495 | ||
|
|
fa5792914a | ||
| 0117091f3e | |||
|
37101d3340
|
|||
| de4bed92a4 | |||
|
|
3767923175 | ||
|
|
1d77cf95d3 | ||
|
|
1f21d0b265 | ||
| 7846a6d31e | |||
| d28b73a0b8 | |||
|
|
5c2e8b391c | ||
|
|
548bc1df81 | ||
|
|
c1d2bce8fb | ||
|
c71beab278
|
|||
|
259932a548
|
|||
|
7526485837
|
|||
| 39ed5aefb4 |
151
.github/workflows/combine-prs.yml
vendored
Normal file
151
.github/workflows/combine-prs.yml
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
name: 'Combine PRs'
|
||||
|
||||
# Controls when the action will run - in this case triggered manually
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branchPrefix:
|
||||
description: 'Branch prefix to find combinable PRs based on'
|
||||
required: true
|
||||
default: 'dependabot'
|
||||
mustBeGreen:
|
||||
description: 'Only combine PRs that are green (status is success)'
|
||||
required: true
|
||||
default: true
|
||||
combineBranchName:
|
||||
description: 'Name of the branch to combine PRs into'
|
||||
required: true
|
||||
default: 'combine-prs-branch'
|
||||
ignoreLabel:
|
||||
description: 'Exclude PRs with this label'
|
||||
required: true
|
||||
default: 'nocombine'
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "combine-prs"
|
||||
combine-prs:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
id: create-combined-pr
|
||||
name: Create Combined PR
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
let branchesAndPRStrings = [];
|
||||
let baseBranch = null;
|
||||
let baseBranchSHA = null;
|
||||
for (const pull of pulls) {
|
||||
const branch = pull['head']['ref'];
|
||||
console.log('Pull for branch: ' + branch);
|
||||
if (branch.startsWith('${{ github.event.inputs.branchPrefix }}')) {
|
||||
console.log('Branch matched prefix: ' + branch);
|
||||
let statusOK = true;
|
||||
if(${{ github.event.inputs.mustBeGreen }}) {
|
||||
console.log('Checking green status: ' + branch);
|
||||
const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number:$pull_number) {
|
||||
commits(last: 1) {
|
||||
nodes {
|
||||
commit {
|
||||
statusCheckRollup {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
const vars = {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pull['number']
|
||||
};
|
||||
const result = await github.graphql(stateQuery, vars);
|
||||
const [{ commit }] = result.repository.pullRequest.commits.nodes;
|
||||
const state = commit.statusCheckRollup.state
|
||||
console.log('Validating status: ' + state);
|
||||
if(state != 'SUCCESS') {
|
||||
console.log('Discarding ' + branch + ' with status ' + state);
|
||||
statusOK = false;
|
||||
}
|
||||
}
|
||||
console.log('Checking labels: ' + branch);
|
||||
const labels = pull['labels'];
|
||||
for(const label of labels) {
|
||||
const labelName = label['name'];
|
||||
console.log('Checking label: ' + labelName);
|
||||
if(labelName == '${{ github.event.inputs.ignoreLabel }}') {
|
||||
console.log('Discarding ' + branch + ' with label ' + labelName);
|
||||
statusOK = false;
|
||||
}
|
||||
}
|
||||
if (statusOK) {
|
||||
console.log('Adding branch to array: ' + branch);
|
||||
const prString = '#' + pull['number'] + ' ' + pull['title'];
|
||||
branchesAndPRStrings.push({ branch, prString });
|
||||
baseBranch = pull['base']['ref'];
|
||||
baseBranchSHA = pull['base']['sha'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (branchesAndPRStrings.length == 0) {
|
||||
core.setFailed('No PRs/branches matched criteria');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}',
|
||||
sha: baseBranchSHA
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?');
|
||||
return;
|
||||
}
|
||||
|
||||
let combinedPRs = [];
|
||||
let mergeFailedPRs = [];
|
||||
for(const { branch, prString } of branchesAndPRStrings) {
|
||||
try {
|
||||
await github.rest.repos.merge({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
base: '${{ github.event.inputs.combineBranchName }}',
|
||||
head: branch,
|
||||
});
|
||||
console.log('Merged branch ' + branch);
|
||||
combinedPRs.push(prString);
|
||||
} catch (error) {
|
||||
console.log('Failed to merge branch ' + branch);
|
||||
mergeFailedPRs.push(prString);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Creating combined PR');
|
||||
const combinedPRsString = combinedPRs.join('\n');
|
||||
let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString;
|
||||
if(mergeFailedPRs.length > 0) {
|
||||
const mergeFailedPRsString = mergeFailedPRs.join('\n');
|
||||
body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString
|
||||
}
|
||||
await github.rest.pulls.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: 'Combined PR',
|
||||
head: '${{ github.event.inputs.combineBranchName }}',
|
||||
base: baseBranch,
|
||||
body: body
|
||||
});
|
||||
32
.github/workflows/django.yml
vendored
32
.github/workflows/django.yml
vendored
@@ -12,27 +12,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PYTHONDONTWRITEBYTECODE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get install libcairo2-dev
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9.1
|
||||
- uses: actions/cache@v2
|
||||
id: pcache
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pipenv-
|
||||
python-version: "3.10"
|
||||
cache: 'pipenv'
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip pipenv
|
||||
python3 -m pip install --upgrade pip pipenv
|
||||
pipenv install -d
|
||||
# if: steps.pcache.outputs.cache-hit != 'true'
|
||||
- name: Cache Static Files
|
||||
id: static-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: 'pipeline/built_assets'
|
||||
key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
|
||||
@@ -43,12 +41,12 @@ jobs:
|
||||
- name: Basic Checks
|
||||
run: |
|
||||
pipenv run pycodestyle . --exclude=migrations,node_modules
|
||||
pipenv run python manage.py check
|
||||
pipenv run python manage.py makemigrations --check --dry-run
|
||||
pipenv run python manage.py collectstatic --noinput
|
||||
pipenv run python3 manage.py check
|
||||
pipenv run python3 manage.py makemigrations --check --dry-run
|
||||
pipenv run python3 manage.py collectstatic --noinput
|
||||
- name: Run Tests
|
||||
run: pipenv run pytest -n auto -vv --cov
|
||||
- uses: actions/upload-artifact@v2
|
||||
run: pipenv run pytest -n auto --cov
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: failure-screenshots ${{ matrix.test-group }}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -101,3 +101,6 @@ crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
.vscode/
|
||||
screenshots/
|
||||
|
||||
# Virutal Environments
|
||||
.venv/
|
||||
|
||||
43
Pipfile
43
Pipfile
@@ -11,7 +11,6 @@ asgiref = "~=3.3.1"
|
||||
beautifulsoup4 = "~=4.9.3"
|
||||
Brotli = "~=1.0.9"
|
||||
cachetools = "~=4.2.1"
|
||||
certifi = "~=2020.12.5"
|
||||
chardet = "~=4.0.0"
|
||||
configparser = "~=5.0.1"
|
||||
contextlib2 = "~=0.6.0.post1"
|
||||
@@ -20,47 +19,45 @@ cssutils = "~=1.0.2"
|
||||
dj-database-url = "~=0.5.0"
|
||||
dj-static = "~=0.0.6"
|
||||
Django = "~=3.2"
|
||||
django-debug-toolbar = "~=3.2"
|
||||
django-debug-toolbar = "~=4.0.0"
|
||||
django-filter = "~=2.4.0"
|
||||
django-ical = "~=1.7.1"
|
||||
django-recurrence = "~=1.10.3"
|
||||
django-registration-redux = "~=2.9"
|
||||
django-reversion = "~=3.0.9"
|
||||
django-toolbelt = "~=0.0.1"
|
||||
django-widget-tweaks = "~=1.4.8"
|
||||
django-htmlmin = "~=0.11.0"
|
||||
envparse = "~=0.2.0"
|
||||
gunicorn = "~=20.0.4"
|
||||
envparse = "*"
|
||||
gunicorn = "~=22.0.0"
|
||||
icalendar = "~=4.0.7"
|
||||
idna = "~=2.10"
|
||||
lxml = "~=4.7.1"
|
||||
idna = "~=3.7"
|
||||
Markdown = "~=3.3.3"
|
||||
msgpack = "~=1.0.2"
|
||||
pep517 = "~=0.9.1"
|
||||
Pillow = "~=9.0.0"
|
||||
Pillow = "~=10.0.1"
|
||||
premailer = "~=3.7.0"
|
||||
progress = "~=1.5"
|
||||
psutil = "~=5.8.0"
|
||||
psycopg2 = "~=2.8.6"
|
||||
Pygments = "~=2.7.4"
|
||||
Pygments = "~=2.15.0"
|
||||
pyparsing = "~=2.4.7"
|
||||
PyPDF2 = "~=1.27.5"
|
||||
PyPOM = "~=2.2.0"
|
||||
PyPOM = "~=2.2.4"
|
||||
python-dateutil = "~=2.8.1"
|
||||
pytoml = "~=0.1.21"
|
||||
pytz = "~=2020.5"
|
||||
reportlab = "*"
|
||||
requests = "~=2.25.1"
|
||||
requests = "~=2.32.3"
|
||||
retrying = "~=1.3.3"
|
||||
simplejson = "~=3.17.2"
|
||||
six = "~=1.15.0"
|
||||
soupsieve = "~=2.1"
|
||||
sqlparse = "~=0.4.2"
|
||||
sqlparse = "~=0.5.0"
|
||||
static3 = "~=0.7.0"
|
||||
svg2rlg = "~=0.3"
|
||||
tini = "~=3.0.1"
|
||||
tornado = "~=6.1"
|
||||
urllib3 = "~=1.26.5"
|
||||
tornado = "~=6.3"
|
||||
urllib3 = "~=1.26.19"
|
||||
whitenoise = "~=5.2.0"
|
||||
yolk = "~=0.4.3"
|
||||
zipp = "~=3.4.0"
|
||||
@@ -69,7 +66,6 @@ zipp = "~=3.4.0"
|
||||
"zope.deprecation" = "~=4.4.0"
|
||||
"zope.event" = "~=4.5.0"
|
||||
"zope.hookable" = "~=5.0.1"
|
||||
"zope.interface" = "~=5.2.0"
|
||||
"zope.proxy" = "~=4.3.5"
|
||||
"zope.schema" = "~=6.0.1"
|
||||
sentry-sdk = "*"
|
||||
@@ -82,10 +78,11 @@ django-hcaptcha = "*"
|
||||
pikepdf = "*"
|
||||
django-queryable-properties = "*"
|
||||
django-mass-edit = "*"
|
||||
selenium = "~=4.9.1"
|
||||
"zope.interface" = "*"
|
||||
|
||||
[dev-packages]
|
||||
selenium = "~=3.141.0"
|
||||
pycodestyle = "*"
|
||||
pycodestyle = "~=2.9.1"
|
||||
coveralls = "*"
|
||||
django-coverage-plugin = "*"
|
||||
pytest-cov = "*"
|
||||
@@ -94,14 +91,8 @@ pluggy = "*"
|
||||
pytest-splinter = "*"
|
||||
pytest = "*"
|
||||
pytest-reverse = "*"
|
||||
pytest-xdist = {extras = [ "psutil",], version = "*"}
|
||||
PyPOM = {extras = [ "splinter",], version = "*"}
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
|
||||
[dev-packages.pytest-xdist]
|
||||
extras = [ "psutil",]
|
||||
version = "*"
|
||||
|
||||
[dev-packages.PyPOM]
|
||||
extras = [ "splinter",]
|
||||
version = "*"
|
||||
python_version = "3.10"
|
||||
|
||||
2039
Pipfile.lock
generated
2039
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -79,7 +79,9 @@ def api_key_required(function):
|
||||
"""
|
||||
Decorator for views that checks api_pk and api_key.
|
||||
Failed users will be given a 403 error.
|
||||
Should only be used for urls which include <api_pk> and <api_key> kwargs
|
||||
Should only be used for urls which include <api_pk> and <api_key> kwargs.
|
||||
|
||||
Will update the kwargs to include the user object if successful (under the key 'user').
|
||||
"""
|
||||
|
||||
def wrap(request, *args, **kwargs):
|
||||
@@ -97,6 +99,7 @@ def api_key_required(function):
|
||||
|
||||
try:
|
||||
user_object = models.Profile.objects.get(pk=userid)
|
||||
kwargs = {**kwargs, 'user': user_object}
|
||||
except models.Profile.DoesNotExist:
|
||||
return error_resp
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ if DEBUG:
|
||||
ALLOWED_HOSTS.append('localhost')
|
||||
ALLOWED_HOSTS.append('example.com')
|
||||
ALLOWED_HOSTS.append('127.0.0.1')
|
||||
ALLOWED_HOSTS.append('.app.github.dev')
|
||||
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
if not DEBUG:
|
||||
@@ -42,8 +44,9 @@ if not DEBUG:
|
||||
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
|
||||
ADMINS = [('Tom Price', 'tomtom5152@gmail.com'), ('IT Manager', 'it@nottinghamtec.co.uk'),
|
||||
('Arona Jones', 'arona.jones@nottinghamtec.co.uk')]
|
||||
DOMAIN = env('DOMAIN', default='example.com')
|
||||
|
||||
ADMINS = [('IT Manager', f'it@{DOMAIN}'), ('Arona Jones', f'arona.jones@{DOMAIN}')]
|
||||
if DEBUG:
|
||||
ADMINS.append(('Testing Superuser', 'superuser@example.com'))
|
||||
|
||||
@@ -63,7 +66,7 @@ INSTALLED_APPS = (
|
||||
'assets',
|
||||
'training',
|
||||
|
||||
'debug_toolbar',
|
||||
# 'debug_toolbar',
|
||||
'registration',
|
||||
'reversion',
|
||||
'widget_tweaks',
|
||||
@@ -74,7 +77,7 @@ INSTALLED_APPS = (
|
||||
MIDDLEWARE = (
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
# 'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
'reversion.middleware.RevisionMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
@@ -217,12 +220,12 @@ TIME_ZONE = 'Europe/London'
|
||||
|
||||
FORMAT_MODULE_PATH = 'PyRIGS.formats'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
USE_THOUSAND_SEPARATOR = False
|
||||
|
||||
# Need to allow seconds as datetime-local input type spits out a time that has seconds
|
||||
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
@@ -263,3 +266,10 @@ TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
||||
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
SECURE_HSTS_SECONDS = 3600
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
SESSION_COOKIE_SECURE = env('SESSION_COOKIE_SECURE_ENABLED', True)
|
||||
CSRF_COOKIE_SECURE = env('CSRF_COOKIE_SECURE_ENABLED', True)
|
||||
SECURE_HSTS_PRELOAD = True
|
||||
|
||||
@@ -63,7 +63,7 @@ def screenshot_failure(func):
|
||||
if not pathlib.Path("screenshots").is_dir():
|
||||
os.mkdir("screenshots")
|
||||
self.driver.save_screenshot(screenshot_file)
|
||||
print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr)
|
||||
print(f"Error in test {screenshot_name} is at path {screenshot_file}", file=sys.stderr)
|
||||
raise e
|
||||
|
||||
return wrapper_func
|
||||
|
||||
@@ -59,8 +59,8 @@ class TestSampleDataGenerator(TestCase):
|
||||
assert Asset.objects.all().count() > 50
|
||||
assert Event.objects.all().count() > 100
|
||||
call_command('deleteSampleData')
|
||||
assert Asset.objects.all().count() == 0
|
||||
assert Event.objects.all().count() == 0
|
||||
assert not Asset.objects.all().exists()
|
||||
assert not Event.objects.all().exists()
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
@@ -76,9 +76,9 @@ def test_unauthenticated(client): # Nothing should be available to the unauthen
|
||||
assertTemplateUsed(response, 'login_redirect.html')
|
||||
else:
|
||||
if "embed" in str(url):
|
||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
||||
expected_url = f"{reverse('login_embed')}?next={request_url}"
|
||||
else:
|
||||
expected_url = "{0}?next={1}".format(reverse('login'), request_url)
|
||||
expected_url = f"{reverse('login')}?next={request_url}"
|
||||
assertRedirects(response, expected_url)
|
||||
call_command('deleteSampleData')
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ class Index(generic.TemplateView): # Displays the current rig count along with
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['rig_count'] = models.Event.objects.rig_count()
|
||||
context['now'] = models.Event.objects.events_in_bounds(timezone.now(), timezone.now()).exclude(status=models.Event.CANCELLED).filter(is_rig=True, dry_hire=False)
|
||||
return context
|
||||
|
||||
|
||||
@@ -133,11 +134,15 @@ class SecureAPIRequest(generic.View):
|
||||
results = []
|
||||
query = reduce(operator.and_, queries)
|
||||
objects = self.models[model].objects.filter(query)
|
||||
# Returning unactivated or unapproved users when they are elsewhere filtered out of the default queryset leads to some *very* unexpected results
|
||||
if model == "profile":
|
||||
objects = objects.filter(is_active=True, is_approved=True)
|
||||
for o in objects:
|
||||
name = o.display_name if hasattr(o, 'display_name') else o.name
|
||||
data = {
|
||||
'pk': o.pk,
|
||||
'value': o.pk,
|
||||
'text': o.name,
|
||||
'text': name,
|
||||
}
|
||||
try: # See if there is a valid update URL
|
||||
data['update'] = reverse(f"{model}_update", kwargs={'pk': o.pk})
|
||||
@@ -182,7 +187,7 @@ class ModalURLMixin:
|
||||
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 + "'")
|
||||
messages.info(self.request, f"modalobject[0]['update_url']='{update_url}'")
|
||||
else:
|
||||
url = reverse_lazy(detail, kwargs={
|
||||
'pk': self.object.pk,
|
||||
@@ -321,45 +326,60 @@ class OEmbedView(generic.View):
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
def get_info_string(user):
|
||||
user_str = f"by {user.name} " if user else ""
|
||||
time = timezone.now().strftime('%d/%m/%Y %H:%I')
|
||||
return f"[Paperwork generated {user_str}on {time}"
|
||||
|
||||
|
||||
def render_pdf_response(template, context, append_terms):
|
||||
merger = PdfFileMerger()
|
||||
rml = template.render(context)
|
||||
buffer = rml2pdf.parseString(rml)
|
||||
merger.append(PdfFileReader(buffer))
|
||||
buffer.close()
|
||||
|
||||
if append_terms:
|
||||
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
|
||||
merger.append(BytesIO(terms.read()))
|
||||
|
||||
merged = BytesIO()
|
||||
merger.write(merged)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
f = context['filename']
|
||||
response['Content-Disposition'] = f'filename="{f}"'
|
||||
response.write(merged.getvalue())
|
||||
return response
|
||||
|
||||
|
||||
class PrintView(generic.View):
|
||||
append_terms = False
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
obj = get_object_or_404(self.model, pk=self.kwargs['pk'])
|
||||
user_str = f"by {self.request.user.name} " if self.request.user is not None else ""
|
||||
time = timezone.now().strftime('%d/%m/%Y %H:%I')
|
||||
object_name = re.sub(r'[^a-zA-Z0-9 \n\.]', '', obj.name)
|
||||
|
||||
context = {
|
||||
'object': obj,
|
||||
'current_user': self.request.user,
|
||||
'object_name': object_name,
|
||||
'info_string': f"[Paperwork generated {user_str}on {time} - {obj.current_version_id}]",
|
||||
'info_string': get_info_string(self.request.user) + f"- {obj.current_version_id}]",
|
||||
}
|
||||
|
||||
return context
|
||||
|
||||
def get(self, request, pk):
|
||||
template = get_template(self.template_name)
|
||||
return render_pdf_response(get_template(self.template_name), self.get_context_data(), self.append_terms)
|
||||
|
||||
merger = PdfFileMerger()
|
||||
|
||||
context = self.get_context_data()
|
||||
class PrintListView(generic.ListView):
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['current_user'] = self.request.user
|
||||
context['info_string'] = get_info_string(self.request.user) + "]"
|
||||
return context
|
||||
|
||||
rml = template.render(context)
|
||||
buffer = rml2pdf.parseString(rml)
|
||||
merger.append(PdfFileReader(buffer))
|
||||
buffer.close()
|
||||
|
||||
if self.append_terms:
|
||||
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
|
||||
merger.append(BytesIO(terms.read()))
|
||||
|
||||
merged = BytesIO()
|
||||
merger.write(merged)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
f = context['filename']
|
||||
response['Content-Disposition'] = f'filename="{f}"'
|
||||
response.write(merged.getvalue())
|
||||
return response
|
||||
def get(self, request):
|
||||
self.object_list = self.get_queryset()
|
||||
return render_pdf_response(get_template(self.template_name), self.get_context_data(), False)
|
||||
|
||||
@@ -11,8 +11,9 @@ For setup information and other such helpful stuff check the [Wiki](https://gith
|
||||
- PyRIGS: Base app, stores 'global' information
|
||||
- RIGS: Rigboard stuff - event calendar etc
|
||||
- assets: Database of our kit, testing data etc
|
||||
- training: Logs in-house training within various "departments" (sound, lighting 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)
|
||||
|
||||
@@ -20,6 +20,7 @@ admin.site.register(models.VatRate, VersionAdmin)
|
||||
admin.site.register(models.Event, VersionAdmin)
|
||||
admin.site.register(models.EventItem, VersionAdmin)
|
||||
admin.site.register(models.Invoice, VersionAdmin)
|
||||
admin.site.register(models.EventCheckIn)
|
||||
|
||||
|
||||
@transaction.atomic() # Copied from django-extensions. GenericForeignKey support removed as unnecessary.
|
||||
@@ -153,8 +154,9 @@ class AssociateAdmin(VersionAdmin):
|
||||
|
||||
@admin.register(models.Profile)
|
||||
class ProfileAdmin(UserAdmin, AssociateAdmin):
|
||||
list_display = ('username', 'name', 'is_approved', 'is_staff', 'is_superuser', 'is_supervisor', 'number_of_events')
|
||||
list_display = ('username', 'name', 'is_approved', 'is_superuser', 'is_supervisor', 'number_of_events', 'last_login', 'date_joined')
|
||||
list_display_links = ['username']
|
||||
list_filter = UserAdmin.list_filter + ('is_approved', 'date_joined')
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
(_('Personal info'), {
|
||||
@@ -206,3 +208,8 @@ class RiskAssessmentAdmin(VersionAdmin):
|
||||
@admin.register(models.EventChecklist)
|
||||
class EventChecklistAdmin(VersionAdmin):
|
||||
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
||||
|
||||
|
||||
@admin.register(models.PowerTestRecord)
|
||||
class EventChecklistAdmin(VersionAdmin):
|
||||
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
||||
|
||||
131
RIGS/forms.py
131
RIGS/forms.py
@@ -1,10 +1,11 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import simplejson
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core import serializers
|
||||
from django.utils import timezone
|
||||
from django.utils.html import format_html
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from RIGS import models
|
||||
@@ -21,6 +22,7 @@ class EventForm(forms.ModelForm):
|
||||
datetime_input_formats = list(settings.DATETIME_INPUT_FORMATS)
|
||||
meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
|
||||
access_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
|
||||
parking_and_access = forms.BooleanField(label="Additional parking or access requirements (i.e. campus parking permits, wristbands)?", required=False)
|
||||
|
||||
items_json = forms.CharField()
|
||||
|
||||
@@ -44,7 +46,7 @@ class EventForm(forms.ModelForm):
|
||||
return simplejson.dumps(items)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EventForm, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['items_json'].initial = self._get_items_json
|
||||
self.fields['start_date'].widget.format = '%Y-%m-%d'
|
||||
@@ -97,6 +99,9 @@ class EventForm(forms.ModelForm):
|
||||
raise forms.ValidationError(
|
||||
'You haven\'t provided any client contact details. Please add a person or organisation.',
|
||||
code='contact')
|
||||
access = self.cleaned_data.get("access_at")
|
||||
if 'warn-access' not in self.data and access is not None and access.date() < (self.cleaned_data.get("start_date") - timedelta(days=7)):
|
||||
raise forms.ValidationError(format_html("Are you sure about that? Your access time seems a bit optimistic. If you're sure, save again. <input type='hidden' id='warn-access' name='warn-access' value='0'/>"), code='access_sanity')
|
||||
return super().clean()
|
||||
|
||||
def save(self, commit=True):
|
||||
@@ -121,7 +126,7 @@ class EventForm(forms.ModelForm):
|
||||
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
|
||||
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
|
||||
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
|
||||
'purchase_order', 'collector']
|
||||
'purchase_order', 'collector', 'forum_url', 'parking_and_access']
|
||||
|
||||
|
||||
class BaseClientEventAuthorisationForm(forms.ModelForm):
|
||||
@@ -131,7 +136,7 @@ class BaseClientEventAuthorisationForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
if self.cleaned_data.get('amount') != self.instance.event.total:
|
||||
self.add_error('amount', 'The amount authorised must equal the total for the event (inc VAT).')
|
||||
return super(BaseClientEventAuthorisationForm, self).clean()
|
||||
return super().clean()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@@ -179,7 +184,7 @@ class EventRiskAssessmentForm(forms.ModelForm):
|
||||
unexpected_values.append(f"<li>{self._meta.model._meta.get_field(field).help_text}</li>")
|
||||
if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'):
|
||||
raise forms.ValidationError(f"Your answers to these questions: <ul>{''.join([str(elem) for elem in unexpected_values])}</ul> require consulting with a supervisor.", code='unusual_answers')
|
||||
return super(EventRiskAssessmentForm, self).clean()
|
||||
return super().clean()
|
||||
|
||||
class Meta:
|
||||
model = models.RiskAssessment
|
||||
@@ -195,91 +200,49 @@ class EventChecklistForm(forms.ModelForm):
|
||||
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,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
model = models.EventChecklist
|
||||
fields = '__all__'
|
||||
exclude = ['reviewed_at', 'reviewed_by']
|
||||
|
||||
|
||||
class PowerTestRecordForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
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()
|
||||
|
||||
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[f'{field}_{pk}']
|
||||
if value == '':
|
||||
raise forms.ValidationError(f'Add a {field} to crewmember {pk}', code=f'{field}_mismatch')
|
||||
|
||||
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
|
||||
model = models.PowerTestRecord
|
||||
fields = '__all__'
|
||||
exclude = ['reviewed_at', 'reviewed_by']
|
||||
|
||||
|
||||
class EventCheckInForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['time'].initial = timezone.now()
|
||||
self.fields['role'].initial = "Crew"
|
||||
|
||||
class Meta:
|
||||
model = models.EventCheckIn
|
||||
fields = '__all__'
|
||||
exclude = ['end_time']
|
||||
|
||||
|
||||
class EditCheckInForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = models.EventCheckIn
|
||||
fields = '__all__'
|
||||
|
||||
@@ -254,7 +254,7 @@ class Command(BaseCommand):
|
||||
new_invoice.void = True
|
||||
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
|
||||
models.Payment.objects.create(invoice=new_invoice, amount=new_invoice.balance,
|
||||
date=datetime.date.today())
|
||||
date=datetime.date.today(), method=random.choice(models.Payment.METHODS)[0])
|
||||
if i == 1 or random.randint(0, 5) > 0: # Event 1 and 1 in 5 have a RA
|
||||
models.RiskAssessment.objects.create(event=new_event, supervisor_consulted=bool(random.getrandbits(1)),
|
||||
nonstandard_equipment=bool(random.getrandbits(1)),
|
||||
@@ -276,9 +276,10 @@ class Command(BaseCommand):
|
||||
nonstandard_emergency_procedure=bool(random.getrandbits(1)),
|
||||
special_structures=bool(random.getrandbits(1)),
|
||||
suspended_structures=bool(random.getrandbits(1)),
|
||||
parking_and_access=bool(random.getrandbits(1)),
|
||||
outside=bool(random.getrandbits(1)))
|
||||
if i == 0 or random.randint(0, 1) > 0: # Event 1 and 1 in 10 have a Checklist
|
||||
models.EventChecklist.objects.create(event=new_event, power_mic=random.choice(self.profiles),
|
||||
models.EventChecklist.objects.create(event=new_event,
|
||||
safe_parking=bool(random.getrandbits(1)),
|
||||
safe_packing=bool(random.getrandbits(1)),
|
||||
exits=bool(random.getrandbits(1)),
|
||||
@@ -287,6 +288,4 @@ class Command(BaseCommand):
|
||||
ear_plugs=bool(random.getrandbits(1)),
|
||||
hs_location="Locked away safely",
|
||||
extinguishers_location="Somewhere, I forgot",
|
||||
earthing=bool(random.getrandbits(1)),
|
||||
pat=bool(random.getrandbits(1)),
|
||||
date=timezone.now(), venue=random.choice(self.venues))
|
||||
|
||||
38
RIGS/management/commands/send_reminders.py
Normal file
38
RIGS/management/commands/send_reminders.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import premailer
|
||||
import datetime
|
||||
|
||||
from django.template.loader import get_template
|
||||
from django.contrib.staticfiles import finders
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.utils import timezone
|
||||
from django.urls import reverse
|
||||
|
||||
from RIGS import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Sends email reminders as required. Triggered daily through heroku-scheduler in production.'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
events = models.Event.objects.current_events().select_related('riskassessment')
|
||||
for event in events:
|
||||
earliest_time = event.earliest_time if isinstance(event.earliest_time, datetime.datetime) else timezone.make_aware(datetime.datetime.combine(event.earliest_time, datetime.time(00, 00)))
|
||||
# 48 hours = 172800 seconds
|
||||
if event.is_rig and not event.cancelled and not event.dry_hire and (earliest_time - timezone.now()).total_seconds() <= 172800 and not hasattr(event, 'riskassessment'):
|
||||
context = {
|
||||
"event": event,
|
||||
"url": "https://" + settings.DOMAIN + reverse('event_ra', kwargs={'pk': event.pk})
|
||||
}
|
||||
target = event.mic.email if event.mic else f"productions@{settings.DOMAIN}"
|
||||
msg = EmailMultiAlternatives(
|
||||
f"{event} - Risk Assessment Incomplete",
|
||||
get_template("email/ra_reminder.txt").render(context),
|
||||
to=[target],
|
||||
reply_to=[f"h.s.manager@{settings.DOMAIN}"],
|
||||
)
|
||||
css = finders.find('css/email.css')
|
||||
html = premailer.Premailer(get_template("email/ra_reminder.html").render(context), external_styles=css).transform()
|
||||
msg.attach_alternative(html, 'text/html')
|
||||
msg.send()
|
||||
18
RIGS/migrations/0045_alter_profile_is_approved.py
Normal file
18
RIGS/migrations/0045_alter_profile_is_approved.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.12 on 2022-10-20 23:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0044_profile_is_supervisor'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='is_approved',
|
||||
field=models.BooleanField(default=False, help_text='Designates whether a staff member has approved this user.', verbose_name='Approval Status'),
|
||||
),
|
||||
]
|
||||
71
RIGS/migrations/0046_create_powertests.py
Normal file
71
RIGS/migrations/0046_create_powertests.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# Generated by Django 3.2.16 on 2023-05-08 15:58
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import versioning.versioning
|
||||
|
||||
def migrate_old_data(apps, schema_editor):
|
||||
EventChecklist = apps.get_model('RIGS', 'EventChecklist')
|
||||
PowerTestRecord = apps.get_model('RIGS', 'PowerTestRecord')
|
||||
for ec in EventChecklist.objects.all():
|
||||
# New highscore for the most pythonic BS I've ever written.
|
||||
PowerTestRecord.objects.create(event=ec.event, venue=ec.venue, reviewed_by=ec.reviewed_by, **{i.name:getattr(ec, i.attname) for i in PowerTestRecord._meta.get_fields() if not (i.is_relation or i.auto_created or i.name == "notes")})
|
||||
|
||||
|
||||
def revert(apps, schema_editor):
|
||||
apps.get_model('RIGS', 'PowerTestRecord').objects.all().delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0045_alter_profile_is_approved'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PowerTestRecord',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='power_tests', to='RIGS.event')),
|
||||
('notes', models.TextField(blank=True, default='')),
|
||||
('venue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='RIGS.venue')),
|
||||
('reviewed_at', models.DateTimeField(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.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', max_digits=5, 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, default='', help_text='Description', max_length=255)),
|
||||
('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.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', max_digits=5, null=True, verbose_name='Earth Fault Loop Impedance')),
|
||||
('w2_description', models.CharField(blank=True, default='', help_text='Description', max_length=255)),
|
||||
('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.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', max_digits=5, null=True, verbose_name='Earth Fault Loop Impedance')),
|
||||
('w3_description', models.CharField(blank=True, default='', help_text='Description', max_length=255)),
|
||||
('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.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', max_digits=5, null=True, verbose_name='Earth Fault Loop Impedance')),
|
||||
('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_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'ordering': ['event'],
|
||||
'permissions': [('review_power', 'Can review Power Test Records')],
|
||||
},
|
||||
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||
),
|
||||
migrations.RunPython(migrate_old_data, reverse_code=revert),
|
||||
]
|
||||
44
RIGS/migrations/0047_auto_20230517_0944.py
Normal file
44
RIGS/migrations/0047_auto_20230517_0944.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-17 08:44
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
|
||||
def migrate_old_data(apps, schema_editor):
|
||||
EventChecklist = apps.get_model('RIGS', 'EventChecklist')
|
||||
EventCheckIn = apps.get_model('RIGS', 'EventCheckIn')
|
||||
for ec in EventChecklist.objects.all():
|
||||
for crew in ec.crew.all():
|
||||
try:
|
||||
EventCheckIn.objects.create(event=ec.event, person=crew.crewmember, role=crew.role, time=crew.start, end_time=crew.end, vehicle=ec.vehicles.get(driver=crew.crewmember).vehicle)
|
||||
except ObjectDoesNotExist:
|
||||
EventCheckIn.objects.create(event=ec.event, person=crew.crewmember, role=crew.role, time=crew.start, end_time=crew.end)
|
||||
|
||||
|
||||
def revert(apps, schema_editor):
|
||||
apps.get_model('RIGS', 'EventCheckIn').objects.all().delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0046_create_powertests'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EventCheckIn',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('time', models.DateTimeField()),
|
||||
('role', models.CharField(blank=True, max_length=50)),
|
||||
('vehicle', models.CharField(blank=True, max_length=100)),
|
||||
('end_time', models.DateTimeField(blank=True, null=True)),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.event')),
|
||||
('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checkins', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.RunPython(migrate_old_data, reverse_code=revert),
|
||||
]
|
||||
156
RIGS/migrations/0048_auto_20230518_1256.py
Normal file
156
RIGS/migrations/0048_auto_20230518_1256.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-18 11:56
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0047_auto_20230517_0944'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklistvehicle',
|
||||
name='checklist',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklistvehicle',
|
||||
name='driver',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='all_rcds_tested',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='earthing',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='fd_earth_fault',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='fd_phase_rotation',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='fd_pssc',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='fd_voltage_l1',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='fd_voltage_l2',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='fd_voltage_l3',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='labelling',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='pat',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='power_mic',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='public_sockets_tested',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='rcds',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='source_rcd',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='supply_test',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w1_description',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w1_earth_fault',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w1_polarity',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w1_voltage',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w2_description',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w2_earth_fault',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w2_polarity',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w2_voltage',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w3_description',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w3_earth_fault',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w3_polarity',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventchecklist',
|
||||
name='w3_voltage',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powertestrecord',
|
||||
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.AlterField(
|
||||
model_name='eventchecklist',
|
||||
name='reviewed_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='reviewed_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='riskassessment',
|
||||
name='reviewed_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EventChecklistCrew',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EventChecklistVehicle',
|
||||
),
|
||||
]
|
||||
53
RIGS/migrations/0049_auto_20230529_1123.py
Normal file
53
RIGS/migrations/0049_auto_20230529_1123.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-29 10:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0048_auto_20230518_1256'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='fd_earth_fault',
|
||||
field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>) / Ω', max_digits=6, null=True, verbose_name='Earth Fault Loop Impedance'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='fd_pssc',
|
||||
field=models.IntegerField(blank=True, help_text='Prospective Short Circuit Current / A', null=True, verbose_name='PSCC'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='w1_earth_fault',
|
||||
field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>) / Ω', max_digits=6, null=True, verbose_name='Earth Fault Loop Impedance'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='w1_voltage',
|
||||
field=models.IntegerField(blank=True, help_text='Voltage / V', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='w2_earth_fault',
|
||||
field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>) / Ω', max_digits=6, null=True, verbose_name='Earth Fault Loop Impedance'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='w2_voltage',
|
||||
field=models.IntegerField(blank=True, help_text='Voltage / V', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='w3_earth_fault',
|
||||
field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>) / Ω', max_digits=6, null=True, verbose_name='Earth Fault Loop Impedance'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powertestrecord',
|
||||
name='w3_voltage',
|
||||
field=models.IntegerField(blank=True, help_text='Voltage / V', null=True),
|
||||
),
|
||||
]
|
||||
19
RIGS/migrations/0050_event_forum_url.py
Normal file
19
RIGS/migrations/0050_event_forum_url.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.19 on 2023-06-27 11:28
|
||||
|
||||
import RIGS.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0049_auto_20230529_1123'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='forum_url',
|
||||
field=models.URLField(blank=True, default='', validators=[RIGS.models.validate_forum_url]),
|
||||
),
|
||||
]
|
||||
18
RIGS/migrations/0051_alter_payment_method.py
Normal file
18
RIGS/migrations/0051_alter_payment_method.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.19 on 2023-07-09 21:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0050_event_forum_url'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='method',
|
||||
field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('T', 'TEC Adjustment')], default='', max_length=2),
|
||||
),
|
||||
]
|
||||
18
RIGS/migrations/0052_event_parking_and_access.py
Normal file
18
RIGS/migrations/0052_event_parking_and_access.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.25 on 2024-11-20 20:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0051_alter_payment_method'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='parking_and_access',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
19
RIGS/migrations/0053_riskassessment_parking_and_access.py
Normal file
19
RIGS/migrations/0053_riskassessment_parking_and_access.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.25 on 2024-11-20 21:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('RIGS', '0052_event_parking_and_access'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='riskassessment',
|
||||
name='parking_and_access',
|
||||
field=models.BooleanField(default=False, help_text='Are there additional requirements for parking and access to the venue? (i.e. campus parking permits, event access wristbands)'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
208
RIGS/models.py
208
RIGS/models.py
@@ -36,7 +36,7 @@ class Profile(AbstractUser):
|
||||
initials = models.CharField(max_length=5, null=True, blank=False)
|
||||
phone = models.CharField(max_length=13, blank=True, default='')
|
||||
api_key = models.CharField(max_length=40, blank=True, editable=False, default='')
|
||||
is_approved = models.BooleanField(default=False)
|
||||
is_approved = models.BooleanField(default=False, verbose_name="Approval Status", help_text="Designates whether a staff member has approved this user.")
|
||||
# Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
|
||||
last_emailed = models.DateTimeField(blank=True, null=True)
|
||||
dark_theme = models.BooleanField(default=False)
|
||||
@@ -76,11 +76,16 @@ class Profile(AbstractUser):
|
||||
|
||||
@classmethod
|
||||
def users_awaiting_approval_count(cls):
|
||||
return Profile.objects.filter(models.Q(is_approved=False)).count()
|
||||
# last_login = None ensures we only pick up genuinely new users, not those that have been deactivated for inactivity
|
||||
return Profile.objects.filter(is_approved=False, last_login=None, date_joined_date=timezone.now().date()).count()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def current_event(self):
|
||||
q = EventCheckIn.objects.filter(person=self, end_time=None)
|
||||
return q.latest('time') if q.exists() else None
|
||||
|
||||
|
||||
class ContactableManager(models.Manager):
|
||||
def search(self, query=None):
|
||||
@@ -304,6 +309,14 @@ class EventManager(models.Manager):
|
||||
return qs
|
||||
|
||||
|
||||
def validate_forum_url(value):
|
||||
if not value:
|
||||
return # Required error is done the field
|
||||
obj = urlparse(value)
|
||||
if obj.hostname not in ('forum.nottinghamtec.co.uk'):
|
||||
raise ValidationError('URL must point to a location on the TEC Forum')
|
||||
|
||||
|
||||
@reversion.register(follow=['items'])
|
||||
class Event(models.Model, RevisionMixin):
|
||||
# Done to make it much nicer on the database
|
||||
@@ -338,6 +351,9 @@ class Event(models.Model, RevisionMixin):
|
||||
access_at = models.DateTimeField(blank=True, null=True)
|
||||
meet_at = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
# Venue requirements
|
||||
parking_and_access = models.BooleanField(default=False)
|
||||
|
||||
# Crew management
|
||||
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True,
|
||||
on_delete=models.CASCADE)
|
||||
@@ -353,6 +369,8 @@ class Event(models.Model, RevisionMixin):
|
||||
auth_request_at = models.DateTimeField(null=True, blank=True)
|
||||
auth_request_to = models.EmailField(blank=True, default='')
|
||||
|
||||
forum_url = models.URLField(default='', blank=True, validators=[validate_forum_url])
|
||||
|
||||
@property
|
||||
def display_id(self):
|
||||
if self.pk:
|
||||
@@ -361,6 +379,10 @@ class Event(models.Model, RevisionMixin):
|
||||
return self.pk
|
||||
return "????"
|
||||
|
||||
@property
|
||||
def needs_mic(self):
|
||||
return self.is_rig and not self.dry_hire
|
||||
|
||||
# Calculated values
|
||||
"""
|
||||
EX Vat
|
||||
@@ -405,7 +427,15 @@ class Event(models.Model, RevisionMixin):
|
||||
|
||||
@property
|
||||
def hs_done(self):
|
||||
return self.riskassessment is not None and len(self.checklists.all()) > 0
|
||||
return self.riskassessment is not None and self.has_checklist and self.has_power
|
||||
|
||||
@property
|
||||
def has_checklist(self):
|
||||
return self.checklists.exists()
|
||||
|
||||
@property
|
||||
def has_power(self):
|
||||
return self.power_tests.exists()
|
||||
|
||||
@property
|
||||
def has_start_time(self):
|
||||
@@ -478,13 +508,22 @@ class Event(models.Model, RevisionMixin):
|
||||
else:
|
||||
return bool(self.purchase_order)
|
||||
|
||||
@property
|
||||
def can_check_in(self):
|
||||
earliest = self.earliest_time
|
||||
if isinstance(self.earliest_time, datetime.date):
|
||||
earliest = datetime.datetime.combine(self.earliest_time, datetime.time(00, 00))
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
earliest = tz.localize(earliest)
|
||||
return not self.dry_hire and not self.status == Event.CANCELLED and earliest <= timezone.now()
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('event_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.display_id}: {self.name}"
|
||||
return f"{self.display_id} | {self.name}"
|
||||
|
||||
def clean(self):
|
||||
errdict = {}
|
||||
@@ -656,13 +695,11 @@ class Payment(models.Model, RevisionMixin):
|
||||
CASH = 'C'
|
||||
INTERNAL = 'I'
|
||||
EXTERNAL = 'E'
|
||||
SUCORE = 'SU'
|
||||
ADJUSTMENT = 'T'
|
||||
METHODS = (
|
||||
(CASH, 'Cash'),
|
||||
(INTERNAL, 'Internal'),
|
||||
(EXTERNAL, 'External'),
|
||||
(SUCORE, 'SU Core'),
|
||||
(ADJUSTMENT, 'TEC Adjustment'),
|
||||
)
|
||||
|
||||
@@ -689,8 +726,21 @@ def validate_url(value):
|
||||
raise ValidationError('URL must point to a location on the TEC Sharepoint')
|
||||
|
||||
|
||||
class ReviewableModel(models.Model):
|
||||
reviewed_at = models.DateTimeField(null=True, blank=True)
|
||||
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||
verbose_name="Reviewer", on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@cached_property
|
||||
def fieldz(self):
|
||||
return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||
|
||||
|
||||
@reversion.register
|
||||
class RiskAssessment(models.Model, RevisionMixin):
|
||||
class RiskAssessment(ReviewableModel, RevisionMixin):
|
||||
SMALL = (0, 'Small')
|
||||
MEDIUM = (1, 'Medium')
|
||||
LARGE = (2, 'Large')
|
||||
@@ -736,11 +786,10 @@ class RiskAssessment(models.Model, RevisionMixin):
|
||||
persons_responsible_structures = models.TextField(blank=True, default='', help_text="Who are the persons on site responsible for their use?")
|
||||
rigging_plan = models.URLField(blank=True, default='', 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
|
||||
# Venue Access
|
||||
parking_and_access = models.BooleanField(help_text="Are there additional requirements for parking and access to the venue? (i.e. campus parking permits, event access wristbands)")
|
||||
|
||||
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)
|
||||
# Blimey that was a lot of options
|
||||
|
||||
supervisor_consulted = models.BooleanField(null=True)
|
||||
|
||||
@@ -764,6 +813,7 @@ class RiskAssessment(models.Model, RevisionMixin):
|
||||
'nonstandard_emergency_procedure': False,
|
||||
'special_structures': False,
|
||||
'suspended_structures': False,
|
||||
'parking_and_access': False
|
||||
}
|
||||
inverted_fields = {key: value for (key, value) in expected_values.items() if not value}.keys()
|
||||
|
||||
@@ -778,10 +828,6 @@ class RiskAssessment(models.Model, RevisionMixin):
|
||||
('review_riskassessment', 'Can review Risk Assessments')
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def fieldz(self):
|
||||
return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||
|
||||
@property
|
||||
def event_size(self):
|
||||
# Confirm event size. Check all except generators, since generators entails outside
|
||||
@@ -795,6 +841,12 @@ class RiskAssessment(models.Model, RevisionMixin):
|
||||
def get_event_size_display(self):
|
||||
return self.SIZES[self.event_size][1] + " Event"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.pk} | {self.event}"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('ra_detail', kwargs={'pk': self.pk})
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str(self.event)
|
||||
@@ -803,20 +855,12 @@ class RiskAssessment(models.Model, RevisionMixin):
|
||||
def name(self):
|
||||
return str(self)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('ra_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.pk} | {self.event}"
|
||||
|
||||
|
||||
@reversion.register(follow=['vehicles', 'crew'])
|
||||
class EventChecklist(models.Model, RevisionMixin):
|
||||
@reversion.register
|
||||
class EventChecklist(ReviewableModel, 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()
|
||||
|
||||
@@ -830,6 +874,35 @@ class EventChecklist(models.Model, RevisionMixin):
|
||||
hs_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of Safety Bag/Box")
|
||||
extinguishers_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of fire extinguishers")
|
||||
|
||||
inverted_fields = []
|
||||
|
||||
class Meta:
|
||||
ordering = ['event']
|
||||
permissions = [
|
||||
('review_eventchecklist', 'Can review Event Checklists')
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.pk} - {self.event}"
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str(self.event)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('ec_detail', kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
@reversion.register
|
||||
class PowerTestRecord(ReviewableModel, RevisionMixin):
|
||||
earth_fault_text = "Earth Fault Loop Impedance (Z<small>S</small>) / Ω"
|
||||
pssc_text = "Prospective Short Circuit Current / A"
|
||||
|
||||
event = models.ForeignKey('Event', related_name='power_tests', on_delete=models.CASCADE)
|
||||
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)
|
||||
notes = models.TextField(blank=True, default='')
|
||||
# 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>")
|
||||
@@ -846,77 +919,66 @@ class EventChecklist(models.Model, RevisionMixin):
|
||||
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.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, 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")
|
||||
fd_earth_fault = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text=earth_fault_text)
|
||||
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text=pssc_text)
|
||||
# Worst case points
|
||||
w1_description = models.CharField(blank=True, default='', 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.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V")
|
||||
w1_earth_fault = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text=earth_fault_text)
|
||||
w2_description = models.CharField(blank=True, default='', 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.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V")
|
||||
w2_earth_fault = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text=earth_fault_text)
|
||||
w3_description = models.CharField(blank=True, default='', 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.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V")
|
||||
w3_earth_fault = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text=earth_fault_text)
|
||||
|
||||
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')
|
||||
('review_power', 'Can review Power Test Records')
|
||||
]
|
||||
|
||||
@cached_property
|
||||
def fieldz(self):
|
||||
return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||
def __str__(self):
|
||||
return f"{self.pk} - {self.event}"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('pt_detail', kwargs={'pk': self.pk})
|
||||
|
||||
@property
|
||||
def activity_feed_string(self):
|
||||
return str(self.event)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('ec_detail', kwargs={'pk': self.pk})
|
||||
@property
|
||||
def name(self):
|
||||
return f"Power Test Record - {self.event}"
|
||||
|
||||
|
||||
class EventCheckIn(models.Model):
|
||||
event = models.ForeignKey('Event', related_name='crew', on_delete=models.CASCADE)
|
||||
person = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='checkins', on_delete=models.CASCADE)
|
||||
time = models.DateTimeField()
|
||||
role = models.CharField(max_length=50, blank=True)
|
||||
vehicle = models.CharField(max_length=100, blank=True)
|
||||
end_time = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{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 f"{self.vehicle} driven by {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
|
||||
return f"{self.person} on {self.event}"
|
||||
|
||||
def clean(self):
|
||||
if self.start > self.end:
|
||||
raise ValidationError('Unless you\'ve invented time travel, crew can\'t finish before they have started.')
|
||||
sass = " Please invent time travel and retry."
|
||||
if self.time > timezone.now():
|
||||
raise ValidationError("May not check in in the future." + sass)
|
||||
if self.end_time and self.end_time < self.time:
|
||||
raise ValidationError("May not check out before you've checked in." + sass)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.crewmember} ({self.role})"
|
||||
def get_absolute_url(self):
|
||||
return reverse('event_detail', kwargs={'pk': self.event_id})
|
||||
|
||||
def active(self):
|
||||
return end_time is not None
|
||||
|
||||
@@ -3,6 +3,7 @@ import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from io import BytesIO
|
||||
import datetime
|
||||
|
||||
from PyPDF2 import PdfFileReader, PdfFileMerger
|
||||
from django.conf import settings
|
||||
@@ -58,13 +59,13 @@ def send_eventauthorisation_success_email(instance):
|
||||
|
||||
client_email = EmailMultiAlternatives(
|
||||
subject,
|
||||
get_template("eventauthorisation_client_success.txt").render(context),
|
||||
get_template("email/eventauthorisation_client_success.txt").render(context),
|
||||
to=[instance.email],
|
||||
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
||||
)
|
||||
|
||||
css = finders.find('css/email.css')
|
||||
html = Premailer(get_template("eventauthorisation_client_success.html").render(context),
|
||||
html = Premailer(get_template("email/eventauthorisation_client_success.html").render(context),
|
||||
external_styles=css).transform()
|
||||
client_email.attach_alternative(html, 'text/html')
|
||||
|
||||
@@ -82,7 +83,7 @@ def send_eventauthorisation_success_email(instance):
|
||||
|
||||
mic_email = EmailMessage(
|
||||
subject,
|
||||
get_template("eventauthorisation_mic_success.txt").render(context),
|
||||
get_template("email/eventauthorisation_mic_success.txt").render(context),
|
||||
to=[mic_email_address]
|
||||
)
|
||||
|
||||
@@ -110,19 +111,19 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
|
||||
if admin.last_emailed is None or admin.last_emailed + settings.EMAIL_COOLDOWN <= timezone.now():
|
||||
context = {
|
||||
'request': request,
|
||||
'link_suffix': reverse("admin:RIGS_profile_changelist") + '?is_approved__exact=0',
|
||||
'link_suffix': reverse("admin:RIGS_profile_changelist") + f'?is_approved__exact=0&date_joined__date={timezone.now().date()}',
|
||||
'number_of_users': models.Profile.users_awaiting_approval_count(),
|
||||
'to_name': admin.first_name
|
||||
}
|
||||
|
||||
email = EmailMultiAlternatives(
|
||||
f"{context['number_of_users']} new users awaiting approval on RIGS",
|
||||
get_template("admin_awaiting_approval.txt").render(context),
|
||||
get_template("email/admin_awaiting_approval.txt").render(context),
|
||||
to=[admin.email],
|
||||
reply_to=[user.email],
|
||||
)
|
||||
css = finders.find('css/email.css')
|
||||
html = Premailer(get_template("admin_awaiting_approval.html").render(context),
|
||||
html = Premailer(get_template("email/admin_awaiting_approval.html").render(context),
|
||||
external_styles=css).transform()
|
||||
email.attach_alternative(html, 'text/html')
|
||||
email.send()
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 63 KiB |
@@ -11,6 +11,7 @@
|
||||
<initialize>
|
||||
<color id="LightGray" RGB="#D3D3D3"/>
|
||||
<color id="DarkGray" RGB="#707070"/>
|
||||
<color id="Brand" RGB="#3853a4"/>
|
||||
</initialize>
|
||||
|
||||
<paraStyle name="style.para" fontName="OpenSans" />
|
||||
@@ -21,12 +22,17 @@
|
||||
<paraStyle name="center" alignment="center"/>
|
||||
<paraStyle name="page-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/>
|
||||
|
||||
{% block extrastyles %}
|
||||
{% endblock %}
|
||||
|
||||
<paraStyle name="style.event_description" fontName="OpenSans" textColor="DarkGray" />
|
||||
<paraStyle name="style.item_description" fontName="OpenSans" textColor="DarkGray" leftIndent="10" />
|
||||
<paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10" />
|
||||
<paraStyle name="style.times" fontName="OpenSans" fontSize="10" />
|
||||
<paraStyle name="style.head_titles" fontName="OpenSans-Bold" fontSize="10" />
|
||||
<paraStyle name="style.head_numbers" fontName="OpenSans" fontSize="10" />
|
||||
<paraStyle name="style.emheader" fontName="OpenSans" textColor="White" fontSize="12" backColor="Brand" leading="20" borderPadding="4"/>
|
||||
<paraStyle name="style.breakbefore" parent="emheader" pageBreakBefore="1"/>
|
||||
|
||||
<blockTableStyle id="eventSpecifics">
|
||||
<blockValign value="top"/>
|
||||
@@ -81,10 +87,12 @@
|
||||
|
||||
<listStyle name="ul"
|
||||
start="bulletchar"
|
||||
leftIndent="0"
|
||||
bulletDedent="10"
|
||||
bulletFontSize="10"/>
|
||||
</stylesheet>
|
||||
|
||||
<template > {# Note: page is 595x842 points (1 point=1/72in) #}
|
||||
<template title="{{filename}}"> {# Note: page is 595x842 points (1 point=1/72in) #}
|
||||
<pageTemplate id="Headed" >
|
||||
<pageGraphics>
|
||||
<image file="static/imgs/paperwork/corner-tr-su.jpg" x="395" y="642" height="200" width="200"/>
|
||||
@@ -134,6 +142,7 @@
|
||||
<nextFrame/>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
<namedString id="lastPage"><pageNumber/></namedString>
|
||||
</story>
|
||||
|
||||
</document>
|
||||
|
||||
@@ -34,16 +34,7 @@
|
||||
</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>
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'hs_list' %}">H&S</a></li>
|
||||
{% endif %}
|
||||
{% if perms.RIGS.view_invoice %}
|
||||
<li class="nav-item dropdown">
|
||||
@@ -54,6 +45,7 @@
|
||||
Invoices <span class="badge {% if todo == 0 %}badge-success{% else %}badge-danger{% endif %} badge-pill">{{ todo }}</span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownInvoices">
|
||||
<a class="dropdown-item" href="{% url 'invoice_dashboard' %}"><span class="fas fa-chart-line"></span> Dashboard</a>
|
||||
{% if perms.RIGS.add_invoice %}
|
||||
<a class="dropdown-item text-nowrap" href="{% url 'invoice_waiting' %}"><span class="fas fa-briefcase text-danger"></span> Waiting <span class="badge {% if waiting == 0 %}badge-success{% else %}badge-danger{% endif %} badge-pill">{{ waiting }}</span></a>
|
||||
{% endif %}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
firstDay: 1,
|
||||
themeSystem: 'bootstrap',
|
||||
aspectRatio: 1.5,
|
||||
eventTimeFormat: {
|
||||
|
||||
5
RIGS/templates/email/eventauthorisation_mic_success.txt
Normal file
5
RIGS/templates/email/eventauthorisation_mic_success.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}},
|
||||
|
||||
Just to let you know your event N{{object.eventdisplay_id}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.event.last_edited_at}}.
|
||||
|
||||
The TEC Rig Information Gathering System
|
||||
16
RIGS/templates/email/ra_reminder.html
Normal file
16
RIGS/templates/email/ra_reminder.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{% extends 'base_client_email.html' %}
|
||||
|
||||
{% block content %}
|
||||
<p>Hi {{event.mic.get_full_name|default_if_none:"Productions Manager"}},</p>
|
||||
|
||||
{% if event.mic %}
|
||||
<p>Just to let you know your event {{event.display_id}} <em>requires<em> a pre-event risk assessment completing prior to the event. Please do so as soon as possible.</p>
|
||||
{% else %}
|
||||
<p>This is a reminder that event {{event.display_id}} requires a MIC assigning and a risk assessment completing.</p>
|
||||
{% endif %}
|
||||
|
||||
<p>Fill it out here:</p>
|
||||
<a href="{{url}}" class="btn btn-info"><span class="fas fa-paperclip"></span> Create Risk Assessment</a>
|
||||
|
||||
<p>TEC PA & Lighting</p>
|
||||
{% endblock %}
|
||||
9
RIGS/templates/email/ra_reminder.txt
Normal file
9
RIGS/templates/email/ra_reminder.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Hi {{event.mic.get_full_name|default_if_none:"Productions Manager"}},
|
||||
|
||||
{% if event.mic %}
|
||||
Just to let you know your event {{event.display_id}} requires a risk assessment completing prior to the event. Please do so as soon as possible.
|
||||
{% else %}
|
||||
This is a reminder that event {{event.display_id}} requires a MIC assigning and a risk assessment completing.
|
||||
{% endif %}
|
||||
|
||||
The TEC Rig Information Gathering System
|
||||
@@ -46,7 +46,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12" style="container-type: inline-size;">
|
||||
{% with object_list as events %}
|
||||
{% include 'partials/event_table.html' %}
|
||||
{% endwith %}
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
||||
|
||||
{% load markdown_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
|
||||
<script>
|
||||
$(document).keydown(function(e) {
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode == 80) {
|
||||
window.open("{% url 'event_print' object.pk %}", '_blank');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row my-3 py-3">
|
||||
@@ -13,8 +27,11 @@
|
||||
{% endif %}
|
||||
{% if object.is_rig and perms.RIGS.view_event %}
|
||||
{# only need contact details for a rig #}
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-6 mb-3">
|
||||
{% include 'partials/contact_details.html' %}
|
||||
{% if object.parking_and_access or object.riskassessment.parking_and_access %}
|
||||
{% include 'partials/parking_and_access.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-md-6">
|
||||
@@ -52,6 +69,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'partials/crew_list.html' %}
|
||||
|
||||
{% if not request.is_ajax and perms.RIGS.view_event %}
|
||||
<div class="col-sm-12 text-right">
|
||||
{% include 'partials/event_detail_buttons.html' %}
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="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>
|
||||
<option value="{{venue.id}}" selected="selected" data-update_url="{% url 'venue_update' venue.id %}">{{ venue }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
@@ -231,7 +231,7 @@
|
||||
<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="col-sm-10">
|
||||
<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" %}
|
||||
@@ -246,7 +246,7 @@
|
||||
<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="col-sm-10">
|
||||
<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" %}
|
||||
@@ -281,8 +281,15 @@
|
||||
{{ form.dry_hire.label }} {% render_field form.dry_hire %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-4 col-sm-8">
|
||||
<label data-toggle="tooltip" title="Do we need to secure campus parking permits, wristbands for backstage access or other non-standard requirements?">
|
||||
{{ form.parking_and_access.label }} {% render_field form.parking_and_access %}
|
||||
</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">
|
||||
@@ -334,12 +341,26 @@
|
||||
|
||||
<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-fitem_tableorm-label">{{ form.purchase_order.label }}</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 class="form-group" data-toggle="tooltip" title="The thread for this event on the TEC Forum">
|
||||
<label for="{{ form.forum_url.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">Forum Thread</label>
|
||||
<div class="col-sm-12">
|
||||
<p class="small mb-0">Paste URL</p>
|
||||
{% render_field form.forum_url class+="form-control" %}
|
||||
{% if object.pk %}
|
||||
<p class="small mb-0">or</p>
|
||||
<a href="{% url 'event_thread' object.pk %}" class="btn btn-primary" title="Create Forum Thread" target="_blank">
|
||||
<span class="fas fa-plus"></span> <span class="hidden-xs">Create Forum Thread</span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}},
|
||||
|
||||
Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.event.last_edited_at}}.
|
||||
|
||||
The TEC Rig Information Gathering System
|
||||
@@ -9,6 +9,8 @@
|
||||
<div class="col-12 text-right my-3">
|
||||
{% button 'edit' url='ec_edit' pk=object.pk %}
|
||||
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %}
|
||||
<a href="{% url 'event_pt' object.event.pk %}" class="btn btn-info"><span class="fas fa-paperclip"></span> <span
|
||||
class="hidden-xs">Create Power Test</span></a>
|
||||
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,21 +32,7 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt class="col-6">{{ object|help_text:'power_mic' }}</dt>
|
||||
<dd class="col-6">
|
||||
{% if object.power_mic %}
|
||||
<a href="{% url 'profile_detail' object.power_mic.pk %}">{{ object.power_mic.name }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</dd>
|
||||
</dl>
|
||||
<p>List vehicles and their drivers</p>
|
||||
<ul>
|
||||
{% for i in object.vehicles.all %}
|
||||
<li>{{i}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,169 +69,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'partials/crew_list.html' with event=object.event %}
|
||||
</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>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center bg-warning">Apparently this event happened by magic...</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>
|
||||
<div class="card-body">
|
||||
{% if object.event.riskassessment.event_size == 0 %}
|
||||
<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>
|
||||
{% else %}
|
||||
<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' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
|
||||
<div class="col-12 text-right mt-4">
|
||||
{% button 'edit' url='ec_edit' pk=object.pk %}
|
||||
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
|
||||
<a href="{% url 'event_pt' object.event.pk %}" class="btn btn-info"><span class="fas fa-paperclip"></span> <span
|
||||
class="hidden-xs">Create Power Test</span></a>
|
||||
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
|
||||
@@ -13,57 +13,19 @@
|
||||
{% block preload_js %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/selects.js' %}"></script>
|
||||
<script src="{% static 'js/interaction.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/autocompleter.js' %}"></script>
|
||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('button[data-action=add]').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
let target = $($(this).attr('data-target'));
|
||||
let newID = Number(target.attr('data-pk'));
|
||||
let 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 %}
|
||||
|
||||
<form role="form" method="POST" action="{% if edit %}{% url 'ec_edit' pk=object.pk %}{% else %}{% 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 %}
|
||||
@@ -94,60 +56,12 @@
|
||||
<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' %}">
|
||||
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="selectpicker" 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 data-container="body" 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 data-container="body" 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>
|
||||
@@ -175,176 +89,6 @@
|
||||
</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-container="body" 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=""/></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 data-container="body" 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 (approx. 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>
|
||||
{% else %}
|
||||
<div class="row my-3" id="size-1">
|
||||
<div class="col-12">
|
||||
{% if event.riskassessment.event_size == 1 %}
|
||||
<div class="card border-warning">
|
||||
<div class="card-header">Electrical Checks <small>for ‘Medium’ TEC Events </small></div>
|
||||
<div class="card-body">
|
||||
{% else %}
|
||||
<div class="card border-danger">
|
||||
<div class="card-header">Electrical Checks <small>for ‘Large’ TEC Events</small></div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-danger"><strong>Here be dragons. Ensure you have appeased the Power Gods before continuing... (If you didn't check with a Supervisor, <em>you cannot continue your event!</em>)</strong></div>
|
||||
{% endif %}
|
||||
{% 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>
|
||||
{% endif %}
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm-12 text-right">
|
||||
{% button 'submit' %}
|
||||
|
||||
105
RIGS/templates/hs/eventcheckin_form.html
Normal file
105
RIGS/templates/hs/eventcheckin_form.html
Normal file
@@ -0,0 +1,105 @@
|
||||
{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
{% load button from filters %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/easymde.min.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block preload_js %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/selects.js' %}"></script>
|
||||
<script src="{% static 'js/easymde.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/autocompleter.js' %}"></script>
|
||||
<script src="{% static 'js/interaction.js' %}"></script>
|
||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-12">
|
||||
{% include 'form_errors.html' %}
|
||||
<form id="checkin" role="form" method="POST" action="{{ form.action|default:request.path }}">
|
||||
<input type="hidden" name="{{ form.event.name }}" id="{{ form.event.id_for_label }}"
|
||||
value="{{event.pk}}"/>
|
||||
{% if not request.is_ajax and self.request.user.pk is form.event.mic.pk %}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.person.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">{{ form.person.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}" class="px-0 selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||
{% if person %}
|
||||
<option value="{{form.person.value}}" selected="selected" >{{ person.name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<input type="hidden" name="{{ form.person.name }}" id="{{ form.person.id_for_label }}"
|
||||
value="{{request.user.pk}}"/>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.time.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">Start Time</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.time class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.role.id_for_label }}" class="col col-form-label">Role</label>
|
||||
<div class="row pl-3">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<button type="button" class="btn btn-primary" onclick="document.getElementById('id_role').value='MIC'">MIC</button>
|
||||
<button type="button" class="btn btn-danger" onclick="document.getElementById('id_role').value='Power MIC'">Power MIC</button>
|
||||
<button type="button" class="btn btn-info" onclick="document.getElementById('id_role').value='Crew'">Crew</button>
|
||||
</div>
|
||||
<div class="col-md-6 mt-2">
|
||||
{% render_field form.role class+="form-control" placeholder="Other (enter text)" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.vehicle.id_for_label }}" class="col col-form-label">Vehicle (if applicable)</label>
|
||||
<div class="row pl-3">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<button type="button" class="btn btn-primary" onclick="document.getElementById('id_vehicle').value='Virgil'"><span class="fas fa-truck-moving"></span> Virgil</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="document.getElementById('id_vehicle').value='Virgil + Erms'"><span class="fas fa-trailer"></span><span class="fas fa-truck-moving"></span> Virgil + Erms</button>
|
||||
</div>
|
||||
<div class="col-md-6 mt-2">
|
||||
{% render_field form.vehicle class+="form-control" placeholder="Other (enter text)" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if edit or manual %}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.end_time.id_for_label }}"
|
||||
class="col-sm-4 col-form-label">End Time</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.end_time class+="form-control" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not request.is_ajax %}
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm-12 text-right">
|
||||
{% button 'submit' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
<div class="col-sm-12 text-right pr-0">
|
||||
<button type="submit" class="btn btn-primary" title="Save" form="checkin"
|
||||
><span class="fas fa-save align-middle"></span> <span class="d-none d-sm-inline align-middle">Save</span></button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -12,6 +12,7 @@
|
||||
<th scope="col">Dates</th>
|
||||
<th scope="col">RA</th>
|
||||
<th scope="col">Checklists</th>
|
||||
<th scope="col">Power Records</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -35,6 +36,14 @@
|
||||
<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>
|
||||
<td>
|
||||
{% for record in event.power_tests.all %}
|
||||
{% include 'partials/hs_status.html' with event=event object=record view='pt_detail' edit='pt_edit' create='event_pt' review='pt_review' perm=perms.RIGS.review_power %}
|
||||
<br/>
|
||||
{% endfor %}
|
||||
<a href="{% url 'event_pt' event.pk %}" class="btn btn-info"><span class="fas fa-paperclip"></span> <span
|
||||
class="hidden-xs">Create</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr class="bg-warning text-dark">
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
{% 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 table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Event</th>
|
||||
{# mmm hax #}
|
||||
{% if object_list.0 != None %}
|
||||
{% for field in object_list.0.fieldz %}
|
||||
<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><br><small>{{ object.event.get_status_display }}</small></th>
|
||||
{% for field in object_list.0.fieldz %}
|
||||
<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 %}
|
||||
176
RIGS/templates/hs/power_detail.html
Normal file
176
RIGS/templates/hs/power_detail.html
Normal file
@@ -0,0 +1,176 @@
|
||||
{% 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='pt_edit' pk=object.pk %}
|
||||
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %}
|
||||
{% include 'partials/review_status.html' with perm=perms.RIGS.review_power review='pt_review' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">{% include 'partials/event_size.html' with object=object.event.riskassessment %}</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-6">{{ object|help_text:'power_mic' }}</dt>
|
||||
<dd class="col-6">
|
||||
{% if object.power_mic %}
|
||||
<a href="{% url 'profile_detail' object.power_mic.pk %}">{{ object.power_mic.name }}</a>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</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">Notes</dt>
|
||||
<dd class="col-6">
|
||||
{{ object.notes }}
|
||||
</dd>
|
||||
</dl>
|
||||
{% if object.event.riskassessment.event_size == 0 %}
|
||||
<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>
|
||||
{% else %}
|
||||
<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> / V</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' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
{% button 'print' 'pt_print' object.pk %}
|
||||
{% button 'edit' url='pt_edit' pk=object.pk %}
|
||||
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %}
|
||||
{% include 'partials/review_status.html' with perm=perms.RIGS.review_power review='pt_review' %}
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
{% include 'partials/last_edited.html' with target="powertestrecord_history" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
199
RIGS/templates/hs/power_form.html
Normal file
199
RIGS/templates/hs/power_form.html
Normal file
@@ -0,0 +1,199 @@
|
||||
{% 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/selects.css' %}"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload_js %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/selects.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/autocompleter.js' %}"></script>
|
||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-12">
|
||||
{% include 'form_errors.html' %}
|
||||
{% if edit %}
|
||||
<form role="form" method="POST" action="{% url 'pt_edit' pk=object.pk %}">
|
||||
{% else %}
|
||||
<form role="form" method="POST" action="{% url 'event_pt' 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>
|
||||
<hr>
|
||||
<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="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>
|
||||
{% endif %}
|
||||
</select>
|
||||
</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="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>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<label for="{{ form.notes.id_for_label }}">Notes</label>
|
||||
{% render_field form.notes class+="form-control" %}
|
||||
</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 (approx. 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>
|
||||
{% else %}
|
||||
<div class="row my-3" id="size-1">
|
||||
<div class="col-12">
|
||||
{% if event.riskassessment.event_size == 1 %}
|
||||
<div class="card border-warning">
|
||||
<div class="card-header">Electrical Checks <small>for ‘Medium’ TEC Events </small></div>
|
||||
<div class="card-body">
|
||||
{% else %}
|
||||
<div class="card border-danger">
|
||||
<div class="card-header">Electrical Checks <small>for ‘Large’ TEC Events</small></div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-danger"><strong>Here be dragons. Ensure you have appeased the Power Gods before continuing... (If you didn't check with a Supervisor, <em>you cannot continue your event!</em>)</strong></div>
|
||||
{% endif %}
|
||||
{% 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> / V</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>
|
||||
{% endif %}
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm-12 text-right">
|
||||
{% button 'submit' %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
235
RIGS/templates/hs/power_print.xml
Normal file
235
RIGS/templates/hs/power_print.xml
Normal file
@@ -0,0 +1,235 @@
|
||||
{% extends 'base_print.xml' %}
|
||||
{% load filters %}
|
||||
|
||||
{% block extrastyles %}
|
||||
<paraStyle name="style.powerReviewed" borderPadding="3" alignment="center" backColor="green" textColor="white"/>
|
||||
<paraStyle name="style.powerUnreviewed" borderPadding="3" alignment="center" backColor="red" textColor="white"/>
|
||||
<paraStyle name="style.smallText" fontSize="8"/>
|
||||
|
||||
<paraStyle leftIndent="2in" rightIndent="2in" name="style.smallEvent" fontSize="10" alignment="center" backColor="green" textColor="white" borderPadding="4" borderColor="black"/>
|
||||
<paraStyle leftIndent="2in" rightIndent="2in" name="style.mediumEvent" fontSize="10" alignment="center" backColor="orange" textColor="white" borderPadding="4" borderColor="black"/>
|
||||
<paraStyle leftIndent="2in" rightIndent="2in" name="style.largeEvent" fontSize="10" alignment="center" backColor="red" textColor="white" borderPadding="4" borderColor="black"/>
|
||||
|
||||
<blockTableStyle id="powerTable">
|
||||
<blockValign value="middle"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="black" thickness="1"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="black" thickness="1"/>
|
||||
<lineStyle kind="LINEAFTER" colorName="black" thickness="1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="black" thickness="1"/>
|
||||
</blockTableStyle>
|
||||
|
||||
<blockTableStyle id="voltageTable">
|
||||
<blockValign value="middle"/>
|
||||
</blockTableStyle>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<spacer length="15"/>
|
||||
<h1>Power Test Record for <strong>{{ object.event }}</strong></h1>
|
||||
<spacer length="15"/>
|
||||
<h2>Client: {{ object.event.person|default:object.event.organisation }} | Venue: {{ object.event.venue }} | MIC: {{ object.event.mic }}</h2>
|
||||
<spacer length="15"/>
|
||||
<hr/>
|
||||
<spacer length="15"/>
|
||||
{% if object.reviewed_by %}
|
||||
<para style="style.powerReviewed"><strong>Reviewed by: {{ object.reviewed_by }} at {{ object.reviewed_at|date:"D d/m/Y" }}</strong></para>
|
||||
{% else %}
|
||||
<para style="style.powerUnreviewed"><strong>Power test results not yet reviewed</strong></para>
|
||||
{% endif %}
|
||||
<spacer length="15"/>
|
||||
<hr/>
|
||||
<spacer length="15"/>
|
||||
|
||||
<h2 fontSize="16">Power Plan Information</h2>
|
||||
<spacer length="15"/>
|
||||
|
||||
{% if object.event.riskassessment.event_size == 0 %}
|
||||
<para style="style.smallEvent"><strong>Small Event</strong></para>
|
||||
{% elif object.event.riskassessment.event_size == 1 %}
|
||||
<para style="style.mediumEvent"><strong>Medium Event</strong></para>
|
||||
{% elif object.event.riskassessment.event_size == 2 %}
|
||||
<para style="style.largeEvent"><strong>Large Event</strong></para>
|
||||
{% endif %}
|
||||
|
||||
<spacer length="15"/>
|
||||
|
||||
<blockTable colWidths="250,250">
|
||||
<tr>
|
||||
<td><para><strong>Power MIC:</strong> {{ object.power_mic }}</para></td>
|
||||
<td><para><strong>Venue:</strong> {{ object.event.venue }}</para></td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>Event Date:</strong> {{ object.event.start_date |date:"D d/m/Y" }}</para></td>
|
||||
<td><para><strong>Generators:</strong> {{ object.event.riskassessment.generators|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>Power Test taken at:</strong> {{ object.date_created|date:"D d/m/Y H:i" }}</para></td>
|
||||
<td><para><strong>Other Companies Power:</strong> {{ object.event.riskassessment.other_companies_power|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
||||
<spacer length="15"/>
|
||||
|
||||
{% if object.notes %}
|
||||
<hr/>
|
||||
<spacer length="15"/>
|
||||
<para><strong>Additional Notes:</strong></para>
|
||||
<spacer length="15"/>
|
||||
<para>{{ object.notes }}</para>
|
||||
<spacer length="15"/>
|
||||
{% endif %}
|
||||
|
||||
<hr/>
|
||||
<spacer length="15"/>
|
||||
|
||||
{% comment %}
|
||||
0 - Small event
|
||||
1 - Medium event (extra power records)
|
||||
2 - Large event (extra power records)
|
||||
{% endcomment %}
|
||||
|
||||
{% if object.event.riskassessment.event_size >= 1 %}
|
||||
|
||||
<para alignment="center"><strong>Power Test results enclosed on next page</strong></para>
|
||||
|
||||
<condPageBreak height="10in"/>
|
||||
|
||||
<h2 fontSize="16">Event Power Checklist</h2>
|
||||
<spacer length="15"/>
|
||||
|
||||
<blockTable colWidths="250,270" style="powerTable">
|
||||
<tr>
|
||||
<td><para><strong>All circuit RCDs tested?</strong></para><para style="style.smallText">(using test button)</para></td>
|
||||
<td><para>{{ object.all_rcds_tested|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>Public/performer accessible circuits tested?</strong></para><para style="style.smallText">(using socket tester)</para></td>
|
||||
<td><para>{{ object.public_sockets_tested|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>Source RCD protected?</strong></para><para style="style.smallText">(if cable is more than 3m long)</para></td>
|
||||
<td><para>{{ object.source_rcd|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>Appropriate and clear labelling on distribution and cabling?</strong></para></td>
|
||||
<td><para>{{ object.labelling|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>Equipment appropriately earthed?</strong></para><para style="style.smallText">(truss, stage, generators, etc.)</para></td>
|
||||
<td><para>{{ object.earthing|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>All equipment in PAT period?</strong><br/><br/></para></td>
|
||||
<td><para>{{ object.pat|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
||||
<spacer length="15"/>
|
||||
|
||||
<h2 fontSize="14">Power tests (First Distro)</h2>
|
||||
<spacer length="5"/>
|
||||
|
||||
<blockTable colWidths="100,410" style="voltageTable">
|
||||
<tr>
|
||||
<td><para><strong>Voltage</strong></para><para style="style.smallText">(cube meter) / V</para></td>
|
||||
<td>
|
||||
<blockTable colWidths="100,100,100" style="powerTable">
|
||||
<tr>
|
||||
<td><para><strong>L1 - N</strong></para></td>
|
||||
<td><para><strong>L2 - N</strong></para></td>
|
||||
<td><para><strong>L3 - N</strong></para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ object.fd_voltage_l1}}</td>
|
||||
<td>{{ object.fd_voltage_l2}}</td>
|
||||
<td>{{ object.fd_voltage_l3}}</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
||||
<spacer length="10"/>
|
||||
|
||||
<blockTable colWidths="100,100,190,120" style="voltageTable">
|
||||
<tr>
|
||||
<td><para><strong>Phase Rotation</strong></para><para style="style.smallText">(if required)</para></td>
|
||||
<td><para>{{ object.fd_phase_rotation|yesno|capfirst }}</para></td>
|
||||
<td><para><strong>Earth Fault Loop Impedance (Z<sub>s</sub>) / Ω</strong></para></td>
|
||||
<td><para>{{ object.fd_earth_fault }}</para></td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
|
||||
<spacer length="15"/>
|
||||
|
||||
<para><strong>Prospective Short Circuit Current (PSCC)</strong> {{ object.fd_pssc }} A</para>
|
||||
|
||||
<spacer length="15"/>
|
||||
|
||||
<h2 fontSize="14">Power Tests (Worst Case Points)</h2>
|
||||
<spacer length="15"/>
|
||||
|
||||
<blockTable colWidths="100,100,190,120" style="powerTable">
|
||||
<tr>
|
||||
<td><para><strong>Description</strong></para></td>
|
||||
<td><para><strong>Polarity checked?</strong></para></td>
|
||||
<td><para><strong>Voltage / V</strong></para></td>
|
||||
<td><para><strong>Earth Fault Loop Impedance (Z<sub>s</sub>) / Ω</strong></para></td>
|
||||
</tr>
|
||||
{% if object.w1_description %}
|
||||
<tr>
|
||||
<td><para><strong>{{ object.w1_description }}</strong></para></td>
|
||||
<td><para>{{ object.w1_polarity|yesno|capfirst }}</para></td>
|
||||
<td><para>{{ object.w1_voltage }} V</para></td>
|
||||
<td><para>{{ object.w1_earth_fault }}</para></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if object.w2_description %}
|
||||
<tr>
|
||||
<td><para><strong>{{ object.w2_description }}</strong></para></td>
|
||||
<td><para>{{ object.w2_polarity|yesno|capfirst }}</para></td>
|
||||
<td><para>{{ object.w2_voltage }} V</para></td>
|
||||
<td><para>{{ object.w2_earth_fault }}</para></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if object.w3_description %}
|
||||
<tr>
|
||||
<td><para><strong>{{ object.w3_description }}</strong></para></td>
|
||||
<td><para>{{ object.w3_polarity|yesno|capfirst }}</para></td>
|
||||
<td><para>{{ object.w3_voltage }} V</para></td>
|
||||
<td><para>{{ object.w3_earth_fault }}</para></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</blockTable>
|
||||
|
||||
{% else %}
|
||||
|
||||
{% comment %}
|
||||
Small power test
|
||||
{% endcomment %}
|
||||
|
||||
<h2 fontSize="16">Power Checklist</h2>
|
||||
<spacer length="15"/>
|
||||
|
||||
<blockTable colWidths="250,270" style="powerTable">
|
||||
<tr>
|
||||
<td><para><strong>RCDs installed where needed and tested?</strong><br/><br/></para></td>
|
||||
<td><para>{{ object.rcds|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>Electrical supplies tested?</strong><br/><br/></para></td>
|
||||
<td><para>{{ object.supply_test|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>Equipment appropriately earthed?</strong></para><para style="style.smallText">(truss, stage, generators, etc.)</para></td>
|
||||
<td><para>{{ object.earthing|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para><strong>All equipment in PAT period?</strong><br/><br/></para></td>
|
||||
<td><para>{{ object.pat|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -124,6 +124,13 @@
|
||||
<td><para>{{ object|help_text:'persons_responsible_structures'|striptags }}</para></td>
|
||||
<td><para>{{ object.persons_responsible_structures|default:'N/A' }}</para></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><h3><strong>Venue Access</strong></h3></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><para>{{ object|help_text:'parking_and_access'|striptags }}</para></td>
|
||||
<td><para>{{ object.parking_and_access|yesno|capfirst }}</para></td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
<spacer length="15"/>\
|
||||
<hr/>
|
||||
|
||||
@@ -151,8 +151,19 @@
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-default mb-3">
|
||||
<div class="card-header">Venue Access</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-10">{{ object|help_text:'parking_and_access' }}</dt>
|
||||
<dd class="col-2">
|
||||
{{ object.parking_and_access|yesnoi:'invert' }}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
{% button 'print' 'ra_print' object.pk %}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
});
|
||||
$('input[type=radio][name=outside], input[type=radio][name=generators], input[type=radio][name=other_companies_power], input[type=radio][name=nonstandard_equipment_power], input[type=radio][name=multiple_electrical_environments]').change(function() {
|
||||
$('#{{ form.power_notes.id_for_label }}').prop('required', parseBool(this.value));
|
||||
$('#{{ form.power_plan.id_for_label }}').prop('required', parseBool(this.value));
|
||||
//$('#{{ form.power_plan.id_for_label }}').prop('required', parseBool(this.value));
|
||||
});
|
||||
$('input[type=radio][name=special_structures]').change(function() {
|
||||
$('#{{ form.persons_responsible_structures.id_for_label }}').prop('hidden', !parseBool(this.value)).prop('required', parseBool(this.value));
|
||||
@@ -162,6 +162,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-3">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">Venue Access</div>
|
||||
<div class="card-body">
|
||||
<p><strong>If yes to the below, ensure you have communicated with the client and secured all necessary access prior to the event commencing.</strong></p>
|
||||
{% include 'partials/yes_no_radio.html' with formitem=form.parking_and_access %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm-12 text-right">
|
||||
<div class="btn-group">
|
||||
|
||||
106
RIGS/templates/invoice_dashboard.html
Normal file
106
RIGS/templates/invoice_dashboard.html
Normal file
@@ -0,0 +1,106 @@
|
||||
{% extends 'base_rigs.html' %}
|
||||
|
||||
{% load humanize %}
|
||||
{% block content %}
|
||||
|
||||
<form method="GET" action="{% url 'invoice_dashboard' %}">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label for="time_filter">Time Filter</label>
|
||||
<select id="time_filter" name="time_filter" class="form-control">
|
||||
<option value="week" {% if time_filter == 'week' %}selected{% endif %}>Last Week (7 days)</option>
|
||||
<option value="month" {% if time_filter == 'month' %}selected{% endif %}>Last Month (30 days)</option>
|
||||
<option value="year" {% if time_filter == 'year' %}selected{% endif %}>Last Year</option>
|
||||
<option value="all" {% if time_filter == 'all' %}selected{% endif %}>All Time</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$('#time_filter').change(function () {
|
||||
$(this).closest('form').submit();
|
||||
});
|
||||
</script>
|
||||
|
||||
<h3>Overview</h3>
|
||||
|
||||
<!-- big cards in 2x2 grid with total_outstanding, total_events, total_invoices and total_payments, different backgrounds -->
|
||||
|
||||
<div class="card-deck">
|
||||
<div class="card">
|
||||
<a href="{% url 'invoice_waiting' %}" class="text-decoration-none text-white">
|
||||
<div class="card-body bg-primary">
|
||||
<h5 class="card-title text-center">Total Waiting</h5>
|
||||
<p class="card-text text-center h3"><strong>£{{ total_waiting|floatformat:"2g" }}</strong></p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<a href="{% url 'invoice_list' %}" class="text-decoration-none text-dark">
|
||||
<div class="card-body bg-info">
|
||||
<h5 class="card-title text-center">Total Outstanding</h5>
|
||||
<p class="card-text text-center h3"><strong>£{{ total_outstanding|floatformat:"2g" }}</strong></p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body bg-danger">
|
||||
<h5 class="card-title text-center">Total Events</h5>
|
||||
<p class="card-text text-center h3"><strong>{{ total_events }}</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body bg-success">
|
||||
<h5 class="card-title text-center">Total Invoices</h5>
|
||||
<p class="card-text text-center h3"><strong>{{ total_invoices }}</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<h3>Payments</h3>
|
||||
|
||||
<br/>
|
||||
|
||||
<h4>Sources</h4>
|
||||
|
||||
<br/>
|
||||
|
||||
{% for source in payment_methods %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><strong>{{ source.method }}</strong></h5>
|
||||
<p class="card-text h3">£{{ source.total|floatformat:"2g" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<br/>
|
||||
|
||||
<h4>Total</h4>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center">Total Income</h5>
|
||||
<p class="card-text text-center h3"><strong>£{{ total_income|floatformat:"2g" }}</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<h4>Invoice Payment Time</h4>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center">Average Time to Pay</h5>
|
||||
<p class="card-text text-center h3"><strong>{{ mean_invoice_to_payment|floatformat:"2g" }} days</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -31,7 +31,7 @@
|
||||
{% for event in object_list %}
|
||||
<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>
|
||||
<span class="{% if event.get_status_display == 'Cancelled' %}text-danger{% endif %}">{{ event.get_status_display }}</span></th>
|
||||
<td>{{ event.start_date }}</td>
|
||||
<td>
|
||||
{{ event.name }}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if object.organisation %}
|
||||
<div class="card card-default">
|
||||
<div class="card card-default mb-3">
|
||||
<div class="card-header">Organisation Details</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
@@ -44,4 +44,4 @@
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
50
RIGS/templates/partials/crew_list.html
Normal file
50
RIGS/templates/partials/crew_list.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% load button from filters %}
|
||||
|
||||
{% if event.can_check_in %}
|
||||
<div class="col-sm-12">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Crew Record</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Vehicle</th>
|
||||
<th scope="col">Start Time</th>
|
||||
<th scope="col">Role</th>
|
||||
<th scope="col">End Time</th>
|
||||
<th scope="col">{% if request.user.pk is event.mic.pk %}<a
|
||||
href="{% url 'event_checkin_override' event.pk %}" class="btn btn-sm btn-success"><span
|
||||
class="fas fa-plus"></span> Add</a>{% endif %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="crewmembers">
|
||||
{% for crew in event.crew.all %}
|
||||
<tr>
|
||||
<td>{{crew.person}}</td>
|
||||
<td>{{crew.vehicle|default:"None"}}</td>
|
||||
<td>{{crew.time}}</td>
|
||||
<td>{{crew.role}}</td>
|
||||
<td>{% if crew.end_time %}
|
||||
{{crew.end_time}}
|
||||
{% else %}
|
||||
<span class="text-success fas fa-clock" data-toggle="tooltip"
|
||||
title="This person is currently checked into this event"></span>{% endif %}
|
||||
</td>
|
||||
<td>{% if crew.end_time %}
|
||||
{% if crew.person.pk == request.user.pk or event.mic.pk == request.user.pk %}
|
||||
{% button 'edit' 'edit_checkin' crew.pk clazz='btn-sm modal-href' %}
|
||||
{% endif %}
|
||||
{% endif %}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center bg-warning">Apparently this event happened by magic...</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,6 +1,6 @@
|
||||
<h5 class="py-3"><a class="btn btn-info" data-toggle="collapse" href="#values" aria-expanded="false" aria-controls="values">View Threshold Values</a></h5>
|
||||
<div class="row collapse" id="values">
|
||||
<div class="table-responsive">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -33,20 +33,23 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="row">Distro</th>
|
||||
<th scope="row">Max PSSC (kA)</th>
|
||||
<th scope="row">Max PSCC with Single Phase Supply (kA)</th>
|
||||
<th scope="row">Max PSCC with Three Phase Supply (kA)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Intel & Toblerone distros</td>
|
||||
<td>6</td>
|
||||
<td>3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>All other distros</td>
|
||||
<td>10</td>
|
||||
<td>5</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><strong>Voltage Drop on Circuit:</strong> 5% (approx. 12v)</p>
|
||||
<p><strong>Voltage Drop on Circuit:</strong> ≤5% (approx. 12v)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,5 +49,13 @@
|
||||
{% endif %}
|
||||
|
||||
<a href="https://docs.google.com/forms/d/e/1FAIpQLSf-TBOuJZCTYc2L8DWdAaC3_Werq0ulsUs8-6G85I6pA9WVsg/viewform" class="btn btn-danger"><span class="fas fa-file-invoice-dollar"></span> <span class="d-none d-sm-inline">Subhire Insurance Form</span></a>
|
||||
|
||||
{% if event.can_check_in %}
|
||||
{% if request.user.current_event %}
|
||||
<a href="{% url 'event_checkout' %}" class="btn btn-warning">Check Out</a>
|
||||
{% else %}
|
||||
<a href="{% url 'event_checkin' event.pk %}" class="btn btn-success modal-href"><span class="fas fa-user-clock"></span> <span class="d-none d-sm-inline">Check In</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
{{ object.venue|namewithnotes:'venue_detail' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if object.parking_and_access or object.riskassessment.parking_and_access %}<span class="badge badge-warning">Additional Access Requirements</span>{% endif %}
|
||||
</dd>
|
||||
{% if object.venue %}
|
||||
<dt class="col-sm-6">Venue Notes</dt>
|
||||
@@ -77,6 +78,15 @@
|
||||
<dt class="col-sm-6">PO</dt>
|
||||
<dd class="col-sm-6">{{ object.purchase_order }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-6">Forum Thread</dt>
|
||||
{% if object.forum_url %}
|
||||
<dd class="col-6"><a href="{{object.forum_url}}">{{object.forum_url}}</a></dd>
|
||||
{% else %}
|
||||
<a href="{% url 'event_thread' object.pk %}" class="btn btn-primary" title="Create Forum Thread" target="_blank"><span
|
||||
class="fas fa-plus"></span> <span
|
||||
class="hidden-xs">Create Forum Thread</span></a>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
147
RIGS/templates/partials/event_row.html
Normal file
147
RIGS/templates/partials/event_row.html
Normal file
@@ -0,0 +1,147 @@
|
||||
{% load namewithnotes from filters %}
|
||||
{% load markdown_tags %}
|
||||
|
||||
<div class="card h-100 border-3 {{ border_class }} event-row">
|
||||
<div class="card-header {{ header_bg }} {{ header_text }} py-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="d-flex align-items-center">
|
||||
<h5 class="mb-0 mr-3">
|
||||
<a href="{% url 'event_detail' event.pk %}"
|
||||
class="{{ header_text }} text-decoration-underline fw-bold">
|
||||
<strong>{{ event.display_id }}</strong> - {{ event.name }}
|
||||
</a>
|
||||
</h5>
|
||||
{% if event.dry_hire %}
|
||||
<span class="badge px-3 py-2 rounded-pill fs-6 text-dark bg-light">Dry Hire</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="badge fs-6 px-3 py-2 bg-light text-dark rounded-pill">{{ event.get_status_display }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row align-items-start">
|
||||
|
||||
<div class="col-md-2 border-end event-dates">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Meet at:</small>
|
||||
{% if event.meet_at %}
|
||||
<p class="mb-1">{{ event.meet_at|date:"D j M Y, H:i" }}</p>
|
||||
{% else %}
|
||||
<p class="mb-1">Not specified</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Access from:</small>
|
||||
{% if event.access_at %}
|
||||
<p class="mb-1">{{ event.access_at|date:"D j M Y, H:i" }}</p>
|
||||
{% else %}
|
||||
<p class="mb-1">Not specified</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Start:</small>
|
||||
<p class="mb-1">
|
||||
{% if event.start_date and event.start_time %}
|
||||
{{ event.start_date|date:"D j M Y" }}, {{ event.start_time|date:"H:i" }}
|
||||
{% elif event.start_date %}
|
||||
{{ event.start_date|date:"D j M Y" }}
|
||||
{% elif event.start_time %}
|
||||
{{ event.start_time|date:"H:i" }}
|
||||
{% else %}
|
||||
Not specified
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">End:</small>
|
||||
<p class="mb-1">
|
||||
{% if event.end_date and event.end_time %}
|
||||
{{ event.end_date|date:"D j M Y" }}, {{ event.end_time|date:"H:i" }}
|
||||
{% elif event.end_date %}
|
||||
{{ event.end_date|date:"D j M Y" }}
|
||||
{% elif event.end_time %}
|
||||
{{ event.end_time|date:"H:i" }}
|
||||
{% else %}
|
||||
Not specified
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-10">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
|
||||
{% if event.venue %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Venue:</small>
|
||||
<p class="mb-1">{{ event.venue|namewithnotes:'venue_detail' }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if event.is_rig %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Client:</small>
|
||||
<p class="mb-1">
|
||||
{% if event.person %}
|
||||
<a href="{{ event.person.get_absolute_url }}">{{ event.person.name }}</a>
|
||||
{% if event.organisation %}
|
||||
for <a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation }}</a>
|
||||
{% endif %}
|
||||
{% elif event.organisation %}
|
||||
<a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation }}</a>
|
||||
{% else %}
|
||||
No client specified
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if event.mic or event.needs_mic %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Member in Charge (MIC):</small>
|
||||
<div class="d-flex align-items-center mt-1">
|
||||
{% if event.mic %}
|
||||
<img src="{{ event.mic.profile_picture }}" alt="{{ event.mic.name }}"
|
||||
class="rounded-circle mr-1" width="32" height="32">
|
||||
<span>
|
||||
{% if perms.RIGS.view_profile %}
|
||||
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
|
||||
{% endif %}
|
||||
{{ event.mic.name }}
|
||||
{% if perms.RIGS.view_profile %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-danger">No MIC assigned</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Description:</small>
|
||||
<p class="mb-1">{{ event.description|markdown }}</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Status:</small>
|
||||
<div class="mt-1">
|
||||
{% include "partials/event_status.html" with status=event.status %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,9 +1,9 @@
|
||||
<div>
|
||||
<div id="event_status">
|
||||
<span class="badge badge-{% if event.confirmed %}success{% elif event.cancelled %}dark{% else %}warning{% endif %}">Status: {{ event.get_status_display }}</span>
|
||||
{% if event.is_rig %}
|
||||
{% if event.sum_total > 0 %}
|
||||
{% if event.purchase_order %}
|
||||
<span class="badge badge-success">PO: {{ event.purchase_order }}</span>
|
||||
<span class="badge badge-success">PO: Received</span>
|
||||
{% elif event.authorised %}
|
||||
<span class="badge badge-success">Authorisation: Complete <span class="fas fa-check"></span></span>
|
||||
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
|
||||
@@ -20,14 +20,16 @@
|
||||
{% else %}
|
||||
<span class="badge badge-danger">RA: <span class="fas fa-times"></span></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not event.dry_hire %}
|
||||
{% if event.hs_done %}
|
||||
{# TODO Display status of all checklists #}
|
||||
<span class="badge badge-success">Checklist: <span class="fas fa-check"></span></span>
|
||||
{% if event.has_checklist %}
|
||||
<span class="badge badge-success">Checklist: <span class="fas fa-check"></span> {% if event.checklists.count > 1 %}({{event.checklists.count}}){% endif %}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span>
|
||||
{% endif %}
|
||||
{% if event.has_power %}
|
||||
<span class="badge badge-success">Power Record: <span class="fas fa-check"></span> {% if event.power_tests.count > 1 %}({{event.power_tests.count}}){% endif %}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-danger">Power Record: <span class="fas fa-times"></span></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if perms.RIGS.view_invoice %}
|
||||
{% if event.invoice %}
|
||||
@@ -42,5 +44,8 @@
|
||||
<span class="badge badge-info">Invoice: Not Generated</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if event.parking_and_access %}
|
||||
<span class="badge badge-warning">Addititional Access Requirements</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,105 +1,70 @@
|
||||
{% load namewithnotes from filters %}
|
||||
{% load markdown_tags %}
|
||||
<div class="table-responsive">
|
||||
<table class="table mb-0" id="event_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Dates & Times</th>
|
||||
<th scope="col">Event Details</th>
|
||||
<th scope="col">MIC</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in events %}
|
||||
<tr class="{% if event.cancelled %}
|
||||
table-secondary
|
||||
{% elif not event.is_rig %}
|
||||
table-info
|
||||
{% elif not event.mic %}
|
||||
table-danger
|
||||
{% elif event.confirmed and event.authorised %}
|
||||
{% if event.dry_hire or event.riskassessment %}
|
||||
table-success
|
||||
{% else %}
|
||||
table-warning
|
||||
{% endif %}
|
||||
{% else %}
|
||||
table-warning
|
||||
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
|
||||
<!---Number-->
|
||||
<th scope="row" id="event_number">{{ event.display_id }}</th>
|
||||
<!--Dates & Times-->
|
||||
<td id="event_dates" style="text-align: justify;">
|
||||
{% if not event.cancelled %}
|
||||
{% if event.meet_at %}
|
||||
<span class="text-nowrap">Meet: <strong>{{ event.meet_at|date:"D d/m/Y H:i" }}</strong></span>
|
||||
{% endif %}
|
||||
{% if event.access_at %}
|
||||
<br><span class="text-nowrap">Access: <strong>{{ event.access_at|date:"D d/m/Y H:i" }}</strong></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<span class="text-nowrap">Start: <strong>{{ event.start_date|date:"D d/m/Y" }}
|
||||
{% if event.has_start_time %}
|
||||
{{ event.start_time|date:"H:i" }}
|
||||
{% endif %}</strong>
|
||||
</span>
|
||||
{% if event.end_date %}
|
||||
<br>
|
||||
<span class="text-nowrap">End: {% if event.end_date != event.start_date %}<strong>{{ event.end_date|date:"D d/m/Y" }}{% endif %}
|
||||
{% if event.has_end_time %}
|
||||
{{ event.end_time|date:"H:i" }}
|
||||
{% endif %}</strong>
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<!---Details-->
|
||||
<td id="event_details" class="w-100">
|
||||
<h4>
|
||||
<a href="{% url 'event_detail' event.pk %}">
|
||||
{{ event.name }}
|
||||
</a>
|
||||
{% if event.venue %}
|
||||
<small>at {{ event.venue|namewithnotes:'venue_detail' }}</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>
|
||||
<a href="{{ event.person.get_absolute_url }}">{{ event.person.name }}</a>
|
||||
{% if event.organisation %}
|
||||
for <a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation.name }}</a>
|
||||
{% endif %}
|
||||
</h5>
|
||||
{% endif %}
|
||||
{% if not event.cancelled and event.description %}
|
||||
<p>{{ event.description|markdown }}</p>
|
||||
{% endif %}
|
||||
{% include 'partials/event_status.html' %}
|
||||
</td>
|
||||
<!---MIC-->
|
||||
<td id="event_mic" class="text-nowrap">
|
||||
{% if event.mic %}
|
||||
{% 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"/>
|
||||
{{ event.mic }}
|
||||
{% if perms.RIGS.view_profile %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% elif event.is_rig %}
|
||||
<span class="fas fa-user-slash"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr class="bg-warning">
|
||||
<td colspan="4">No events found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.light-link {
|
||||
color: #ebf5ff !important;
|
||||
}
|
||||
|
||||
.dark-link {
|
||||
color: #4495ff !important;
|
||||
}
|
||||
|
||||
.link-on-green {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="row">
|
||||
{% for event in events %}
|
||||
<div class="col-12 mb-4">
|
||||
{% comment %} Determine card style based on event status {% endcomment %}
|
||||
{% if event.cancelled %}
|
||||
|
||||
{% with border_class="border-secondary" header_bg="bg-secondary" header_text="light-link" %}
|
||||
{% include "partials/event_row.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% elif not event.is_rig %}
|
||||
|
||||
{% with border_class="border-primary" header_bg="bg-primary" header_text="light-link" %}
|
||||
{% include "partials/event_row.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% elif not event.mic %}
|
||||
|
||||
{% with border_class="border-danger" header_bg="bg-danger" header_text="light-link" %}
|
||||
{% include "partials/event_row.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% elif event.confirmed and event.authorised %}
|
||||
|
||||
{% if event.dry_hire or event.riskassessment %}
|
||||
|
||||
{% with border_class="border-success" header_bg="bg-success" header_text="link-on-green" %}
|
||||
{% include "partials/event_row.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% with border_class="border-warning" header_bg="bg-warning" header_text="dark-link" %}
|
||||
{% include "partials/event_row.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% with border_class="border-warning" header_bg="bg-warning" header_text="dark-link" %}
|
||||
{% include "partials/event_row.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
No events currently scheduled.
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
@@ -10,10 +10,18 @@
|
||||
<hr>
|
||||
<h5>Event Checklists:</h5>
|
||||
{% 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 mt-2"><span class="fas fa-paperclip"></span> <span
|
||||
class="hidden-xs">Create</span></a>
|
||||
{% 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 mt-2"><span class="fas fa-paperclip"></span> <span
|
||||
class="hidden-xs">Create</span></a>
|
||||
<hr>
|
||||
<h5>Power Test Records:</h5>
|
||||
{% for record in event.power_tests.all %}
|
||||
{% include 'partials/hs_status.html' with event=event object=record view='pt_detail' edit='pt_edit' create='event_pt' review='pt_review' perm=perms.RIGS.review_power %}
|
||||
<br/>
|
||||
{% endfor %}
|
||||
<a href="{% url 'event_pt' event.pk %}" class="btn btn-info mt-2"><span class="fas fa-paperclip"></span> <span
|
||||
class="hidden-xs">Create</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
195
RIGS/templates/partials/legacy_event_table.html
Normal file
195
RIGS/templates/partials/legacy_event_table.html
Normal file
@@ -0,0 +1,195 @@
|
||||
{% load namewithnotes from filters %}
|
||||
{% load markdown_tags %}
|
||||
<style>
|
||||
#event_table {
|
||||
display: grid;
|
||||
grid-template-columns: max-content min-content minmax(max-content, 1fr) max-content;
|
||||
column-gap: 1em;
|
||||
}
|
||||
.eventgrid {
|
||||
display: inherit;
|
||||
grid-column: 1/5;
|
||||
grid-template-columns: subgrid;
|
||||
padding: 1em;
|
||||
dt, dd { display: block; float: left; }
|
||||
dt { clear: both; }
|
||||
dd { float: right; }
|
||||
}
|
||||
.grid-header {
|
||||
border-bottom: 1px solid grey;
|
||||
border-top: 1px solid grey;
|
||||
}
|
||||
#event_status {
|
||||
grid-column-start: 3;
|
||||
}
|
||||
#event_mic {
|
||||
grid-row-start: 1;
|
||||
grid-column-start: 4;
|
||||
}
|
||||
.c-none {
|
||||
display: none;
|
||||
}
|
||||
.c-inline {
|
||||
display: inline;
|
||||
}
|
||||
@container (width <= 500px) {
|
||||
#event_table {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
.eventgrid {
|
||||
grid-column: 1/1 !important;
|
||||
padding: 0.5em;
|
||||
}
|
||||
.grid-header {
|
||||
display: none;
|
||||
}
|
||||
#event_dates {
|
||||
order: 2;
|
||||
}
|
||||
#event_status {
|
||||
order: 3;
|
||||
}
|
||||
#event_mic {
|
||||
grid-row-start: auto;
|
||||
grid-column-start: 4;
|
||||
}
|
||||
}
|
||||
@container (width <= 700px) {
|
||||
#event_table {
|
||||
grid-template-columns: max-content;
|
||||
column-gap: 0.5em;
|
||||
}
|
||||
.eventgrid {
|
||||
grid-column: 1/3;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
#event_dates {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
}
|
||||
#event_number {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
#event_mic {
|
||||
grid-column: 2;
|
||||
}
|
||||
#event_status {
|
||||
grid-column: span 2;
|
||||
}
|
||||
.grid-header, .c-md-none {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@container (width > 700px) {
|
||||
.c-lg-block {
|
||||
display: block;
|
||||
}
|
||||
.c-lg-inline {
|
||||
display: inline;
|
||||
}
|
||||
.c-lg-none, .c-md-none {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="event_table">
|
||||
<div class="eventgrid grid-header font-weight-bold">
|
||||
<div id="event_number">#</div>
|
||||
<div id="event_dates">Dates & Times</div>
|
||||
<div>Event Details</div>
|
||||
<div id="event_mic">MIC</div>
|
||||
</div>
|
||||
{% for event in events %}
|
||||
<div class="eventgrid {% if event.cancelled %}
|
||||
table-secondary
|
||||
{% elif not event.is_rig %}
|
||||
table-info
|
||||
{% elif not event.mic %}
|
||||
table-danger
|
||||
{% elif event.confirmed and event.authorised %}
|
||||
{% if event.dry_hire or event.riskassessment %}
|
||||
table-success
|
||||
{% else %}
|
||||
table-warning
|
||||
{% endif %}
|
||||
{% else %}
|
||||
table-warning
|
||||
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
|
||||
<!---Number-->
|
||||
<div class="font-weight-bold c-none c-lg-block" id="event_number">{{ event.display_id }}</div>
|
||||
<!--Dates & Times-->
|
||||
<div id="event_dates" style="min-width: 180px;">
|
||||
<dl>
|
||||
{% if not event.cancelled %}
|
||||
{% if event.meet_at %}
|
||||
<dt class="font-weight-normal">Meet:</dt>
|
||||
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.meet_at|date:"D d/m/Y H:i" }}</dd>
|
||||
{% endif %}
|
||||
{% if event.access_at %}
|
||||
<dt class="font-weight-normal">Access:</dt>
|
||||
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.access_at|date:"D d/m/Y H:i" }}</dd>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<dt class="font-weight-normal">Start:</dt>
|
||||
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.start_date|date:"D d/m/Y" }}
|
||||
{% if event.has_start_time %}
|
||||
{{ event.start_time|date:"H:i" }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if event.end_date %}
|
||||
<dt class="font-weight-normal">End:</dt>
|
||||
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.end_date|date:"D d/m/Y" }}
|
||||
{% if event.has_end_time %}
|
||||
{{ event.end_time|date:"H:i" }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
<!---Details-->
|
||||
<div id="event_details" class="w-100">
|
||||
<h4>
|
||||
<a href="{% url 'event_detail' event.pk %}">
|
||||
<span class="c-inline c-lg-none">{{ event }}</span><span class="c-none c-lg-inline">{{ event.name }}</span>
|
||||
</a>
|
||||
{% if event.dry_hire %}
|
||||
<span class="badge badge-secondary">Dry Hire</span>
|
||||
{% endif %}
|
||||
<br class="c-none c-lg-inline">
|
||||
{% if event.venue %}
|
||||
<small>at {{ event.venue|namewithnotes:'venue_detail' }}</small>
|
||||
{% endif %}
|
||||
</h4>
|
||||
{% if event.is_rig and not event.cancelled %}
|
||||
<h5>
|
||||
<a href="{{ event.person.get_absolute_url }}">{{ event.person.name }}</a>
|
||||
{% if event.organisation %}
|
||||
for <a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation.name }}</a>
|
||||
{% endif %}
|
||||
</h5>
|
||||
{% endif %}
|
||||
{% if not event.cancelled and event.description %}
|
||||
<p>{{ event.description|markdown }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% include 'partials/event_status.html' %}
|
||||
<!---MIC-->
|
||||
<div id="event_mic" class="text-nowrap">
|
||||
<span class="c-md-none align-middle">MIC:</span>
|
||||
{% if event.mic %}
|
||||
{% 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"/>
|
||||
{{ event.mic }}
|
||||
{% if perms.RIGS.view_profile %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% elif event.is_rig %}
|
||||
<span class="fas fa-exclamation"></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
22
RIGS/templates/partials/parking_and_access.html
Normal file
22
RIGS/templates/partials/parking_and_access.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="card card-default">
|
||||
<div class="card-header">Parking and Access</div>
|
||||
<div class="card-body">
|
||||
<p>This venue has additional parking and/or access requirements.</p>
|
||||
|
||||
<p>Ensure the MIC has:</p>
|
||||
<ul>
|
||||
<li>Details of where to park</li>
|
||||
<li>Details of how to access the venue</li>
|
||||
<li>Details of any access restrictions</li>
|
||||
<li>If on campus, sorted parking permits</li>
|
||||
</ul>
|
||||
|
||||
{% if object.parking_and_access and object.riskassessment.parking_and_access %}
|
||||
<small>Additional parking marked on both rig details and risk assessment.</small>
|
||||
{% elif object.parking_and_access %}
|
||||
<small>Additional parking marked on rig details.</small>
|
||||
{% elif object.riskassessment.parking_and_access %}
|
||||
<small>Additional parking marked on risk assessment.</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,7 +29,15 @@
|
||||
</div>
|
||||
<div class="row pt-3">
|
||||
<label class="col-sm-4 col-form-label"
|
||||
for="{{ form.method.id_for_label }}">{{ form.method.label }}</label>
|
||||
for="{{ form.method.id_for_label }}">{{ form.method.label }}
|
||||
<span class="fas fa-info-circle text-info" data-toggle="collapse" data-target="#collapse" aria-expanded="false" aria-controls="collapse"></span>
|
||||
<ul class="collapse" id="collapse">
|
||||
<li>Cash - Self Explanatory</li>
|
||||
<li>Internal - Transfers within the Students' Union only</li>
|
||||
<li>External - All other transfers (<em>including</em> the University)</li>
|
||||
<li>TEC Adjustment - Manual corrections</li>
|
||||
</ul>
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
{% render_field form.method class+="form-control" %}
|
||||
</div>
|
||||
|
||||
@@ -3,16 +3,45 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row align-items-center justify-content-between py-2 align-middle">
|
||||
<div class="col-sm-12 col-md align-middle">
|
||||
Key: <span class="table-success mr-1 px-2 rounded">Ready</span><span class="table-warning mr-1 px-2 rounded">Action Required</span><span class="table-danger mr-1 px-2 rounded">Needs MIC</span><span class="table-secondary mr-1 px-2 rounded">Cancelled</span><span class="table-info px-2 rounded">Non-Rig</span>
|
||||
<div class="col-sm-12 col-md align-middle d-flex flex-wrap">
|
||||
Key: <span class="table-success mr-1 px-2 rounded">Ready</span><span
|
||||
class="table-warning mr-1 px-2 rounded text-nowrap">Action Required</span><span
|
||||
class="table-danger mr-1 px-2 rounded text-nowrap">Needs MIC</span><span
|
||||
class="table-secondary mr-1 px-2 rounded">Cancelled</span><span
|
||||
class="table-info px-2 rounded text-nowrap">Non-Rig</span>
|
||||
</div>
|
||||
{% if perms.RIGS.add_event %}
|
||||
<div class="col text-right">
|
||||
{% button 'new' 'event_create' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not request.GET.legacy %}
|
||||
|
||||
{% if not request.GET.hide_cancelled %}
|
||||
<a href="?hide_cancelled=true" class="btn btn-primary mr-3">Hide cancelled</a>
|
||||
{% else %}
|
||||
<a href="." class="btn btn-primary mr-3">Show cancelled</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="?legacy=true" class="btn btn-secondary">Legacy rigboard</a>
|
||||
{% else %}
|
||||
<a href="." class="btn btn-secondary">New rigboard</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'partials/event_table.html' %}
|
||||
{% if request.GET.legacy %}
|
||||
<div class="alert alert-warning">
|
||||
<strong>Warning:</strong> The legacy rigboard is being deprecated and will be removed in the future. Please use the
|
||||
new rigboard.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div style="container-type: inline-size;">
|
||||
{% if request.GET.legacy %}
|
||||
{% include 'partials/legacy_event_table.html' %}
|
||||
{% else %}
|
||||
{% include 'partials/event_table.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -118,9 +118,9 @@ def orderby(request, field, attr):
|
||||
@register.filter(needs_autoescape=True) # Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
|
||||
def get_field(obj, field, autoescape=True):
|
||||
value = getattr(obj, field)
|
||||
if(isinstance(value, bool)):
|
||||
if (isinstance(value, bool)):
|
||||
value = yesnoi(value, field in obj.inverted_fields)
|
||||
elif(isinstance(value, str)):
|
||||
elif (isinstance(value, str)):
|
||||
value = truncatewords(value, 20)
|
||||
return mark_safe(value)
|
||||
|
||||
@@ -144,7 +144,7 @@ def get_list(dictionary, key):
|
||||
|
||||
@register.filter
|
||||
def profile_by_index(value):
|
||||
if(value):
|
||||
if (value):
|
||||
return models.Profile.objects.get(pk=int(value))
|
||||
else:
|
||||
return ""
|
||||
@@ -171,7 +171,7 @@ def title_spaced(string):
|
||||
@register.filter(needs_autoescape=True)
|
||||
def namewithnotes(obj, url, autoescape=True):
|
||||
if hasattr(obj, 'notes') and obj.notes is not None and len(obj.notes) > 0:
|
||||
return mark_safe(obj.name + " <a href='{}'><span class='fas fa-sticky-note'></span></a>".format(reverse(url, kwargs={'pk': obj.pk})))
|
||||
return mark_safe(obj.name + f" <a href='{reverse(url, kwargs={'pk': obj.pk})}'><span class='fas fa-sticky-note'></span></a>")
|
||||
else:
|
||||
return obj.name
|
||||
|
||||
@@ -183,7 +183,7 @@ def linkornone(target, namespace=None, autoescape=True):
|
||||
link = namespace + "://" + target
|
||||
else:
|
||||
link = target
|
||||
return mark_safe("<a href='{}' target='_blank'><span class='overflow-ellipsis'>{}</span></a>".format(link, str(target)))
|
||||
return mark_safe(f"<a href='{link}' target='_blank'><span class='overflow-ellipsis'>{target}</span></a>")
|
||||
else:
|
||||
return "None"
|
||||
|
||||
@@ -196,8 +196,8 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
|
||||
text = "Edit"
|
||||
elif type == 'print':
|
||||
clazz += " btn-primary "
|
||||
icon = "fa-print"
|
||||
text = "Print"
|
||||
icon = "fa-download"
|
||||
text = "Export"
|
||||
elif type == 'duplicate':
|
||||
clazz += " btn-info "
|
||||
icon = "fa-copy"
|
||||
@@ -216,6 +216,8 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
|
||||
return {'submit': True, 'class': 'btn-info', 'icon': 'fa-search', 'text': 'Search', 'id': id, 'style': style}
|
||||
elif type == 'submit':
|
||||
return {'submit': True, 'class': 'btn-primary', 'icon': 'fa-save', 'text': 'Save', 'id': id, 'style': style}
|
||||
elif type == 'today':
|
||||
return {'today': True, 'id': id}
|
||||
return {'target': url, 'pk': pk, 'class': clazz, 'icon': icon, 'text': text, 'id': id, 'style': style}
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ def ra(basic_event, admin_user):
|
||||
known_venue=True, safe_loading=True, safe_storage=True,
|
||||
area_outside_of_control=True, barrier_required=True,
|
||||
nonstandard_emergency_procedure=True, special_structures=False,
|
||||
suspended_structures=False, outside=False)
|
||||
suspended_structures=False, outside=False, parking_and_access=False)
|
||||
yield ra
|
||||
ra.delete()
|
||||
|
||||
@@ -43,15 +43,22 @@ def venue(db):
|
||||
|
||||
@pytest.fixture # TODO parameterise with Event sizes
|
||||
def checklist(basic_event, venue, admin_user, ra):
|
||||
checklist = models.EventChecklist.objects.create(event=basic_event, power_mic=admin_user, safe_parking=False,
|
||||
checklist = models.EventChecklist.objects.create(event=basic_event, safe_parking=False,
|
||||
safe_packing=False, exits=False, trip_hazard=False, warning_signs=False,
|
||||
ear_plugs=False, hs_location="Locked away safely",
|
||||
extinguishers_location="Somewhere, I forgot", earthing=False, pat=False,
|
||||
extinguishers_location="Somewhere, I forgot",
|
||||
date=timezone.now(), venue=venue)
|
||||
yield checklist
|
||||
checklist.delete()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def power_test(basic_event, venue, admin_user, ra):
|
||||
power_test = models.PowerTestRecord.objects.create(event=basic_event, venue=venue)
|
||||
yield power_test
|
||||
power_test.delete()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def many_events(db, admin_user, scope="class"):
|
||||
many_events = {
|
||||
|
||||
@@ -16,14 +16,14 @@ class Rigboard(BasePage):
|
||||
URL_TEMPLATE = reverse('rigboard')
|
||||
|
||||
_add_item_selector = (By.XPATH, "//a[contains(@class,'btn-primary') and contains(., 'New')]")
|
||||
_event_row_locator = (By.ID, 'event_row')
|
||||
_event_row_locator = (By.CLASS_NAME, "event-row")
|
||||
|
||||
def add(self):
|
||||
self.find_element(*self._add_item_selector).click()
|
||||
|
||||
class EventListRow(Region):
|
||||
_event_number_locator = (By.ID, "event_number")
|
||||
_event_dates_locator = (By.ID, "event_dates")
|
||||
_event_dates_locator = (By.CLASS_NAME, "event-dates")
|
||||
_event_details_locator = (By.ID, "event_details")
|
||||
_event_mic_locator = (By.ID, "event_mic")
|
||||
|
||||
@@ -114,7 +114,7 @@ class CreateEvent(FormPage):
|
||||
}
|
||||
|
||||
def select_event_type(self, type_name):
|
||||
self.find_element(By.XPATH, '//button[.="{}"]'.format(type_name)).click()
|
||||
self.find_element(By.XPATH, f'//button[.="{type_name}"]').click()
|
||||
|
||||
def item_row(self, ID):
|
||||
return rigs_regions.ItemRow(self, self.find_element(By.ID, "item-" + ID))
|
||||
@@ -207,6 +207,7 @@ class CreateRiskAssessment(FormPage):
|
||||
'suspended_structures': (regions.RadioSelect, (By.ID, 'id_suspended_structures')),
|
||||
'supervisor_consulted': (regions.CheckBox, (By.ID, 'id_supervisor_consulted')),
|
||||
'rigging_plan': (regions.TextBox, (By.ID, 'id_rigging_plan')),
|
||||
'parking_and_access': (regions.RadioSelect, (By.ID, 'id_parking_and_access')),
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -230,11 +231,6 @@ class CreateEventChecklist(FormPage):
|
||||
URL_TEMPLATE = 'event/{event_id}/checklist'
|
||||
|
||||
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
|
||||
_power_mic_selector = (By.XPATH, "//div[select[@id='id_power_mic']]")
|
||||
_add_vehicle_locator = (By.XPATH, "//button[contains(., 'Vehicle')]")
|
||||
_add_crew_locator = (By.XPATH, "//button[contains(., 'Crew')]")
|
||||
_vehicle_row_locator = ('xpath', "//tr[@id[starts-with(., 'vehicle') and not(contains(.,'new'))]]")
|
||||
_crew_row_locator = ('xpath', "//tr[@id[starts-with(., 'crew') and not(contains(.,'new'))]]")
|
||||
|
||||
form_items = {
|
||||
'safe_parking': (regions.CheckBox, (By.ID, 'id_safe_parking')),
|
||||
@@ -245,6 +241,20 @@ class CreateEventChecklist(FormPage):
|
||||
'ear_plugs': (regions.CheckBox, (By.ID, 'id_ear_plugs')),
|
||||
'hs_location': (regions.TextBox, (By.ID, 'id_hs_location')),
|
||||
'extinguishers_location': (regions.TextBox, (By.ID, 'id_extinguishers_location')),
|
||||
}
|
||||
|
||||
@property
|
||||
def success(self):
|
||||
return '{event_id}' not in self.driver.current_url
|
||||
|
||||
|
||||
class CreatePowerTestRecord(FormPage):
|
||||
URL_TEMPLATE = 'event/{event_id}/power'
|
||||
|
||||
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
|
||||
_power_mic_selector = (By.XPATH, "//div[select[@id='id_power_mic']]")
|
||||
|
||||
form_items = {
|
||||
'rcds': (regions.CheckBox, (By.ID, 'id_rcds')),
|
||||
'supply_test': (regions.CheckBox, (By.ID, 'id_supply_test')),
|
||||
'earthing': (regions.CheckBox, (By.ID, 'id_earthing')),
|
||||
@@ -263,58 +273,10 @@ class CreateEventChecklist(FormPage):
|
||||
'w1_earth_fault': (regions.TextBox, (By.ID, 'id_w1_earth_fault')),
|
||||
}
|
||||
|
||||
def add_vehicle(self):
|
||||
self.find_element(*self._add_vehicle_locator).click()
|
||||
|
||||
def add_crew(self):
|
||||
self.find_element(*self._add_crew_locator).click()
|
||||
|
||||
@property
|
||||
def power_mic(self):
|
||||
return regions.BootstrapSelectElement(self, self.find_element(*self._power_mic_selector))
|
||||
|
||||
@property
|
||||
def vehicles(self):
|
||||
return [self.VehicleRow(self, el) for el in self.find_elements(*self._vehicle_row_locator)]
|
||||
|
||||
class VehicleRow(Region):
|
||||
_name_locator = ('xpath', ".//input")
|
||||
_select_locator = ('xpath', ".//div[contains(@class,'bootstrap-select')]/..")
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return regions.TextBox(self, self.root.find_element(*self._name_locator))
|
||||
|
||||
@property
|
||||
def vehicle(self):
|
||||
return regions.BootstrapSelectElement(self, self.root.find_element(*self._select_locator))
|
||||
|
||||
@property
|
||||
def crew(self):
|
||||
return [self.CrewRow(self, el) for el in self.find_elements(*self._crew_row_locator)]
|
||||
|
||||
class CrewRow(Region):
|
||||
_select_locator = ('xpath', ".//div[contains(@class,'bootstrap-select')]/..")
|
||||
_start_time_locator = ('xpath', ".//input[@name[starts-with(., 'start') and not(contains(.,'new'))]]")
|
||||
_end_time_locator = ('xpath', ".//input[@name[starts-with(., 'end') and not(contains(.,'new'))]]")
|
||||
_role_locator = ('xpath', ".//input[@name[starts-with(., 'role') and not(contains(.,'new'))]]")
|
||||
|
||||
@property
|
||||
def crewmember(self):
|
||||
return regions.BootstrapSelectElement(self, self.root.find_element(*self._select_locator))
|
||||
|
||||
@property
|
||||
def start_time(self):
|
||||
return regions.DateTimePicker(self, self.root.find_element(*self._start_time_locator))
|
||||
|
||||
@property
|
||||
def end_time(self):
|
||||
return regions.DateTimePicker(self, self.root.find_element(*self._end_time_locator))
|
||||
|
||||
@property
|
||||
def role(self):
|
||||
return regions.TextBox(self, self.root.find_element(*self._role_locator))
|
||||
|
||||
@property
|
||||
def success(self):
|
||||
return '{event_id}' not in self.driver.current_url
|
||||
|
||||
@@ -6,7 +6,7 @@ from PyRIGS.tests.regions import TextBox, Modal, SimpleMDETextArea
|
||||
|
||||
class Header(Region):
|
||||
def find_link(self, link_text):
|
||||
return self.driver.find_element_by_partial_link_text(link_text)
|
||||
return self.driver.find_element(By.PARTIAL_LINK_TEXT, link_text)
|
||||
|
||||
|
||||
class ItemRow(Region):
|
||||
|
||||
@@ -91,8 +91,8 @@ class TestRigboard(BaseRigboardTest):
|
||||
# self.live_server_url + '/event/create/', self.driver.current_url)
|
||||
|
||||
def test_event_order(self):
|
||||
self.assertIn(self.testEvent.start_date.strftime('%a %d/%m/%Y'), self.page.events[0].dates)
|
||||
self.assertIn(self.testEvent2.start_date.strftime('%a %d/%m/%Y'), self.page.events[1].dates)
|
||||
self.assertIn(self.testEvent.start_date.strftime('%-d %b %Y'), self.page.events[0].dates)
|
||||
self.assertIn(self.testEvent2.start_date.strftime('%-d %b %Y'), self.page.events[1].dates)
|
||||
|
||||
def test_add_button(self):
|
||||
self.page.add()
|
||||
@@ -127,7 +127,7 @@ class TestEventCreate(BaseRigboardTest):
|
||||
|
||||
# Fix it
|
||||
self.page.end_date = datetime.date(2020, 1, 11)
|
||||
self.page.access_at = datetime.datetime(2020, 1, 1, 9)
|
||||
self.page.access_at = datetime.datetime(2020, 1, 8, 9)
|
||||
self.page.dry_hire = True
|
||||
self.page.status = "Booked"
|
||||
self.page.collected_by = "Fred"
|
||||
@@ -318,7 +318,7 @@ class TestEventDuplicate(BaseRigboardTest):
|
||||
|
||||
self.assertFalse(newEvent.authorised)
|
||||
|
||||
self.assertNotIn("N%05d" % self.testEvent.pk, self.driver.find_element_by_xpath('//h2').text)
|
||||
self.assertNotIn("N%05d" % self.testEvent.pk, self.driver.find_element(By.XPATH, '//h2').text)
|
||||
self.assertNotIn("Event data duplicated but not yet saved", self.page.warning) # Check info message not visible
|
||||
|
||||
# Check the new items are visible
|
||||
@@ -327,26 +327,25 @@ class TestEventDuplicate(BaseRigboardTest):
|
||||
self.assertIn("Test Item 2", table.text)
|
||||
self.assertIn("Test Item 3", table.text)
|
||||
|
||||
infoPanel = self.driver.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
|
||||
self.assertIn("N%05d" % self.testEvent.pk,
|
||||
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
infoPanel = self.driver.find_element(By.XPATH, '//div[contains(text(), "Event Info")]/..')
|
||||
self.assertIn("N%05d" % self.testEvent.pk, infoPanel.find_element(By.XPATH, '//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
# Check the PO hasn't carried through
|
||||
self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text)
|
||||
self.assertNotIn("TESTPO", infoPanel.find_element(By.XPATH, '//dt[text()="PO"]/following-sibling::dd[1]').text)
|
||||
|
||||
self.assertIn("N%05d" % self.testEvent.pk,
|
||||
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
infoPanel.find_element(By.XPATH, '//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
|
||||
self.driver.get(self.live_server_url + '/event/' + str(self.testEvent.pk)) # Go back to the old event
|
||||
|
||||
# Check that based-on hasn't crept into the old event
|
||||
infoPanel = self.driver.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
|
||||
infoPanel = self.driver.find_element(By.XPATH, '//div[contains(text(), "Event Info")]/..')
|
||||
self.assertNotIn("N%05d" % self.testEvent.pk,
|
||||
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
infoPanel.find_element(By.XPATH, '//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
# Check the PO remains on the old event
|
||||
self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text)
|
||||
self.assertIn("TESTPO", infoPanel.find_element(By.XPATH, '//dt[text()="PO"]/following-sibling::dd[1]').text)
|
||||
|
||||
self.assertNotIn("N%05d" % self.testEvent.pk,
|
||||
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
infoPanel.find_element(By.XPATH, '//dt[text()="Based On"]/following-sibling::dd[1]').text)
|
||||
|
||||
# Check the items are as they were
|
||||
table = self.page.item_table # ID number is known, see above
|
||||
@@ -531,10 +530,11 @@ class TestCalendar(BaseRigboardTest):
|
||||
self.page.toggle_filter('cancelled')
|
||||
self.page.toggle_filter('provisional')
|
||||
self.page.toggle_filter('confirmed')
|
||||
self.page.toggle_filter('only_mic')
|
||||
|
||||
# and then check the url is correct
|
||||
self.assertIn(
|
||||
"rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false",
|
||||
"rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false&only_mic=true",
|
||||
self.page.cal_url)
|
||||
|
||||
# Awesome - all seems to work
|
||||
@@ -677,14 +677,6 @@ def small_ec(page, admin_user):
|
||||
page.ear_plugs = True
|
||||
page.hs_location = "The Moon"
|
||||
page.extinguishers_location = "With the rest of the fire"
|
||||
# If we do this first the search fails, for ... reasons
|
||||
page.power_mic.search(admin_user.name)
|
||||
page.power_mic.toggle()
|
||||
assert not page.power_mic.is_open
|
||||
page.earthing = True
|
||||
page.rcds = True
|
||||
page.supply_test = True
|
||||
page.pat = True
|
||||
|
||||
|
||||
def test_ec_create_small(logged_in_browser, live_server, admin_user, ra):
|
||||
@@ -705,14 +697,15 @@ def test_ec_create_medium(logged_in_browser, live_server, admin_user, medium_ra)
|
||||
page.ear_plugs = True
|
||||
page.hs_location = "Death Valley"
|
||||
page.extinguishers_location = "With the rest of the fire"
|
||||
# If we do this first the search fails, for ... reasons
|
||||
page.power_mic.search(admin_user.name)
|
||||
page.power_mic.toggle()
|
||||
assert not page.power_mic.is_open
|
||||
|
||||
# Gotta scroll to make the button clickable
|
||||
logged_in_browser.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
|
||||
page.submit()
|
||||
assert page.success
|
||||
|
||||
|
||||
def test_power_checklist(logged_in_browser, live_server, admin_user, power_test, medium_ra):
|
||||
page = pages.CreatePowerTestRecord(logged_in_browser.driver, live_server.url, event_id=medium_ra.event.pk).open()
|
||||
page.earthing = True
|
||||
page.pat = True
|
||||
page.source_rcd = True
|
||||
@@ -727,56 +720,15 @@ def test_ec_create_medium(logged_in_browser, live_server, admin_user, medium_ra)
|
||||
page.w1_polarity = True
|
||||
page.w1_voltage = 240
|
||||
page.w1_earth_fault = "0.42"
|
||||
# If we do this first the search fails, for ... reasons
|
||||
page.power_mic.search(admin_user.name)
|
||||
page.power_mic.toggle()
|
||||
assert not page.power_mic.is_open
|
||||
|
||||
page.submit()
|
||||
assert page.success
|
||||
|
||||
|
||||
def test_ec_create_vehicle(logged_in_browser, live_server, admin_user, checklist):
|
||||
page = pages.EditEventChecklist(logged_in_browser.driver, live_server.url, pk=checklist.pk).open()
|
||||
small_ec(page, admin_user)
|
||||
page.add_vehicle()
|
||||
assert len(page.vehicles) == 1
|
||||
vehicle_name = 'Brian'
|
||||
page.vehicles[0].name.set_value(vehicle_name)
|
||||
# Appears we're moving too fast for javascript...
|
||||
t.sleep(1)
|
||||
page.vehicles[0].vehicle.search(admin_user.first_name)
|
||||
t.sleep(1)
|
||||
page.submit()
|
||||
assert page.success
|
||||
# Check data is correct
|
||||
checklist.refresh_from_db()
|
||||
vehicle = models.EventChecklistVehicle.objects.get(checklist=checklist.pk)
|
||||
assert vehicle_name == vehicle.vehicle
|
||||
|
||||
|
||||
# TODO Test validation of end before start
|
||||
def test_ec_create_crew(logged_in_browser, live_server, admin_user, checklist):
|
||||
page = pages.EditEventChecklist(logged_in_browser.driver, live_server.url, pk=checklist.pk).open()
|
||||
small_ec(page, admin_user)
|
||||
page.add_crew()
|
||||
assert len(page.crew) == 1
|
||||
role = "MIC"
|
||||
start_time = timezone.make_aware(datetime.datetime(2015, 1, 1, 9, 0))
|
||||
end_time = timezone.make_aware(datetime.datetime(2015, 1, 1, 10, 30))
|
||||
crew = page.crew[0]
|
||||
t.sleep(2)
|
||||
crew.crewmember.search(admin_user.first_name)
|
||||
t.sleep(2)
|
||||
crew.role.set_value(role)
|
||||
crew.start_time.set_value(start_time)
|
||||
crew.end_time.set_value(end_time)
|
||||
page.submit()
|
||||
assert page.success
|
||||
# Check data is correct
|
||||
crew_obj = models.EventChecklistCrew.objects.get(checklist=checklist.pk)
|
||||
assert admin_user.pk == crew_obj.crewmember.pk
|
||||
assert role == crew_obj.role
|
||||
assert start_time == crew_obj.start
|
||||
assert end_time == crew_obj.end
|
||||
|
||||
|
||||
# TODO Can I loop through all the boolean fields and test them at once?
|
||||
def test_ra_creation(logged_in_browser, live_server, admin_user, basic_event):
|
||||
page = pages.CreateRiskAssessment(logged_in_browser.driver, live_server.url, event_id=basic_event.pk).open()
|
||||
@@ -808,6 +760,7 @@ def test_ra_creation(logged_in_browser, live_server, admin_user, basic_event):
|
||||
page.barrier_required = False
|
||||
page.nonstandard_emergency_procedure = False
|
||||
page.special_structures = False
|
||||
page.parking_and_access = False
|
||||
# self.page.persons_responsible_structures = "Nobody and her cat, She"
|
||||
|
||||
page.suspended_structures = True
|
||||
|
||||
@@ -259,7 +259,7 @@ class TestPrintPaperwork(TestCase):
|
||||
|
||||
def test_login_redirect(client, django_user_model):
|
||||
request_url = reverse('event_embed', kwargs={'pk': 1})
|
||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
||||
expected_url = f"{reverse('login_embed')}?next={request_url}"
|
||||
|
||||
# Request the page and check it redirects
|
||||
response = client.get(request_url, follow=True)
|
||||
@@ -372,7 +372,8 @@ def test_ra_redirect(admin_client, admin_user, ra):
|
||||
|
||||
|
||||
class TestMarkdownTemplateTags(TestCase):
|
||||
markdown = open(os.path.join(settings.BASE_DIR, "RIGS/tests/sample.md")).read()
|
||||
with open(os.path.join(settings.BASE_DIR, "RIGS/tests/sample.md"), encoding="utf-8") as f:
|
||||
markdown = f.read()
|
||||
|
||||
def test_html_safe(self):
|
||||
html = markdown_filter(self.markdown)
|
||||
|
||||
41
RIGS/urls.py
41
RIGS/urls.py
@@ -75,29 +75,48 @@ urlpatterns = [
|
||||
|
||||
path('event/<int:pk>/ra/', permission_required_with_403('RIGS.add_riskassessment')(views.EventRiskAssessmentCreate.as_view()),
|
||||
name='event_ra'),
|
||||
path('event/ra/<int:pk>/', permission_required_with_403('RIGS.view_riskassessment')(views.EventRiskAssessmentDetail.as_view()),
|
||||
path('event/ra/<int:pk>/', login_required(views.EventRiskAssessmentDetail.as_view()),
|
||||
name='ra_detail'),
|
||||
path('event/ra/<int:pk>/edit/', permission_required_with_403('RIGS.change_riskassessment')(views.EventRiskAssessmentEdit.as_view()),
|
||||
name='ra_edit'),
|
||||
path('event/ra/list', permission_required_with_403('RIGS.view_riskassessment')(views.EventRiskAssessmentList.as_view()),
|
||||
name='ra_list'),
|
||||
path('event/ra/<int:pk>/review/', permission_required_with_403('RIGS.review_riskassessment')(views.EventRiskAssessmentReview.as_view()),
|
||||
name='ra_review'),
|
||||
path('event/ra/<int:pk>/review/', permission_required_with_403('RIGS.review_riskassessment')(views.MarkReviewed.as_view()),
|
||||
name='ra_review', kwargs={'model': 'RiskAssessment'}),
|
||||
path('event/ra/<int:pk>/print/', permission_required_with_403('RIGS.view_riskassessment')(views.RAPrint.as_view()), name='ra_print'),
|
||||
|
||||
path('event/<int:pk>/checklist/', permission_required_with_403('RIGS.add_eventchecklist')(views.EventChecklistCreate.as_view()),
|
||||
name='event_ec'),
|
||||
path('event/checklist/<int:pk>/', permission_required_with_403('RIGS.view_eventchecklist')(views.EventChecklistDetail.as_view()),
|
||||
path('event/checklist/<int:pk>/', login_required(views.EventChecklistDetail.as_view()),
|
||||
name='ec_detail'),
|
||||
path('event/checklist/<int:pk>/edit/', permission_required_with_403('RIGS.change_eventchecklist')(views.EventChecklistEdit.as_view()),
|
||||
name='ec_edit'),
|
||||
path('event/checklist/list', permission_required_with_403('RIGS.view_eventchecklist')(views.EventChecklistList.as_view()),
|
||||
name='ec_list'),
|
||||
path('event/checklist/<int:pk>/review/', permission_required_with_403('RIGS.review_eventchecklist')(views.EventChecklistReview.as_view()),
|
||||
name='ec_review'),
|
||||
path('event/checklist/<int:pk>/review/', permission_required_with_403('RIGS.review_eventchecklist')(views.MarkReviewed.as_view()),
|
||||
name='ec_review', kwargs={'model': 'EventChecklist'}),
|
||||
|
||||
path('event/<int:pk>/power/', permission_required_with_403('RIGS.add_powertestrecord')(views.PowerTestCreate.as_view()),
|
||||
name='event_pt'),
|
||||
path('event/power/<int:pk>/', login_required(views.PowerTestDetail.as_view()),
|
||||
name='pt_detail'),
|
||||
path('event/power/<int:pk>/edit/', permission_required_with_403('RIGS.change_powertestrecord')(views.PowerTestEdit.as_view()),
|
||||
name='pt_edit'),
|
||||
path('event/power/<int:pk>/review/', permission_required_with_403('RIGS.review_power')(views.MarkReviewed.as_view()),
|
||||
name='pt_review', kwargs={'model': 'PowerTestRecord'}),
|
||||
path('event/power/<int:pk>/print/', permission_required_with_403('RIGS.view_powertestrecord')(views.PowerPrint.as_view()), name='pt_print'),
|
||||
|
||||
path('event/<int:pk>/checkin/', login_required(views.EventCheckIn.as_view()),
|
||||
name='event_checkin'),
|
||||
path('event/checkout/', login_required(views.EventCheckOut.as_view()),
|
||||
name='event_checkout'),
|
||||
path('event/<int:pk>/checkin/edit/', login_required(views.EventCheckInEdit.as_view()),
|
||||
name='edit_checkin'),
|
||||
path('event/<int:pk>/checkin/add/', login_required(views.EventCheckInOverride.as_view()),
|
||||
name='event_checkin_override'),
|
||||
|
||||
path('event/<int:pk>/thread/', permission_required_with_403('RIGS.change_event')(views.CreateForumThread.as_view()), name='event_thread'),
|
||||
path('event/webhook/', views.RecieveForumWebhook.as_view(), name='webhook_recieve'),
|
||||
|
||||
# Finance
|
||||
path('invoice/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceIndex.as_view()),
|
||||
path('invoice/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceDashboard.as_view()), name='invoice_dashboard'),
|
||||
path('invoice/outstanding', permission_required_with_403('RIGS.view_invoice')(views.InvoiceOutstanding.as_view()),
|
||||
name='invoice_list'),
|
||||
path('invoice/archive/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceArchive.as_view()),
|
||||
name='invoice_archive'),
|
||||
|
||||
@@ -5,7 +5,7 @@ import reversion
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.db.models import Sum
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
@@ -18,8 +18,76 @@ from RIGS import models
|
||||
|
||||
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
||||
|
||||
TIME_FILTERS = ["all", "year", "month", "week"]
|
||||
|
||||
class InvoiceIndex(generic.ListView):
|
||||
|
||||
def days_between(d1, d2):
|
||||
diff = d2 - d1
|
||||
return diff.total_seconds() / datetime.timedelta(days=1).total_seconds()
|
||||
|
||||
|
||||
class InvoiceDashboard(generic.TemplateView):
|
||||
template_name = 'invoice_dashboard.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['page_title'] = "Invoice Dashboard"
|
||||
context['description'] = "Overview of financial status of TEC rigs."
|
||||
|
||||
time_filter = self.request.GET.get('time_filter', 'all')
|
||||
|
||||
if time_filter not in TIME_FILTERS:
|
||||
time_filter = 'all'
|
||||
|
||||
if time_filter == 'all':
|
||||
context['events'] = models.Event.objects.filter(is_rig=True)
|
||||
context['invoices'] = models.Invoice.objects.all()
|
||||
context['payments'] = models.Payment.objects.all()
|
||||
elif time_filter == 'year':
|
||||
context['events'] = models.Event.objects.filter(is_rig=True, start_date__gte=datetime.date.today() - datetime.timedelta(days=365))
|
||||
context['invoices'] = models.Invoice.objects.filter(invoice_date__gte=datetime.date.today() - datetime.timedelta(days=365))
|
||||
context['payments'] = models.Payment.objects.filter(date__gte=datetime.date.today() - datetime.timedelta(days=365))
|
||||
elif time_filter == 'month':
|
||||
context['events'] = models.Event.objects.filter(is_rig=True, start_date__gte=datetime.date.today() - datetime.timedelta(days=30))
|
||||
context['invoices'] = models.Invoice.objects.filter(invoice_date__gte=datetime.date.today() - datetime.timedelta(days=30))
|
||||
context['payments'] = models.Payment.objects.filter(date__gte=datetime.date.today() - datetime.timedelta(days=30))
|
||||
elif time_filter == 'week':
|
||||
context['events'] = models.Event.objects.filter(is_rig=True, start_date__gte=datetime.date.today() - datetime.timedelta(days=7))
|
||||
context['invoices'] = models.Invoice.objects.filter(invoice_date__gte=datetime.date.today() - datetime.timedelta(days=7))
|
||||
context['payments'] = models.Payment.objects.filter(date__gte=datetime.date.today() - datetime.timedelta(days=7))
|
||||
|
||||
context["time_filter"] = time_filter
|
||||
|
||||
context['total_outstanding'] = sum([i.balance for i in models.Invoice.objects.outstanding_invoices()])
|
||||
context['total_waiting'] = sum([i.sum_total for i in models.Event.objects.waiting_invoices()])
|
||||
context['total_events'] = len(context['events'])
|
||||
context['total_invoices'] = len(context['invoices'])
|
||||
context['total_payments'] = len(context['payments'])
|
||||
|
||||
payment_methods = dict(models.Payment.METHODS)
|
||||
|
||||
context['payment_methods'] = context["payments"].values('method').annotate(total=Sum('amount')).order_by('method')
|
||||
|
||||
for method in context['payment_methods']:
|
||||
method['method'] = payment_methods.get(method['method'], f"Unknown method ({method['method']})")
|
||||
|
||||
context["total_income"] = sum([i['total'] for i in context['payment_methods']])
|
||||
|
||||
payments = context['payments']
|
||||
mean_duration = 0
|
||||
|
||||
for payment in payments:
|
||||
mean_duration += days_between(payment.invoice.invoice_date, payment.date)
|
||||
|
||||
if len(payments) > 0:
|
||||
mean_duration /= len(payments)
|
||||
|
||||
context['mean_invoice_to_payment'] = mean_duration
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class InvoiceOutstanding(generic.ListView):
|
||||
model = models.Invoice
|
||||
template_name = 'invoice_list.html'
|
||||
|
||||
|
||||
282
RIGS/views/hs.py
282
RIGS/views/hs.py
@@ -1,16 +1,39 @@
|
||||
from django.apps import apps
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.views import generic
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from RIGS import models, forms
|
||||
from RIGS.views.rigboard import get_related
|
||||
from PyRIGS.views import PrintView
|
||||
from PyRIGS.views import PrintView, ModalURLMixin
|
||||
from django.shortcuts import redirect
|
||||
|
||||
|
||||
class EventRiskAssessmentCreate(generic.CreateView):
|
||||
class HSCreateView(generic.CreateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
event = models.Event.objects.get(pk=self.kwargs.get('pk'))
|
||||
context['event'] = event
|
||||
context['page_title'] = f'Create {self.model.__name__} for Event {event.display_id}'
|
||||
get_related(context['form'], context)
|
||||
return context
|
||||
|
||||
|
||||
class MarkReviewed(generic.RedirectView):
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
obj = apps.get_model('RIGS', kwargs.get('model')).objects.get(pk=kwargs.get('pk'))
|
||||
with reversion.create_revision():
|
||||
reversion.set_user(self.request.user)
|
||||
obj.reviewed_by = self.request.user
|
||||
obj.reviewed_at = timezone.now()
|
||||
obj.save()
|
||||
return self.request.META.get('HTTP_REFERER', reverse('hs_list'))
|
||||
|
||||
|
||||
class EventRiskAssessmentCreate(HSCreateView):
|
||||
model = models.RiskAssessment
|
||||
template_name = 'hs/risk_assessment_form.html'
|
||||
form_class = forms.EventRiskAssessmentForm
|
||||
@@ -23,28 +46,12 @@ class EventRiskAssessmentCreate(generic.CreateView):
|
||||
ra = models.RiskAssessment.objects.filter(event=event).first()
|
||||
|
||||
if ra is not None:
|
||||
return HttpResponseRedirect(reverse_lazy('ra_edit', kwargs={'pk': ra.pk}))
|
||||
return HttpResponseRedirect(reverse('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'] = f'Create Risk Assessment for Event {event.display_id}'
|
||||
get_related(context['form'], context)
|
||||
return context
|
||||
return super().get(self)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
|
||||
return reverse('ra_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
class EventRiskAssessmentEdit(generic.UpdateView):
|
||||
@@ -57,10 +64,10 @@ class EventRiskAssessmentEdit(generic.UpdateView):
|
||||
ra.reviewed_by = None
|
||||
ra.reviewed_at = None
|
||||
ra.save()
|
||||
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
|
||||
return reverse('ra_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentEdit, self).get_context_data(**kwargs)
|
||||
context = super().get_context_data(**kwargs)
|
||||
rpk = self.kwargs.get('pk')
|
||||
ra = models.RiskAssessment.objects.get(pk=rpk)
|
||||
context['event'] = ra.event
|
||||
@@ -75,47 +82,17 @@ class EventRiskAssessmentDetail(generic.DetailView):
|
||||
template_name = 'hs/risk_assessment_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentDetail, self).get_context_data(**kwargs)
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['page_title'] = f"Risk Assessment for Event <a href='{self.object.event.get_absolute_url()}'>{self.object.event.display_id} {self.object.event.name}</a>"
|
||||
return context
|
||||
|
||||
|
||||
class EventRiskAssessmentList(generic.ListView):
|
||||
paginate_by = 20
|
||||
model = models.RiskAssessment
|
||||
template_name = 'hs/hs_object_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.exclude(event__status=models.Event.CANCELLED).order_by('reviewed_at').select_related('event')
|
||||
|
||||
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'
|
||||
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 = 'hs/event_checklist_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistDetail, self).get_context_data(**kwargs)
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['page_title'] = f"Event Checklist for Event <a href='{self.object.event.get_absolute_url()}'>{self.object.event.display_id} {self.object.event.name}</a>"
|
||||
return context
|
||||
|
||||
@@ -130,10 +107,10 @@ class EventChecklistEdit(generic.UpdateView):
|
||||
ec.reviewed_by = None
|
||||
ec.reviewed_at = None
|
||||
ec.save()
|
||||
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
|
||||
return reverse('ec_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistEdit, self).get_context_data(**kwargs)
|
||||
context = super().get_context_data(**kwargs)
|
||||
pk = self.kwargs.get('pk')
|
||||
ec = models.EventChecklist.objects.get(pk=pk)
|
||||
context['event'] = ec.event
|
||||
@@ -143,7 +120,7 @@ class EventChecklistEdit(generic.UpdateView):
|
||||
return context
|
||||
|
||||
|
||||
class EventChecklistCreate(generic.CreateView):
|
||||
class EventChecklistCreate(HSCreateView):
|
||||
model = models.EventChecklist
|
||||
template_name = 'hs/event_checklist_form.html'
|
||||
form_class = forms.EventChecklistForm
|
||||
@@ -152,75 +129,95 @@ class EventChecklistCreate(generic.CreateView):
|
||||
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, f'A Risk Assessment must exist prior to creating any Event Checklists for {event}! Please create one now.')
|
||||
return HttpResponseRedirect(reverse('event_ra', kwargs={'pk': epk}))
|
||||
return super().get(self)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('ec_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if context['event'].venue:
|
||||
context['venue'] = context['event'].venue
|
||||
return context
|
||||
|
||||
|
||||
class PowerTestDetail(generic.DetailView):
|
||||
model = models.PowerTestRecord
|
||||
template_name = 'hs/power_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['page_title'] = f"Power Test Record for Event <a href='{self.object.event.get_absolute_url()}'>{self.object.event.display_id} {self.object.event.name}</a>"
|
||||
return context
|
||||
|
||||
|
||||
class PowerTestEdit(generic.UpdateView):
|
||||
model = models.PowerTestRecord
|
||||
template_name = 'hs/power_form.html'
|
||||
form_class = forms.PowerTestRecordForm
|
||||
|
||||
def get_success_url(self):
|
||||
ec = self.get_object()
|
||||
ec.reviewed_by = None
|
||||
ec.reviewed_at = None
|
||||
ec.save()
|
||||
return reverse('pt_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pk = self.kwargs.get('pk')
|
||||
ec = models.PowerTestRecord.objects.get(pk=pk)
|
||||
context['event'] = ec.event
|
||||
context['edit'] = True
|
||||
context['page_title'] = f'Edit Power Test Record for Event {ec.event.display_id}'
|
||||
get_related(context['form'], context)
|
||||
return context
|
||||
|
||||
|
||||
class PowerTestCreate(HSCreateView):
|
||||
model = models.PowerTestRecord
|
||||
template_name = 'hs/power_form.html'
|
||||
form_class = forms.PowerTestRecordForm
|
||||
|
||||
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, f'A Risk Assessment must exist prior to creating any Event Checklists for {event}! Please create one now.')
|
||||
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
|
||||
messages.error(self.request, f'A Risk Assessment must exist prior to creating any Power Test Records for {event}! Please create one now.')
|
||||
return HttpResponseRedirect(reverse('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'] = f'Create Event Checklist for Event {event.display_id}'
|
||||
return context
|
||||
return super().get(self)
|
||||
|
||||
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/hs_object_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.exclude(event__status=models.Event.CANCELLED).order_by('reviewed_at').select_related('event')
|
||||
return reverse('pt_detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
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 = super().get_context_data(**kwargs)
|
||||
if context['event'].venue:
|
||||
context['venue'] = context['event'].venue
|
||||
if context['event'].riskassessment.power_mic:
|
||||
context['power_mic'] = context['event'].riskassessment.power_mic
|
||||
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/hs_list.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return models.Event.objects.all().exclude(status=models.Event.CANCELLED).order_by('-start_date').select_related('riskassessment').prefetch_related('checklists')
|
||||
return models.Event.objects.all().exclude(status=models.Event.CANCELLED).exclude(dry_hire=True).order_by('-start_date').select_related('riskassessment').prefetch_related('checklists')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(HSList, self).get_context_data(**kwargs)
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['page_title'] = 'H&S Overview'
|
||||
return context
|
||||
|
||||
@@ -233,3 +230,74 @@ class RAPrint(PrintView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['filename'] = f"EventSpecificRiskAssessment_for_{context['object'].event.display_id}.pdf"
|
||||
return context
|
||||
|
||||
|
||||
class PowerPrint(PrintView):
|
||||
model = models.PowerTestRecord
|
||||
template_name = 'hs/power_print.xml'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['filename'] = f"PowerTestRecord_for_{context['object'].event.display_id}.pdf"
|
||||
return context
|
||||
|
||||
|
||||
class EventCheckIn(generic.CreateView, ModalURLMixin):
|
||||
model = models.EventCheckIn
|
||||
template_name = 'hs/eventcheckin_form.html'
|
||||
form_class = forms.EventCheckInForm
|
||||
|
||||
def get_success_url(self):
|
||||
return self.get_close_url('event_detail', 'event_detail') # Well, that's one way of doing that...!
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['event'] = models.Event.objects.get(pk=self.kwargs.get('pk'))
|
||||
context['page_title'] = f'Check In to Event {context["event"].display_id}'
|
||||
# get_related(context['form'], context)
|
||||
return context
|
||||
|
||||
|
||||
class EventCheckInOverride(generic.CreateView):
|
||||
model = models.EventCheckIn
|
||||
template_name = 'hs/eventcheckin_form.html'
|
||||
form_class = forms.EditCheckInForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['event'] = models.Event.objects.get(pk=self.kwargs.get('pk'))
|
||||
context['page_title'] = f'Manually add Check In to Event {context["event"].display_id}'
|
||||
context['manual'] = True
|
||||
return context
|
||||
|
||||
|
||||
class EventCheckInEdit(generic.UpdateView, ModalURLMixin):
|
||||
model = models.EventCheckIn
|
||||
template_name = 'hs/eventcheckin_form.html'
|
||||
form_class = forms.EditCheckInForm
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
if not obj.person == self.request.user and not obj.event.mic == self.request.user:
|
||||
return redirect(self.request.META.get('HTTP_REFERER', '/'))
|
||||
return super().dispatch(request)
|
||||
|
||||
def get_success_url(self):
|
||||
return self.get_close_url('event_detail', 'event_detail') # Well, that's one way of doing that...!
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['event'] = self.object.event
|
||||
context['page_title'] = f'Edit Check In for Event {context["event"].display_id}'
|
||||
context['edit'] = True
|
||||
# get_related(context['form'], context)
|
||||
return context
|
||||
|
||||
|
||||
class EventCheckOut(generic.RedirectView):
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
checkin = self.request.user.current_event()
|
||||
if checkin:
|
||||
checkin.end_time = timezone.now()
|
||||
checkin.save()
|
||||
return self.request.META.get('HTTP_REFERER', '/')
|
||||
|
||||
@@ -24,6 +24,7 @@ class CalendarICS(ICalFeed):
|
||||
# Rig = 'rig' = True
|
||||
# Provisional = 'provisional' = True
|
||||
# Confirmed/Booked = 'confirmed' = True
|
||||
# Only MIC = 'mic' = False
|
||||
|
||||
def get_object(self, request, *args, **kwargs):
|
||||
params = {}
|
||||
@@ -35,6 +36,9 @@ class CalendarICS(ICalFeed):
|
||||
params['cancelled'] = request.GET.get('cancelled', 'false') == 'true'
|
||||
params['provisional'] = request.GET.get('provisional', 'true') == 'true'
|
||||
params['confirmed'] = request.GET.get('confirmed', 'true') == 'true'
|
||||
params['only_mic'] = request.GET.get('only_mic', 'false') == 'true'
|
||||
|
||||
params['user'] = kwargs['user']
|
||||
|
||||
return params
|
||||
|
||||
@@ -73,6 +77,9 @@ class CalendarICS(ICalFeed):
|
||||
|
||||
filter = filter & typeFilters & statusFilters
|
||||
|
||||
if params['only_mic']:
|
||||
filter = filter & Q(mic=params['user'])
|
||||
|
||||
return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation',
|
||||
'venue', 'mic')
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@ import datetime
|
||||
import re
|
||||
import premailer
|
||||
import simplejson
|
||||
import urllib
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
from envparse import env
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -19,6 +25,7 @@ from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import generic
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from PyRIGS import decorators
|
||||
from PyRIGS.views import OEmbedView, is_ajax, ModalURLMixin, PrintView, get_related
|
||||
@@ -34,8 +41,13 @@ class RigboardIndex(generic.TemplateView):
|
||||
# get super context
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
objects = models.Event.objects.current_events()
|
||||
|
||||
if self.request.GET.get('hide_cancelled', False):
|
||||
objects = objects.exclude(status=models.Event.CANCELLED)
|
||||
|
||||
# call out method to get current events
|
||||
context['events'] = models.Event.objects.current_events().select_related('riskassessment', 'invoice').prefetch_related('checklists')
|
||||
context['events'] = objects.select_related('riskassessment', 'invoice').prefetch_related('checklists')
|
||||
context['page_title'] = "Rigboard"
|
||||
return context
|
||||
|
||||
@@ -342,12 +354,12 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
f"{self.object.display_id} | {self.object.name} - Event Authorisation Request",
|
||||
get_template("eventauthorisation_client_request.txt").render(context),
|
||||
get_template("email/eventauthorisation_client_request.txt").render(context),
|
||||
to=[email],
|
||||
reply_to=[self.request.user.email],
|
||||
)
|
||||
css = finders.find('css/email.css')
|
||||
html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context),
|
||||
html = premailer.Premailer(get_template("email/eventauthorisation_client_request.html").render(context),
|
||||
external_styles=css).transform()
|
||||
msg.attach_alternative(html, 'text/html')
|
||||
|
||||
@@ -357,7 +369,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
||||
|
||||
|
||||
class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
||||
template_name = "eventauthorisation_client_request.html"
|
||||
template_name = "email/eventauthorisation_client_request.html"
|
||||
model = models.Event
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
@@ -377,3 +389,41 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
||||
context['to_name'] = self.request.GET.get('to_name', None)
|
||||
context['target'] = 'event_authorise_form_preview'
|
||||
return context
|
||||
|
||||
|
||||
class CreateForumThread(generic.base.RedirectView):
|
||||
permanent = False
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
event = get_object_or_404(models.Event, pk=kwargs['pk'])
|
||||
|
||||
if event.forum_url:
|
||||
return event.forum_url
|
||||
|
||||
params = {
|
||||
'title': str(event),
|
||||
'body': f'https://rigs.nottinghamtec.co.uk/event/{event.pk}',
|
||||
'category': 'rig-info'
|
||||
}
|
||||
return f'https://forum.nottinghamtec.co.uk/new-topic?{urllib.parse.urlencode(params)}'
|
||||
|
||||
|
||||
class RecieveForumWebhook(generic.View):
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
computed = f"sha256={hmac.new(env('FORUM_WEBHOOK_SECRET').encode(), request.body, hashlib.sha256).hexdigest()}"
|
||||
if not hmac.compare_digest(request.headers.get('X-Discourse-Event-Signature'), computed):
|
||||
return HttpResponseForbidden('Invalid signature header')
|
||||
# Check if this is the right kind of event. The webhook filters by category on the forum side
|
||||
if request.headers.get('X-Discourse-Event') == "topic_created":
|
||||
body = simplejson.loads(request.body.decode('utf-8'))
|
||||
event_id = int(body['topic']['title'][1:6]) # find the ID, force convert it to an int to eliminate leading zeros
|
||||
event = models.Event.objects.filter(pk=event_id).first()
|
||||
if event:
|
||||
event.forum_url = f"https://forum.nottinghamtec.co.uk/t/{body['topic']['slug']}"
|
||||
event.save()
|
||||
return HttpResponse(status=202)
|
||||
return HttpResponse(status=204)
|
||||
|
||||
4
app.json
4
app.json
@@ -4,7 +4,7 @@
|
||||
"scripts": {
|
||||
"postdeploy": "python manage.py migrate && python manage.py generateSampleData"
|
||||
},
|
||||
"stack": "heroku-20",
|
||||
"stack": "heroku-22",
|
||||
"env": {
|
||||
"DEBUG": {
|
||||
"required": true
|
||||
@@ -51,7 +51,7 @@
|
||||
"url": "heroku/nodejs"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nottinghamtec/heroku-buildpack-python"
|
||||
"url": "heroku/python"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -105,8 +105,7 @@ class Command(BaseCommand):
|
||||
|
||||
for i in range(100):
|
||||
prefix = random.choice(asset_prefixes)
|
||||
asset_id = str(get_available_asset_id(wanted_prefix=prefix))
|
||||
asset_id = prefix + asset_id
|
||||
asset_id = get_available_asset_id(wanted_prefix=prefix)
|
||||
asset = models.Asset(
|
||||
asset_id=asset_id,
|
||||
description=random.choice(asset_description),
|
||||
|
||||
18
assets/migrations/0027_asset_nickname.py
Normal file
18
assets/migrations/0027_asset_nickname.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.16 on 2022-12-11 00:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0026_auto_20220526_1623'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='nickname',
|
||||
field=models.CharField(blank=True, max_length=120),
|
||||
),
|
||||
]
|
||||
18
assets/migrations/0028_alter_asset_length.py
Normal file
18
assets/migrations/0028_alter_asset_length.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-24 22:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0027_asset_nickname'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='length',
|
||||
field=models.DecimalField(blank=True, decimal_places=2, help_text='m', max_digits=10, null=True),
|
||||
),
|
||||
]
|
||||
@@ -95,14 +95,15 @@ class AssetManager(models.Manager):
|
||||
def search(self, query=None):
|
||||
qs = self.get_queryset()
|
||||
if query is not None:
|
||||
or_lookup = (Q(asset_id__exact=query.upper()) | Q(description__icontains=query) | Q(serial_number__exact=query))
|
||||
or_lookup = (Q(asset_id__exact=query.upper()) | Q(description__icontains=query) | Q(serial_number__exact=query) | Q(nickname__icontains=query))
|
||||
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||
return qs
|
||||
|
||||
|
||||
def get_available_asset_id(wanted_prefix=""):
|
||||
last_asset = Asset.objects.filter(asset_id_prefix=wanted_prefix).last()
|
||||
return 9000 if last_asset is None else wanted_prefix + str(last_asset.asset_id_number + 1)
|
||||
last_asset_id = last_asset.asset_id_number if last_asset else 0
|
||||
return wanted_prefix + str(last_asset_id + 1)
|
||||
|
||||
|
||||
def validate_positive(value):
|
||||
@@ -125,6 +126,7 @@ class Asset(models.Model, RevisionMixin):
|
||||
purchase_price = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10, validators=[validate_positive])
|
||||
replacement_cost = models.DecimalField(null=True, decimal_places=2, max_digits=10, validators=[validate_positive])
|
||||
comments = models.TextField(blank=True)
|
||||
nickname = models.CharField(max_length=120, blank=True)
|
||||
|
||||
# Audit
|
||||
last_audited_at = models.DateTimeField(blank=True, null=True)
|
||||
@@ -133,7 +135,7 @@ class Asset(models.Model, RevisionMixin):
|
||||
# Cable assets
|
||||
is_cable = models.BooleanField(default=False)
|
||||
cable_type = models.ForeignKey(to=CableType, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
length = models.DecimalField(decimal_places=1, max_digits=10,
|
||||
length = models.DecimalField(decimal_places=2, max_digits=10,
|
||||
blank=True, null=True, help_text='m')
|
||||
csa = models.DecimalField(decimal_places=2, max_digits=10,
|
||||
blank=True, null=True, help_text='mm²')
|
||||
@@ -190,5 +192,5 @@ class Asset(models.Model, RevisionMixin):
|
||||
return str(self.asset_id)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def display_name(self):
|
||||
return f"{self.display_id} | {self.description}"
|
||||
|
||||
@@ -35,6 +35,11 @@
|
||||
function onAuditClick(assetID) {
|
||||
$('#' + assetID).remove();
|
||||
}
|
||||
$('#modal').on('hidden.bs.modal', function (e) {
|
||||
searchbar = document.getElementById('id_q');
|
||||
searchbar.value = "";
|
||||
setTimeout(searchbar.focus(), 2000);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
{{ block.super }}
|
||||
<script src="{% static 'js/selects.js' %}"></script>
|
||||
<script src="{% static 'js/easymde.min.js' %}"></script>
|
||||
<script src="{% static 'js/interaction.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
@@ -35,9 +34,10 @@
|
||||
$(document).find(".selectpicker").selectpicker().each(function(){initPicker($(this))});
|
||||
});
|
||||
</script>
|
||||
<script src="{% static "js/tooltip.js" %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
setupMDE('#id_comments');
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<label for="{{ form.description.id_for_label }}">Description</label>
|
||||
{% render_field form.description|add_class:'form-control' value=object.description %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.nickname.id_for_label }}">Nickname</label>
|
||||
{% render_field form.nickname|add_class:'form-control' value=object.nickname %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.category.id_for_label }}" >Category</label>
|
||||
{% render_field form.category|add_class:'form-control'%}
|
||||
@@ -45,7 +49,10 @@
|
||||
{% else %}
|
||||
<dt>Asset ID</dt>
|
||||
<dd>{{ object.asset_id }}</dd>
|
||||
|
||||
{% if object.nickname %}
|
||||
<dt>Nickname</dt>
|
||||
<dd>"{{ object.nickname }}"</dd>
|
||||
{% endif %}
|
||||
<dt>Description</dt>
|
||||
<dd>{{ object.description }}</dd>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</thead>
|
||||
<tbody id="asset_table_body">
|
||||
{% for item in object_list %}
|
||||
<tr class="table-{{ item.status.display_class|default:'' }} assetRow">
|
||||
<tr class="table-{{ item.status.display_class|default:'' }} assetRow" id="{{ item.asset_id }}">
|
||||
<th scope="row" class="align-middle"><a class="assetID" href="{% url 'asset_detail' item.asset_id %}">{{ item.asset_id }}</a></th>
|
||||
<td class="assetDesc"><span class="text-truncate d-inline-block align-middle">{{ item.description }}</span></td>
|
||||
<td class="assetCategory align-middle">{{ item.category }}</td>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<label for="{{ form.purchase_price.id_for_label }}">Purchase Price</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend"><span class="input-group-text">£</span></div>
|
||||
{% render_field form.purchase_price|add_class:'form-control' value=object.purchase_price %}
|
||||
{% render_field form.purchase_price|add_class:'form-control'|set_data:"toggle:tooltip" value=object.purchase_price title="Ex. VAT" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<label for="{{ form.salvage_value.id_for_label }}">Replacement Cost</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend"><span class="input-group-text">£</span></div>
|
||||
{% render_field form.replacement_cost|add_class:'form-control' value=object.replacement_cost %}
|
||||
{% render_field form.replacement_cost|add_class:'form-control'|set_data:"toggle:tooltip" value=object.replacement_cost title="Ex. VAT" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -38,3 +38,17 @@ def test_asset(db, category, status):
|
||||
asset, created = models.Asset.objects.get_or_create(asset_id="91991", description="Spaceflower", status=status, category=category, date_acquired=datetime.date(1991, 12, 26), replacement_cost=100)
|
||||
yield asset
|
||||
asset.delete()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_status_2(db):
|
||||
status = models.AssetStatus.objects.create(name="Lost", should_show=False)
|
||||
yield status
|
||||
status.delete()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_asset_2(db, category, test_status_2):
|
||||
asset, created = models.Asset.objects.get_or_create(asset_id="10", description="Working Mic", status=test_status_2, category=category, date_acquired=datetime.date(2001, 10, 20), replacement_cost=1000)
|
||||
yield asset
|
||||
asset.delete()
|
||||
|
||||
@@ -77,7 +77,7 @@ class AssetForm(FormPage):
|
||||
'description': (regions.TextBox, (By.ID, 'id_description')),
|
||||
'is_cable': (regions.CheckBox, (By.ID, 'id_is_cable')),
|
||||
'serial_number': (regions.TextBox, (By.ID, 'id_serial_number')),
|
||||
'comments': (regions.SimpleMDETextArea, (By.ID, 'id_comments')),
|
||||
'comments': (regions.TextBox, (By.ID, 'id_comments')),
|
||||
'purchase_price': (regions.TextBox, (By.ID, 'id_purchase_price')),
|
||||
'replacement_cost': (regions.TextBox, (By.ID, 'id_replacement_cost')),
|
||||
'date_acquired': (regions.DatePicker, (By.ID, 'id_date_acquired')),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import time
|
||||
import datetime
|
||||
import pytest
|
||||
|
||||
from django.utils import timezone
|
||||
from selenium.webdriver.common.by import By
|
||||
@@ -53,45 +54,45 @@ class TestAssetList(AutoLoginTest):
|
||||
self.assertEqual("10", asset_ids[2])
|
||||
self.assertEqual("C1", asset_ids[3])
|
||||
|
||||
def test_search(self):
|
||||
self.page.set_query("10")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 1)
|
||||
self.assertEqual("Working Mic", self.page.assets[0].description)
|
||||
self.assertEqual("10", self.page.assets[0].id)
|
||||
|
||||
self.page.set_query("light")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 1)
|
||||
self.assertEqual("A light", self.page.assets[0].description)
|
||||
@pytest.mark.xfail(reason="Fails on CI for unknown reason", raises=AssertionError)
|
||||
def test_search(logged_in_browser, admin_user, live_server, test_asset, test_asset_2, category, status, cable_type):
|
||||
page = pages.AssetList(logged_in_browser.driver, live_server.url).open()
|
||||
page.set_query(test_asset.asset_id)
|
||||
page.search()
|
||||
assert len(page.assets) == 1
|
||||
assert page.assets[0].description == test_asset.description
|
||||
assert page.assets[0].id == test_asset.asset_id
|
||||
|
||||
self.page.set_query("Random string")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 0)
|
||||
page.set_query(test_asset.description)
|
||||
page.search()
|
||||
assert len(page.assets) == 1
|
||||
assert page.assets[0].description == test_asset.description
|
||||
|
||||
self.page.set_query("")
|
||||
self.page.search()
|
||||
# Only working stuff shown by default
|
||||
self.assertTrue(len(self.page.assets) == 2)
|
||||
page.set_query("Random string")
|
||||
page.search()
|
||||
assert len(page.assets) == 0
|
||||
|
||||
self.page.status_selector.toggle()
|
||||
self.assertTrue(self.page.status_selector.is_open)
|
||||
self.page.status_selector.select_all()
|
||||
self.page.status_selector.toggle()
|
||||
self.assertFalse(self.page.status_selector.is_open)
|
||||
self.page.filter()
|
||||
self.assertTrue(len(self.page.assets) == 4)
|
||||
page.set_query("")
|
||||
page.search()
|
||||
# Only working stuff shown by default
|
||||
assert len(page.assets) == 1
|
||||
|
||||
self.page.category_selector.toggle()
|
||||
self.assertTrue(self.page.category_selector.is_open)
|
||||
self.page.category_selector.set_option("Sound", True)
|
||||
self.page.category_selector.close()
|
||||
self.assertFalse(self.page.category_selector.is_open)
|
||||
self.page.filter()
|
||||
self.assertTrue(len(self.page.assets) == 2)
|
||||
asset_ids = list(map(lambda x: x.id, self.page.assets))
|
||||
self.assertEqual("1", asset_ids[0])
|
||||
self.assertEqual("10", asset_ids[1])
|
||||
page.status_selector.toggle()
|
||||
assert page.status_selector.is_open
|
||||
page.status_selector.select_all()
|
||||
page.status_selector.toggle()
|
||||
assert not page.status_selector.is_open
|
||||
page.filter()
|
||||
assert len(page.assets) == 2
|
||||
|
||||
page.category_selector.toggle()
|
||||
assert page.category_selector.is_open
|
||||
page.category_selector.set_option(category.name, True)
|
||||
page.category_selector.close()
|
||||
assert not page.category_selector.is_open
|
||||
page.filter()
|
||||
assert len(page.assets) == 2
|
||||
|
||||
|
||||
def test_cable_create(logged_in_browser, admin_user, live_server, test_asset, category, status, cable_type):
|
||||
@@ -195,7 +196,7 @@ class TestAssetForm(AutoLoginTest):
|
||||
# self.assertTrue(self.page.parent_selector.options[0].selected)
|
||||
self.page.parent_selector.toggle()
|
||||
|
||||
self.assertFalse(self.driver.find_element_by_id('cable-table').is_displayed())
|
||||
self.assertFalse(self.driver.find_element(By.ID, 'cable-table').is_displayed())
|
||||
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
@@ -243,6 +244,7 @@ class TestSupplierList(AutoLoginTest):
|
||||
|
||||
self.page.set_query("")
|
||||
self.page.search()
|
||||
time.sleep(1)
|
||||
self.assertTrue(len(self.page.suppliers) == 7)
|
||||
|
||||
self.page.set_query("This is not a supplier")
|
||||
@@ -350,7 +352,7 @@ class TestAssetAudit(AutoLoginTest):
|
||||
self.wait.until(ec.visibility_of_element_located((By.ID, 'modal')))
|
||||
self.assertEqual(self.page.modal.asset_id, asset_row.id)
|
||||
self.page.modal.close()
|
||||
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||
self.assertFalse(self.driver.find_element(By.ID, 'modal').is_displayed())
|
||||
# Make sure audit log was NOT filled out
|
||||
audited = models.Asset.objects.get(asset_id=asset_row.id)
|
||||
assert audited.last_audited_by is None
|
||||
|
||||
@@ -168,7 +168,7 @@ class DuplicateMixin:
|
||||
class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate):
|
||||
def get_initial(self, *args, **kwargs):
|
||||
initial = super().get_initial(*args, **kwargs)
|
||||
initial["asset_id"] = models.get_available_asset_id(wanted_prefix=self.get_object().asset_id_prefix)
|
||||
initial["asset_id"] = models.get_available_asset_id()
|
||||
return initial
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -374,7 +374,7 @@ def generate_label(pk):
|
||||
barcode = Code39(str(obj.asset_id), writer=ImageWriter())
|
||||
|
||||
logo_size = (200, 200)
|
||||
image.paste(logo.resize(logo_size, Image.ANTIALIAS), box=(5, 5))
|
||||
image.paste(logo.resize(logo_size, Image.LANCZOS), box=(5, 5))
|
||||
barcode_image = barcode.render(writer_options={"quiet_zone": 0, "write_text": False})
|
||||
width, height = barcode_image.size
|
||||
image.paste(barcode_image.crop((0, 0, width, 100)), (int(((size[0] + logo_size[0]) - width) / 2), 40))
|
||||
|
||||
@@ -28,6 +28,7 @@ def admin_user(admin_user):
|
||||
admin_user.last_name = "Test"
|
||||
admin_user.initials = "ETU"
|
||||
admin_user.is_approved = True
|
||||
admin_user.is_supervisor = True
|
||||
admin_user.save()
|
||||
return admin_user
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const con = require('gulp-concat');
|
||||
const gulpif = require('gulp-if');
|
||||
|
||||
function fonts(done) {
|
||||
return gulp.src('node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.*')
|
||||
return gulp.src('node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.*', { encoding: false })
|
||||
.pipe(gulp.dest('pipeline/built_assets/fonts'))
|
||||
.pipe(browsersync.stream());
|
||||
}
|
||||
@@ -70,16 +70,16 @@ function scripts() {
|
||||
.pipe(gulpif(function(file) { return interaction.includes(file.relative);}, con('interaction.js')))
|
||||
.pipe(gulpif(function(file) { return jpop.includes(file.relative);}, con('jpop.js')))
|
||||
.pipe(flatten())
|
||||
.pipe(terser())
|
||||
// Only minify if filename does not already denote it as minified
|
||||
.pipe(gulpif(function(file) { return file.path.indexOf("min") == -1;},terser()))
|
||||
.pipe(gulp.dest(dest))
|
||||
.pipe(browsersync.stream());
|
||||
}
|
||||
|
||||
function browserSync(done) {
|
||||
spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'});
|
||||
// TODO Wait for Django server to come up before browsersync, it seems inconsistent
|
||||
browsersync.init({
|
||||
notify: false,
|
||||
notify: true,
|
||||
open: false,
|
||||
port: 8001,
|
||||
proxy: '127.0.0.1:8000'
|
||||
|
||||
14211
package-lock.json
generated
14211
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -16,7 +16,6 @@
|
||||
"cssnano": "^5.0.13",
|
||||
"easymde": "^2.16.1",
|
||||
"fullcalendar": "^5.10.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-flatten": "^0.4.0",
|
||||
"gulp-if": "^3.0.0",
|
||||
@@ -27,14 +26,15 @@
|
||||
"html5sortable": "^0.13.3",
|
||||
"jquery": "^3.6.0",
|
||||
"konami": "^1.6.3",
|
||||
"moment": "^2.29.2",
|
||||
"node-sass": "^7.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"node-sass": "^9.0.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"postcss": "^8.4.5",
|
||||
"postcss": "^8.4.31",
|
||||
"uglify-js": "^3.14.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browser-sync": "^2.27.7"
|
||||
"browser-sync": "^3.0.2",
|
||||
"gulp": "^5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"gulp": "gulp",
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
|
||||
//console.log('Changing selected value');
|
||||
obj.find('option').remove(); //Remove all the available options
|
||||
obj.append( //Add the new option
|
||||
$("<option></option>")
|
||||
.attr("value",pk)
|
||||
.text(text)
|
||||
.data('update_url',update_url)
|
||||
);
|
||||
obj.selectpicker('render'); //Re-render the UI
|
||||
obj.selectpicker('refresh'); //Re-render the UI
|
||||
obj.selectpicker('val', pk); //Set the new value to be selected
|
||||
obj[0].add(new Option(text, pk, true, true)); // Add new option
|
||||
//obj.selectpicker('val', pk); //Set the new value to be selected
|
||||
obj.selectpicker('refresh');
|
||||
obj.change(); //Trigger the change function manually
|
||||
//console.log(obj);
|
||||
}
|
||||
|
||||
function refreshUpdateHref(obj) {
|
||||
@@ -47,14 +42,16 @@ function initPicker(obj) {
|
||||
//log: 3,
|
||||
preprocessData: function (data) {
|
||||
var i, l = data.length, array = [];
|
||||
array.push({
|
||||
text: clearSelectionLabel,
|
||||
value: '',
|
||||
data:{
|
||||
update_url: '',
|
||||
subtext:''
|
||||
}
|
||||
});
|
||||
if (!obj.data('noclear')) {
|
||||
array.push({
|
||||
text: clearSelectionLabel,
|
||||
value: '',
|
||||
data:{
|
||||
update_url: '',
|
||||
subtext:''
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (l) {
|
||||
for(i = 0; i < l; i++){
|
||||
@@ -71,11 +68,13 @@ function initPicker(obj) {
|
||||
return array;
|
||||
}
|
||||
};
|
||||
|
||||
obj.prepend($("<option></option>")
|
||||
.attr("value",'')
|
||||
.text(clearSelectionLabel)
|
||||
.data('update_url','')); //Add "clear selection" option
|
||||
//console.log(obj.data);
|
||||
if (!obj.data('noclear')) {
|
||||
obj.prepend($("<option></option>")
|
||||
.attr("value",'')
|
||||
.text(clearSelectionLabel)
|
||||
.data('update_url','')); //Add "clear selection" option
|
||||
}
|
||||
|
||||
|
||||
obj.selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
|
||||
|
||||
@@ -6,11 +6,6 @@ function setupItemTable(items_json) {
|
||||
newitem = -1;
|
||||
}
|
||||
|
||||
function nl2br(str, is_xhtml) {
|
||||
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
|
||||
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return $('<div/>').text(str).html();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user