Compare commits
206 Commits
41c1c44754
...
PSSC-Value
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7835d38f9d | ||
|
|
83f5990050 | ||
|
|
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 | |||
|
e7e760de2e
|
|||
| 9091197639 | |||
|
4f4baa62c1
|
|||
|
b9f8621e1a
|
|||
|
4b1dc37a7f
|
|||
|
|
9273ca35cf | ||
|
|
4a4b7fa30d | ||
| a44a532c7d | |||
| 3a2e5c943b | |||
| 426a9088cc | |||
|
|
1369a2f978 | ||
|
38eafbced3
|
|||
|
900002bf71
|
|||
|
2869c9fcc3
|
|||
|
00eb4e0e27
|
|||
|
23e17b0e34
|
|||
|
bf268a4566
|
|||
|
dedb8d81fe
|
|||
|
7d785f4f1b
|
|||
|
5eb113156b
|
|||
|
ab03ad081a
|
|||
| cd5889f60e | |||
| f18bf3b077 | |||
|
3d36d986a4
|
|||
|
41f5a23ef0
|
|||
|
09f48f740d
|
|||
|
805d77af20
|
|||
|
fabab87e23
|
|||
|
a95779e04e
|
|||
|
24e6ba540d
|
|||
|
14d3522b81
|
|||
|
e4cfaba57d
|
|||
|
d9664422c5
|
|||
|
27bb3f1d8e
|
|||
|
151ac8b3bd
|
|||
|
c2dcd86d5d
|
|||
|
6c14b30c13
|
|||
|
5215af349a
|
|||
|
a5e888fef5
|
|||
|
|
2ae4e4142c | ||
|
8799f822bb
|
|||
|
2dd3d306b4
|
|||
|
042004e1ae
|
|||
|
733ea69cc5
|
|||
|
bbea47e8ec
|
|||
|
c4aafbd7e5
|
|||
|
ccdc13df93
|
|||
|
aa19ceaf18
|
|||
|
05d280172d
|
|||
|
2f51b7b1d3
|
|||
|
8d1edb54ea
|
|||
| 54c90a7be4 | |||
|
3e1e0079d8
|
|||
|
b6952aeb52
|
|||
|
d33a4231fb
|
|||
|
8dea6aeab0
|
|||
|
34c03e379d
|
|||
|
988fb78b45
|
|||
|
eda314c092
|
|||
|
8ef520619a
|
|||
|
95931f86b4
|
|||
|
cc2cb5c4d1
|
|||
|
3ae507b469
|
|||
|
33754eed60
|
|||
|
15ab626593
|
|||
|
7bc47b446c
|
|||
|
83b287a418
|
|||
|
3b9848d457
|
|||
|
308d0c697e
|
|||
|
f243a589fa
|
|||
|
79c90ac92c
|
|||
|
8244287a64
|
|||
|
da4d62729b
|
|||
|
f8a48798de
|
|||
|
fc817fa9b5
|
|||
|
b04a168f01
|
|||
|
cc6992976e
|
|||
|
a556b17d2d
|
|||
|
f9e38338dc
|
|||
|
ce83ae6dd1
|
|||
|
9e1d54dc02
|
|||
| 375b0af2fd | |||
|
|
0354662864 | ||
|
c537118037
|
|||
|
466a9a9693
|
|||
| d25381b2de | |||
|
|
eaf891daf7 | ||
|
|
801d2e8a7d | ||
|
|
3d329219b8 | ||
|
2ddc8923ba
|
|||
|
276a86c5be
|
|||
|
484f155e43
|
|||
|
fdbdaab52e
|
|||
|
|
a01e351e89 | ||
|
|
708a387774 | ||
|
|
af6fe582e0 | ||
|
|
905a144e7d | ||
| 0e64021f01 | |||
| 2eb87a51f8 | |||
| 30fac1d1b9 | |||
|
c4fec483ae
|
|||
|
3028fb92d9
|
|||
| 215697ba64 | |||
|
d966bddfd7
|
|||
|
0d5e48b89c
|
|||
|
bd2c94d3e3
|
|||
|
21d09d951d
|
|||
|
014b00bc30
|
|||
|
3f8fc82260
|
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
|
||||||
|
});
|
||||||
14
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: Manual Deploy
|
||||||
|
|
||||||
|
on: workflow_dispatch
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: akhileshns/heroku-deploy@v3.12.12 # This is the action
|
||||||
|
with:
|
||||||
|
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
|
||||||
|
heroku_app_name: "pyrigs" #Must be unique in Heroku
|
||||||
|
heroku_email: "aj@aronajones.com"
|
||||||
29
.github/workflows/django.yml
vendored
@@ -12,27 +12,22 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PYTHONDONTWRITEBYTECODE: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.9.1
|
python-version: 3.9
|
||||||
- uses: actions/cache@v2
|
cache: 'pipenv'
|
||||||
id: pcache
|
|
||||||
with:
|
|
||||||
path: ~/.local/share/virtualenvs
|
|
||||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pipenv-
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip pipenv
|
python3 -m pip install --upgrade pip pipenv
|
||||||
pipenv install -d
|
pipenv install -d
|
||||||
# if: steps.pcache.outputs.cache-hit != 'true'
|
# if: steps.pcache.outputs.cache-hit != 'true'
|
||||||
- name: Cache Static Files
|
- name: Cache Static Files
|
||||||
id: static-cache
|
id: static-cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: 'pipeline/built_assets'
|
path: 'pipeline/built_assets'
|
||||||
key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
|
key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
|
||||||
@@ -43,12 +38,12 @@ jobs:
|
|||||||
- name: Basic Checks
|
- name: Basic Checks
|
||||||
run: |
|
run: |
|
||||||
pipenv run pycodestyle . --exclude=migrations,node_modules
|
pipenv run pycodestyle . --exclude=migrations,node_modules
|
||||||
pipenv run python manage.py check
|
pipenv run python3 manage.py check
|
||||||
pipenv run python manage.py makemigrations --check --dry-run
|
pipenv run python3 manage.py makemigrations --check --dry-run
|
||||||
pipenv run python manage.py collectstatic --noinput
|
pipenv run python3 manage.py collectstatic --noinput
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: pipenv run pytest -n auto -vv --cov
|
run: pipenv run pytest -n auto --cov
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: failure-screenshots ${{ matrix.test-group }}
|
name: failure-screenshots ${{ matrix.test-group }}
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -26,6 +26,7 @@ var/
|
|||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
node_modules/
|
node_modules/
|
||||||
|
data/
|
||||||
|
|
||||||
# Continer extras
|
# Continer extras
|
||||||
.vagrant
|
.vagrant
|
||||||
|
|||||||
12
Dockerfile
@@ -1,12 +0,0 @@
|
|||||||
FROM python:3.6
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ADD . /app
|
|
||||||
|
|
||||||
RUN pip install -r requirements.txt && \
|
|
||||||
python manage.py collectstatic --noinput
|
|
||||||
|
|
||||||
EXPOSE 8000
|
|
||||||
|
|
||||||
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
|
||||||
40
Pipfile
@@ -11,7 +11,6 @@ asgiref = "~=3.3.1"
|
|||||||
beautifulsoup4 = "~=4.9.3"
|
beautifulsoup4 = "~=4.9.3"
|
||||||
Brotli = "~=1.0.9"
|
Brotli = "~=1.0.9"
|
||||||
cachetools = "~=4.2.1"
|
cachetools = "~=4.2.1"
|
||||||
certifi = "~=2020.12.5"
|
|
||||||
chardet = "~=4.0.0"
|
chardet = "~=4.0.0"
|
||||||
configparser = "~=5.0.1"
|
configparser = "~=5.0.1"
|
||||||
contextlib2 = "~=0.6.0.post1"
|
contextlib2 = "~=0.6.0.post1"
|
||||||
@@ -19,39 +18,36 @@ cssselect = "~=1.1.0"
|
|||||||
cssutils = "~=1.0.2"
|
cssutils = "~=1.0.2"
|
||||||
dj-database-url = "~=0.5.0"
|
dj-database-url = "~=0.5.0"
|
||||||
dj-static = "~=0.0.6"
|
dj-static = "~=0.0.6"
|
||||||
Django = "~=3.1.12"
|
Django = "~=3.2"
|
||||||
django-debug-toolbar = "~=3.2"
|
django-debug-toolbar = "~=4.0.0"
|
||||||
django-filter = "~=2.4.0"
|
django-filter = "~=2.4.0"
|
||||||
django-ical = "~=1.7.1"
|
django-ical = "~=1.7.1"
|
||||||
django-recurrence = "~=1.10.3"
|
django-recurrence = "~=1.10.3"
|
||||||
django-registration-redux = "~=2.9"
|
django-registration-redux = "~=2.9"
|
||||||
django-reversion = "~=3.0.9"
|
django-reversion = "~=3.0.9"
|
||||||
django-toolbelt = "~=0.0.1"
|
|
||||||
django-widget-tweaks = "~=1.4.8"
|
django-widget-tweaks = "~=1.4.8"
|
||||||
django-htmlmin = "~=0.11.0"
|
django-htmlmin = "~=0.11.0"
|
||||||
envparse = "~=0.2.0"
|
envparse = "*"
|
||||||
gunicorn = "~=20.0.4"
|
gunicorn = "~=20.0.4"
|
||||||
icalendar = "~=4.0.7"
|
icalendar = "~=4.0.7"
|
||||||
idna = "~=2.10"
|
idna = "~=2.10"
|
||||||
importlib-metadata = "~=3.4.0"
|
|
||||||
lxml = "~=4.6.3"
|
|
||||||
Markdown = "~=3.3.3"
|
Markdown = "~=3.3.3"
|
||||||
msgpack = "~=1.0.2"
|
msgpack = "~=1.0.2"
|
||||||
pep517 = "~=0.9.1"
|
pep517 = "~=0.9.1"
|
||||||
Pillow = "~=8.3.2"
|
Pillow = "~=10.0.1"
|
||||||
premailer = "~=3.7.0"
|
premailer = "~=3.7.0"
|
||||||
progress = "~=1.5"
|
progress = "~=1.5"
|
||||||
psutil = "~=5.8.0"
|
psutil = "~=5.8.0"
|
||||||
psycopg2 = "~=2.8.6"
|
psycopg2 = "~=2.8.6"
|
||||||
Pygments = "~=2.7.4"
|
Pygments = "~=2.15.0"
|
||||||
pyparsing = "~=2.4.7"
|
pyparsing = "~=2.4.7"
|
||||||
PyPDF2 = "~=1.26.0"
|
PyPDF2 = "~=1.27.5"
|
||||||
PyPOM = "~=2.2.0"
|
PyPOM = "~=2.2.4"
|
||||||
python-dateutil = "~=2.8.1"
|
python-dateutil = "~=2.8.1"
|
||||||
pytoml = "~=0.1.21"
|
pytoml = "~=0.1.21"
|
||||||
pytz = "~=2020.5"
|
pytz = "~=2020.5"
|
||||||
reportlab = "~=3.5.59"
|
reportlab = "*"
|
||||||
requests = "~=2.25.1"
|
requests = "~=2.31.0"
|
||||||
retrying = "~=1.3.3"
|
retrying = "~=1.3.3"
|
||||||
simplejson = "~=3.17.2"
|
simplejson = "~=3.17.2"
|
||||||
six = "~=1.15.0"
|
six = "~=1.15.0"
|
||||||
@@ -60,11 +56,10 @@ sqlparse = "~=0.4.2"
|
|||||||
static3 = "~=0.7.0"
|
static3 = "~=0.7.0"
|
||||||
svg2rlg = "~=0.3"
|
svg2rlg = "~=0.3"
|
||||||
tini = "~=3.0.1"
|
tini = "~=3.0.1"
|
||||||
tornado = "~=6.1"
|
tornado = "~=6.3"
|
||||||
urllib3 = "~=1.26.5"
|
urllib3 = "~=1.26.18"
|
||||||
whitenoise = "~=5.2.0"
|
whitenoise = "~=5.2.0"
|
||||||
yolk = "~=0.4.3"
|
yolk = "~=0.4.3"
|
||||||
"z3c.rml" = "~=4.1.2"
|
|
||||||
zipp = "~=3.4.0"
|
zipp = "~=3.4.0"
|
||||||
"zope.component" = "~=4.6.2"
|
"zope.component" = "~=4.6.2"
|
||||||
"zope.deferredimport" = "~=4.3.1"
|
"zope.deferredimport" = "~=4.3.1"
|
||||||
@@ -78,10 +73,16 @@ sentry-sdk = "*"
|
|||||||
diff-match-patch = "*"
|
diff-match-patch = "*"
|
||||||
python-barcode = "*"
|
python-barcode = "*"
|
||||||
django-hCaptcha = "*"
|
django-hCaptcha = "*"
|
||||||
|
importlib-metadata = "*"
|
||||||
|
django-hcaptcha = "*"
|
||||||
|
"z3c.rml" = "*"
|
||||||
|
pikepdf = "*"
|
||||||
|
django-queryable-properties = "*"
|
||||||
|
django-mass-edit = "*"
|
||||||
|
selenium = "~=4.9.1"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
selenium = "~=3.141.0"
|
pycodestyle = "~=2.9.1"
|
||||||
pycodestyle = "*"
|
|
||||||
coveralls = "*"
|
coveralls = "*"
|
||||||
django-coverage-plugin = "*"
|
django-coverage-plugin = "*"
|
||||||
pytest-cov = "*"
|
pytest-cov = "*"
|
||||||
@@ -89,9 +90,10 @@ pytest-django = "*"
|
|||||||
pluggy = "*"
|
pluggy = "*"
|
||||||
pytest-splinter = "*"
|
pytest-splinter = "*"
|
||||||
pytest = "*"
|
pytest = "*"
|
||||||
|
pytest-reverse = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.9"
|
python_version = "3.10"
|
||||||
|
|
||||||
[dev-packages.pytest-xdist]
|
[dev-packages.pytest-xdist]
|
||||||
extras = [ "psutil",]
|
extras = [ "psutil",]
|
||||||
|
|||||||
1643
Pipfile.lock
generated
@@ -9,9 +9,8 @@ from RIGS import models
|
|||||||
|
|
||||||
def get_oembed(login_url, request, oembed_view, kwargs):
|
def get_oembed(login_url, request, oembed_view, kwargs):
|
||||||
context = {}
|
context = {}
|
||||||
context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'],
|
context['oembed_url'] = f"{request.scheme}://{request.META['HTTP_HOST']}{reverse(oembed_view, kwargs=kwargs)}"
|
||||||
reverse(oembed_view, kwargs=kwargs))
|
context['login_url'] = f"{login_url}?{REDIRECT_FIELD_NAME}={request.get_full_path()}"
|
||||||
context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path())
|
|
||||||
resp = render(request, 'login_redirect.html', context=context)
|
resp = render(request, 'login_redirect.html', context=context)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ def has_oembed(oembed_view, login_url=settings.LOGIN_URL):
|
|||||||
if oembed_view is not None:
|
if oembed_view is not None:
|
||||||
return get_oembed(login_url, request, oembed_view, kwargs)
|
return get_oembed(login_url, request, oembed_view, kwargs)
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
return HttpResponseRedirect(f'{login_url}?{REDIRECT_FIELD_NAME}={request.get_full_path()}')
|
||||||
|
|
||||||
_checklogin.__doc__ = view_func.__doc__
|
_checklogin.__doc__ = view_func.__doc__
|
||||||
_checklogin.__dict__ = view_func.__dict__
|
_checklogin.__dict__ = view_func.__dict__
|
||||||
@@ -55,7 +54,7 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None):
|
|||||||
if oembed_view is not None:
|
if oembed_view is not None:
|
||||||
return get_oembed(login_url, request, oembed_view, kwargs)
|
return get_oembed(login_url, request, oembed_view, kwargs)
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path()))
|
return HttpResponseRedirect(f'{login_url}?{REDIRECT_FIELD_NAME}={request.get_full_path()}')
|
||||||
else:
|
else:
|
||||||
resp = render(request, '403.html')
|
resp = render(request, '403.html')
|
||||||
resp.status_code = 403
|
resp.status_code = 403
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ if DEBUG:
|
|||||||
ALLOWED_HOSTS.append('localhost')
|
ALLOWED_HOSTS.append('localhost')
|
||||||
ALLOWED_HOSTS.append('example.com')
|
ALLOWED_HOSTS.append('example.com')
|
||||||
ALLOWED_HOSTS.append('127.0.0.1')
|
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')
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
@@ -42,8 +44,9 @@ if not DEBUG:
|
|||||||
|
|
||||||
INTERNAL_IPS = ['127.0.0.1']
|
INTERNAL_IPS = ['127.0.0.1']
|
||||||
|
|
||||||
ADMINS = [('Tom Price', 'tomtom5152@gmail.com'), ('IT Manager', 'it@nottinghamtec.co.uk'),
|
DOMAIN = env('DOMAIN', default='example.com')
|
||||||
('Arona Jones', 'arona.jones@nottinghamtec.co.uk')]
|
|
||||||
|
ADMINS = [('IT Manager', f'it@{DOMAIN}'), ('Arona Jones', f'arona.jones@{DOMAIN}')]
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
ADMINS.append(('Testing Superuser', 'superuser@example.com'))
|
ADMINS.append(('Testing Superuser', 'superuser@example.com'))
|
||||||
|
|
||||||
@@ -61,18 +64,20 @@ INSTALLED_APPS = (
|
|||||||
'users',
|
'users',
|
||||||
'RIGS',
|
'RIGS',
|
||||||
'assets',
|
'assets',
|
||||||
|
'training',
|
||||||
|
|
||||||
'debug_toolbar',
|
# 'debug_toolbar',
|
||||||
'registration',
|
'registration',
|
||||||
'reversion',
|
'reversion',
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
'hcaptcha',
|
'hcaptcha',
|
||||||
|
'massadmin',
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE = (
|
MIDDLEWARE = (
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
# 'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||||
'reversion.middleware.RevisionMiddleware',
|
'reversion.middleware.RevisionMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
@@ -187,12 +192,8 @@ LOGOUT_URL = '/user/logout/'
|
|||||||
ACCOUNT_ACTIVATION_DAYS = 7
|
ACCOUNT_ACTIVATION_DAYS = 7
|
||||||
|
|
||||||
# CAPTCHA settings
|
# CAPTCHA settings
|
||||||
if DEBUG or CI:
|
HCAPTCHA_SITEKEY = env('HCAPTCHA_SITEKEY', '10000000-ffff-ffff-ffff-000000000001')
|
||||||
HCAPTCHA_SITEKEY = '10000000-ffff-ffff-ffff-000000000001'
|
HCAPTCHA_SECRET = env('HCAPTCHA_SECRET', '0x0000000000000000000000000000000000000000')
|
||||||
HCAPTCHA_SECRET = '0x0000000000000000000000000000000000000000'
|
|
||||||
else:
|
|
||||||
HCAPTCHA_SITEKEY = env('HCAPTCHA_SITEKEY')
|
|
||||||
HCAPTCHA_SECRET = env('HCAPTCHA_SECRET')
|
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
EMAILER_TEST = False
|
EMAILER_TEST = False
|
||||||
@@ -219,8 +220,6 @@ TIME_ZONE = 'Europe/London'
|
|||||||
|
|
||||||
FORMAT_MODULE_PATH = 'PyRIGS.formats'
|
FORMAT_MODULE_PATH = 'PyRIGS.formats'
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
@@ -263,3 +262,12 @@ USE_GRAVATAR = True
|
|||||||
|
|
||||||
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
|
||||||
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
|
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():
|
if not pathlib.Path("screenshots").is_dir():
|
||||||
os.mkdir("screenshots")
|
os.mkdir("screenshots")
|
||||||
self.driver.save_screenshot(screenshot_file)
|
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
|
raise e
|
||||||
|
|
||||||
return wrapper_func
|
return wrapper_func
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class BootstrapSelectElement(Region):
|
|||||||
return [self.BootstrapSelectOption(self, i) for i in options]
|
return [self.BootstrapSelectOption(self, i) for i in options]
|
||||||
|
|
||||||
def set_option(self, name, selected):
|
def set_option(self, name, selected):
|
||||||
options = list((x for x in self.options if x.name == name))
|
options = [x for x in self.options if x.name == name]
|
||||||
assert len(options) == 1
|
assert len(options) == 1
|
||||||
options[0].set_selected(selected)
|
options[0].set_selected(selected)
|
||||||
|
|
||||||
@@ -117,6 +117,15 @@ class TextBox(Region):
|
|||||||
self.root.send_keys(value)
|
self.root.send_keys(value)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleMDETextArea(Region):
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.driver.execute_script("return document.querySelector('#' + arguments[0]).nextSibling.children[1].CodeMirror.getDoc().getValue();", self.root.get_attribute("id"))
|
||||||
|
|
||||||
|
def set_value(self, value):
|
||||||
|
self.driver.execute_script("document.querySelector('#' + arguments[0]).nextSibling.children[1].CodeMirror.getDoc().setValue(arguments[1]);", self.root.get_attribute("id"), value)
|
||||||
|
|
||||||
|
|
||||||
class CheckBox(Region):
|
class CheckBox(Region):
|
||||||
def toggle(self):
|
def toggle(self):
|
||||||
self.root.click()
|
self.root.click()
|
||||||
@@ -136,7 +145,7 @@ class RadioSelect(Region): # Currently only works for yes/no radio selects
|
|||||||
value = "0"
|
value = "0"
|
||||||
else:
|
else:
|
||||||
value = "1"
|
value = "1"
|
||||||
self.find_element(By.XPATH, "//label[@for='{}_{}']".format(self.root.get_attribute("id"), value)).click()
|
self.find_element(By.XPATH, f"//label[@for='{self.root.get_attribute('id')}_{value}']").click()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
|||||||
@@ -8,18 +8,14 @@ from pytest_django.asserts import assertRedirects, assertContains, assertNotCont
|
|||||||
from pytest_django.asserts import assertTemplateUsed, assertInHTML
|
from pytest_django.asserts import assertTemplateUsed, assertInHTML
|
||||||
|
|
||||||
from PyRIGS import urls
|
from PyRIGS import urls
|
||||||
from RIGS.models import Event
|
from RIGS.models import Event, Profile
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
|
from training.tests.test_unit import get_response
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
import pytest
|
|
||||||
from django.core.management import call_command
|
|
||||||
from django.template.defaultfilters import striptags
|
from django.template.defaultfilters import striptags
|
||||||
from django.urls.exceptions import NoReverseMatch
|
from django.urls.exceptions import NoReverseMatch
|
||||||
|
|
||||||
from RIGS.models import Event
|
from django.test import TestCase, TransactionTestCase
|
||||||
from assets.models import Asset
|
|
||||||
from django.db import connection
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +45,7 @@ def get_request_url(url):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("command", ['generateSampleAssetsData', 'generateSampleRIGSData', 'generateSampleUserData',
|
@pytest.mark.parametrize("command", ['generateSampleAssetsData', 'generateSampleRIGSData', 'generateSampleUserData',
|
||||||
'deleteSampleData'])
|
'deleteSampleData', 'generateSampleTrainingData', 'generate_sample_training_users'])
|
||||||
def test_production_exception(command):
|
def test_production_exception(command):
|
||||||
from django.core.management.base import CommandError
|
from django.core.management.base import CommandError
|
||||||
with pytest.raises(CommandError, match=".*production"):
|
with pytest.raises(CommandError, match=".*production"):
|
||||||
@@ -63,83 +59,88 @@ class TestSampleDataGenerator(TestCase):
|
|||||||
assert Asset.objects.all().count() > 50
|
assert Asset.objects.all().count() > 50
|
||||||
assert Event.objects.all().count() > 100
|
assert Event.objects.all().count() > 100
|
||||||
call_command('deleteSampleData')
|
call_command('deleteSampleData')
|
||||||
assert Asset.objects.all().count() == 0
|
assert not Asset.objects.all().exists()
|
||||||
assert Event.objects.all().count() == 0
|
assert not Event.objects.all().exists()
|
||||||
|
|
||||||
|
|
||||||
class TestSampleDataGenerator(TestCase):
|
@override_settings(DEBUG=True)
|
||||||
@override_settings(DEBUG=True)
|
@pytest.mark.skip(reason="broken")
|
||||||
def setUp(self):
|
def test_unauthenticated(client): # Nothing should be available to the unauthenticated
|
||||||
call_command('generateSampleData')
|
call_command('generateSampleData')
|
||||||
|
|
||||||
def test_unauthenticated(self): # Nothing should be available to the unauthenticated
|
|
||||||
for url in find_urls_recursive(urls.urlpatterns):
|
for url in find_urls_recursive(urls.urlpatterns):
|
||||||
request_url = get_request_url(url)
|
request_url = get_request_url(url)
|
||||||
if request_url and 'user' not in request_url: # User module is full of edge cases
|
if request_url and 'user' not in request_url: # User module is full of edge cases
|
||||||
response = self.client.get(request_url, follow=True, HTTP_HOST='example.com')
|
response = client.get(request_url, follow=True, HTTP_HOST='example.com')
|
||||||
assertContains(response, 'Login')
|
assertContains(response, 'Login')
|
||||||
if 'application/json+oembed' in response.content.decode():
|
if 'application/json+oembed' in response.content.decode():
|
||||||
assertTemplateUsed(response, 'login_redirect.html')
|
assertTemplateUsed(response, 'login_redirect.html')
|
||||||
else:
|
else:
|
||||||
if "embed" in str(url):
|
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:
|
else:
|
||||||
expected_url = "{0}?next={1}".format(reverse('login'), request_url)
|
expected_url = f"{reverse('login')}?next={request_url}"
|
||||||
assertRedirects(response, expected_url)
|
assertRedirects(response, expected_url)
|
||||||
|
call_command('deleteSampleData')
|
||||||
|
|
||||||
def test_page_titles(self):
|
|
||||||
assert self.client.login(username='superuser', password='superuser')
|
|
||||||
for url in filter((lambda u: "embed" not in u.name), find_urls_recursive(urls.urlpatterns)):
|
|
||||||
request_url = get_request_url(url)
|
|
||||||
response = self.client.get(request_url)
|
|
||||||
if hasattr(response, "context_data") and "page_title" in response.context_data:
|
|
||||||
expected_title = striptags(response.context_data["page_title"])
|
|
||||||
assertInHTML('<title>{} | Rig Information Gathering System'.format(expected_title),
|
|
||||||
response.content.decode())
|
|
||||||
print("{} | {}".format(request_url, expected_title)) # If test fails, tell me where!
|
|
||||||
self.client.logout()
|
|
||||||
|
|
||||||
def test_basic_access(self):
|
@override_settings(DEBUG=True)
|
||||||
assert self.client.login(username="basic", password="basic")
|
@pytest.mark.skip(reason="broken")
|
||||||
|
def test_basic_access(client):
|
||||||
|
call_command('generateSampleData')
|
||||||
|
assert client.login(username="basic", password="basic")
|
||||||
|
|
||||||
url = reverse('asset_list')
|
url = reverse('asset_list')
|
||||||
response = self.client.get(url)
|
response = client.get(url)
|
||||||
# Check edit and duplicate buttons NOT shown in list
|
# Check edit and duplicate buttons NOT shown in list
|
||||||
assertNotContains(response, 'Edit')
|
assertNotContains(response, 'Edit')
|
||||||
assertNotContains(response,
|
assertNotContains(response,
|
||||||
'Duplicate') # If this line is randomly failing, check the debug toolbar HTML hasn't crept in
|
'Duplicate') # If this line is randomly failing, check the debug toolbar HTML hasn't crept in
|
||||||
|
|
||||||
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
|
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
|
||||||
response = self.client.get(url)
|
response = client.get(url)
|
||||||
assertNotContains(response, 'Purchase Details')
|
assertNotContains(response, 'Purchase Details')
|
||||||
assertNotContains(response, 'View Revision History')
|
assertNotContains(response, 'View Revision History')
|
||||||
|
|
||||||
urlz = {'asset_history', 'asset_update', 'asset_duplicate'}
|
urlz = {'asset_history', 'asset_update', 'asset_duplicate'}
|
||||||
for url_name in urlz:
|
for url_name in urlz:
|
||||||
request_url = reverse(url_name, kwargs={'pk': Asset.objects.first().asset_id})
|
request_url = reverse(url_name, kwargs={'pk': Asset.objects.first().asset_id})
|
||||||
response = self.client.get(request_url, follow=True)
|
response = client.get(request_url, follow=True)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
request_url = reverse('supplier_create')
|
request_url = reverse('supplier_create')
|
||||||
response = self.client.get(request_url, follow=True)
|
response = client.get(request_url, follow=True)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
request_url = reverse('supplier_update', kwargs={'pk': 1})
|
request_url = reverse('supplier_update', kwargs={'pk': 1})
|
||||||
response = self.client.get(request_url, follow=True)
|
response = client.get(request_url, follow=True)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
self.client.logout()
|
client.logout()
|
||||||
|
call_command('deleteSampleData')
|
||||||
|
|
||||||
def test_keyholder_access(self):
|
|
||||||
assert self.client.login(username="keyholder", password="keyholder")
|
@override_settings(DEBUG=True)
|
||||||
|
@pytest.mark.skip(reason="broken")
|
||||||
|
def test_keyholder_access(client):
|
||||||
|
call_command('generateSampleData')
|
||||||
|
assert client.login(username="keyholder", password="keyholder")
|
||||||
|
|
||||||
url = reverse('asset_list')
|
url = reverse('asset_list')
|
||||||
response = self.client.get(url)
|
response = client.get(url)
|
||||||
# Check edit and duplicate buttons shown in list
|
# Check edit and duplicate buttons shown in list
|
||||||
assertContains(response, 'Edit')
|
assertContains(response, 'Edit')
|
||||||
assertContains(response, 'Duplicate')
|
assertContains(response, 'Duplicate')
|
||||||
|
|
||||||
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
|
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
|
||||||
response = self.client.get(url)
|
response = client.get(url)
|
||||||
assertContains(response, 'Purchase Details')
|
assertContains(response, 'Purchase Details')
|
||||||
assertContains(response, 'View Revision History')
|
assertContains(response, 'View Revision History')
|
||||||
self.client.logout()
|
client.logout()
|
||||||
|
call_command('deleteSampleData')
|
||||||
|
|
||||||
|
|
||||||
|
def test_search(admin_client, admin_user):
|
||||||
|
url = reverse('search')
|
||||||
|
response = admin_client.get(url, {'q': "Definetelynothingfoundifwesearchthis"})
|
||||||
|
assertContains(response, "No results found")
|
||||||
|
response = admin_client.get(url, {'q': admin_user.first_name})
|
||||||
|
assertContains(response, admin_user.first_name)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ urlpatterns = [
|
|||||||
path('', include('versioning.urls')),
|
path('', include('versioning.urls')),
|
||||||
path('', include('RIGS.urls')),
|
path('', include('RIGS.urls')),
|
||||||
path('assets/', include('assets.urls')),
|
path('assets/', include('assets.urls')),
|
||||||
|
path('training/', include('training.urls')),
|
||||||
|
|
||||||
path('', login_required(views.Index.as_view()), name='index'),
|
path('', login_required(views.Index.as_view()), name='index'),
|
||||||
|
|
||||||
@@ -22,10 +23,12 @@ urlpatterns = [
|
|||||||
name="api_secure"),
|
name="api_secure"),
|
||||||
|
|
||||||
path('closemodal/', views.CloseModal.as_view(), name='closemodal'),
|
path('closemodal/', views.CloseModal.as_view(), name='closemodal'),
|
||||||
|
path('search/', login_required(views.Search.as_view()), name='search'),
|
||||||
path('search_help/', login_required(views.SearchHelp.as_view()), name='search_help'),
|
path('search_help/', login_required(views.SearchHelp.as_view()), name='search_help'),
|
||||||
|
|
||||||
path('', include('users.urls')),
|
path('', include('users.urls')),
|
||||||
|
|
||||||
|
path('admin/', include('massadmin.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path("robots.txt", TemplateView.as_view(template_name="robots.txt", content_type="text/plain")),
|
path("robots.txt", TemplateView.as_view(template_name="robots.txt", content_type="text/plain")),
|
||||||
]
|
]
|
||||||
|
|||||||
203
PyRIGS/views.py
@@ -1,33 +1,54 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import operator
|
import operator
|
||||||
from functools import reduce
|
import re
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
import simplejson
|
from functools import reduce
|
||||||
|
from itertools import chain
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from PyPDF2 import PdfFileMerger, PdfFileReader
|
||||||
|
from z3c.rml import rml2pdf
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
|
from django.template.loader import get_template
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
from assets import models as asset_models
|
from assets import models as asset_models
|
||||||
|
from training import models as training_models
|
||||||
|
|
||||||
|
|
||||||
def is_ajax(request):
|
def is_ajax(request):
|
||||||
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
|
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
|
||||||
|
|
||||||
|
|
||||||
|
def get_related(form, context): # Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
||||||
|
for field, model in form.related_models.items():
|
||||||
|
value = form[field].value()
|
||||||
|
if value is not None and value != '':
|
||||||
|
context[field] = model.objects.get(pk=value)
|
||||||
|
|
||||||
|
|
||||||
class Index(generic.TemplateView): # Displays the current rig count along with a few other bits and pieces
|
class Index(generic.TemplateView): # Displays the current rig count along with a few other bits and pieces
|
||||||
template_name = 'index.html'
|
template_name = 'index.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(Index, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['rig_count'] = models.Event.objects.rig_count()
|
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
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +59,9 @@ class SecureAPIRequest(generic.View):
|
|||||||
'organisation': models.Organisation,
|
'organisation': models.Organisation,
|
||||||
'profile': models.Profile,
|
'profile': models.Profile,
|
||||||
'event': models.Event,
|
'event': models.Event,
|
||||||
'supplier': asset_models.Supplier
|
'asset': asset_models.Asset,
|
||||||
|
'supplier': asset_models.Supplier,
|
||||||
|
'training_item': training_models.TrainingItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
perms = {
|
perms = {
|
||||||
@@ -47,7 +70,9 @@ class SecureAPIRequest(generic.View):
|
|||||||
'organisation': 'RIGS.view_organisation',
|
'organisation': 'RIGS.view_organisation',
|
||||||
'profile': 'RIGS.view_profile',
|
'profile': 'RIGS.view_profile',
|
||||||
'event': None,
|
'event': None,
|
||||||
'supplier': None
|
'asset': None,
|
||||||
|
'supplier': None,
|
||||||
|
'training_item': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@@ -75,6 +100,9 @@ class SecureAPIRequest(generic.View):
|
|||||||
fields = request.GET.get('fields', None)
|
fields = request.GET.get('fields', None)
|
||||||
if fields:
|
if fields:
|
||||||
fields = fields.split(",")
|
fields = fields.split(",")
|
||||||
|
filters = request.GET.get('filters', [])
|
||||||
|
if filters:
|
||||||
|
filters = filters.split(",")
|
||||||
|
|
||||||
# Supply data for one record
|
# Supply data for one record
|
||||||
if pk:
|
if pk:
|
||||||
@@ -95,27 +123,35 @@ class SecureAPIRequest(generic.View):
|
|||||||
for field in fields:
|
for field in fields:
|
||||||
q = Q(**{field + "__icontains": part})
|
q = Q(**{field + "__icontains": part})
|
||||||
qs.append(q)
|
qs.append(q)
|
||||||
|
|
||||||
queries.append(reduce(operator.or_, qs))
|
queries.append(reduce(operator.or_, qs))
|
||||||
|
|
||||||
|
for f in filters:
|
||||||
|
q = Q(**{f: True})
|
||||||
|
queries.append(q)
|
||||||
|
|
||||||
# Build the data response list
|
# Build the data response list
|
||||||
results = []
|
results = []
|
||||||
query = reduce(operator.and_, queries)
|
query = reduce(operator.and_, queries)
|
||||||
objects = self.models[model].objects.filter(query)
|
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:
|
for o in objects:
|
||||||
|
name = o.display_name if hasattr(o, 'display_name') else o.name
|
||||||
data = {
|
data = {
|
||||||
'pk': o.pk,
|
'pk': o.pk,
|
||||||
'value': o.pk,
|
'value': o.pk,
|
||||||
'text': o.name,
|
'text': name,
|
||||||
}
|
}
|
||||||
try: # See if there is a valid update URL
|
try: # See if there is a valid update URL
|
||||||
data['update'] = reverse("%s_update" % model, kwargs={'pk': o.pk})
|
data['update'] = reverse(f"{model}_update", kwargs={'pk': o.pk})
|
||||||
except NoReverseMatch:
|
except NoReverseMatch:
|
||||||
pass
|
pass
|
||||||
results.append(data)
|
results.append(data)
|
||||||
|
|
||||||
# return a data response
|
# return a data response
|
||||||
json = simplejson.dumps(results)
|
return JsonResponse(results, safe=False)
|
||||||
return HttpResponse(json, content_type="application/json") # Always json
|
|
||||||
|
|
||||||
start = request.GET.get('start', None)
|
start = request.GET.get('start', None)
|
||||||
end = request.GET.get('end', None)
|
end = request.GET.get('end', None)
|
||||||
@@ -140,8 +176,7 @@ class SecureAPIRequest(generic.View):
|
|||||||
}
|
}
|
||||||
|
|
||||||
results.append(data)
|
results.append(data)
|
||||||
json = simplejson.dumps(results)
|
return JsonResponse(results, safe=False)
|
||||||
return HttpResponse(json, content_type="application/json") # Always json
|
|
||||||
|
|
||||||
return HttpResponse(model)
|
return HttpResponse(model)
|
||||||
|
|
||||||
@@ -152,7 +187,7 @@ class ModalURLMixin:
|
|||||||
url = reverse_lazy('closemodal')
|
url = reverse_lazy('closemodal')
|
||||||
update_url = str(reverse_lazy(update, kwargs={'pk': self.object.pk}))
|
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=" + 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:
|
else:
|
||||||
url = reverse_lazy(detail, kwargs={
|
url = reverse_lazy(detail, kwargs={
|
||||||
'pk': self.object.pk,
|
'pk': self.object.pk,
|
||||||
@@ -165,27 +200,14 @@ class GenericListView(generic.ListView):
|
|||||||
paginate_by = 20
|
paginate_by = 20
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GenericListView, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['page_title'] = self.model.__name__ + "s"
|
context['page_title'] = self.model.__name__ + "s"
|
||||||
if is_ajax(self.request):
|
if is_ajax(self.request):
|
||||||
context['override'] = "base_ajax.html"
|
context['override'] = "base_ajax.html"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
q = self.request.GET.get('q', "")
|
object_list = self.model.objects.search(query=self.request.GET.get('q', ""))
|
||||||
|
|
||||||
filter = Q(name__icontains=q) | Q(email__icontains=q) | Q(address__icontains=q) | Q(notes__icontains=q) | Q(
|
|
||||||
phone__startswith=q) | Q(phone__endswith=q)
|
|
||||||
|
|
||||||
# try and parse an int
|
|
||||||
try:
|
|
||||||
val = int(q)
|
|
||||||
filter = filter | Q(pk=val)
|
|
||||||
except: # noqa
|
|
||||||
# not an integer
|
|
||||||
pass
|
|
||||||
|
|
||||||
object_list = self.model.objects.filter(filter)
|
|
||||||
|
|
||||||
orderBy = self.request.GET.get('orderBy', "name")
|
orderBy = self.request.GET.get('orderBy', "name")
|
||||||
if orderBy != "":
|
if orderBy != "":
|
||||||
@@ -197,8 +219,8 @@ class GenericDetailView(generic.DetailView):
|
|||||||
template_name = "generic_detail.html"
|
template_name = "generic_detail.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GenericDetailView, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name)
|
context['page_title'] = f"{self.model.__name__} | {self.object.name}"
|
||||||
if is_ajax(self.request):
|
if is_ajax(self.request):
|
||||||
context['override'] = "base_ajax.html"
|
context['override'] = "base_ajax.html"
|
||||||
return context
|
return context
|
||||||
@@ -208,8 +230,8 @@ class GenericUpdateView(generic.UpdateView):
|
|||||||
template_name = "generic_form.html"
|
template_name = "generic_form.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GenericUpdateView, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['page_title'] = "Edit {}".format(self.model.__name__)
|
context['page_title'] = f"Edit {self.model.__name__}"
|
||||||
if is_ajax(self.request):
|
if is_ajax(self.request):
|
||||||
context['override'] = "base_ajax.html"
|
context['override'] = "base_ajax.html"
|
||||||
return context
|
return context
|
||||||
@@ -219,13 +241,60 @@ class GenericCreateView(generic.CreateView):
|
|||||||
template_name = "generic_form.html"
|
template_name = "generic_form.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GenericCreateView, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['page_title'] = "Create {}".format(self.model.__name__)
|
context['page_title'] = f"Create {self.model.__name__}"
|
||||||
if is_ajax(self.request):
|
if is_ajax(self.request):
|
||||||
context['override'] = "base_ajax.html"
|
context['override'] = "base_ajax.html"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class Search(generic.ListView):
|
||||||
|
template_name = 'search_results.html'
|
||||||
|
paginate_by = 20
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
context = super().get_context_data(*args, **kwargs)
|
||||||
|
context['count'] = self.count or 0
|
||||||
|
context['query'] = self.request.GET.get('q')
|
||||||
|
context['page_title'] = f"{context['count']} search results for <b>{context['query']}</b>"
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
request = self.request
|
||||||
|
query = request.GET.get('q', None)
|
||||||
|
|
||||||
|
if query is not None:
|
||||||
|
event_results = models.Event.objects.search(query)
|
||||||
|
person_results = models.Person.objects.search(query)
|
||||||
|
organisation_results = models.Organisation.objects.search(query)
|
||||||
|
venue_results = models.Venue.objects.search(query)
|
||||||
|
invoice_results = models.Invoice.objects.search(query)
|
||||||
|
asset_results = asset_models.Asset.objects.search(query)
|
||||||
|
supplier_results = asset_models.Supplier.objects.search(query)
|
||||||
|
trainee_results = training_models.Trainee.objects.search(query)
|
||||||
|
training_item_results = training_models.TrainingItem.objects.search(query)
|
||||||
|
|
||||||
|
# combine querysets
|
||||||
|
queryset_chain = chain(
|
||||||
|
event_results,
|
||||||
|
person_results,
|
||||||
|
organisation_results,
|
||||||
|
venue_results,
|
||||||
|
invoice_results,
|
||||||
|
asset_results,
|
||||||
|
supplier_results,
|
||||||
|
trainee_results,
|
||||||
|
training_item_results,
|
||||||
|
)
|
||||||
|
qs = sorted(queryset_chain,
|
||||||
|
key=lambda instance: instance.pk,
|
||||||
|
reverse=True)
|
||||||
|
self.count = len(qs) # since qs is actually a list
|
||||||
|
return qs
|
||||||
|
return models.Event.objects.none() # just an empty queryset as default
|
||||||
|
|
||||||
|
|
||||||
class SearchHelp(generic.TemplateView):
|
class SearchHelp(generic.TemplateView):
|
||||||
template_name = 'search_help.html'
|
template_name = 'search_help.html'
|
||||||
|
|
||||||
@@ -245,14 +314,72 @@ class CloseModal(generic.TemplateView):
|
|||||||
class OEmbedView(generic.View):
|
class OEmbedView(generic.View):
|
||||||
def get(self, request, pk=None):
|
def get(self, request, pk=None):
|
||||||
embed_url = reverse(self.url_name, args=[pk])
|
embed_url = reverse(self.url_name, args=[pk])
|
||||||
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
full_url = f"{request.scheme}://{request.META['HTTP_HOST']}{embed_url}"
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
|
'html': f'<iframe src="{full_url}" frameborder="0" width="100%" height="250"></iframe>',
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
'type': 'rich',
|
'type': 'rich',
|
||||||
'height': '250'
|
'height': '250'
|
||||||
}
|
}
|
||||||
|
|
||||||
json = simplejson.JSONEncoderForHTML().encode(data)
|
return JsonResponse(data)
|
||||||
return HttpResponse(json, content_type="application/json")
|
|
||||||
|
|
||||||
|
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'])
|
||||||
|
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': get_info_string(self.request.user) + f"- {obj.current_version_id}]",
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
return render_pdf_response(get_template(self.template_name), self.get_context_data(), self.append_terms)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
- PyRIGS: Base app, stores 'global' information
|
||||||
- RIGS: Rigboard stuff - event calendar etc
|
- RIGS: Rigboard stuff - event calendar etc
|
||||||
- assets: Database of our kit, testing data 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.
|
- versioning: Our custom logic built on top of django-reversion. Semi-modular.
|
||||||
- users: Our custom logic for registration and profiles. Semi-modular.
|
- users: Our custom logic for registration and profiles. Semi-modular.
|
||||||
- training: SoonTM
|
|
||||||
|
|
||||||
[](https://forthebadge.com) [](https://forthebadge.com)
|
[](https://forthebadge.com) [](https://forthebadge.com)
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
default_app_config = 'RIGS.apps.RIGSAppConfig'
|
|
||||||
|
|||||||
202
RIGS/admin.py
@@ -8,30 +8,155 @@ from django.db.models import Count
|
|||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import IntegrityError
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
from users import forms as user_forms
|
from users import forms as user_forms
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
admin.site.register(models.VatRate, VersionAdmin)
|
admin.site.register(models.VatRate, VersionAdmin)
|
||||||
admin.site.register(models.Event, VersionAdmin)
|
admin.site.register(models.Event, VersionAdmin)
|
||||||
admin.site.register(models.EventItem, VersionAdmin)
|
admin.site.register(models.EventItem, VersionAdmin)
|
||||||
admin.site.register(models.Invoice, VersionAdmin)
|
admin.site.register(models.Invoice, VersionAdmin)
|
||||||
|
admin.site.register(models.EventCheckIn)
|
||||||
|
|
||||||
|
|
||||||
def approve_user(modeladmin, request, queryset):
|
@transaction.atomic() # Copied from django-extensions. GenericForeignKey support removed as unnecessary.
|
||||||
queryset.update(is_approved=True)
|
def merge_model_instances(primary_object, alias_objects):
|
||||||
|
"""
|
||||||
|
Merge several model instances into one, the `primary_object`.
|
||||||
|
Use this function to merge model objects and migrate all of the related
|
||||||
|
fields from the alias objects the primary object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# get related fields
|
||||||
|
related_fields = list(filter(
|
||||||
|
lambda x: x.is_relation is True,
|
||||||
|
primary_object._meta.get_fields()))
|
||||||
|
|
||||||
|
many_to_many_fields = list(filter(
|
||||||
|
lambda x: x.many_to_many is True, related_fields))
|
||||||
|
|
||||||
|
related_fields = list(filter(
|
||||||
|
lambda x: x.many_to_many is False, related_fields))
|
||||||
|
|
||||||
|
# Loop through all alias objects and migrate their references to the
|
||||||
|
# primary object
|
||||||
|
deleted_objects = []
|
||||||
|
deleted_objects_count = 0
|
||||||
|
for alias_object in alias_objects:
|
||||||
|
# Migrate all foreign key references from alias object to primary
|
||||||
|
# object.
|
||||||
|
for many_to_many_field in many_to_many_fields:
|
||||||
|
alias_varname = many_to_many_field.name
|
||||||
|
related_objects = getattr(alias_object, alias_varname)
|
||||||
|
for obj in related_objects.all():
|
||||||
|
try:
|
||||||
|
# Handle regular M2M relationships.
|
||||||
|
getattr(alias_object, alias_varname).remove(obj)
|
||||||
|
getattr(primary_object, alias_varname).add(obj)
|
||||||
|
except AttributeError:
|
||||||
|
# Handle M2M relationships with a 'through' model.
|
||||||
|
# This does not delete the 'through model.
|
||||||
|
# TODO: Allow the user to delete a duplicate 'through' model.
|
||||||
|
through_model = getattr(alias_object, alias_varname).through
|
||||||
|
kwargs = {
|
||||||
|
many_to_many_field.m2m_reverse_field_name(): obj,
|
||||||
|
many_to_many_field.m2m_field_name(): alias_object,
|
||||||
|
}
|
||||||
|
through_model_instances = through_model.objects.filter(**kwargs)
|
||||||
|
for instance in through_model_instances:
|
||||||
|
# Re-attach the through model to the primary_object
|
||||||
|
setattr(
|
||||||
|
instance,
|
||||||
|
many_to_many_field.m2m_field_name(),
|
||||||
|
primary_object)
|
||||||
|
instance.save()
|
||||||
|
# TODO: Here, try to delete duplicate instances that are
|
||||||
|
# disallowed by a unique_together constraint
|
||||||
|
|
||||||
|
for related_field in related_fields:
|
||||||
|
if related_field.one_to_many:
|
||||||
|
with transaction.atomic():
|
||||||
|
try:
|
||||||
|
alias_varname = related_field.get_accessor_name()
|
||||||
|
related_objects = getattr(alias_object, alias_varname)
|
||||||
|
for obj in related_objects.all():
|
||||||
|
field_name = related_field.field.name
|
||||||
|
setattr(obj, field_name, primary_object)
|
||||||
|
obj.save()
|
||||||
|
except IntegrityError:
|
||||||
|
pass # Skip to avoid integrity error from unique_together
|
||||||
|
elif related_field.one_to_one or related_field.many_to_one:
|
||||||
|
alias_varname = related_field.name
|
||||||
|
if hasattr(alias_object, alias_varname):
|
||||||
|
related_object = getattr(alias_object, alias_varname)
|
||||||
|
primary_related_object = getattr(primary_object, alias_varname)
|
||||||
|
if primary_related_object is None:
|
||||||
|
setattr(primary_object, alias_varname, related_object)
|
||||||
|
primary_object.save()
|
||||||
|
elif related_field.one_to_one:
|
||||||
|
related_object.delete()
|
||||||
|
|
||||||
|
if alias_object.id:
|
||||||
|
deleted_objects += [alias_object]
|
||||||
|
alias_object.delete()
|
||||||
|
deleted_objects_count += 1
|
||||||
|
|
||||||
|
return primary_object, deleted_objects, deleted_objects_count
|
||||||
|
|
||||||
|
|
||||||
approve_user.short_description = "Approve selected users"
|
class AssociateAdmin(VersionAdmin):
|
||||||
|
search_fields = ['id', 'name']
|
||||||
|
list_display_links = ['id', 'name']
|
||||||
|
actions = ['merge']
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
return super().get_queryset(request).annotate(event_count=Count('event'))
|
||||||
|
|
||||||
|
def number_of_events(self, obj):
|
||||||
|
return obj.latest_events.count()
|
||||||
|
|
||||||
|
number_of_events.admin_order_field = 'event_count'
|
||||||
|
|
||||||
|
def merge(self, request, queryset):
|
||||||
|
if request.POST.get('post'): # Has the user confirmed which is the master record?
|
||||||
|
try:
|
||||||
|
master_object_pk = request.POST.get('master')
|
||||||
|
master_object = queryset.get(pk=master_object_pk)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
self.message_user(request, "An error occured. Did you select a 'master' record?", level=messages.ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
|
primary_object, deleted_objects, deleted_objects_count = merge_model_instances(master_object, queryset.exclude(pk=master_object_pk).all())
|
||||||
|
reversion.set_comment('Merging Objects')
|
||||||
|
self.message_user(request, f"Objects successfully merged. {deleted_objects_count} old objects deleted.")
|
||||||
|
else: # Present the confirmation screen
|
||||||
|
class TempForm(ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = queryset.model
|
||||||
|
fields = self.merge_fields
|
||||||
|
|
||||||
|
forms = []
|
||||||
|
for obj in queryset:
|
||||||
|
forms.append(TempForm(instance=obj))
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': _("Are you sure?"),
|
||||||
|
'queryset': queryset,
|
||||||
|
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
|
||||||
|
'forms': forms
|
||||||
|
}
|
||||||
|
return TemplateResponse(request, 'admin_associate_merge.html', context)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Profile)
|
@admin.register(models.Profile)
|
||||||
class ProfileAdmin(UserAdmin):
|
class ProfileAdmin(UserAdmin, AssociateAdmin):
|
||||||
# Don't know how to add 'is_approved' whilst preserving the default list...
|
list_display = ('username', 'name', 'is_approved', 'is_superuser', 'is_supervisor', 'number_of_events', 'last_login')
|
||||||
list_filter = ('is_approved', 'is_active', 'is_staff', 'is_superuser', 'groups')
|
list_display_links = ['username']
|
||||||
|
list_filter = UserAdmin.list_filter + ('is_approved',)
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {'fields': ('username', 'password')}),
|
(None, {'fields': ('username', 'password')}),
|
||||||
(_('Personal info'), {
|
(_('Personal info'), {
|
||||||
@@ -49,62 +174,12 @@ class ProfileAdmin(UserAdmin):
|
|||||||
)
|
)
|
||||||
form = user_forms.ProfileChangeForm
|
form = user_forms.ProfileChangeForm
|
||||||
add_form = user_forms.ProfileCreationForm
|
add_form = user_forms.ProfileCreationForm
|
||||||
actions = [approve_user]
|
actions = ['approve_user', 'merge']
|
||||||
|
|
||||||
|
merge_fields = ['username', 'first_name', 'last_name', 'initials', 'email', 'phone', 'is_supervisor']
|
||||||
|
|
||||||
class AssociateAdmin(VersionAdmin):
|
def approve_user(modeladmin, request, queryset):
|
||||||
list_display = ('id', 'name', 'number_of_events')
|
queryset.update(is_approved=True)
|
||||||
search_fields = ['id', 'name']
|
|
||||||
list_display_links = ['id', 'name']
|
|
||||||
actions = ['merge']
|
|
||||||
|
|
||||||
merge_fields = ['name']
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
return super(AssociateAdmin, self).get_queryset(request).annotate(event_count=Count('event'))
|
|
||||||
|
|
||||||
def number_of_events(self, obj):
|
|
||||||
return obj.latest_events.count()
|
|
||||||
|
|
||||||
number_of_events.admin_order_field = 'event_count'
|
|
||||||
|
|
||||||
def merge(self, request, queryset):
|
|
||||||
if request.POST.get('post'): # Has the user confirmed which is the master record?
|
|
||||||
try:
|
|
||||||
masterObjectPk = request.POST.get('master')
|
|
||||||
masterObject = queryset.get(pk=masterObjectPk)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
self.message_user(request, "An error occured. Did you select a 'master' record?", level=messages.ERROR)
|
|
||||||
return
|
|
||||||
|
|
||||||
with transaction.atomic(), reversion.create_revision():
|
|
||||||
for obj in queryset.exclude(pk=masterObjectPk):
|
|
||||||
events = obj.event_set.all()
|
|
||||||
for event in events:
|
|
||||||
masterObject.event_set.add(event)
|
|
||||||
obj.delete()
|
|
||||||
reversion.set_comment('Merging Objects')
|
|
||||||
|
|
||||||
self.message_user(request, "Objects successfully merged.")
|
|
||||||
return
|
|
||||||
else: # Present the confirmation screen
|
|
||||||
|
|
||||||
class TempForm(ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = queryset.model
|
|
||||||
fields = self.merge_fields
|
|
||||||
|
|
||||||
forms = []
|
|
||||||
for obj in queryset:
|
|
||||||
forms.append(TempForm(instance=obj))
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'title': _("Are you sure?"),
|
|
||||||
'queryset': queryset,
|
|
||||||
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
|
|
||||||
'forms': forms
|
|
||||||
}
|
|
||||||
return TemplateResponse(request, 'admin_associate_merge.html', context)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Person)
|
@admin.register(models.Person)
|
||||||
@@ -133,3 +208,8 @@ class RiskAssessmentAdmin(VersionAdmin):
|
|||||||
@admin.register(models.EventChecklist)
|
@admin.register(models.EventChecklist)
|
||||||
class EventChecklistAdmin(VersionAdmin):
|
class EventChecklistAdmin(VersionAdmin):
|
||||||
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.PowerTestRecord)
|
||||||
|
class EventChecklistAdmin(VersionAdmin):
|
||||||
|
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
|
||||||
|
|||||||
146
RIGS/forms.py
@@ -8,6 +8,7 @@ from django.utils import timezone
|
|||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
from training.models import TrainingLevel
|
||||||
|
|
||||||
# Override the django form defaults to use the HTML date/time/datetime UI elements
|
# Override the django form defaults to use the HTML date/time/datetime UI elements
|
||||||
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
||||||
@@ -43,7 +44,7 @@ class EventForm(forms.ModelForm):
|
|||||||
return simplejson.dumps(items)
|
return simplejson.dumps(items)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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['items_json'].initial = self._get_items_json
|
||||||
self.fields['start_date'].widget.format = '%Y-%m-%d'
|
self.fields['start_date'].widget.format = '%Y-%m-%d'
|
||||||
@@ -96,10 +97,10 @@ class EventForm(forms.ModelForm):
|
|||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
'You haven\'t provided any client contact details. Please add a person or organisation.',
|
'You haven\'t provided any client contact details. Please add a person or organisation.',
|
||||||
code='contact')
|
code='contact')
|
||||||
return super(EventForm, self).clean()
|
return super().clean()
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
m = super(EventForm, self).save(commit=False)
|
m = super().save(commit=False)
|
||||||
|
|
||||||
if (commit):
|
if (commit):
|
||||||
m.save()
|
m.save()
|
||||||
@@ -120,7 +121,7 @@ class EventForm(forms.ModelForm):
|
|||||||
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
|
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
|
||||||
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
|
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
|
||||||
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
|
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
|
||||||
'purchase_order', 'collector']
|
'purchase_order', 'collector', 'forum_url']
|
||||||
|
|
||||||
|
|
||||||
class BaseClientEventAuthorisationForm(forms.ModelForm):
|
class BaseClientEventAuthorisationForm(forms.ModelForm):
|
||||||
@@ -130,7 +131,7 @@ class BaseClientEventAuthorisationForm(forms.ModelForm):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
if self.cleaned_data.get('amount') != self.instance.event.total:
|
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).')
|
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:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
@@ -138,7 +139,7 @@ class BaseClientEventAuthorisationForm(forms.ModelForm):
|
|||||||
|
|
||||||
class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
|
class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(InternalClientEventAuthorisationForm, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.fields['uni_id'].required = True
|
self.fields['uni_id'].required = True
|
||||||
self.fields['account_code'].required = True
|
self.fields['account_code'].required = True
|
||||||
|
|
||||||
@@ -152,8 +153,12 @@ class EventAuthorisationRequestForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class EventRiskAssessmentForm(forms.ModelForm):
|
class EventRiskAssessmentForm(forms.ModelForm):
|
||||||
|
related_models = {
|
||||||
|
'power_mic': models.Profile,
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EventRiskAssessmentForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
for name, field in self.fields.items():
|
for name, field in self.fields.items():
|
||||||
if str(name) == 'supervisor_consulted':
|
if str(name) == 'supervisor_consulted':
|
||||||
field.widget = forms.CheckboxInput()
|
field.widget = forms.CheckboxInput()
|
||||||
@@ -164,14 +169,17 @@ class EventRiskAssessmentForm(forms.ModelForm):
|
|||||||
], attrs={'class': 'custom-control-input', 'required': 'true'})
|
], attrs={'class': 'custom-control-input', 'required': 'true'})
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
if self.cleaned_data.get('big_power'):
|
||||||
|
if not self.cleaned_data.get('power_mic').level_qualifications.filter(level__department=TrainingLevel.POWER).exists():
|
||||||
|
self.add_error('power_mic', forms.ValidationError("Your Power MIC must be a Power Technician.", code="power_tech_required"))
|
||||||
# Check expected values
|
# Check expected values
|
||||||
unexpected_values = []
|
unexpected_values = []
|
||||||
for field, value in models.RiskAssessment.expected_values.items():
|
for field, value in models.RiskAssessment.expected_values.items():
|
||||||
if self.cleaned_data.get(field) != value:
|
if self.cleaned_data.get(field) != value:
|
||||||
unexpected_values.append("<li>{}</li>".format(self._meta.model._meta.get_field(field).help_text))
|
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'):
|
if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'):
|
||||||
raise forms.ValidationError("Your answers to these questions: <ul>{}</ul> require consulting with a supervisor.".format(''.join([str(elem) for elem in unexpected_values])), code='unusual_answers')
|
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:
|
class Meta:
|
||||||
model = models.RiskAssessment
|
model = models.RiskAssessment
|
||||||
@@ -181,97 +189,55 @@ class EventRiskAssessmentForm(forms.ModelForm):
|
|||||||
|
|
||||||
class EventChecklistForm(forms.ModelForm):
|
class EventChecklistForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(EventChecklistForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['date'].widget.format = '%Y-%m-%d'
|
self.fields['date'].widget.format = '%Y-%m-%d'
|
||||||
for name, field in self.fields.items():
|
for name, field in self.fields.items():
|
||||||
if field.__class__ == forms.NullBooleanField:
|
if field.__class__ == forms.NullBooleanField:
|
||||||
# Only display yes/no to user, the 'none' is only ever set in the background
|
# Only display yes/no to user, the 'none' is only ever set in the background
|
||||||
field.widget = forms.CheckboxInput()
|
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 = {
|
related_models = {
|
||||||
'venue': models.Venue,
|
'venue': models.Venue,
|
||||||
'power_mic': models.Profile,
|
'power_mic': models.Profile,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Two possible formats
|
|
||||||
def parsedatetime(self, date_string):
|
|
||||||
try:
|
|
||||||
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S'))
|
|
||||||
except ValueError:
|
|
||||||
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M'))
|
|
||||||
|
|
||||||
# There's probably a thousand better ways to do this, but this one is mine
|
|
||||||
def clean(self):
|
|
||||||
vehicles = {key: val for key, val in self.data.items()
|
|
||||||
if key.startswith('vehicle')}
|
|
||||||
for key in vehicles:
|
|
||||||
pk = int(key.split('_')[1])
|
|
||||||
driver_key = 'driver_' + str(pk)
|
|
||||||
if(self.data[driver_key] == ''):
|
|
||||||
raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
item = models.EventChecklistVehicle.objects.get(pk=pk)
|
|
||||||
except models.EventChecklistVehicle.DoesNotExist:
|
|
||||||
item = models.EventChecklistVehicle()
|
|
||||||
|
|
||||||
item.vehicle = vehicles['vehicle_' + str(pk)]
|
|
||||||
item.driver = models.Profile.objects.get(pk=self.data[driver_key])
|
|
||||||
item.full_clean('checklist')
|
|
||||||
|
|
||||||
# item does not have a database pk yet as it isn't saved
|
|
||||||
self.items['v' + str(pk)] = item
|
|
||||||
|
|
||||||
crewmembers = {key: val for key, val in self.data.items()
|
|
||||||
if key.startswith('crewmember')}
|
|
||||||
other_fields = ['start', 'role', 'end']
|
|
||||||
for key in crewmembers:
|
|
||||||
pk = int(key.split('_')[1])
|
|
||||||
|
|
||||||
for field in other_fields:
|
|
||||||
value = self.data['{}_{}'.format(field, pk)]
|
|
||||||
if value == '':
|
|
||||||
raise forms.ValidationError('Add a {} to crewmember {}'.format(field, pk), code='{}_mismatch'.format(field))
|
|
||||||
|
|
||||||
try:
|
|
||||||
item = models.EventChecklistCrew.objects.get(pk=pk)
|
|
||||||
except models.EventChecklistCrew.DoesNotExist:
|
|
||||||
item = models.EventChecklistCrew()
|
|
||||||
|
|
||||||
item.crewmember = models.Profile.objects.get(pk=self.data['crewmember_' + str(pk)])
|
|
||||||
item.start = self.parsedatetime(self.data['start_' + str(pk)])
|
|
||||||
item.role = self.data['role_' + str(pk)]
|
|
||||||
item.end = self.parsedatetime(self.data['end_' + str(pk)])
|
|
||||||
item.full_clean('checklist')
|
|
||||||
|
|
||||||
# item does not have a database pk yet as it isn't saved
|
|
||||||
self.items['c' + str(pk)] = item
|
|
||||||
|
|
||||||
return super(EventChecklistForm, self).clean()
|
|
||||||
|
|
||||||
def save(self, commit=True):
|
|
||||||
checklist = super(EventChecklistForm, self).save(commit=False)
|
|
||||||
if (commit):
|
|
||||||
# Remove all existing, to be recreated from the form
|
|
||||||
checklist.vehicles.all().delete()
|
|
||||||
checklist.crew.all().delete()
|
|
||||||
checklist.save()
|
|
||||||
|
|
||||||
for key in self.items:
|
|
||||||
item = self.items[key]
|
|
||||||
reversion.add_to_revision(item)
|
|
||||||
# finish and save new database items
|
|
||||||
item.checklist = checklist
|
|
||||||
item.full_clean()
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
self.items.clear()
|
|
||||||
|
|
||||||
return checklist
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.EventChecklist
|
model = models.PowerTestRecord
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
exclude = ['reviewed_at', 'reviewed_by']
|
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__'
|
||||||
|
|||||||
226
RIGS/hs.py
@@ -1,226 +0,0 @@
|
|||||||
from django.contrib import messages
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.views import generic
|
|
||||||
from reversion import revisions as reversion
|
|
||||||
|
|
||||||
from RIGS import models, forms
|
|
||||||
|
|
||||||
|
|
||||||
class EventRiskAssessmentCreate(generic.CreateView):
|
|
||||||
model = models.RiskAssessment
|
|
||||||
template_name = 'risk_assessment_form.html'
|
|
||||||
form_class = forms.EventRiskAssessmentForm
|
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
epk = kwargs.get('pk')
|
|
||||||
event = models.Event.objects.get(pk=epk)
|
|
||||||
|
|
||||||
# Check if RA exists
|
|
||||||
ra = models.RiskAssessment.objects.filter(event=event).first()
|
|
||||||
|
|
||||||
if ra is not None:
|
|
||||||
return HttpResponseRedirect(reverse_lazy('ra_edit', kwargs={'pk': ra.pk}))
|
|
||||||
|
|
||||||
return super(EventRiskAssessmentCreate, self).get(self)
|
|
||||||
|
|
||||||
def get_form(self, **kwargs):
|
|
||||||
form = super(EventRiskAssessmentCreate, self).get_form(**kwargs)
|
|
||||||
epk = self.kwargs.get('pk')
|
|
||||||
event = models.Event.objects.get(pk=epk)
|
|
||||||
form.instance.event = event
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(EventRiskAssessmentCreate, self).get_context_data(**kwargs)
|
|
||||||
epk = self.kwargs.get('pk')
|
|
||||||
event = models.Event.objects.get(pk=epk)
|
|
||||||
context['event'] = event
|
|
||||||
context['page_title'] = 'Create Risk Assessment for Event {}'.format(event.display_id)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
|
|
||||||
|
|
||||||
|
|
||||||
class EventRiskAssessmentEdit(generic.UpdateView):
|
|
||||||
model = models.RiskAssessment
|
|
||||||
template_name = 'risk_assessment_form.html'
|
|
||||||
form_class = forms.EventRiskAssessmentForm
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
ra = self.get_object()
|
|
||||||
ra.reviewed_by = None
|
|
||||||
ra.reviewed_at = None
|
|
||||||
ra.save()
|
|
||||||
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(EventRiskAssessmentEdit, self).get_context_data(**kwargs)
|
|
||||||
rpk = self.kwargs.get('pk')
|
|
||||||
ra = models.RiskAssessment.objects.get(pk=rpk)
|
|
||||||
context['event'] = ra.event
|
|
||||||
context['edit'] = True
|
|
||||||
context['page_title'] = 'Edit Risk Assessment for Event {}'.format(ra.event.display_id)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class EventRiskAssessmentDetail(generic.DetailView):
|
|
||||||
model = models.RiskAssessment
|
|
||||||
template_name = 'risk_assessment_detail.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(EventRiskAssessmentDetail, self).get_context_data(**kwargs)
|
|
||||||
context['page_title'] = "Risk Assessment for Event <a href='{}'>{} {}</a>".format(self.object.event.get_absolute_url(), self.object.event.display_id, self.object.event.name)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class EventRiskAssessmentList(generic.ListView):
|
|
||||||
paginate_by = 20
|
|
||||||
model = models.RiskAssessment
|
|
||||||
template_name = '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 = 'event_checklist_detail.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(EventChecklistDetail, self).get_context_data(**kwargs)
|
|
||||||
context['page_title'] = "Event Checklist for Event <a href='{}'>{} {}</a>".format(self.object.event.get_absolute_url(), self.object.event.display_id, self.object.event.name)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class EventChecklistEdit(generic.UpdateView):
|
|
||||||
model = models.EventChecklist
|
|
||||||
template_name = 'event_checklist_form.html'
|
|
||||||
form_class = forms.EventChecklistForm
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
ec = self.get_object()
|
|
||||||
ec.reviewed_by = None
|
|
||||||
ec.reviewed_at = None
|
|
||||||
ec.save()
|
|
||||||
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(EventChecklistEdit, self).get_context_data(**kwargs)
|
|
||||||
pk = self.kwargs.get('pk')
|
|
||||||
ec = models.EventChecklist.objects.get(pk=pk)
|
|
||||||
context['event'] = ec.event
|
|
||||||
context['edit'] = True
|
|
||||||
context['page_title'] = 'Edit Event Checklist for Event {}'.format(ec.event.display_id)
|
|
||||||
form = context['form']
|
|
||||||
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
|
||||||
for field, model in form.related_models.items():
|
|
||||||
value = form[field].value()
|
|
||||||
if value is not None and value != '':
|
|
||||||
context[field] = model.objects.get(pk=value)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class EventChecklistCreate(generic.CreateView):
|
|
||||||
model = models.EventChecklist
|
|
||||||
template_name = 'event_checklist_form.html'
|
|
||||||
form_class = forms.EventChecklistForm
|
|
||||||
|
|
||||||
# From both business logic and programming POVs, RAs must exist before ECs!
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
epk = kwargs.get('pk')
|
|
||||||
event = models.Event.objects.get(pk=epk)
|
|
||||||
|
|
||||||
# Check if RA exists
|
|
||||||
ra = models.RiskAssessment.objects.filter(event=event).first()
|
|
||||||
|
|
||||||
if ra is None:
|
|
||||||
messages.error(self.request, 'A Risk Assessment must exist prior to creating any Event Checklists for {}! Please create one now.'.format(event))
|
|
||||||
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
|
|
||||||
|
|
||||||
return super(EventChecklistCreate, self).get(self)
|
|
||||||
|
|
||||||
def get_form(self, **kwargs):
|
|
||||||
form = super(EventChecklistCreate, self).get_form(**kwargs)
|
|
||||||
epk = self.kwargs.get('pk')
|
|
||||||
event = models.Event.objects.get(pk=epk)
|
|
||||||
form.instance.event = event
|
|
||||||
return form
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(EventChecklistCreate, self).get_context_data(**kwargs)
|
|
||||||
epk = self.kwargs.get('pk')
|
|
||||||
event = models.Event.objects.get(pk=epk)
|
|
||||||
context['event'] = event
|
|
||||||
context['page_title'] = 'Create Event Checklist for Event {}'.format(event.display_id)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
|
|
||||||
|
|
||||||
|
|
||||||
class EventChecklistList(generic.ListView):
|
|
||||||
paginate_by = 20
|
|
||||||
model = models.EventChecklist
|
|
||||||
template_name = 'hs_object_list.html'
|
|
||||||
|
|
||||||
def get_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(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'
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class EventChecklistReview(generic.View):
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
rpk = kwargs.get('pk')
|
|
||||||
ec = models.EventChecklist.objects.get(pk=rpk)
|
|
||||||
with reversion.create_revision():
|
|
||||||
reversion.set_user(self.request.user)
|
|
||||||
ec.reviewed_by = self.request.user
|
|
||||||
ec.reviewed_at = timezone.now()
|
|
||||||
ec.save()
|
|
||||||
return HttpResponseRedirect(reverse_lazy('ec_list'))
|
|
||||||
|
|
||||||
|
|
||||||
class HSList(generic.ListView):
|
|
||||||
paginate_by = 20
|
|
||||||
model = models.Event
|
|
||||||
template_name = 'hs_list.html'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return models.Event.objects.all().exclude(status=models.Event.CANCELLED).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['page_title'] = 'H&S Overview'
|
|
||||||
return context
|
|
||||||
@@ -3,6 +3,7 @@ from django.core.management.base import BaseCommand, CommandError
|
|||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from assets import models
|
from assets import models
|
||||||
from RIGS import models as rigsmodels
|
from RIGS import models as rigsmodels
|
||||||
|
from training import models as tmodels
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@@ -31,6 +32,11 @@ class Command(BaseCommand):
|
|||||||
self.delete_objects(rigsmodels.Payment)
|
self.delete_objects(rigsmodels.Payment)
|
||||||
self.delete_objects(rigsmodels.RiskAssessment)
|
self.delete_objects(rigsmodels.RiskAssessment)
|
||||||
self.delete_objects(rigsmodels.EventChecklist)
|
self.delete_objects(rigsmodels.EventChecklist)
|
||||||
|
self.delete_objects(tmodels.TrainingCategory)
|
||||||
|
self.delete_objects(tmodels.TrainingItem)
|
||||||
|
self.delete_objects(tmodels.TrainingLevel)
|
||||||
|
self.delete_objects(tmodels.TrainingItemQualification)
|
||||||
|
self.delete_objects(tmodels.TrainingLevelRequirement)
|
||||||
|
|
||||||
def delete_objects(self, model):
|
def delete_objects(self, model):
|
||||||
for obj in model.objects.all():
|
for obj in model.objects.all():
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ class Command(BaseCommand):
|
|||||||
call_command('generateSampleUserData')
|
call_command('generateSampleUserData')
|
||||||
call_command('generateSampleRIGSData')
|
call_command('generateSampleRIGSData')
|
||||||
call_command('generateSampleAssetsData')
|
call_command('generateSampleAssetsData')
|
||||||
|
call_command('generateSampleTrainingData')
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class Command(BaseCommand):
|
|||||||
profiles = models.Profile.objects.all()
|
profiles = models.Profile.objects.all()
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
print("Generating rigboard data")
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
if not (settings.DEBUG or settings.STAGING):
|
if not (settings.DEBUG or settings.STAGING):
|
||||||
@@ -35,6 +36,7 @@ class Command(BaseCommand):
|
|||||||
self.setup_organisations()
|
self.setup_organisations()
|
||||||
self.setup_venues()
|
self.setup_venues()
|
||||||
self.setup_events()
|
self.setup_events()
|
||||||
|
print("Done generating rigboard data")
|
||||||
|
|
||||||
def setup_people(self):
|
def setup_people(self):
|
||||||
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
|
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
|
||||||
@@ -276,7 +278,7 @@ class Command(BaseCommand):
|
|||||||
suspended_structures=bool(random.getrandbits(1)),
|
suspended_structures=bool(random.getrandbits(1)),
|
||||||
outside=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
|
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_parking=bool(random.getrandbits(1)),
|
||||||
safe_packing=bool(random.getrandbits(1)),
|
safe_packing=bool(random.getrandbits(1)),
|
||||||
exits=bool(random.getrandbits(1)),
|
exits=bool(random.getrandbits(1)),
|
||||||
@@ -285,6 +287,4 @@ class Command(BaseCommand):
|
|||||||
ear_plugs=bool(random.getrandbits(1)),
|
ear_plugs=bool(random.getrandbits(1)),
|
||||||
hs_location="Locked away safely",
|
hs_location="Locked away safely",
|
||||||
extinguishers_location="Somewhere, I forgot",
|
extinguishers_location="Somewhere, I forgot",
|
||||||
earthing=bool(random.getrandbits(1)),
|
|
||||||
pat=bool(random.getrandbits(1)),
|
|
||||||
date=timezone.now(), venue=random.choice(self.venues))
|
date=timezone.now(), venue=random.choice(self.venues))
|
||||||
|
|||||||
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()
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
import RIGS.models
|
import RIGS.models
|
||||||
|
import versioning
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -25,6 +26,6 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
},
|
},
|
||||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
import RIGS.models
|
import RIGS.models
|
||||||
|
import versioning
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -21,6 +22,6 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
},
|
},
|
||||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import RIGS.models
|
import RIGS.models
|
||||||
|
import versioning
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -41,7 +42,7 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
},
|
},
|
||||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='EventItem',
|
name='EventItem',
|
||||||
@@ -70,7 +71,7 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
},
|
},
|
||||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='event',
|
model_name='event',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import RIGS.models
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import versioning
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -58,7 +59,7 @@ class Migration(migrations.Migration):
|
|||||||
'ordering': ['event'],
|
'ordering': ['event'],
|
||||||
'permissions': [('review_eventchecklist', 'Can review Event Checklists')],
|
'permissions': [('review_eventchecklist', 'Can review Event Checklists')],
|
||||||
},
|
},
|
||||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='EventChecklistCrew',
|
name='EventChecklistCrew',
|
||||||
@@ -69,7 +70,7 @@ class Migration(migrations.Migration):
|
|||||||
('end', models.DateTimeField()),
|
('end', models.DateTimeField()),
|
||||||
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')),
|
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')),
|
||||||
],
|
],
|
||||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='EventChecklistVehicle',
|
name='EventChecklistVehicle',
|
||||||
@@ -78,7 +79,7 @@ class Migration(migrations.Migration):
|
|||||||
('vehicle', models.CharField(max_length=255)),
|
('vehicle', models.CharField(max_length=255)),
|
||||||
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')),
|
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')),
|
||||||
],
|
],
|
||||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='RiskAssessment',
|
name='RiskAssessment',
|
||||||
@@ -117,7 +118,7 @@ class Migration(migrations.Migration):
|
|||||||
'ordering': ['event'],
|
'ordering': ['event'],
|
||||||
'permissions': [('review_riskassessment', 'Can review Risk Assessments')],
|
'permissions': [('review_riskassessment', 'Can review Risk Assessments')],
|
||||||
},
|
},
|
||||||
bases=(models.Model, RIGS.models.RevisionMixin),
|
bases=(models.Model, versioning.versioning.RevisionMixin),
|
||||||
),
|
),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='eventcrew',
|
model_name='eventcrew',
|
||||||
|
|||||||
18
RIGS/migrations/0043_auto_20211027_1519.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1.13 on 2021-10-27 14:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0042_auto_20211007_2338'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='profile',
|
||||||
|
name='initials',
|
||||||
|
field=models.CharField(max_length=5, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
RIGS/migrations/0044_profile_is_supervisor.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.11 on 2022-01-09 14:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0043_auto_20211027_1519'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='is_supervisor',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
338
RIGS/models.py
@@ -8,6 +8,7 @@ from urllib.parse import urlparse
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models import Q, F
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@@ -17,16 +18,31 @@ from django.utils import timezone
|
|||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
|
from versioning.versioning import RevisionMixin
|
||||||
|
|
||||||
|
|
||||||
|
def filter_by_pk(filt, query):
|
||||||
|
# try and parse an int
|
||||||
|
try:
|
||||||
|
val = int(query)
|
||||||
|
filt = filt | Q(pk=val)
|
||||||
|
except: # noqa
|
||||||
|
# not an integer
|
||||||
|
pass
|
||||||
|
return filt
|
||||||
|
|
||||||
|
|
||||||
class Profile(AbstractUser):
|
class Profile(AbstractUser):
|
||||||
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
initials = models.CharField(max_length=5, null=True, blank=False)
|
||||||
phone = models.CharField(max_length=13, blank=True, default='')
|
phone = models.CharField(max_length=13, blank=True, default='')
|
||||||
api_key = models.CharField(max_length=40, blank=True, editable=False, 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...
|
# 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)
|
last_emailed = models.DateTimeField(blank=True, null=True)
|
||||||
dark_theme = models.BooleanField(default=False)
|
dark_theme = models.BooleanField(default=False)
|
||||||
|
is_supervisor = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
reversion_hide = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_api_key(cls):
|
def make_api_key(cls):
|
||||||
@@ -47,7 +63,7 @@ class Profile(AbstractUser):
|
|||||||
def name(self):
|
def name(self):
|
||||||
name = self.get_full_name()
|
name = self.get_full_name()
|
||||||
if self.initials:
|
if self.initials:
|
||||||
name += ' "{}"'.format(self.initials)
|
name += f' "{self.initials}"'
|
||||||
return name
|
return name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -60,56 +76,39 @@ class Profile(AbstractUser):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def users_awaiting_approval_count(cls):
|
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):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
# TODO move to versioning - currently get import errors with that
|
def current_event(self):
|
||||||
|
q = EventCheckIn.objects.filter(person=self, end_time=None)
|
||||||
|
return q.latest('time') if q.exists() else None
|
||||||
|
|
||||||
|
|
||||||
class RevisionMixin(object):
|
class ContactableManager(models.Manager):
|
||||||
@property
|
def search(self, query=None):
|
||||||
def is_first_version(self):
|
qs = self.get_queryset()
|
||||||
versions = Version.objects.get_for_object(self)
|
if query is not None:
|
||||||
return len(versions) == 1
|
or_lookup = Q(name__icontains=query) | Q(email__icontains=query) | Q(address__icontains=query) | Q(notes__icontains=query) | Q(
|
||||||
|
phone__startswith=query) | Q(phone__endswith=query)
|
||||||
|
|
||||||
@property
|
or_lookup = filter_by_pk(or_lookup, query)
|
||||||
def current_version(self):
|
|
||||||
version = Version.objects.get_for_object(self).select_related('revision').first()
|
|
||||||
return version
|
|
||||||
|
|
||||||
@property
|
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||||
def last_edited_at(self):
|
return qs
|
||||||
version = self.current_version
|
|
||||||
if version is None:
|
|
||||||
return None
|
|
||||||
return version.revision.date_created
|
|
||||||
|
|
||||||
@property
|
|
||||||
def last_edited_by(self):
|
|
||||||
version = self.current_version
|
|
||||||
if version is None:
|
|
||||||
return None
|
|
||||||
return version.revision.user
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_version_id(self):
|
|
||||||
version = self.current_version
|
|
||||||
if version is None:
|
|
||||||
return None
|
|
||||||
return "V{0} | R{1}".format(version.pk, version.revision.pk)
|
|
||||||
|
|
||||||
|
|
||||||
class Person(models.Model, RevisionMixin):
|
class Person(models.Model, RevisionMixin):
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
phone = models.CharField(max_length=15, blank=True, default='')
|
phone = models.CharField(max_length=15, blank=True, default='')
|
||||||
email = models.EmailField(blank=True, default='')
|
email = models.EmailField(blank=True, default='')
|
||||||
|
|
||||||
address = models.TextField(blank=True, default='')
|
address = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
notes = models.TextField(blank=True, default='')
|
notes = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
|
objects = ContactableManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
string = self.name
|
string = self.name
|
||||||
if self.notes is not None:
|
if self.notes is not None:
|
||||||
@@ -141,12 +140,12 @@ class Organisation(models.Model, RevisionMixin):
|
|||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
phone = models.CharField(max_length=15, blank=True, default='')
|
phone = models.CharField(max_length=15, blank=True, default='')
|
||||||
email = models.EmailField(blank=True, default='')
|
email = models.EmailField(blank=True, default='')
|
||||||
|
|
||||||
address = models.TextField(blank=True, default='')
|
address = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
notes = models.TextField(blank=True, default='')
|
notes = models.TextField(blank=True, default='')
|
||||||
union_account = models.BooleanField(default=False)
|
union_account = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
objects = ContactableManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
string = self.name
|
string = self.name
|
||||||
if self.notes is not None:
|
if self.notes is not None:
|
||||||
@@ -206,7 +205,7 @@ class VatRate(models.Model, RevisionMixin):
|
|||||||
get_latest_by = 'start_at'
|
get_latest_by = 'start_at'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.comment + " " + str(self.start_at) + " @ " + str(self.as_percent) + "%"
|
return f"{self.comment} {self.start_at} @ {self.as_percent}%"
|
||||||
|
|
||||||
|
|
||||||
class Venue(models.Model, RevisionMixin):
|
class Venue(models.Model, RevisionMixin):
|
||||||
@@ -215,9 +214,10 @@ class Venue(models.Model, RevisionMixin):
|
|||||||
email = models.EmailField(blank=True, default='')
|
email = models.EmailField(blank=True, default='')
|
||||||
three_phase_available = models.BooleanField(default=False)
|
three_phase_available = models.BooleanField(default=False)
|
||||||
notes = models.TextField(blank=True, default='')
|
notes = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
address = models.TextField(blank=True, default='')
|
address = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
|
objects = ContactableManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
string = self.name
|
string = self.name
|
||||||
if self.notes and len(self.notes) > 0:
|
if self.notes and len(self.notes) > 0:
|
||||||
@@ -291,6 +291,31 @@ class EventManager(models.Manager):
|
|||||||
|
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
def search(self, query=None):
|
||||||
|
qs = self.get_queryset()
|
||||||
|
if query is not None:
|
||||||
|
or_lookup = Q(name__icontains=query) | Q(description__icontains=query) | Q(notes__icontains=query)
|
||||||
|
|
||||||
|
or_lookup = filter_by_pk(or_lookup, query)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if query[0] == "N":
|
||||||
|
val = int(query[1:])
|
||||||
|
or_lookup = Q(pk=val) # If string is N###### then do a simple PK filter
|
||||||
|
except: # noqa
|
||||||
|
pass
|
||||||
|
|
||||||
|
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||||
|
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'])
|
@reversion.register(follow=['items'])
|
||||||
class Event(models.Model, RevisionMixin):
|
class Event(models.Model, RevisionMixin):
|
||||||
@@ -341,12 +366,15 @@ class Event(models.Model, RevisionMixin):
|
|||||||
auth_request_at = models.DateTimeField(null=True, blank=True)
|
auth_request_at = models.DateTimeField(null=True, blank=True)
|
||||||
auth_request_to = models.EmailField(blank=True, default='')
|
auth_request_to = models.EmailField(blank=True, default='')
|
||||||
|
|
||||||
|
forum_url = models.URLField(default='', blank=True, validators=[validate_forum_url])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_id(self):
|
def display_id(self):
|
||||||
|
if self.pk:
|
||||||
if self.is_rig:
|
if self.is_rig:
|
||||||
return str("N%05d" % self.pk)
|
return f"N{self.pk:05d}"
|
||||||
else:
|
|
||||||
return self.pk
|
return self.pk
|
||||||
|
return "????"
|
||||||
|
|
||||||
# Calculated values
|
# Calculated values
|
||||||
"""
|
"""
|
||||||
@@ -392,7 +420,15 @@ class Event(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def hs_done(self):
|
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
|
@property
|
||||||
def has_start_time(self):
|
def has_start_time(self):
|
||||||
@@ -465,13 +501,22 @@ class Event(models.Model, RevisionMixin):
|
|||||||
else:
|
else:
|
||||||
return bool(self.purchase_order)
|
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.start_date, 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()
|
objects = EventManager()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('event_detail', kwargs={'pk': self.pk})
|
return reverse('event_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}: {}".format(self.display_id, self.name)
|
return f"{self.display_id} | {self.name}"
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
errdict = {}
|
errdict = {}
|
||||||
@@ -517,11 +562,11 @@ class EventItem(models.Model, RevisionMixin):
|
|||||||
ordering = ['order']
|
ordering = ['order']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}.{}: {} | {}".format(self.event_id, self.order, self.event.name, self.name)
|
return f"{self.event_id}.{self.order}: {self.event.name} | {self.name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return str("item {}".format(self.name))
|
return f"item {self.name}"
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
@@ -539,7 +584,7 @@ class EventAuthorisation(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return "{} (requested by {})".format(self.event.display_id, self.sent_by.initials)
|
return f"{self.event.display_id} (requested by {self.sent_by.initials})"
|
||||||
|
|
||||||
|
|
||||||
class InvoiceManager(models.Manager):
|
class InvoiceManager(models.Manager):
|
||||||
@@ -558,6 +603,34 @@ class InvoiceManager(models.Manager):
|
|||||||
query = self.raw(sql)
|
query = self.raw(sql)
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
def search(self, query=None):
|
||||||
|
qs = self.get_queryset()
|
||||||
|
if query is not None:
|
||||||
|
or_lookup = Q(event__name__icontains=query)
|
||||||
|
|
||||||
|
or_lookup = filter_by_pk(or_lookup, query)
|
||||||
|
|
||||||
|
# try and parse an int
|
||||||
|
try:
|
||||||
|
val = int(query)
|
||||||
|
or_lookup = or_lookup | Q(event__pk=val)
|
||||||
|
except: # noqa
|
||||||
|
# not an integer
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if query[0] == "N":
|
||||||
|
val = int(query[1:])
|
||||||
|
or_lookup = Q(event__pk=val) # If string is Nxxxxx then filter by event number
|
||||||
|
elif query[0] == "#":
|
||||||
|
val = int(query[1:])
|
||||||
|
or_lookup = Q(pk=val) # If string is #xxxxx then filter by invoice number
|
||||||
|
except: # noqa
|
||||||
|
pass
|
||||||
|
|
||||||
|
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
@reversion.register(follow=['payment_set'])
|
@reversion.register(follow=['payment_set'])
|
||||||
class Invoice(models.Model, RevisionMixin):
|
class Invoice(models.Model, RevisionMixin):
|
||||||
@@ -597,14 +670,14 @@ class Invoice(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return "#{} for Event {}".format(self.display_id, self.event.display_id)
|
return f"{self.display_id} for Event {self.event.display_id}"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
return f"{self.display_id}: {self.event} (£{self.balance:.2f})"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_id(self):
|
def display_id(self):
|
||||||
return "{:05d}".format(self.pk)
|
return f"#{self.pk:05d}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-invoice_date']
|
ordering = ['-invoice_date']
|
||||||
@@ -615,13 +688,11 @@ class Payment(models.Model, RevisionMixin):
|
|||||||
CASH = 'C'
|
CASH = 'C'
|
||||||
INTERNAL = 'I'
|
INTERNAL = 'I'
|
||||||
EXTERNAL = 'E'
|
EXTERNAL = 'E'
|
||||||
SUCORE = 'SU'
|
|
||||||
ADJUSTMENT = 'T'
|
ADJUSTMENT = 'T'
|
||||||
METHODS = (
|
METHODS = (
|
||||||
(CASH, 'Cash'),
|
(CASH, 'Cash'),
|
||||||
(INTERNAL, 'Internal'),
|
(INTERNAL, 'Internal'),
|
||||||
(EXTERNAL, 'External'),
|
(EXTERNAL, 'External'),
|
||||||
(SUCORE, 'SU Core'),
|
|
||||||
(ADJUSTMENT, 'TEC Adjustment'),
|
(ADJUSTMENT, 'TEC Adjustment'),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -633,11 +704,11 @@ class Payment(models.Model, RevisionMixin):
|
|||||||
reversion_hide = True
|
reversion_hide = True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s: %d" % (self.get_method_display(), self.amount)
|
return f"{self.get_method_display()}: {self.amount}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return str("payment of £{}".format(self.amount))
|
return f"payment of £{self.amount}"
|
||||||
|
|
||||||
|
|
||||||
def validate_url(value):
|
def validate_url(value):
|
||||||
@@ -648,8 +719,21 @@ def validate_url(value):
|
|||||||
raise ValidationError('URL must point to a location on the TEC Sharepoint')
|
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
|
@reversion.register
|
||||||
class RiskAssessment(models.Model, RevisionMixin):
|
class RiskAssessment(ReviewableModel, RevisionMixin):
|
||||||
SMALL = (0, 'Small')
|
SMALL = (0, 'Small')
|
||||||
MEDIUM = (1, 'Medium')
|
MEDIUM = (1, 'Medium')
|
||||||
LARGE = (2, 'Large')
|
LARGE = (2, 'Large')
|
||||||
@@ -667,7 +751,6 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
# Power
|
# Power
|
||||||
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
|
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
|
||||||
# If yes to the above two, you must answer...
|
|
||||||
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
|
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
|
||||||
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)")
|
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)")
|
||||||
outside = models.BooleanField(help_text="Is the event outdoors?")
|
outside = models.BooleanField(help_text="Is the event outdoors?")
|
||||||
@@ -698,10 +781,6 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
# Blimey that was a lot of options
|
# Blimey that was a lot of options
|
||||||
|
|
||||||
reviewed_at = models.DateTimeField(null=True)
|
|
||||||
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
|
||||||
verbose_name="Reviewer", on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
supervisor_consulted = models.BooleanField(null=True)
|
supervisor_consulted = models.BooleanField(null=True)
|
||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
@@ -710,7 +789,7 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
'contractors': False,
|
'contractors': False,
|
||||||
'other_companies': False,
|
'other_companies': False,
|
||||||
'crew_fatigue': False,
|
'crew_fatigue': False,
|
||||||
'big_power': False,
|
# 'big_power': False Doesn't require checking with a super either way
|
||||||
'generators': False,
|
'generators': False,
|
||||||
'other_companies_power': False,
|
'other_companies_power': False,
|
||||||
'nonstandard_equipment_power': False,
|
'nonstandard_equipment_power': False,
|
||||||
@@ -738,10 +817,6 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
('review_riskassessment', 'Can review Risk Assessments')
|
('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
|
@property
|
||||||
def event_size(self):
|
def event_size(self):
|
||||||
# Confirm event size. Check all except generators, since generators entails outside
|
# Confirm event size. Check all except generators, since generators entails outside
|
||||||
@@ -752,24 +827,29 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
else:
|
else:
|
||||||
return self.SMALL[0]
|
return self.SMALL[0]
|
||||||
|
|
||||||
@property
|
def get_event_size_display(self):
|
||||||
def activity_feed_string(self):
|
return self.SIZES[self.event_size][1] + " Event"
|
||||||
return str(self.event)
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.pk} | {self.event}"
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('ra_detail', kwargs={'pk': self.pk})
|
return reverse('ra_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
@property
|
||||||
return "%i - %s" % (self.pk, self.event)
|
def activity_feed_string(self):
|
||||||
|
return str(self.event)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
@reversion.register(follow=['vehicles', 'crew'])
|
@reversion.register
|
||||||
class EventChecklist(models.Model, RevisionMixin):
|
class EventChecklist(ReviewableModel, RevisionMixin):
|
||||||
event = models.ForeignKey('Event', related_name='checklists', on_delete=models.CASCADE)
|
event = models.ForeignKey('Event', related_name='checklists', on_delete=models.CASCADE)
|
||||||
|
|
||||||
# General
|
# 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)
|
venue = models.ForeignKey('Venue', on_delete=models.CASCADE)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
|
|
||||||
@@ -783,6 +863,35 @@ class EventChecklist(models.Model, RevisionMixin):
|
|||||||
hs_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of Safety Bag/Box")
|
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")
|
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
|
# Small Electrical Checks
|
||||||
rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?")
|
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>")
|
supply_test = models.BooleanField(blank=True, null=True, help_text="Electrical supplies tested?<br><small>(using socket tester)</small>")
|
||||||
@@ -799,77 +908,62 @@ 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_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_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_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_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="Prospective Short Circuit Current")
|
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text=pssc_text)
|
||||||
# Worst case points
|
# Worst case points
|
||||||
w1_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
|
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_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||||
w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V")
|
||||||
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_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_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_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||||
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V")
|
||||||
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_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_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_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
||||||
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
|
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V")
|
||||||
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_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>")
|
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>")
|
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:
|
class Meta:
|
||||||
ordering = ['event']
|
ordering = ['event']
|
||||||
permissions = [
|
permissions = [
|
||||||
('review_eventchecklist', 'Can review Event Checklists')
|
('review_power', 'Can review Power Test Records')
|
||||||
]
|
]
|
||||||
|
|
||||||
@cached_property
|
def __str__(self):
|
||||||
def fieldz(self):
|
return f"{self.pk} - {self.event}"
|
||||||
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 get_absolute_url(self):
|
||||||
|
return reverse('pt_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return str(self.event)
|
return str(self.event)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('ec_detail', kwargs={'pk': self.pk})
|
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):
|
def __str__(self):
|
||||||
return "%i - %s" % (self.pk, self.event)
|
return f"{self.person} on {self.event}"
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
|
||||||
class EventChecklistVehicle(models.Model, RevisionMixin):
|
|
||||||
checklist = models.ForeignKey('EventChecklist', related_name='vehicles', blank=True, on_delete=models.CASCADE)
|
|
||||||
vehicle = models.CharField(max_length=255)
|
|
||||||
driver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='vehicles', on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
reversion_hide = True
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{} driven by {}".format(self.vehicle, str(self.driver))
|
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
|
||||||
class EventChecklistCrew(models.Model, RevisionMixin):
|
|
||||||
checklist = models.ForeignKey('EventChecklist', related_name='crew', blank=True, on_delete=models.CASCADE)
|
|
||||||
crewmember = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='crewed', on_delete=models.CASCADE)
|
|
||||||
role = models.CharField(max_length=255)
|
|
||||||
start = models.DateTimeField()
|
|
||||||
end = models.DateTimeField()
|
|
||||||
|
|
||||||
reversion_hide = True
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.start > self.end:
|
sass = " Please invent time travel and retry."
|
||||||
raise ValidationError('Unless you\'ve invented time travel, crew can\'t finish before they have started.')
|
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):
|
def get_absolute_url(self):
|
||||||
return "{} ({})".format(str(self.crewmember), self.role)
|
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.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import datetime
|
||||||
|
|
||||||
from PyPDF2 import PdfFileReader, PdfFileMerger
|
from PyPDF2 import PdfFileReader, PdfFileMerger
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -54,23 +55,23 @@ def send_eventauthorisation_success_email(instance):
|
|||||||
elif instance.event.organisation is not None and instance.email == instance.event.organisation.email:
|
elif instance.event.organisation is not None and instance.email == instance.event.organisation.email:
|
||||||
context['to_name'] = instance.event.organisation.name
|
context['to_name'] = instance.event.organisation.name
|
||||||
|
|
||||||
subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name)
|
subject = f"{instance.event.display_id} | {instance.event.name} - Event Authorised"
|
||||||
|
|
||||||
client_email = EmailMultiAlternatives(
|
client_email = EmailMultiAlternatives(
|
||||||
subject,
|
subject,
|
||||||
get_template("eventauthorisation_client_success.txt").render(context),
|
get_template("email/eventauthorisation_client_success.txt").render(context),
|
||||||
to=[instance.email],
|
to=[instance.email],
|
||||||
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
||||||
)
|
)
|
||||||
|
|
||||||
css = finders.find('css/email.css')
|
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()
|
external_styles=css).transform()
|
||||||
client_email.attach_alternative(html, 'text/html')
|
client_email.attach_alternative(html, 'text/html')
|
||||||
|
|
||||||
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', instance.event.name)
|
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', instance.event.name)
|
||||||
|
|
||||||
client_email.attach('N%05d - %s - CONFIRMATION.pdf' % (instance.event.pk, escapedEventName),
|
client_email.attach(f'{instance.event.display_id} - {escapedEventName} - CONFIRMATION.pdf',
|
||||||
merged.getvalue(),
|
merged.getvalue(),
|
||||||
'application/pdf'
|
'application/pdf'
|
||||||
)
|
)
|
||||||
@@ -82,7 +83,7 @@ def send_eventauthorisation_success_email(instance):
|
|||||||
|
|
||||||
mic_email = EmailMessage(
|
mic_email = EmailMessage(
|
||||||
subject,
|
subject,
|
||||||
get_template("eventauthorisation_mic_success.txt").render(context),
|
get_template("email/eventauthorisation_mic_success.txt").render(context),
|
||||||
to=[mic_email_address]
|
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():
|
if admin.last_emailed is None or admin.last_emailed + settings.EMAIL_COOLDOWN <= timezone.now():
|
||||||
context = {
|
context = {
|
||||||
'request': request,
|
'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(),
|
'number_of_users': models.Profile.users_awaiting_approval_count(),
|
||||||
'to_name': admin.first_name
|
'to_name': admin.first_name
|
||||||
}
|
}
|
||||||
|
|
||||||
email = EmailMultiAlternatives(
|
email = EmailMultiAlternatives(
|
||||||
"%s new users awaiting approval on RIGS" % (context['number_of_users']),
|
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],
|
to=[admin.email],
|
||||||
reply_to=[user.email],
|
reply_to=[user.email],
|
||||||
)
|
)
|
||||||
css = finders.find('css/email.css')
|
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()
|
external_styles=css).transform()
|
||||||
email.attach_alternative(html, 'text/html')
|
email.attach_alternative(html, 'text/html')
|
||||||
email.send()
|
email.send()
|
||||||
|
|||||||
BIN
RIGS/static/imgs/assets.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 63 KiB |
BIN
RIGS/static/imgs/rigs.jpg
Normal file
|
After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 13 KiB |
BIN
RIGS/static/imgs/tappytaptap.gif
Normal file
|
After Width: | Height: | Size: 5.4 MiB |
BIN
RIGS/static/imgs/training.jpg
Normal file
|
After Width: | Height: | Size: 852 KiB |
6
RIGS/static/js/marked.min.js
vendored
Normal file
142
RIGS/templates/base_print.xml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE document SYSTEM "rml.dtd">
|
||||||
|
<document filename="{{filename}}">
|
||||||
|
<docinit>
|
||||||
|
<registerTTFont faceName="OpenSans" fileName="static/fonts/OpenSans-Regular.tff"/>
|
||||||
|
<registerTTFont faceName="OpenSans-Bold" fileName="static/fonts/OpenSans-Bold.tff"/>
|
||||||
|
<registerFontFamily name="OpenSans" bold="OpenSans-Bold" boldItalic="OpenSans-Bold"/>
|
||||||
|
</docinit>
|
||||||
|
|
||||||
|
<stylesheet>
|
||||||
|
<initialize>
|
||||||
|
<color id="LightGray" RGB="#D3D3D3"/>
|
||||||
|
<color id="DarkGray" RGB="#707070"/>
|
||||||
|
<color id="Brand" RGB="#3853a4"/>
|
||||||
|
</initialize>
|
||||||
|
|
||||||
|
<paraStyle name="style.para" fontName="OpenSans" />
|
||||||
|
<paraStyle name="blockPara" spaceAfter="5" spaceBefore="5"/>
|
||||||
|
<paraStyle name="style.Heading1" fontName="OpenSans" fontSize="16" leading="18" spaceAfter="0"/>
|
||||||
|
<paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="10" spaceAfter="2"/>
|
||||||
|
<paraStyle name="style.Heading3" fontName="OpenSans" fontSize="10" spaceAfter="0"/>
|
||||||
|
<paraStyle name="center" alignment="center"/>
|
||||||
|
<paraStyle name="page-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/>
|
||||||
|
|
||||||
|
<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"/>
|
||||||
|
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
|
||||||
|
</blockTableStyle>
|
||||||
|
|
||||||
|
<blockTableStyle id="headLayout">
|
||||||
|
<blockValign value="top"/>
|
||||||
|
|
||||||
|
</blockTableStyle>
|
||||||
|
|
||||||
|
<blockTableStyle id="eventDetails">
|
||||||
|
<blockValign value="top"/>
|
||||||
|
<blockTopPadding start="0,0" stop="-1,0" length="0"/>
|
||||||
|
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
|
||||||
|
</blockTableStyle>
|
||||||
|
|
||||||
|
<blockTableStyle id="itemTable">
|
||||||
|
<blockValign value="top"/>
|
||||||
|
<lineStyle kind="LINEBELOW" colorName="LightGrey" start="0,0" stop="-1,-1" thickness="1"/>
|
||||||
|
{#<lineStyle kind="box" colorName="black" thickness="1" start="0,0" stop="-1,-1"/>#}
|
||||||
|
</blockTableStyle>
|
||||||
|
|
||||||
|
<blockTableStyle id="totalTable">
|
||||||
|
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
|
||||||
|
<lineStyle kind="LINEBELOW" colorName="LightGrey" start="-2,0" stop="-1,-1" thickness="1"/>
|
||||||
|
{# <lineStyle cap="default" kind="grid" colorName="black" thickness="1" start="1,0" stop="-1,-1"/> #}
|
||||||
|
</blockTableStyle>
|
||||||
|
|
||||||
|
<blockTableStyle id="infoTable" keepWithNext="true">
|
||||||
|
<blockLeftPadding start="0,0" stop="-1,-1" length="0"/>
|
||||||
|
</blockTableStyle>
|
||||||
|
|
||||||
|
<blockTableStyle id="paymentTable">
|
||||||
|
<blockBackground colorName="LightGray" start="0,1" stop="3,1"/>
|
||||||
|
<blockFont name="OpenSans-Bold" start="0,1" stop="0,1"/>
|
||||||
|
<blockFont name="OpenSans-Bold" start="2,1" stop="2,1"/>
|
||||||
|
<lineStyle kind="outline" colorName="black" thickness="1" start="0,1" stop="3,1"/>
|
||||||
|
</blockTableStyle>
|
||||||
|
|
||||||
|
<blockTableStyle id="signatureTable">
|
||||||
|
<blockTopPadding length="20" />
|
||||||
|
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
|
||||||
|
<lineStyle kind="linebelow" start="1,0" stop="1,0" colorName="black"/>
|
||||||
|
<lineStyle kind="linebelow" start="3,0" stop="3,0" colorName="black"/>
|
||||||
|
<lineStyle kind="linebelow" start="5,0" stop="5,0" colorName="black"/>
|
||||||
|
</blockTableStyle>
|
||||||
|
|
||||||
|
<listStyle name="ol"
|
||||||
|
bulletFormat="%s."
|
||||||
|
bulletFontSize="10" />
|
||||||
|
|
||||||
|
<listStyle name="ul"
|
||||||
|
start="bulletchar"
|
||||||
|
bulletFontSize="10"/>
|
||||||
|
</stylesheet>
|
||||||
|
|
||||||
|
<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"/>
|
||||||
|
<image file="static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
||||||
|
|
||||||
|
{# logo positioned 42 from left, 33 from top #}
|
||||||
|
<image file="static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
|
||||||
|
|
||||||
|
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
|
||||||
|
<drawString x="137" y="780">TEC PA & Lighting</drawString>
|
||||||
|
|
||||||
|
<setFont name="OpenSans" size="9"/>
|
||||||
|
<drawString x="137" y="760">Portland Building, University Park, Nottingham, NG7 2RD</drawString>
|
||||||
|
<drawString x="137" y="746">www.nottinghamtec.co.uk</drawString>
|
||||||
|
<drawString x="265" y="746">info@nottinghamtec.co.uk</drawString>
|
||||||
|
<drawString x="137" y="732">Phone: (0115) 846 8720</drawString>
|
||||||
|
|
||||||
|
<setFont name="OpenSans" size="10" />
|
||||||
|
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||||
|
<setFont name="OpenSans" size="7" />
|
||||||
|
<drawCenteredString x="302.5" y="26">
|
||||||
|
{{info_string}}
|
||||||
|
</drawCenteredString>
|
||||||
|
</pageGraphics>
|
||||||
|
|
||||||
|
<frame id="main" x1="50" y1="65" width="495" height="645"/>
|
||||||
|
</pageTemplate>
|
||||||
|
|
||||||
|
<pageTemplate id="Main">
|
||||||
|
<pageGraphics>
|
||||||
|
<image file="static/imgs/paperwork/corner-tr.jpg" x="395" y="642" height="200" width="200"/>
|
||||||
|
<image file="static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
||||||
|
|
||||||
|
<setFont name="OpenSans" size="10"/>
|
||||||
|
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||||
|
<setFont name="OpenSans" size="7" />
|
||||||
|
<drawCenteredString x="302.5" y="26">
|
||||||
|
{{info_string}}
|
||||||
|
</drawCenteredString>
|
||||||
|
</pageGraphics>
|
||||||
|
<frame id="main" x1="50" y1="65" width="495" height="727"/>
|
||||||
|
</pageTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<story firstPageTemplate="Headed">
|
||||||
|
<setNextFrame name="main"/>
|
||||||
|
<nextFrame/>
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</story>
|
||||||
|
|
||||||
|
</document>
|
||||||
@@ -34,16 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% if perms.RIGS.view_riskassessment %}
|
{% if perms.RIGS.view_riskassessment %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item"><a class="nav-link" href="{% url 'hs_list' %}">H&S</a></li>
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownHS" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
H&S
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownHS">
|
|
||||||
<a class="dropdown-item" href="{% url 'hs_list' %}"><span class="fas fa-eye"></span> Overview</a>
|
|
||||||
<a class="dropdown-item" href="{% url 'ra_list' %}"><span class="fas fa-file-medical"></span> Risk Assessments</a>
|
|
||||||
<a class="dropdown-item" href="{% url 'ec_list' %}"><span class="fas fa-tasks"></span> Event Checklists</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.RIGS.view_invoice %}
|
{% if perms.RIGS.view_invoice %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
|
|||||||
@@ -26,16 +26,14 @@
|
|||||||
var calendarEl = document.getElementById('calendar');
|
var calendarEl = document.getElementById('calendar');
|
||||||
|
|
||||||
calendar = new FullCalendar.Calendar(calendarEl, {
|
calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
|
firstDay: 1,
|
||||||
themeSystem: 'bootstrap',
|
themeSystem: 'bootstrap',
|
||||||
//defaultView: 'dayGridMonth', This is now default
|
|
||||||
aspectRatio: 1.5,
|
aspectRatio: 1.5,
|
||||||
eventTimeFormat: {
|
eventTimeFormat: {
|
||||||
'hour': '2-digit',
|
'hour': '2-digit',
|
||||||
'minute': '2-digit',
|
'minute': '2-digit',
|
||||||
'hour12': false
|
'hour12': false
|
||||||
},
|
},
|
||||||
//nowIndicator: true,
|
|
||||||
//firstDay: 1,
|
|
||||||
headerToolbar: false,
|
headerToolbar: false,
|
||||||
editable: false,
|
editable: false,
|
||||||
dayMaxEventRows: true, // allow "more" link when too many events
|
dayMaxEventRows: true, // allow "more" link when too many events
|
||||||
@@ -58,8 +56,10 @@
|
|||||||
};
|
};
|
||||||
$(doc).each(function() {
|
$(doc).each(function() {
|
||||||
end = $(this).attr('latest')
|
end = $(this).attr('latest')
|
||||||
|
allDay = false
|
||||||
if(end.indexOf("T") < 0){ //If latest does not contain a time
|
if(end.indexOf("T") < 0){ //If latest does not contain a time
|
||||||
end = moment(end).add(1, 'days') //End date is non-inclusive, so add a day
|
end = moment(end + " 23:59").format("YYYY-MM-DD[T]HH:mm:ss")
|
||||||
|
allDay = true
|
||||||
}
|
}
|
||||||
|
|
||||||
thisEvent = {
|
thisEvent = {
|
||||||
@@ -67,7 +67,8 @@
|
|||||||
'end': end,
|
'end': end,
|
||||||
'className': 'modal-href',
|
'className': 'modal-href',
|
||||||
'title': $(this).attr('title'),
|
'title': $(this).attr('title'),
|
||||||
'url': $(this).attr('url')
|
'url': $(this).attr('url'),
|
||||||
|
'allDay': allDay
|
||||||
}
|
}
|
||||||
|
|
||||||
if($(this).attr('is_rig')===true || $(this).attr('status') === "Cancelled"){
|
if($(this).attr('is_rig')===true || $(this).attr('status') === "Cancelled"){
|
||||||
|
|||||||
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
@@ -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
@@ -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
|
||||||
@@ -1,357 +0,0 @@
|
|||||||
{% 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>
|
|
||||||
|
|
||||||
{% include 'partials/datetime-fix.html' %}
|
|
||||||
|
|
||||||
<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 %}
|
|
||||||
<input type="hidden" name="{{ form.event.name }}" id="{{ form.event.id_for_label }}"
|
|
||||||
value="{{event.pk}}"/>
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">Event Information</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<dl class="row">
|
|
||||||
<dt class="col-4">Event Date</dt>
|
|
||||||
<dd class="col-8">{{ event.start_date}}{%if event.end_date %}-{{ event.end_date}}{%endif%}</dd>
|
|
||||||
<dt class="col-4">Event Name</dt>
|
|
||||||
<dd class="col-8">{{ event.name }}</dd>
|
|
||||||
<dt class="col-4">Client</dt>
|
|
||||||
<dd class="col-8">{{ event.person }}</dd>
|
|
||||||
<dt class="col-4">Event Size</dt>
|
|
||||||
<dd class="col-8">{% include 'partials/event_size.html' with object=event.riskassessment %}</dd>
|
|
||||||
</dl>
|
|
||||||
<div class="form-group form-row">
|
|
||||||
<label for="{{ form.date.id_for_label }}"
|
|
||||||
class="col-4 col-form-label">{{ form.date.label }}</label>
|
|
||||||
{% if not form.date.value %}
|
|
||||||
{% render_field form.date class+="form-control col-8" value=event.start_date %}
|
|
||||||
{% else %}
|
|
||||||
{% render_field form.date class+="form-control col-8" %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="form-group form-row" id="{{ form.venue.id_for_label }}-group">
|
|
||||||
<label for="{{ form.venue.id_for_label }}"
|
|
||||||
class="col-4 col-form-label">{{ form.venue.label }}</label>
|
|
||||||
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker col-8" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
|
|
||||||
{% if venue %}
|
|
||||||
<option value="{{venue.pk}}" selected="selected">{{ venue.name }}</option>
|
|
||||||
{% elif event.venue %}
|
|
||||||
<option value="{{event.venue.pk}}" selected="selected">{{ event.venue.name }}</option>
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group form-row" id="{{ form.power_mic.id_for_label }}-group">
|
|
||||||
<label for="{{ form.power_mic.id_for_label }}"
|
|
||||||
class="col-4 col-form-label">{{ form.power_mic.help_text }}</label>
|
|
||||||
<select id="{{ form.power_mic.id_for_label }}" name="{{ form.power_mic.name }}" class="form-control selectpicker col-8" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required="true">
|
|
||||||
{% if power_mic %}
|
|
||||||
<option value="{{power_mic.pk}}" selected="selected">{{ power_mic.name }}</option>
|
|
||||||
{% elif event.riskassessment.power_mic %}
|
|
||||||
<option value="{{event.riskassessment.power_mic.pk}}" selected="selected">{{ event.riskassessment.power_mic.name }}</option>
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<p class="pt-3 font-weight-bold">List vehicles and their drivers</p>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-sm">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Vehicle</th>
|
|
||||||
<th scope="col">Driver</th>
|
|
||||||
<th scope="col"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="vehiclest" data-pk="-1">
|
|
||||||
<tr id="vehicles_new" style="display: none;">
|
|
||||||
<td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td>
|
|
||||||
<td><select 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>
|
|
||||||
</div>
|
|
||||||
<div class="row my-3">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">Safety Checks</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{% include 'partials/checklist_checkbox.html' with formitem=form.safe_parking %}
|
|
||||||
{% include 'partials/checklist_checkbox.html' with formitem=form.safe_packing %}
|
|
||||||
{% include 'partials/checklist_checkbox.html' with formitem=form.exits %}
|
|
||||||
{% include 'partials/checklist_checkbox.html' with formitem=form.trip_hazard %}
|
|
||||||
{% include 'partials/checklist_checkbox.html' with formitem=form.warning_signs %}
|
|
||||||
{% include 'partials/checklist_checkbox.html' with formitem=form.ear_plugs %}
|
|
||||||
<div class="row pt-3">
|
|
||||||
<label class="col-5" for="{{ form.hs_location.id_for_label }}">{{ form.hs_location.help_text }}</label>
|
|
||||||
{% render_field form.hs_location class+="form-control col-7 col-md-4" %}
|
|
||||||
</div>
|
|
||||||
<div class="row pt-1">
|
|
||||||
<label class="col-5" for="{{ form.extinguishers_location.id_for_label }}">{{ form.extinguishers_location.help_text }}</label>
|
|
||||||
{% render_field form.extinguishers_location class+="form-control col-7 col-md-4" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row my-3">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">Crew Record</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-sm">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Person</th>
|
|
||||||
<th scope="col">Start Time</th>
|
|
||||||
<th scope="col">Role</th>
|
|
||||||
<th scope="col">End Time</th>
|
|
||||||
<th scope="col"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="crewmemberst" data-pk="-1">
|
|
||||||
<tr id="crew_new" style="display: none;">
|
|
||||||
<td>
|
|
||||||
<select name="crewmember_new" class="form-control" data-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' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
||||||
|
|
||||||
|
{% load markdown_tags %}
|
||||||
|
{% load button from filters %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row my-3 py-3">
|
<div class="row my-3 py-3">
|
||||||
{% if not request.is_ajax %}
|
{% if not request.is_ajax %}
|
||||||
@@ -43,13 +47,50 @@
|
|||||||
{% if perms.RIGS.view_event %}
|
{% if perms.RIGS.view_event %}
|
||||||
<h4>Notes</h4>
|
<h4>Notes</h4>
|
||||||
<hr>
|
<hr>
|
||||||
<p class="dont-break-out">{{ event.notes|linebreaksbr }}</p>
|
<p class="dont-break-out">{{ event.notes|markdown }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
<br>
|
||||||
{% include 'item_table.html' %}
|
{% include 'partials/item_table.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% 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 object.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>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% if not request.is_ajax and perms.RIGS.view_event %}
|
{% if not request.is_ajax and perms.RIGS.view_event %}
|
||||||
<div class="col-sm-12 text-right">
|
<div class="col-sm-12 text-right">
|
||||||
{% include 'partials/event_detail_buttons.html' %}
|
{% include 'partials/event_detail_buttons.html' %}
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
{% block css %}
|
{% block css %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
|
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'css/easymde.min.css' %}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block preload_js %}
|
{% block preload_js %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<script src="{% static 'js/selects.js' %}"></script>
|
<script src="{% static 'js/selects.js' %}"></script>
|
||||||
|
<script src="{% static 'js/easymde.min.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
@@ -21,8 +23,6 @@
|
|||||||
<script src="{% static 'js/interaction.js' %}"></script>
|
<script src="{% static 'js/interaction.js' %}"></script>
|
||||||
<script src="{% static 'js/tooltip.js' %}"></script>
|
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||||
|
|
||||||
{% include 'partials/datetime-fix.html' %}
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches;
|
const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches;
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
@@ -63,16 +63,26 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
});
|
});
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
setupMDE('#id_description');
|
||||||
|
setupMDE('#id_notes');
|
||||||
|
setupMDE('#item_description');
|
||||||
|
|
||||||
|
$('#itemModal').on('shown.bs.modal', function (e) {
|
||||||
|
$('#item_description').data('mde_editor').value(
|
||||||
|
$('#item_description').val()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
setupItemTable($("#{{ form.items_json.id_for_label }}").val());
|
setupItemTable($("#{{ form.items_json.id_for_label }}").val());
|
||||||
});
|
});
|
||||||
$(function () {
|
$(function () {
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'item_modal.html' %}
|
{% include 'partials/item_modal.html' %}
|
||||||
<form class="itemised_form" role="form" method="POST">
|
<form class="itemised_form" role="form" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -112,7 +122,7 @@
|
|||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||||
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='person' %}">
|
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}" class="selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='person' %}">
|
||||||
{% if person %}
|
{% if person %}
|
||||||
<option value="{{form.person.value}}" selected="selected" data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
|
<option value="{{form.person.value}}" selected="selected" data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -139,7 +149,7 @@
|
|||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||||
<select id="{{ form.organisation.id_for_label }}" name="{{ form.organisation.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='organisation' %}" >
|
<select id="{{ form.organisation.id_for_label }}" name="{{ form.organisation.name }}" class="selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='organisation' %}" >
|
||||||
{% if organisation %}
|
{% if organisation %}
|
||||||
<option value="{{form.organisation.value}}" selected="selected" data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
|
<option value="{{form.organisation.value}}" selected="selected" data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -168,7 +178,7 @@
|
|||||||
<label for="{{ form.description.id_for_label }}"
|
<label for="{{ form.description.id_for_label }}"
|
||||||
class="col-sm-4 col-form-label">{{ form.description.label }}</label>
|
class="col-sm-4 col-form-label">{{ form.description.label }}</label>
|
||||||
|
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-12">
|
||||||
{% render_field form.description class+="form-control" %}
|
{% render_field form.description class+="form-control" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,9 +207,9 @@
|
|||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-9 col-md-7 col-lg-8">
|
<div class="col-sm-9 col-md-7 col-lg-8">
|
||||||
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
|
<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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,7 +231,7 @@
|
|||||||
<label for="{{ form.start_date.id_for_label }}"
|
<label for="{{ form.start_date.id_for_label }}"
|
||||||
class="col-sm-4 col-form-label">{{ form.start_date.label }}</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="row">
|
||||||
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="Start date for event, required">
|
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="Start date for event, required">
|
||||||
{% render_field form.start_date class+="form-control" %}
|
{% render_field form.start_date class+="form-control" %}
|
||||||
@@ -236,7 +246,7 @@
|
|||||||
<label for="{{ form.end_date.id_for_label }}"
|
<label for="{{ form.end_date.id_for_label }}"
|
||||||
class="col-sm-4 col-form-label">{{ form.end_date.label }}</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="row">
|
||||||
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="End date of event, leave blank if unknown or same as start date">
|
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="End date of event, leave blank if unknown or same as start date">
|
||||||
{% render_field form.end_date class+="form-control" %}
|
{% render_field form.end_date class+="form-control" %}
|
||||||
@@ -267,10 +277,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-offset-4 col-sm-8">
|
<div class="col-sm-offset-4 col-sm-8">
|
||||||
<div class="checkbox">
|
|
||||||
<label data-toggle="tooltip" title="Mark this event as a dry-hire, so it needs to be checked in at the end">
|
<label data-toggle="tooltip" title="Mark this event as a dry-hire, so it needs to be checked in at the end">
|
||||||
{% render_field form.dry_hire %}{{ form.dry_hire.label }}
|
{{ form.dry_hire.label }} {% render_field form.dry_hire %}
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -292,7 +300,7 @@
|
|||||||
class="col-sm-4 col-form-label">{{ form.mic.label }}</label>
|
class="col-sm-4 col-form-label">{{ form.mic.label }}</label>
|
||||||
|
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<select id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
<select id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}" class="px-0 selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||||
{% if mic %}
|
{% if mic %}
|
||||||
<option value="{{form.mic.value}}" selected="selected" >{{ mic.name }}</option>
|
<option value="{{form.mic.value}}" selected="selected" >{{ mic.name }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -306,7 +314,7 @@
|
|||||||
class="col-sm-4 col-form-label">{{ form.checked_in_by.label }}</label>
|
class="col-sm-4 col-form-label">{{ form.checked_in_by.label }}</label>
|
||||||
|
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<select id="{{ form.checked_in_by.id_for_label }}" name="{{ form.checked_in_by.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
<select id="{{ form.checked_in_by.id_for_label }}" name="{{ form.checked_in_by.name }}" class="px-0 selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||||
{% if checked_in_by %}
|
{% if checked_in_by %}
|
||||||
<option value="{{form.checked_in_by.value}}" selected="selected" >{{ checked_in_by.name }}</option>
|
<option value="{{form.checked_in_by.value}}" selected="selected" >{{ checked_in_by.name }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -332,6 +340,20 @@
|
|||||||
{% render_field form.purchase_order class+="form-control" %}
|
{% render_field form.purchase_order class+="form-control" %}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -345,10 +367,10 @@
|
|||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="form-group" data-toggle="tooltip" title="Notes on the event. This is only visible to keyholders, and is not displayed on the paperwork">
|
<div class="form-group" data-toggle="tooltip" title="Notes on the event. This is only visible to keyholders, and is not displayed on the paperwork">
|
||||||
<label for="{{ form.notes.id_for_label }}">{{ form.notes.label }}</label>
|
<label for="{{ form.notes.id_for_label }}">{{ form.notes.label }}</label>
|
||||||
{% render_field form.notes class+="form-control" %}
|
{% render_field form.notes class+="form-control md-enabled" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'item_table.html' %}
|
{% include 'partials/item_table.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,128 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
{% extends 'base_print.xml' %}
|
||||||
<!DOCTYPE document SYSTEM "rml.dtd">
|
|
||||||
<document filename="{{filename}}">
|
|
||||||
<docinit>
|
|
||||||
<registerTTFont faceName="OpenSans" fileName="static/fonts/OpenSans-Regular.tff"/>
|
|
||||||
<registerTTFont faceName="OpenSans-Bold" fileName="static/fonts/OpenSans-Bold.tff"/>
|
|
||||||
<registerFontFamily name="OpenSans" bold="OpenSans-Bold" boldItalic="OpenSans-Bold"/>
|
|
||||||
</docinit>
|
|
||||||
|
|
||||||
<stylesheet>
|
{% block content %}
|
||||||
<initialize>
|
{% include "event_print_page.xml" %}
|
||||||
<color id="LightGray" RGB="#D3D3D3"/>
|
{% endblock %}
|
||||||
<color id="DarkGray" RGB="#707070"/>
|
|
||||||
</initialize>
|
|
||||||
|
|
||||||
<paraStyle name="style.para" fontName="OpenSans" />
|
|
||||||
<paraStyle name="blockPara" spaceAfter="5" spaceBefore="5"/>
|
|
||||||
<paraStyle name="style.Heading1" fontName="OpenSans" fontSize="16" leading="18" spaceAfter="0"/>
|
|
||||||
<paraStyle name="style.Heading2" fontName="OpenSans-Bold" fontSize="10" spaceAfter="2"/>
|
|
||||||
<paraStyle name="style.Heading3" fontName="OpenSans" fontSize="10" spaceAfter="0"/>
|
|
||||||
<paraStyle name="center" alignment="center"/>
|
|
||||||
<paraStyle name="page-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/>
|
|
||||||
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
<blockTableStyle id="eventSpecifics">
|
|
||||||
<blockValign value="top"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="LightGrey" start="0,0" stop="1,0" thickness="1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
|
|
||||||
<blockTableStyle id="headLayout">
|
|
||||||
<blockValign value="top"/>
|
|
||||||
|
|
||||||
</blockTableStyle>
|
|
||||||
|
|
||||||
<blockTableStyle id="eventDetails">
|
|
||||||
<blockValign value="top"/>
|
|
||||||
<blockTopPadding start="0,0" stop="-1,0" length="0"/>
|
|
||||||
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
|
|
||||||
<blockTableStyle id="itemTable">
|
|
||||||
<blockValign value="top"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="LightGrey" start="0,0" stop="-1,-1" thickness="1"/>
|
|
||||||
{#<lineStyle kind="box" colorName="black" thickness="1" start="0,0" stop="-1,-1"/>#}
|
|
||||||
</blockTableStyle>
|
|
||||||
|
|
||||||
<blockTableStyle id="totalTable">
|
|
||||||
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="LightGrey" start="-2,0" stop="-1,-1" thickness="1"/>
|
|
||||||
{# <lineStyle cap="default" kind="grid" colorName="black" thickness="1" start="1,0" stop="-1,-1"/> #}
|
|
||||||
</blockTableStyle>
|
|
||||||
|
|
||||||
<blockTableStyle id="infoTable" keepWithNext="true">
|
|
||||||
<blockLeftPadding start="0,0" stop="-1,-1" length="0"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
|
|
||||||
<blockTableStyle id="paymentTable">
|
|
||||||
<blockBackground colorName="LightGray" start="0,1" stop="3,1"/>
|
|
||||||
<blockFont name="OpenSans-Bold" start="0,1" stop="0,1"/>
|
|
||||||
<blockFont name="OpenSans-Bold" start="2,1" stop="2,1"/>
|
|
||||||
<lineStyle kind="outline" colorName="black" thickness="1" start="0,1" stop="3,1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
|
|
||||||
<blockTableStyle id="signatureTable">
|
|
||||||
<blockTopPadding length="20" />
|
|
||||||
<blockLeftPadding start="0,0" stop="0,-1" length="0"/>
|
|
||||||
<lineStyle kind="linebelow" start="1,0" stop="1,0" colorName="black"/>
|
|
||||||
<lineStyle kind="linebelow" start="3,0" stop="3,0" colorName="black"/>
|
|
||||||
<lineStyle kind="linebelow" start="5,0" stop="5,0" colorName="black"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
</stylesheet>
|
|
||||||
|
|
||||||
<template > {# 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"/>
|
|
||||||
<image file="static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
|
||||||
|
|
||||||
{# logo positioned 42 from left, 33 from top #}
|
|
||||||
<image file="static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
|
|
||||||
|
|
||||||
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
|
|
||||||
<drawString x="137" y="780">TEC PA & Lighting</drawString>
|
|
||||||
|
|
||||||
<setFont name="OpenSans" size="9"/>
|
|
||||||
<drawString x="137" y="760">Portland Building, University Park, Nottingham, NG7 2RD</drawString>
|
|
||||||
<drawString x="137" y="746">www.nottinghamtec.co.uk</drawString>
|
|
||||||
<drawString x="265" y="746">info@nottinghamtec.co.uk</drawString>
|
|
||||||
<drawString x="137" y="732">Phone: (0115) 846 8720</drawString>
|
|
||||||
|
|
||||||
<setFont name="OpenSans" size="10" />
|
|
||||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
|
||||||
<setFont name="OpenSans" size="7" />
|
|
||||||
<drawCenteredString x="302.5" y="26">
|
|
||||||
[Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
|
|
||||||
</drawCenteredString>
|
|
||||||
</pageGraphics>
|
|
||||||
|
|
||||||
<frame id="main" x1="50" y1="65" width="495" height="645"/>
|
|
||||||
</pageTemplate>
|
|
||||||
|
|
||||||
<pageTemplate id="Main">
|
|
||||||
<pageGraphics>
|
|
||||||
<image file="static/imgs/paperwork/corner-tr.jpg" x="395" y="642" height="200" width="200"/>
|
|
||||||
<image file="static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
|
||||||
|
|
||||||
<setFont name="OpenSans" size="10"/>
|
|
||||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
|
||||||
<setFont name="OpenSans" size="7" />
|
|
||||||
<drawCenteredString x="302.5" y="26">
|
|
||||||
[Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}]
|
|
||||||
</drawCenteredString>
|
|
||||||
</pageGraphics>
|
|
||||||
<frame id="main" x1="50" y1="65" width="495" height="727"/>
|
|
||||||
</pageTemplate>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<story firstPageTemplate="Headed">
|
|
||||||
{% include "event_print_page.xml" %}
|
|
||||||
</story>
|
|
||||||
|
|
||||||
</document>
|
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
|
{% load markdown_tags %}
|
||||||
{% load filters %}
|
{% load filters %}
|
||||||
<setNextFrame name="main"/>
|
|
||||||
<nextFrame/>
|
|
||||||
<blockTable style="headLayout" colWidths="330,165">
|
<blockTable style="headLayout" colWidths="330,165">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1><b>N{{ object.pk|stringformat:"05d" }}:</b> '{{ object.name }}'<small></small></h1>
|
<h1><b>N{{ object.pk|stringformat:"05d" }}:</b> '{{ object.name }}'</h1>
|
||||||
|
|
||||||
<para style="style.event_description">
|
<para style="style.event_description">
|
||||||
<b>{{object.start_date|date:"D jS N Y"}}</b>
|
<b>{{object.start_date|date:"D jS N Y"}}</b>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<keepInFrame>
|
<keepInFrame maxHeight="500" onOverflow="shrink">
|
||||||
<para style="style.event_description">
|
{{ object.description|default_if_none:""|markdown:"rml" }}
|
||||||
{{ object.description|default_if_none:""|linebreaksxml }}
|
|
||||||
</para>
|
|
||||||
</keepInFrame>
|
</keepInFrame>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -180,15 +178,10 @@
|
|||||||
{% for item in object.items.all %}
|
{% for item in object.items.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<para>{{ item.name }}
|
<para><b>{{ item.name }}</b></para>
|
||||||
{% if item.description %}
|
{% if item.description %}
|
||||||
</para>
|
{{ item.description|markdown:"rml" }}
|
||||||
<para style="item_description">
|
|
||||||
<em>{{ item.description|linebreaksxml }}</em>
|
|
||||||
</para>
|
|
||||||
<para>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</para>
|
|
||||||
</td>
|
</td>
|
||||||
<td>£{{ item.cost|floatformat:2 }}</td>
|
<td>£{{ item.cost|floatformat:2 }}</td>
|
||||||
<td>{{ item.quantity }}</td>
|
<td>{{ item.quantity }}</td>
|
||||||
@@ -208,9 +201,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if quote %}
|
{% if quote %}
|
||||||
<para>
|
<para>This quote is valid for 30 days unless otherwise arranged.</para>
|
||||||
This quote is valid for 30 days unless otherwise arranged.
|
|
||||||
</para>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% if object.vat > 0 %}
|
{% if object.vat > 0 %}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
{% with object=event auth=True %}
|
{% with object=event auth=True %}
|
||||||
{% include 'item_table.html' %}
|
{% include 'partials/item_table.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</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
|
|
||||||
@@ -1,24 +1,33 @@
|
|||||||
{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
|
{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
|
{% load static %}
|
||||||
|
{% load button from filters %}
|
||||||
|
|
||||||
{% block title %}Request Authorisation{% endblock %}
|
{% block title %}Request Authorisation{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning pb-0">
|
||||||
<h1>Send authorisation request email.</h1>
|
<h1>Send authorisation request email.</h1>
|
||||||
<p>Pressing send will email the address provided. Please triple check everything before continuing.</p>
|
<p>Pressing send will email the address provided. <strong>Please triple check everything before continuing.</strong></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info pb-0">
|
||||||
|
{% if object.person.email or object.organisation.email %}
|
||||||
<dl class="dl-horizontal">
|
<dl class="dl-horizontal">
|
||||||
|
{% if object.person.email %}
|
||||||
<dt>Person Email</dt>
|
<dt>Person Email</dt>
|
||||||
<dd>{{ object.person.email }}</dd>
|
<dd><span id="person-email" class="pr-1">{{ object.person.email }}</span> {% button 'copy' id='#person-email' %}</dd>
|
||||||
|
{% endif %}
|
||||||
|
{% if object.organisation.email %}
|
||||||
<dt>Organisation Email</dt>
|
<dt>Organisation Email</dt>
|
||||||
<dd>{{ object.organisation.email }}</dd>
|
<dd><span id="org-email" class="pr-1">{{ object.organisation.email }}</span> {% button 'copy' id='#org-email' %}</dd>
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
|
{% else %}
|
||||||
|
<p>No email addresses saved to the client ಠ_ಠ</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<form action="{{ form.action|default:request.path }}" method="POST" id="auth-request-form">
|
<form action="{{ form.action|default:request.path }}" method="POST" id="auth-request-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -33,11 +42,20 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||||
|
<script src="{% static 'js/popover.js' %}"></script>
|
||||||
|
<script src="{% static 'js/clipboard.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
$('#auth-request-form').on('submit', function () {
|
$('#auth-request-form').on('submit', function () {
|
||||||
$('#auth-request-form button').attr('disabled', true);
|
$('#auth-request-form button').attr('disabled', true);
|
||||||
});
|
});
|
||||||
|
var clipboard = new ClipboardJS('.btn');
|
||||||
|
|
||||||
|
clipboard.on('success', function(e) {
|
||||||
|
$(e.trigger).popover('show');
|
||||||
|
window.setTimeout(function () {$(e.trigger).popover('hide')}, 3000);
|
||||||
|
e.clearSelection();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
83
RIGS/templates/hs/event_checklist_detail.html
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
||||||
|
{% load help_text from filters %}
|
||||||
|
{% load profile_by_index from filters %}
|
||||||
|
{% load yesnoi from filters %}
|
||||||
|
{% load button from filters %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 text-right my-3">
|
||||||
|
{% button 'edit' url='ec_edit' pk=object.pk %}
|
||||||
|
{% button 'view' url='event_detail' pk=object.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>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-sm-12">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">General</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-6">Date</dt>
|
||||||
|
<dd class="col-6">
|
||||||
|
{{ object.date }}
|
||||||
|
</dd>
|
||||||
|
<dt class="col-6">Venue</dt>
|
||||||
|
<dd class="col-6">
|
||||||
|
{% if object.venue %}
|
||||||
|
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
|
||||||
|
{{ object.venue }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-sm-12">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">Safety Checks</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-10">{{ object|help_text:'safe_parking'|safe }}</dt>
|
||||||
|
<dd class="col-2">
|
||||||
|
{{ object.safe_parking|yesnoi }}
|
||||||
|
</dd>
|
||||||
|
<dt class="col-10">{{ object|help_text:'safe_packing'|safe }}</dt>
|
||||||
|
<dd class="col-2">
|
||||||
|
{{ object.safe_packing|yesnoi }}
|
||||||
|
</dd>
|
||||||
|
<dt class="col-10">{{ object|help_text:'exits'|safe }}</dt>
|
||||||
|
<dd class="col-2">
|
||||||
|
{{ object.exits|yesnoi }}
|
||||||
|
</dd>
|
||||||
|
<dt class="col-10">{{ object|help_text:'trip_hazard'|safe }}</dt>
|
||||||
|
<dd class="col-2">
|
||||||
|
{{ object.trip_hazard|yesnoi }}
|
||||||
|
</dd>
|
||||||
|
<dt class="col-10">{{ object|help_text:'warning_signs'|safe }}</dt>
|
||||||
|
<dd class="col-2">
|
||||||
|
{{ object.warning_signs|yesnoi }}
|
||||||
|
</dd>
|
||||||
|
<dt class="col-10">{{ object|help_text:'ear_plugs'|safe }}</dt>
|
||||||
|
<dd class="col-2">
|
||||||
|
{{ object.ear_plugs|yesnoi }}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 text-right">
|
||||||
|
{% 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">
|
||||||
|
{% include 'partials/last_edited.html' with target="eventchecklist_history" %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
99
RIGS/templates/hs/event_checklist_form.html
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
{% 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>
|
||||||
|
<script src="{% static 'js/interaction.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ block.super }}
|
||||||
|
<script src="{% static 'js/autocompleter.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="col-12">
|
||||||
|
{% include 'form_errors.html' %}
|
||||||
|
|
||||||
|
<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 %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Event Information</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-4">Event Date</dt>
|
||||||
|
<dd class="col-8">{{ event.start_date}}{%if event.end_date %}-{{ event.end_date}}{%endif%}</dd>
|
||||||
|
<dt class="col-4">Event Name</dt>
|
||||||
|
<dd class="col-8">{{ event.name }}</dd>
|
||||||
|
<dt class="col-4">Client</dt>
|
||||||
|
<dd class="col-8">{{ event.person }}</dd>
|
||||||
|
<dt class="col-4">Event Size</dt>
|
||||||
|
<dd class="col-8">{% include 'partials/event_size.html' with object=event.riskassessment %}</dd>
|
||||||
|
</dl>
|
||||||
|
<div class="form-group form-row">
|
||||||
|
<label for="{{ form.date.id_for_label }}"
|
||||||
|
class="col-4 col-form-label">{{ form.date.label }}</label>
|
||||||
|
{% if not form.date.value %}
|
||||||
|
{% render_field form.date class+="form-control col-8" value=event.start_date %}
|
||||||
|
{% else %}
|
||||||
|
{% render_field form.date class+="form-control col-8" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-row" id="{{ form.venue.id_for_label }}-group">
|
||||||
|
<label for="{{ form.venue.id_for_label }}"
|
||||||
|
class="col-4 col-form-label">{{ form.venue.label }}</label>
|
||||||
|
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="selectpicker" 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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Safety Checks</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'partials/checklist_checkbox.html' with formitem=form.safe_parking %}
|
||||||
|
{% include 'partials/checklist_checkbox.html' with formitem=form.safe_packing %}
|
||||||
|
{% include 'partials/checklist_checkbox.html' with formitem=form.exits %}
|
||||||
|
{% include 'partials/checklist_checkbox.html' with formitem=form.trip_hazard %}
|
||||||
|
{% include 'partials/checklist_checkbox.html' with formitem=form.warning_signs %}
|
||||||
|
{% include 'partials/checklist_checkbox.html' with formitem=form.ear_plugs %}
|
||||||
|
<div class="row pt-3">
|
||||||
|
<label class="col-5" for="{{ form.hs_location.id_for_label }}">{{ form.hs_location.help_text }}</label>
|
||||||
|
{% render_field form.hs_location class+="form-control col-7 col-md-4" %}
|
||||||
|
</div>
|
||||||
|
<div class="row pt-1">
|
||||||
|
<label class="col-5" for="{{ form.extinguishers_location.id_for_label }}">{{ form.extinguishers_location.help_text }}</label>
|
||||||
|
{% render_field form.extinguishers_location class+="form-control col-7 col-md-4" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-sm-12 text-right">
|
||||||
|
{% button 'submit' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
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">Dates</th>
|
||||||
<th scope="col">RA</th>
|
<th scope="col">RA</th>
|
||||||
<th scope="col">Checklists</th>
|
<th scope="col">Checklists</th>
|
||||||
|
<th scope="col">Power Records</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -35,6 +36,14 @@
|
|||||||
<a href="{% url 'event_ec' event.pk %}" class="btn btn-info"><span class="fas fa-paperclip"></span> <span
|
<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>
|
class="d-none d-sm-inline">Create</span></a>
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr class="bg-warning text-dark">
|
<tr class="bg-warning text-dark">
|
||||||
@@ -7,20 +7,22 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 text-right my-3">
|
<div class="col-12 text-right my-3">
|
||||||
{% button 'edit' url='ec_edit' pk=object.pk %}
|
{% button 'edit' url='pt_edit' pk=object.pk %}
|
||||||
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %}
|
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %}
|
||||||
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
|
{% include 'partials/review_status.html' with perm=perms.RIGS.review_power review='pt_review' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="card mb-3">
|
||||||
<div class="col-md-6 col-sm-12">
|
<div class="card-header">{% include 'partials/event_size.html' with object=object.event.riskassessment %}</div>
|
||||||
<div class="card mb-3">
|
|
||||||
<div class="card-header">General</div>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-6">Date</dt>
|
<dt class="col-6">{{ object|help_text:'power_mic' }}</dt>
|
||||||
<dd class="col-6">
|
<dd class="col-6">
|
||||||
{{ object.date }}
|
{% if object.power_mic %}
|
||||||
|
<a href="{% url 'profile_detail' object.power_mic.pk %}">{{ object.power_mic.name }}</a>
|
||||||
|
{% else %}
|
||||||
|
None
|
||||||
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-6">Venue</dt>
|
<dt class="col-6">Venue</dt>
|
||||||
<dd class="col-6">
|
<dd class="col-6">
|
||||||
@@ -30,91 +32,12 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-6">{{ object|help_text:'power_mic' }}</dt>
|
<dt class="col-6">Notes</dt>
|
||||||
<dd class="col-6">
|
<dd class="col-6">
|
||||||
{% if object.power_mic %}
|
{{ object.notes }}
|
||||||
<a href="{% url 'profile_detail' object.power_mic.pk %}">{{ object.power_mic.name }}</a>
|
|
||||||
{% else %}
|
|
||||||
None
|
|
||||||
{% endif %}
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<p>List vehicles and their drivers</p>
|
{% if object.event.riskassessment.event_size == 0 %}
|
||||||
<ul>
|
|
||||||
{% for i in object.vehicles.all %}
|
|
||||||
<li>{{i}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 col-sm-12">
|
|
||||||
<div class="card mb-3">
|
|
||||||
<div class="card-header">Safety Checks</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<dl class="row">
|
|
||||||
<dt class="col-10">{{ object|help_text:'safe_parking'|safe }}</dt>
|
|
||||||
<dd class="col-2">
|
|
||||||
{{ object.safe_parking|yesnoi }}
|
|
||||||
</dd>
|
|
||||||
<dt class="col-10">{{ object|help_text:'safe_packing'|safe }}</dt>
|
|
||||||
<dd class="col-2">
|
|
||||||
{{ object.safe_packing|yesnoi }}
|
|
||||||
</dd>
|
|
||||||
<dt class="col-10">{{ object|help_text:'exits'|safe }}</dt>
|
|
||||||
<dd class="col-2">
|
|
||||||
{{ object.exits|yesnoi }}
|
|
||||||
</dd>
|
|
||||||
<dt class="col-10">{{ object|help_text:'trip_hazard'|safe }}</dt>
|
|
||||||
<dd class="col-2">
|
|
||||||
{{ object.trip_hazard|yesnoi }}
|
|
||||||
</dd>
|
|
||||||
<dt class="col-10">{{ object|help_text:'warning_signs'|safe }}</dt>
|
|
||||||
<dd class="col-2">
|
|
||||||
{{ object.warning_signs|yesnoi }}
|
|
||||||
</dd>
|
|
||||||
<dt class="col-10">{{ object|help_text:'ear_plugs'|safe }}</dt>
|
|
||||||
<dd class="col-2">
|
|
||||||
{{ object.ear_plugs|yesnoi }}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card mb-3">
|
|
||||||
<div class="card-header">Crew Record</div>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-sm">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Crewmember</th>
|
|
||||||
<th scope="col">Start Time</th>
|
|
||||||
<th scope="col">Role</th>
|
|
||||||
<th scope="col">End Time</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="crewmemberst">
|
|
||||||
{% for crew in object.crew.all %}
|
|
||||||
<tr>
|
|
||||||
<td>{{crew.crewmember}}</td>
|
|
||||||
<td>{{crew.start}}</td>
|
|
||||||
<td>{{crew.role}}</td>
|
|
||||||
<td>{{crew.end}}</td>
|
|
||||||
</tr>
|
|
||||||
{% 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 event.riskassessment.event_size == 0 %}
|
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-10">{{ object|help_text:'rcds'|safe }}</dt>
|
<dt class="col-10">{{ object|help_text:'rcds'|safe }}</dt>
|
||||||
<dd class="col-2">
|
<dd class="col-2">
|
||||||
@@ -163,7 +86,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row" rowspan="2">Voltage<br><small>(cube meter)</small></th>
|
<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_l1' }}</th>
|
||||||
<th>{{ object|help_text:'fd_voltage_l2' }}</th>
|
<th>{{ object|help_text:'fd_voltage_l2' }}</th>
|
||||||
<th>{{ object|help_text:'fd_voltage_l3' }}</th>
|
<th>{{ object|help_text:'fd_voltage_l3' }}</th>
|
||||||
@@ -242,11 +165,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 text-right">
|
<div class="col-12 text-right">
|
||||||
{% button 'edit' url='ec_edit' pk=object.pk %}
|
{% button 'edit' url='pt_edit' pk=object.pk %}
|
||||||
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
|
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %}
|
||||||
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
|
{% include 'partials/review_status.html' with perm=perms.RIGS.review_power review='pt_review' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 text-right">
|
<div class="col-12 text-right">
|
||||||
{% include 'partials/last_edited.html' with target="eventchecklist_history" %}
|
{% include 'partials/last_edited.html' with target="powertestrecord_history" %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
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 %}
|
||||||
135
RIGS/templates/hs/ra_print.xml
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
{% extends 'base_print.xml' %}
|
||||||
|
{% load filters %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<spacer length="15"/>
|
||||||
|
<h1>Event Specific Risk Assessment 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/>
|
||||||
|
<blockTable colWidths="425,100" spaceAfter="15">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><h3><strong>General</strong></h3></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'nonstandard_equipment'|striptags }}</para></td>
|
||||||
|
<td>{{ object.nonstandard_equipment|yesno|capfirst }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'nonstandard_use'|striptags }}</para></td>
|
||||||
|
<td>{{ object.nonstandard_use|yesno|capfirst }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'contractors'|striptags }}</para></td>
|
||||||
|
<td>{{ object.contractors|yesno|capfirst }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'other_companies'|striptags }}</para></td>
|
||||||
|
<td>{{ object.other_companies|yesno|capfirst }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'crew_fatigue'|striptags }}</para></td>
|
||||||
|
<td>{{ object.crew_fatigue|yesno|capfirst }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'general_notes'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.general_notes|default:'No' }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><h3><strong>Power</strong></h3><spacer length="4"/><para textColor="white" backColor={% if object.event_size == 0 %}"green"{% elif object.event_size == 1 %}"yellow"{% else %}"red"{% endif %} borderPadding="3">{{ object.get_event_size_display }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'big_power'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.big_power|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'power_mic'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.power_mic|default:object.event.mic }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'outside'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.outside|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'generators'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.generators|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'other_companies_power'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.other_companies_power|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'nonstandard_equipment_power'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.nonstandard_equipment_power|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'multiple_electrical_environments'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.multiple_electrical_environments|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'power_notes'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.power_notes|default:'No' }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><h3><strong>Sound</strong></h3></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'noise_monitoring'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.noise_monitoring|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'sound_notes'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.sound_notes|default:'No' }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><h3><strong>Site Details</strong></h3></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'known_venue'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.known_venue|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'safe_loading'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.safe_loading|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'safe_storage'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.safe_storage|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'area_outside_of_control'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.area_outside_of_control|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'barrier_required'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.barrier_required|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'nonstandard_emergency_procedure'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.nonstandard_emergency_procedure|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><h3><strong>Structures</strong></h3></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'special_structures'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.special_structures|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'suspended_structures'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.suspended_structures|yesno|capfirst }}</para></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><para>{{ object|help_text:'persons_responsible_structures'|striptags }}</para></td>
|
||||||
|
<td><para>{{ object.persons_responsible_structures|default:'N/A' }}</para></td>
|
||||||
|
</tr>
|
||||||
|
</blockTable>
|
||||||
|
<spacer length="15"/>\
|
||||||
|
<hr/>
|
||||||
|
<spacer length="15"/>
|
||||||
|
<para><em>Assessment completed by {{ object.last_edited_by }} on {{ object.last_edited_at }}</em></para>
|
||||||
|
{% if object.reviewed_by %}
|
||||||
|
<para><em>Reviewed by {{ object.reviewed_by }} on {{ object.reviewed.at }}</em></para>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
|
||||||
{% load help_text from filters %}
|
{% load filters %}
|
||||||
{% load yesnoi from filters %}
|
|
||||||
{% load linkornone from filters %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row py-3">
|
<div class="row py-3">
|
||||||
@@ -47,7 +45,7 @@
|
|||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-6">{{ object|help_text:'power_mic'|safe }}</dt>
|
<dt class="col-sm-6">{{ object|help_text:'power_mic'|safe }}</dt>
|
||||||
<dd class="col-sm-6">
|
<dd class="col-sm-6">
|
||||||
{{ object.power_mic.name|default:'None' }}
|
{{ object.power_mic.name|default:object.event.mic }}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-6">{{ object|help_text:'outside' }}</dt>
|
<dt class="col-sm-6">{{ object|help_text:'outside' }}</dt>
|
||||||
<dd class="col-sm-6">
|
<dd class="col-sm-6">
|
||||||
@@ -144,7 +142,7 @@
|
|||||||
</dd>
|
</dd>
|
||||||
<dt class="col-12">{{ object|help_text:'persons_responsible_structures' }}</dt>
|
<dt class="col-12">{{ object|help_text:'persons_responsible_structures' }}</dt>
|
||||||
<dd class="col-12">
|
<dd class="col-12">
|
||||||
{{ object.persons_responsible_structures.name|default:'N/A'|linebreaks }}
|
{{ object.persons_responsible_structures|default:'N/A'|linebreaks }}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-12">{{ object|help_text:'rigging_plan'|safe }}</dt>
|
<dt class="col-12">{{ object|help_text:'rigging_plan'|safe }}</dt>
|
||||||
<dd class="col-12">
|
<dd class="col-12">
|
||||||
@@ -157,6 +155,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 text-right">
|
<div class="col-12 text-right">
|
||||||
|
{% button 'print' 'ra_print' object.pk %}
|
||||||
<a href="{% url 'ra_edit' object.pk %}" class="btn btn-warning my-3"><span class="fas fa-edit"></span> <span
|
<a href="{% url 'ra_edit' object.pk %}" class="btn btn-warning my-3"><span class="fas fa-edit"></span> <span
|
||||||
class="d-none d-sm-inline">Edit</span></a>
|
class="d-none d-sm-inline">Edit</span></a>
|
||||||
<a href="{% url 'event_detail' object.event.pk %}" class="btn btn-primary"><span class="fas fa-eye"></span> View Event</a>
|
<a href="{% url 'event_detail' object.event.pk %}" class="btn btn-primary"><span class="fas fa-eye"></span> View Event</a>
|
||||||
@@ -5,18 +5,16 @@
|
|||||||
{% load nice_errors from filters %}
|
{% load nice_errors from filters %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ block.super }}
|
|
||||||
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/>
|
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block preload_js %}
|
{% block preload_js %}
|
||||||
{{ block.super }}
|
<script src="{% static 'js/selects.js' %}" async></script>
|
||||||
<script src="{% static 'js/selects.js' %}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ block.super }}
|
|
||||||
<script src="{% static 'js/autocompleter.js' %}"></script>
|
<script src="{% static 'js/autocompleter.js' %}"></script>
|
||||||
|
<script src="{% static 'js/tooltip.js' %}"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function parseBool(str) {
|
function parseBool(str) {
|
||||||
@@ -27,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() {
|
$('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_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() {
|
$('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));
|
$('#{{ form.persons_responsible_structures.id_for_label }}').prop('hidden', !parseBool(this.value)).prop('required', parseBool(this.value));
|
||||||
@@ -100,9 +98,9 @@
|
|||||||
<label for="{{ form.power_mic.id_for_label }}"
|
<label for="{{ form.power_mic.id_for_label }}"
|
||||||
class="col col-form-label">{{ form.power_mic.help_text|safe }}</label>
|
class="col col-form-label">{{ form.power_mic.help_text|safe }}</label>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<select id="{{ form.power_mic.id_for_label }}" name="{{ form.power_mic.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
<select id="{{ form.power_mic.id_for_label }}" name="{{ form.power_mic.name }}" class="selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
|
||||||
{% if object.power_mic %}
|
{% if power_mic %}
|
||||||
<option value="{{object.power_mic.pk}}" selected="selected">{{ object.power_mic.name }}</option>
|
<option value="{{form.power_mic.value}}" selected="selected">{{ power_mic }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -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 %}
|
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
{% with object.event as object %}
|
{% with object.event as object %}
|
||||||
{% include 'item_table.html' %}
|
{% include 'partials/item_table.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<dt class="col-sm-6">Phone Number</dt>
|
<dt class="col-sm-6">Phone Number</dt>
|
||||||
<dd class="col-sm-6">{{ object.organisation.phone|linkornone:'tel' }}</dd>
|
<dd class="col-sm-6">{{ object.organisation.phone|linkornone:'tel' }}</dd>
|
||||||
<dt class="col-sm-6">Has SU Account</dt>
|
<dt class="col-sm-6">Has SU Account</dt>
|
||||||
<dd class="col-sm-6">{{ event.organisation.union_account|yesno|capfirst }}</dd>
|
<dd class="col-sm-6">{{ object.organisation.union_account|yesno|capfirst }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
<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="row collapse" id="values">
|
||||||
<div class="table-responsive">
|
<div class="col-md-6 col-sm-12">
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -33,20 +33,23 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Distro</th>
|
<th scope="row">Distro</th>
|
||||||
<th scope="row">Max PSSC (kA)</th>
|
<th scope="row">Max PSSC with Single Phase Supply (kA)</th>
|
||||||
|
<th scope="row">Max PSSC with Three Phase Supply (kA)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Intel & Toblerone distros</td>
|
<td>Intel & Toblerone distros</td>
|
||||||
<td>6</td>
|
<td>6</td>
|
||||||
|
<td>3</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>All other distros</td>
|
<td>All other distros</td>
|
||||||
<td>10</td>
|
<td>10</td>
|
||||||
|
<td>5</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
{% if event.internal %}
|
{% if event.internal %}
|
||||||
<a class="btn item-add modal-href event-authorise-request
|
<a class="btn item-add modal-href event-authorise-request
|
||||||
{% if event.authorised %}
|
{% if event.authorised %}
|
||||||
btn-success active
|
btn-success active disabled
|
||||||
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
|
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
|
||||||
btn-warning
|
btn-warning
|
||||||
{% elif event.auth_request_to %}
|
{% elif event.auth_request_to %}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
btn-secondary
|
btn-secondary
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"
|
"
|
||||||
href="{% url 'event_authorise_request' object.pk %}">
|
{% if event.authorised %}aria-disabled="true"{% else %}href="{% url 'event_authorise_request' object.pk %}"{% endif %}>
|
||||||
<span class="fas fa-paper-plane"></span>
|
<span class="fas fa-paper-plane"></span>
|
||||||
<span class="d-none d-sm-inline">
|
<span class="d-none d-sm-inline">
|
||||||
{% if event.authorised %}
|
{% if event.authorised %}
|
||||||
@@ -49,5 +49,13 @@
|
|||||||
{% endif %}
|
{% 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>
|
<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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% load namewithnotes from filters %}
|
{% load namewithnotes from filters %}
|
||||||
|
{% load markdown_tags %}
|
||||||
<div class="card card-info">
|
<div class="card card-info">
|
||||||
<div class="card-header">Event Info</div>
|
<div class="card-header">Event Info</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
{% if object.venue %}
|
{% if object.venue %}
|
||||||
<dt class="col-sm-6">Venue Notes</dt>
|
<dt class="col-sm-6">Venue Notes</dt>
|
||||||
<dd class="col-sm-6">
|
<dd class="col-sm-6">
|
||||||
{{ object.venue.notes }}{% if object.venue.three_phase_available %}<br>(Three phase available){%endif%}
|
{{ object.venue.notes|markdown }}{% if object.venue.three_phase_available %}<br>(Three phase available){%endif%}
|
||||||
</dd>
|
</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
<dd class="col-sm-12"> </dd>
|
<dd class="col-sm-12"> </dd>
|
||||||
|
|
||||||
<dt class="col-sm-6">Event Description</dt>
|
<dt class="col-sm-6">Event Description</dt>
|
||||||
<dd class="dont-break-out col-sm-12">{{ event.description|linebreaksbr }}</dd>
|
<dd class="dont-break-out col-sm-12">{{ event.description|markdown }}</dd>
|
||||||
|
|
||||||
<dd class="col-sm-12"> </dd>
|
<dd class="col-sm-12"> </dd>
|
||||||
|
|
||||||
@@ -76,6 +77,15 @@
|
|||||||
<dt class="col-sm-6">PO</dt>
|
<dt class="col-sm-6">PO</dt>
|
||||||
<dd class="col-sm-6">{{ object.purchase_order }}</dd>
|
<dd class="col-sm-6">{{ object.purchase_order }}</dd>
|
||||||
{% endif %}
|
{% 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>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<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>
|
<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.is_rig %}
|
||||||
{% if event.sum_total > 0 %}
|
{% if event.sum_total > 0 %}
|
||||||
@@ -16,18 +16,20 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not event.dry_hire %}
|
{% if not event.dry_hire %}
|
||||||
{% if event.riskassessment %}
|
{% if event.riskassessment %}
|
||||||
<span class="badge badge-success">RA: <span class="fas fa-check"></span>{%if event.riskassessment.reviewed_by%}<span class="fas fa-check"></span>{%endif%}</span>
|
<a href="{{ event.riskassessment.get_absolute_url }}"><span class="badge badge-success">RA: <span class="fas fa-check{% if event.riskassessment.reviewed_by %}-double{%endif%}"></span></a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge badge-danger">RA: <span class="fas fa-times"></span></span>
|
<span class="badge badge-danger">RA: <span class="fas fa-times"></span></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% if event.has_checklist %}
|
||||||
{% if not event.dry_hire %}
|
<span class="badge badge-success">Checklist: <span class="fas fa-check"></span> {% if event.checklists.count > 1 %}({{event.checklists.count}}){% endif %}</span>
|
||||||
{% if event.hs_done %}
|
|
||||||
{# TODO Display status of all checklists #}
|
|
||||||
<span class="badge badge-success">Checklist: <span class="fas fa-check"></span></span>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span>
|
<span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span>
|
||||||
{% endif %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if perms.RIGS.view_invoice %}
|
{% if perms.RIGS.view_invoice %}
|
||||||
{% if event.invoice %}
|
{% if event.invoice %}
|
||||||
|
|||||||
@@ -1,17 +1,95 @@
|
|||||||
{% load namewithnotes from filters %}
|
{% load namewithnotes from filters %}
|
||||||
<div class="table-responsive">
|
{% load markdown_tags %}
|
||||||
<table class="table mb-0" id="event_table">
|
<style>
|
||||||
<thead>
|
#event_table {
|
||||||
<tr>
|
display: grid;
|
||||||
<th scope="col">#</th>
|
grid-template-columns: max-content auto;
|
||||||
<th scope="col">Dates & Times</th>
|
column-gap: 1em;
|
||||||
<th scope="col">Event Details</th>
|
}
|
||||||
<th scope="col">MIC</th>
|
.eventgrid {
|
||||||
</tr>
|
display: inherit;
|
||||||
</thead>
|
grid-column: 1/5;
|
||||||
<tbody>
|
grid-template-columns: subgrid;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
#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 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dt {
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
dd {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
</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 %}
|
{% for event in events %}
|
||||||
<tr class="{% if event.cancelled %}
|
<div class="eventgrid {% if event.cancelled %}
|
||||||
table-secondary
|
table-secondary
|
||||||
{% elif not event.is_rig %}
|
{% elif not event.is_rig %}
|
||||||
table-info
|
table-info
|
||||||
@@ -27,43 +105,49 @@
|
|||||||
table-warning
|
table-warning
|
||||||
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
|
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
|
||||||
<!---Number-->
|
<!---Number-->
|
||||||
<th scope="row" id="event_number">{{ event.display_id }}</th>
|
<div class="font-weight-bold d-none d-lg-block" id="event_number">{{ event.display_id }}</div>
|
||||||
<!--Dates & Times-->
|
<!--Dates & Times-->
|
||||||
<td id="event_dates">
|
<div id="event_dates" style="min-width: 180px;">
|
||||||
<span class="text-nowrap">Start: <strong>{{ event.start_date|date:"D d/m/Y" }}
|
<dl>
|
||||||
{% 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 %}
|
|
||||||
{% if not event.cancelled %}
|
{% if not event.cancelled %}
|
||||||
{% if event.meet_at %}
|
{% if event.meet_at %}
|
||||||
<br><span class="text-nowrap">Meet: <strong>{{ event.meet_at|date:"D d/m/Y H:i" }}</strong></span>
|
<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 %}
|
{% endif %}
|
||||||
{% if event.access_at %}
|
{% if event.access_at %}
|
||||||
<br><span class="text-nowrap">Access: <strong>{{ event.access_at|date:" D d/m/Y H:i" }}</strong></span>
|
<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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
<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-->
|
<!---Details-->
|
||||||
<td id="event_details" class="w-100">
|
<div id="event_details" class="w-100">
|
||||||
<h4>
|
<h4>
|
||||||
<a href="{% url 'event_detail' event.pk %}">
|
<a href="{% url 'event_detail' event.pk %}">
|
||||||
{{ event.name }}
|
<span class="d-inline d-lg-none">{{ event }}</span><span class="d-none d-lg-inline">{{ event.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% if event.venue %}
|
|
||||||
<small>at {{ event.venue|namewithnotes:'venue_detail' }}</small>
|
|
||||||
{% endif %}
|
|
||||||
{% if event.dry_hire %}
|
{% if event.dry_hire %}
|
||||||
<span class="badge badge-secondary">Dry Hire</span>
|
<span class="badge badge-secondary">Dry Hire</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<br class="d-none d-lg-inline">
|
||||||
|
{% if event.venue %}
|
||||||
|
<small>at {{ event.venue|namewithnotes:'venue_detail' }}</small>
|
||||||
|
{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
{% if event.is_rig and not event.cancelled %}
|
{% if event.is_rig and not event.cancelled %}
|
||||||
<h5>
|
<h5>
|
||||||
@@ -74,12 +158,13 @@
|
|||||||
</h5>
|
</h5>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not event.cancelled and event.description %}
|
{% if not event.cancelled and event.description %}
|
||||||
<p>{{ event.description|linebreaksbr }}</p>
|
<p>{{ event.description|markdown }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% include 'partials/event_status.html' %}
|
{% include 'partials/event_status.html' %}
|
||||||
</td>
|
|
||||||
<!---MIC-->
|
<!---MIC-->
|
||||||
<td id="event_mic" class="text-nowrap">
|
<div id="event_mic" class="text-nowrap">
|
||||||
|
<span class="d-md-none align-middle">MIC:</span>
|
||||||
{% if event.mic %}
|
{% if event.mic %}
|
||||||
{% if perms.RIGS.view_profile %}
|
{% if perms.RIGS.view_profile %}
|
||||||
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
|
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
|
||||||
@@ -90,15 +175,9 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif event.is_rig %}
|
{% elif event.is_rig %}
|
||||||
<span class="fas fa-user-slash"></span>
|
<span class="fas fa-exclamation"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
{% empty %}
|
{% endfor %}
|
||||||
<tr class="bg-warning">
|
|
||||||
<td colspan="4">No events found</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,5 +15,13 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<a href="{% url 'event_ec' event.pk %}" class="btn btn-info mt-2"><span class="fas fa-paperclip"></span> <span
|
<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>
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
id="item_name"/>
|
id="item_name"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-row">
|
<div class="form-group form-row" data-toggle="tooltip" title="A detailed description of the kit. MD enabled.">
|
||||||
<label for="item_description" class="col-sm-2 col-form-label">Description</label>
|
<label for="item_description" class="col-sm-2 col-form-label">Description</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<textarea type="text" placeholder="Description" class="form-control"
|
<textarea type="text" placeholder="Description" class="form-control md-enabled"
|
||||||
id="item_description" rows="8"></textarea>
|
id="item_description" rows="8"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
{% load markdown_tags %}
|
||||||
<tr id="item-{{item.pk}}" data-pk="{{item.pk}}" class="item_row">
|
<tr id="item-{{item.pk}}" data-pk="{{item.pk}}" class="item_row">
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<span class="name">{{ item.name }}</span>
|
<span class="name">{{ item.name }}</span>
|
||||||
<div class="item-description">
|
<div class="item-description">
|
||||||
<em class="description">{{item.description|linebreaksbr}}</em>
|
<em class="description">{{item.description|markdown}}</em>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
{% if perms.RIGS.view_event %}
|
{% if perms.RIGS.view_event %}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="item-table-body">
|
<tbody id="item-table-body">
|
||||||
{% for item in object.items.all %}
|
{% for item in object.items.all %}
|
||||||
{% include 'item_row.html' %}
|
{% include 'partials/item_row.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
{% if auth or perms.RIGS.view_event %}
|
{% if auth or perms.RIGS.view_event %}
|
||||||
@@ -29,7 +29,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row pt-3">
|
<div class="row pt-3">
|
||||||
<label class="col-sm-4 col-form-label"
|
<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">
|
<div class="col-sm-8">
|
||||||
{% render_field form.method class+="form-control" %}
|
{% render_field form.method class+="form-control" %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row align-items-center justify-content-between py-2 align-middle">
|
<div class="row align-items-center justify-content-between py-2 align-middle">
|
||||||
<div class="col-sm-12 col-md align-middle">
|
<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">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>
|
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>
|
</div>
|
||||||
{% if perms.RIGS.add_event %}
|
{% if perms.RIGS.add_event %}
|
||||||
<div class="col text-right">
|
<div class="col text-right">
|
||||||
|
|||||||
@@ -114,15 +114,13 @@ def orderby(request, field, attr):
|
|||||||
|
|
||||||
return dict_.urlencode()
|
return dict_.urlencode()
|
||||||
|
|
||||||
# Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
|
|
||||||
|
|
||||||
|
@register.filter(needs_autoescape=True) # Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
|
||||||
@register.filter(needs_autoescape=True)
|
|
||||||
def get_field(obj, field, autoescape=True):
|
def get_field(obj, field, autoescape=True):
|
||||||
value = getattr(obj, field)
|
value = getattr(obj, field)
|
||||||
if(isinstance(value, bool)):
|
if (isinstance(value, bool)):
|
||||||
value = yesnoi(value, field in obj.inverted_fields)
|
value = yesnoi(value, field in obj.inverted_fields)
|
||||||
elif(isinstance(value, str)):
|
elif (isinstance(value, str)):
|
||||||
value = truncatewords(value, 20)
|
value = truncatewords(value, 20)
|
||||||
return mark_safe(value)
|
return mark_safe(value)
|
||||||
|
|
||||||
@@ -146,7 +144,7 @@ def get_list(dictionary, key):
|
|||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def profile_by_index(value):
|
def profile_by_index(value):
|
||||||
if(value):
|
if (value):
|
||||||
return models.Profile.objects.get(pk=int(value))
|
return models.Profile.objects.get(pk=int(value))
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
@@ -173,7 +171,7 @@ def title_spaced(string):
|
|||||||
@register.filter(needs_autoescape=True)
|
@register.filter(needs_autoescape=True)
|
||||||
def namewithnotes(obj, url, autoescape=True):
|
def namewithnotes(obj, url, autoescape=True):
|
||||||
if hasattr(obj, 'notes') and obj.notes is not None and len(obj.notes) > 0:
|
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:
|
else:
|
||||||
return obj.name
|
return obj.name
|
||||||
|
|
||||||
@@ -185,7 +183,7 @@ def linkornone(target, namespace=None, autoescape=True):
|
|||||||
link = namespace + "://" + target
|
link = namespace + "://" + target
|
||||||
else:
|
else:
|
||||||
link = target
|
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:
|
else:
|
||||||
return "None"
|
return "None"
|
||||||
|
|
||||||
@@ -198,8 +196,8 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
|
|||||||
text = "Edit"
|
text = "Edit"
|
||||||
elif type == 'print':
|
elif type == 'print':
|
||||||
clazz += " btn-primary "
|
clazz += " btn-primary "
|
||||||
icon = "fa-print"
|
icon = "fa-download"
|
||||||
text = "Print"
|
text = "Export"
|
||||||
elif type == 'duplicate':
|
elif type == 'duplicate':
|
||||||
clazz += " btn-info "
|
clazz += " btn-info "
|
||||||
icon = "fa-copy"
|
icon = "fa-copy"
|
||||||
@@ -212,14 +210,18 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
|
|||||||
clazz += " btn-primary "
|
clazz += " btn-primary "
|
||||||
icon = "fa-plus"
|
icon = "fa-plus"
|
||||||
text = "New"
|
text = "New"
|
||||||
|
elif type == 'copy':
|
||||||
|
return {'copy': True, 'id': id, 'style': style}
|
||||||
elif type == 'search':
|
elif type == 'search':
|
||||||
return {'submit': True, 'class': 'btn-info', 'icon': 'fa-search', 'text': 'Search', 'id': id, 'style': style}
|
return {'submit': True, 'class': 'btn-info', 'icon': 'fa-search', 'text': 'Search', 'id': id, 'style': style}
|
||||||
elif type == 'submit':
|
elif type == 'submit':
|
||||||
return {'submit': True, 'class': 'btn-primary', 'icon': 'fa-save', 'text': 'Save', 'id': id, 'style': style}
|
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}
|
return {'target': url, 'pk': pk, 'class': clazz, 'icon': icon, 'text': text, 'id': id, 'style': style}
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag # TODO Can these be done with annotation/aggregation?
|
||||||
def invoices_waiting():
|
def invoices_waiting():
|
||||||
return len(models.Event.objects.waiting_invoices())
|
return len(models.Event.objects.waiting_invoices())
|
||||||
|
|
||||||
|
|||||||
56
RIGS/templatetags/markdown_tags.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from django import template
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
import markdown
|
||||||
|
|
||||||
|
__author__ = 'ghost'
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="markdown")
|
||||||
|
def markdown_filter(text, input_format='html', add_style=""):
|
||||||
|
# markdown library can't handle text=None
|
||||||
|
if text is None:
|
||||||
|
return text
|
||||||
|
html = markdown.markdown(text, extensions=['markdown.extensions.nl2br'])
|
||||||
|
# Convert format to RML
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
# Prevent code injection
|
||||||
|
for script in soup('script'):
|
||||||
|
script.string = "Your script shall not pass!"
|
||||||
|
if input_format == 'html':
|
||||||
|
return mark_safe('<div class="markdown">' + str(soup) + '</div>')
|
||||||
|
elif input_format == 'rml':
|
||||||
|
|
||||||
|
# Image aren't supported so remove them
|
||||||
|
for img in soup('img'):
|
||||||
|
img.parent.extract()
|
||||||
|
|
||||||
|
# <code> should become <font>
|
||||||
|
for c in soup('code'):
|
||||||
|
c.name = 'font'
|
||||||
|
c['face'] = "Courier"
|
||||||
|
|
||||||
|
# blockquotes don't exist but we can still do something to show
|
||||||
|
for bq in soup('blockquote'):
|
||||||
|
bq.name = 'pre'
|
||||||
|
bq.string = bq.text
|
||||||
|
|
||||||
|
for alist in soup.find_all(['ul', 'ol']):
|
||||||
|
alist['style'] = alist.name
|
||||||
|
for li in alist.find_all('li', recursive=False):
|
||||||
|
text = li.find(text=True)
|
||||||
|
text.wrap(soup.new_tag('p'))
|
||||||
|
|
||||||
|
if alist.parent.name != 'li':
|
||||||
|
indent = soup.new_tag('indent')
|
||||||
|
indent['left'] = '0.6cm'
|
||||||
|
|
||||||
|
alist.wrap(indent)
|
||||||
|
|
||||||
|
# Paragraphs have a different tag
|
||||||
|
for p in soup('p'):
|
||||||
|
p.name = 'para'
|
||||||
|
|
||||||
|
return mark_safe(str(soup))
|
||||||
@@ -43,15 +43,22 @@ def venue(db):
|
|||||||
|
|
||||||
@pytest.fixture # TODO parameterise with Event sizes
|
@pytest.fixture # TODO parameterise with Event sizes
|
||||||
def checklist(basic_event, venue, admin_user, ra):
|
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,
|
safe_packing=False, exits=False, trip_hazard=False, warning_signs=False,
|
||||||
ear_plugs=False, hs_location="Locked away safely",
|
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)
|
date=timezone.now(), venue=venue)
|
||||||
yield checklist
|
yield checklist
|
||||||
checklist.delete()
|
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
|
@pytest.fixture
|
||||||
def many_events(db, admin_user, scope="class"):
|
def many_events(db, admin_user, scope="class"):
|
||||||
many_events = {
|
many_events = {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class CreateEvent(FormPage):
|
|||||||
_warning_selector = (By.XPATH, '/html/body/div[1]/div[1]')
|
_warning_selector = (By.XPATH, '/html/body/div[1]/div[1]')
|
||||||
|
|
||||||
form_items = {
|
form_items = {
|
||||||
'description': (regions.TextBox, (By.ID, 'id_description')),
|
'description': (regions.SimpleMDETextArea, (By.ID, 'id_description')),
|
||||||
|
|
||||||
'name': (regions.TextBox, (By.ID, 'id_name')),
|
'name': (regions.TextBox, (By.ID, 'id_name')),
|
||||||
'start_date': (regions.DatePicker, (By.ID, 'id_start_date')),
|
'start_date': (regions.DatePicker, (By.ID, 'id_start_date')),
|
||||||
@@ -110,11 +110,11 @@ class CreateEvent(FormPage):
|
|||||||
'collected_by': (regions.TextBox, (By.ID, 'id_collector')),
|
'collected_by': (regions.TextBox, (By.ID, 'id_collector')),
|
||||||
'po': (regions.TextBox, (By.ID, 'id_purchase_order')),
|
'po': (regions.TextBox, (By.ID, 'id_purchase_order')),
|
||||||
|
|
||||||
'notes': (regions.TextBox, (By.ID, 'id_notes'))
|
'notes': (regions.SimpleMDETextArea, (By.ID, 'id_notes'))
|
||||||
}
|
}
|
||||||
|
|
||||||
def select_event_type(self, type_name):
|
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):
|
def item_row(self, ID):
|
||||||
return rigs_regions.ItemRow(self, self.find_element(By.ID, "item-" + ID))
|
return rigs_regions.ItemRow(self, self.find_element(By.ID, "item-" + ID))
|
||||||
@@ -145,11 +145,11 @@ class CreateEvent(FormPage):
|
|||||||
|
|
||||||
def add_person(self):
|
def add_person(self):
|
||||||
self.find_element(*self._add_person_selector).click()
|
self.find_element(*self._add_person_selector).click()
|
||||||
return regions.Modal(self, self.driver.find_element_by_id('modal'))
|
return regions.Modal(self, self.driver.find_element(By.ID, 'modal'))
|
||||||
|
|
||||||
def add_event_item(self):
|
def add_event_item(self):
|
||||||
self.find_element(*self._add_item_selector).click()
|
self.find_element(*self._add_item_selector).click()
|
||||||
element = self.driver.find_element_by_id('itemModal')
|
element = self.driver.find_element(By.ID, 'itemModal')
|
||||||
self.wait.until(EC.visibility_of(element))
|
self.wait.until(EC.visibility_of(element))
|
||||||
return rigs_regions.ItemModal(self, element)
|
return rigs_regions.ItemModal(self, element)
|
||||||
|
|
||||||
@@ -230,11 +230,6 @@ class CreateEventChecklist(FormPage):
|
|||||||
URL_TEMPLATE = 'event/{event_id}/checklist'
|
URL_TEMPLATE = 'event/{event_id}/checklist'
|
||||||
|
|
||||||
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
|
_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 = {
|
form_items = {
|
||||||
'safe_parking': (regions.CheckBox, (By.ID, 'id_safe_parking')),
|
'safe_parking': (regions.CheckBox, (By.ID, 'id_safe_parking')),
|
||||||
@@ -245,6 +240,20 @@ class CreateEventChecklist(FormPage):
|
|||||||
'ear_plugs': (regions.CheckBox, (By.ID, 'id_ear_plugs')),
|
'ear_plugs': (regions.CheckBox, (By.ID, 'id_ear_plugs')),
|
||||||
'hs_location': (regions.TextBox, (By.ID, 'id_hs_location')),
|
'hs_location': (regions.TextBox, (By.ID, 'id_hs_location')),
|
||||||
'extinguishers_location': (regions.TextBox, (By.ID, 'id_extinguishers_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')),
|
'rcds': (regions.CheckBox, (By.ID, 'id_rcds')),
|
||||||
'supply_test': (regions.CheckBox, (By.ID, 'id_supply_test')),
|
'supply_test': (regions.CheckBox, (By.ID, 'id_supply_test')),
|
||||||
'earthing': (regions.CheckBox, (By.ID, 'id_earthing')),
|
'earthing': (regions.CheckBox, (By.ID, 'id_earthing')),
|
||||||
@@ -263,58 +272,10 @@ class CreateEventChecklist(FormPage):
|
|||||||
'w1_earth_fault': (regions.TextBox, (By.ID, 'id_w1_earth_fault')),
|
'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
|
@property
|
||||||
def power_mic(self):
|
def power_mic(self):
|
||||||
return regions.BootstrapSelectElement(self, self.find_element(*self._power_mic_selector))
|
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
|
@property
|
||||||
def success(self):
|
def success(self):
|
||||||
return '{event_id}' not in self.driver.current_url
|
return '{event_id}' not in self.driver.current_url
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from pypom import Region
|
from pypom import Region
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
from PyRIGS.tests.regions import TextBox, Modal
|
from PyRIGS.tests.regions import TextBox, Modal, SimpleMDETextArea
|
||||||
|
|
||||||
|
|
||||||
class Header(Region):
|
class Header(Region):
|
||||||
def find_link(self, link_text):
|
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):
|
class ItemRow(Region):
|
||||||
@@ -42,7 +42,7 @@ class ItemModal(Modal):
|
|||||||
|
|
||||||
form_items = {
|
form_items = {
|
||||||
'name': (TextBox, (By.ID, 'item_name')),
|
'name': (TextBox, (By.ID, 'item_name')),
|
||||||
'description': (TextBox, (By.ID, 'item_description')),
|
'description': (SimpleMDETextArea, (By.ID, 'item_description')),
|
||||||
'quantity': (TextBox, (By.ID, 'item_quantity')),
|
'quantity': (TextBox, (By.ID, 'item_quantity')),
|
||||||
'price': (TextBox, (By.ID, 'item_cost'))
|
'price': (TextBox, (By.ID, 'item_cost'))
|
||||||
}
|
}
|
||||||
|
|||||||
155
RIGS/tests/sample.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
An h1 header
|
||||||
|
============
|
||||||
|
|
||||||
|
Paragraphs are separated by a blank line.
|
||||||
|
|
||||||
|
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
|
||||||
|
look like:
|
||||||
|
|
||||||
|
* this one
|
||||||
|
* that one
|
||||||
|
* the other one
|
||||||
|
|
||||||
|
Note that --- not considering the asterisk --- the actual text
|
||||||
|
content starts at 4-columns in.
|
||||||
|
|
||||||
|
> Block quotes are
|
||||||
|
> written like so.
|
||||||
|
>
|
||||||
|
> They can span multiple paragraphs,
|
||||||
|
> if you like.
|
||||||
|
|
||||||
|
Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all
|
||||||
|
in chapters 12--14"). Three dots ... will be converted to an ellipsis.
|
||||||
|
Unicode is supported.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
An h2 header
|
||||||
|
------------
|
||||||
|
|
||||||
|
Here's a numbered list:
|
||||||
|
|
||||||
|
1. first item
|
||||||
|
2. second item
|
||||||
|
3. third item
|
||||||
|
|
||||||
|
Note again how the actual text starts at 4 columns in (4 characters
|
||||||
|
from the left side). Here's a code sample:
|
||||||
|
|
||||||
|
# Let me re-iterate ...
|
||||||
|
for i in 1 .. 10 { do-something(i) }
|
||||||
|
|
||||||
|
As you probably guessed, indented 4 spaces. By the way, instead of
|
||||||
|
indenting the block, you can use delimited blocks, if you like:
|
||||||
|
|
||||||
|
~~~
|
||||||
|
define foobar() {
|
||||||
|
print "Welcome to flavor country!";
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
(which makes copying & pasting easier). You can optionally mark the
|
||||||
|
delimited block for Pandoc to syntax highlight it:
|
||||||
|
|
||||||
|
~~~python
|
||||||
|
import time
|
||||||
|
# Quick, count to ten!
|
||||||
|
for i in range(10):
|
||||||
|
# (but not *too* quick)
|
||||||
|
time.sleep(0.5)
|
||||||
|
print i
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### An h3 header ###
|
||||||
|
|
||||||
|
Now a nested list:
|
||||||
|
|
||||||
|
1. First, get these ingredients:
|
||||||
|
|
||||||
|
* carrots
|
||||||
|
* celery
|
||||||
|
* lentils
|
||||||
|
|
||||||
|
2. Boil some water.
|
||||||
|
|
||||||
|
3. Dump everything in the pot and follow
|
||||||
|
this algorithm:
|
||||||
|
|
||||||
|
find wooden spoon
|
||||||
|
uncover pot
|
||||||
|
stir
|
||||||
|
cover pot
|
||||||
|
balance wooden spoon precariously on pot handle
|
||||||
|
wait 10 minutes
|
||||||
|
goto first step (or shut off burner when done)
|
||||||
|
|
||||||
|
Do not bump wooden spoon or it will fall.
|
||||||
|
|
||||||
|
Notice again how text always lines up on 4-space indents (including
|
||||||
|
that last line which continues item 3 above).
|
||||||
|
|
||||||
|
Here's a link to [a website](http://foo.bar). Here's a footnote [^1].
|
||||||
|
|
||||||
|
[^1]: Footnote text goes here.
|
||||||
|
|
||||||
|
Tables can look like this:
|
||||||
|
|
||||||
|
size material color
|
||||||
|
---- ------------ ------------
|
||||||
|
9 leather brown
|
||||||
|
10 hemp canvas natural
|
||||||
|
11 glass transparent
|
||||||
|
|
||||||
|
Table: Shoes, their sizes, and what they're made of
|
||||||
|
|
||||||
|
(The above is the caption for the table.) Pandoc also supports
|
||||||
|
multi-line tables:
|
||||||
|
|
||||||
|
-------- -----------------------
|
||||||
|
keyword text
|
||||||
|
-------- -----------------------
|
||||||
|
red Sunsets, apples, and
|
||||||
|
other red or reddish
|
||||||
|
things.
|
||||||
|
|
||||||
|
green Leaves, grass, frogs
|
||||||
|
and other things it's
|
||||||
|
not easy being.
|
||||||
|
-------- -----------------------
|
||||||
|
|
||||||
|
A horizontal rule follows.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
Here's a definition list:
|
||||||
|
|
||||||
|
apples
|
||||||
|
: Good for making applesauce.
|
||||||
|
oranges
|
||||||
|
: Citrus!
|
||||||
|
tomatoes
|
||||||
|
: There's no "e" in tomatoe.
|
||||||
|
|
||||||
|
Again, text is indented 4 spaces. (Put a blank line between each
|
||||||
|
term/definition pair to spread things out more.)
|
||||||
|
|
||||||
|
Here's a "line block":
|
||||||
|
|
||||||
|
| Line one
|
||||||
|
| Line too
|
||||||
|
| Line tree
|
||||||
|
|
||||||
|
and images can be specified like so:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Inline math equations go in like so: $\\omega = d\\phi / dt$. Display
|
||||||
|
math should get its own line and be put in in double-dollarsigns:
|
||||||
|
|
||||||
|
$$I = \\int \rho R^{2} dV$$
|
||||||
|
|
||||||
|
And note that you can backslash-escape any punctuation characters
|
||||||
|
which you wish to be displayed literally, ex.: \\`foo\\`, \\*bar\\*, etc.
|
||||||
@@ -211,7 +211,7 @@ class TestEventCreate(BaseRigboardTest):
|
|||||||
self.assertEqual("Test Item 1", testitem['name'])
|
self.assertEqual("Test Item 1", testitem['name'])
|
||||||
self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields
|
self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields
|
||||||
|
|
||||||
total = self.driver.find_element_by_id('total')
|
total = self.driver.find_element(By.ID, 'total')
|
||||||
ActionChains(self.driver).move_to_element(total).perform()
|
ActionChains(self.driver).move_to_element(total).perform()
|
||||||
|
|
||||||
# See new item appear in table
|
# See new item appear in table
|
||||||
@@ -224,9 +224,9 @@ class TestEventCreate(BaseRigboardTest):
|
|||||||
self.assertEqual('47.90', row.subtotal)
|
self.assertEqual('47.90', row.subtotal)
|
||||||
|
|
||||||
# Check totals TODO convert to page properties
|
# Check totals TODO convert to page properties
|
||||||
self.assertEqual("47.90", self.driver.find_element_by_id('sumtotal').text)
|
self.assertEqual("47.90", self.driver.find_element(By.ID, 'sumtotal').text)
|
||||||
self.assertIn("(TBC)", self.driver.find_element_by_id('vat-rate').text)
|
self.assertIn("(TBC)", self.driver.find_element(By.ID, 'vat-rate').text)
|
||||||
self.assertEqual("9.58", self.driver.find_element_by_id('vat').text)
|
self.assertEqual("9.58", self.driver.find_element(By.ID, 'vat').text)
|
||||||
self.assertEqual("57.48", total.text)
|
self.assertEqual("57.48", total.text)
|
||||||
|
|
||||||
self.page.submit()
|
self.page.submit()
|
||||||
@@ -318,7 +318,7 @@ class TestEventDuplicate(BaseRigboardTest):
|
|||||||
|
|
||||||
self.assertFalse(newEvent.authorised)
|
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
|
self.assertNotIn("Event data duplicated but not yet saved", self.page.warning) # Check info message not visible
|
||||||
|
|
||||||
# Check the new items are 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 2", table.text)
|
||||||
self.assertIn("Test Item 3", table.text)
|
self.assertIn("Test Item 3", table.text)
|
||||||
|
|
||||||
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.assertIn("N%05d" % self.testEvent.pk,
|
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)
|
|
||||||
# Check the PO hasn't carried through
|
# 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,
|
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
|
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
|
# 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,
|
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
|
# 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,
|
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
|
# Check the items are as they were
|
||||||
table = self.page.item_table # ID number is known, see above
|
table = self.page.item_table # ID number is known, see above
|
||||||
@@ -677,14 +676,6 @@ def small_ec(page, admin_user):
|
|||||||
page.ear_plugs = True
|
page.ear_plugs = True
|
||||||
page.hs_location = "The Moon"
|
page.hs_location = "The Moon"
|
||||||
page.extinguishers_location = "With the rest of the fire"
|
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):
|
def test_ec_create_small(logged_in_browser, live_server, admin_user, ra):
|
||||||
@@ -705,14 +696,15 @@ def test_ec_create_medium(logged_in_browser, live_server, admin_user, medium_ra)
|
|||||||
page.ear_plugs = True
|
page.ear_plugs = True
|
||||||
page.hs_location = "Death Valley"
|
page.hs_location = "Death Valley"
|
||||||
page.extinguishers_location = "With the rest of the fire"
|
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
|
# Gotta scroll to make the button clickable
|
||||||
logged_in_browser.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
|
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.earthing = True
|
||||||
page.pat = True
|
page.pat = True
|
||||||
page.source_rcd = True
|
page.source_rcd = True
|
||||||
@@ -727,56 +719,15 @@ def test_ec_create_medium(logged_in_browser, live_server, admin_user, medium_ra)
|
|||||||
page.w1_polarity = True
|
page.w1_polarity = True
|
||||||
page.w1_voltage = 240
|
page.w1_voltage = 240
|
||||||
page.w1_earth_fault = "0.42"
|
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()
|
page.submit()
|
||||||
assert page.success
|
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?
|
# 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):
|
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()
|
page = pages.CreateRiskAssessment(logged_in_browser.driver, live_server.url, event_id=basic_event.pk).open()
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
import os
|
||||||
|
import pytest
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
from RIGS.templatetags.markdown_tags import markdown_filter
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from pytest_django.asserts import assertRedirects, assertNotContains, assertContains
|
from pytest_django.asserts import assertRedirects, assertNotContains, assertContains
|
||||||
@@ -10,8 +15,6 @@ from pytest_django.asserts import assertRedirects, assertNotContains, assertCont
|
|||||||
from PyRIGS.tests.base import assert_times_almost_equal, assert_oembed, login
|
from PyRIGS.tests.base import assert_times_almost_equal, assert_oembed, login
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
@@ -170,6 +173,7 @@ class TestInvoiceDelete(TestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
|
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
|
||||||
is_active=True, is_staff=True)
|
is_active=True, is_staff=True)
|
||||||
|
cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||||
cls.events = {
|
cls.events = {
|
||||||
1: models.Event.objects.create(name="TE E1", start_date=date.today()),
|
1: models.Event.objects.create(name="TE E1", start_date=date.today()),
|
||||||
2: models.Event.objects.create(name="TE E2", start_date=date.today())
|
2: models.Event.objects.create(name="TE E2", start_date=date.today())
|
||||||
@@ -255,7 +259,7 @@ class TestPrintPaperwork(TestCase):
|
|||||||
|
|
||||||
def test_login_redirect(client, django_user_model):
|
def test_login_redirect(client, django_user_model):
|
||||||
request_url = reverse('event_embed', kwargs={'pk': 1})
|
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
|
# Request the page and check it redirects
|
||||||
response = client.get(request_url, follow=True)
|
response = client.get(request_url, follow=True)
|
||||||
@@ -281,11 +285,11 @@ def test_xframe_headers(admin_client, basic_event):
|
|||||||
|
|
||||||
response = admin_client.get(event_url, follow=True)
|
response = admin_client.get(event_url, follow=True)
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
response._headers["X-Frame-Options"]
|
response.headers["X-Frame-Options"]
|
||||||
|
|
||||||
response = admin_client.get(login_url, follow=True)
|
response = admin_client.get(login_url, follow=True)
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
response._headers["X-Frame-Options"]
|
response.headers["X-Frame-Options"]
|
||||||
|
|
||||||
|
|
||||||
def test_oembed(client, basic_event):
|
def test_oembed(client, basic_event):
|
||||||
@@ -363,6 +367,61 @@ def test_checklist_review(admin_client, admin_user, checklist):
|
|||||||
def test_ra_redirect(admin_client, admin_user, ra):
|
def test_ra_redirect(admin_client, admin_user, ra):
|
||||||
request_url = reverse('event_ra', kwargs={'pk': ra.event.pk})
|
request_url = reverse('event_ra', kwargs={'pk': ra.event.pk})
|
||||||
expected_url = reverse('ra_edit', kwargs={'pk': ra.pk})
|
expected_url = reverse('ra_edit', kwargs={'pk': ra.pk})
|
||||||
|
|
||||||
response = admin_client.get(request_url, follow=True)
|
response = admin_client.get(request_url, follow=True)
|
||||||
assertRedirects(response, expected_url, status_code=302, target_status_code=200)
|
assertRedirects(response, expected_url, status_code=302, target_status_code=200)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMarkdownTemplateTags(TestCase):
|
||||||
|
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)
|
||||||
|
self.assertIsInstance(html, SafeText)
|
||||||
|
|
||||||
|
def test_img_strip(self):
|
||||||
|
rml = markdown_filter(self.markdown, 'rml')
|
||||||
|
self.assertNotIn("<img", rml)
|
||||||
|
|
||||||
|
def test_code(self):
|
||||||
|
rml = markdown_filter(self.markdown, 'rml')
|
||||||
|
self.assertIn('<font face="Courier">monospace</font>', rml)
|
||||||
|
|
||||||
|
def test_blockquote(self):
|
||||||
|
rml = markdown_filter(self.markdown, 'rml')
|
||||||
|
self.assertIn("<pre>\nBlock quotes", rml)
|
||||||
|
|
||||||
|
def test_lists(self):
|
||||||
|
rml = markdown_filter(self.markdown, 'rml')
|
||||||
|
self.assertIn("<li><para>second item</para></li>", rml) # <ol>
|
||||||
|
self.assertIn("<li><para>that one</para></li>", rml) # <ul>
|
||||||
|
|
||||||
|
def test_in_print(self):
|
||||||
|
event = models.Event.objects.create(
|
||||||
|
name="MD Print Test",
|
||||||
|
description=self.markdown,
|
||||||
|
start_date='2016-01-01',
|
||||||
|
)
|
||||||
|
event_item = models.EventItem.objects.create(event=event, name="TI I1", quantity=1, cost=1.00, order=1, description="* test \n * test \n * test")
|
||||||
|
user = models.Profile.objects.create(
|
||||||
|
username='RML test',
|
||||||
|
is_superuser=True, # Don't care about permissions
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
user.set_password('rmltester')
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
self.assertTrue(self.client.login(username=user.username, password='rmltester'))
|
||||||
|
response = self.client.get(reverse('event_print', kwargs={'pk': event.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# By the time we have a PDF it should be larger than the original by some margin
|
||||||
|
# RML hard fails if something doesn't work
|
||||||
|
self.assertGreater(len(response.content), len(self.markdown))
|
||||||
|
|
||||||
|
def test_nonetype(self):
|
||||||
|
html = markdown_filter(None)
|
||||||
|
self.assertIsNone(html)
|
||||||
|
|
||||||
|
def test_linebreaks(self):
|
||||||
|
html = markdown_filter(self.markdown)
|
||||||
|
self.assertIn("Itemized lists<br/>\nlook like", html)
|
||||||
|
|||||||
106
RIGS/urls.py
@@ -5,7 +5,7 @@ from django.views.generic import RedirectView
|
|||||||
|
|
||||||
from PyRIGS.decorators import (api_key_required, has_oembed,
|
from PyRIGS.decorators import (api_key_required, has_oembed,
|
||||||
permission_required_with_403)
|
permission_required_with_403)
|
||||||
from RIGS import finance, ical, rigboard, views, hs
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# People
|
# People
|
||||||
@@ -42,101 +42,119 @@ urlpatterns = [
|
|||||||
name='venue_update'),
|
name='venue_update'),
|
||||||
|
|
||||||
# Rigboard
|
# Rigboard
|
||||||
path('rigboard/', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'),
|
path('rigboard/', login_required(views.RigboardIndex.as_view()), name='rigboard'),
|
||||||
path('rigboard/calendar/', login_required()(rigboard.WebCalendar.as_view()),
|
path('rigboard/calendar/', login_required()(views.WebCalendar.as_view()),
|
||||||
name='web_calendar'),
|
name='web_calendar'),
|
||||||
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
|
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
|
||||||
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
login_required()(views.WebCalendar.as_view()), name='web_calendar'),
|
||||||
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
|
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
|
||||||
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
login_required()(views.WebCalendar.as_view()), name='web_calendar'),
|
||||||
path('rigboard/archive/', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
path('rigboard/archive/', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
||||||
|
|
||||||
|
|
||||||
path('event/<int:pk>/', has_oembed(oembed_view="event_oembed")(rigboard.EventDetail.as_view()),
|
path('event/<int:pk>/', has_oembed(oembed_view="event_oembed")(views.EventDetail.as_view()),
|
||||||
name='event_detail'),
|
name='event_detail'),
|
||||||
path('event/create/', permission_required_with_403('RIGS.add_event')(rigboard.EventCreate.as_view()),
|
path('event/create/', permission_required_with_403('RIGS.add_event')(views.EventCreate.as_view()),
|
||||||
name='event_create'),
|
name='event_create'),
|
||||||
path('event/archive/', login_required()(rigboard.EventArchive.as_view()),
|
path('event/archive/', login_required()(views.EventArchive.as_view()),
|
||||||
name='event_archive'),
|
name='event_archive'),
|
||||||
path('event/<int:pk>/embed/',
|
path('event/<int:pk>/embed/',
|
||||||
xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())),
|
xframe_options_exempt(login_required(login_url='/user/login/embed/')(views.EventEmbed.as_view())),
|
||||||
name='event_embed'),
|
name='event_embed'),
|
||||||
path('event/<int:pk>/oembed_json/', rigboard.EventOEmbed.as_view(),
|
path('event/<int:pk>/oembed_json/', views.EventOEmbed.as_view(),
|
||||||
name='event_oembed'),
|
name='event_oembed'),
|
||||||
path('event/<int:pk>/print/', permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()),
|
path('event/<int:pk>/print/', permission_required_with_403('RIGS.view_event')(views.EventPrint.as_view()),
|
||||||
name='event_print'),
|
name='event_print'),
|
||||||
path('event/<int:pk>/edit/', permission_required_with_403('RIGS.change_event')(rigboard.EventUpdate.as_view()),
|
path('event/<int:pk>/edit/', permission_required_with_403('RIGS.change_event')(views.EventUpdate.as_view()),
|
||||||
name='event_update'),
|
name='event_update'),
|
||||||
path('event/<int:pk>/duplicate/', permission_required_with_403('RIGS.add_event')(rigboard.EventDuplicate.as_view()),
|
path('event/<int:pk>/duplicate/', permission_required_with_403('RIGS.add_event')(views.EventDuplicate.as_view()),
|
||||||
name='event_duplicate'),
|
name='event_duplicate'),
|
||||||
|
|
||||||
# Event H&S
|
# Event H&S
|
||||||
path('event/hs/', permission_required_with_403('RIGS.view_riskassessment')(hs.HSList.as_view()), name='hs_list'),
|
path('event/hs/', permission_required_with_403('RIGS.view_riskassessment')(views.HSList.as_view()), name='hs_list'),
|
||||||
|
|
||||||
path('event/<int:pk>/ra/', permission_required_with_403('RIGS.add_riskassessment')(hs.EventRiskAssessmentCreate.as_view()),
|
path('event/<int:pk>/ra/', permission_required_with_403('RIGS.add_riskassessment')(views.EventRiskAssessmentCreate.as_view()),
|
||||||
name='event_ra'),
|
name='event_ra'),
|
||||||
path('event/ra/<int:pk>/', permission_required_with_403('RIGS.view_riskassessment')(hs.EventRiskAssessmentDetail.as_view()),
|
path('event/ra/<int:pk>/', login_required(views.EventRiskAssessmentDetail.as_view()),
|
||||||
name='ra_detail'),
|
name='ra_detail'),
|
||||||
path('event/ra/<int:pk>/edit/', permission_required_with_403('RIGS.change_riskassessment')(hs.EventRiskAssessmentEdit.as_view()),
|
path('event/ra/<int:pk>/edit/', permission_required_with_403('RIGS.change_riskassessment')(views.EventRiskAssessmentEdit.as_view()),
|
||||||
name='ra_edit'),
|
name='ra_edit'),
|
||||||
path('event/ra/list', permission_required_with_403('RIGS.view_riskassessment')(hs.EventRiskAssessmentList.as_view()),
|
path('event/ra/<int:pk>/review/', permission_required_with_403('RIGS.review_riskassessment')(views.MarkReviewed.as_view()),
|
||||||
name='ra_list'),
|
name='ra_review', kwargs={'model': 'RiskAssessment'}),
|
||||||
path('event/ra/<int:pk>/review/', permission_required_with_403('RIGS.review_riskassessment')(hs.EventRiskAssessmentReview.as_view()),
|
path('event/ra/<int:pk>/print/', permission_required_with_403('RIGS.view_riskassessment')(views.RAPrint.as_view()), name='ra_print'),
|
||||||
name='ra_review'),
|
|
||||||
|
|
||||||
path('event/<int:pk>/checklist/', permission_required_with_403('RIGS.add_eventchecklist')(hs.EventChecklistCreate.as_view()),
|
path('event/<int:pk>/checklist/', permission_required_with_403('RIGS.add_eventchecklist')(views.EventChecklistCreate.as_view()),
|
||||||
name='event_ec'),
|
name='event_ec'),
|
||||||
path('event/checklist/<int:pk>/', permission_required_with_403('RIGS.view_eventchecklist')(hs.EventChecklistDetail.as_view()),
|
path('event/checklist/<int:pk>/', login_required(views.EventChecklistDetail.as_view()),
|
||||||
name='ec_detail'),
|
name='ec_detail'),
|
||||||
path('event/checklist/<int:pk>/edit/', permission_required_with_403('RIGS.change_eventchecklist')(hs.EventChecklistEdit.as_view()),
|
path('event/checklist/<int:pk>/edit/', permission_required_with_403('RIGS.change_eventchecklist')(views.EventChecklistEdit.as_view()),
|
||||||
name='ec_edit'),
|
name='ec_edit'),
|
||||||
path('event/checklist/list', permission_required_with_403('RIGS.view_eventchecklist')(hs.EventChecklistList.as_view()),
|
path('event/checklist/<int:pk>/review/', permission_required_with_403('RIGS.review_eventchecklist')(views.MarkReviewed.as_view()),
|
||||||
name='ec_list'),
|
name='ec_review', kwargs={'model': 'EventChecklist'}),
|
||||||
path('event/checklist/<int:pk>/review/', permission_required_with_403('RIGS.review_eventchecklist')(hs.EventChecklistReview.as_view()),
|
|
||||||
name='ec_review'),
|
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/<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
|
# Finance
|
||||||
path('invoice/', permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()),
|
path('invoice/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceIndex.as_view()),
|
||||||
name='invoice_list'),
|
name='invoice_list'),
|
||||||
path('invoice/archive/', permission_required_with_403('RIGS.view_invoice')(finance.InvoiceArchive.as_view()),
|
path('invoice/archive/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceArchive.as_view()),
|
||||||
name='invoice_archive'),
|
name='invoice_archive'),
|
||||||
path('invoice/waiting/', permission_required_with_403('RIGS.add_invoice')(finance.InvoiceWaiting.as_view()),
|
path('invoice/waiting/', permission_required_with_403('RIGS.add_invoice')(views.InvoiceWaiting.as_view()),
|
||||||
name='invoice_waiting'),
|
name='invoice_waiting'),
|
||||||
|
|
||||||
path('event/<int:pk>/invoice/', permission_required_with_403('RIGS.add_invoice')(finance.InvoiceEvent.as_view()),
|
path('event/<int:pk>/invoice/', permission_required_with_403('RIGS.add_invoice')(views.InvoiceEvent.as_view()),
|
||||||
name='invoice_event'),
|
name='invoice_event'),
|
||||||
path('event/<int:pk>/invoice/void', permission_required_with_403('RIGS.add_invoice')(finance.InvoiceEvent.as_view()),
|
path('event/<int:pk>/invoice/void', permission_required_with_403('RIGS.add_invoice')(views.InvoiceEvent.as_view()),
|
||||||
name='invoice_event_void', kwargs={'void': True}),
|
name='invoice_event_void', kwargs={'void': True}),
|
||||||
|
|
||||||
path('invoice/<int:pk>/', permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()),
|
path('invoice/<int:pk>/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceDetail.as_view()),
|
||||||
name='invoice_detail'),
|
name='invoice_detail'),
|
||||||
path('invoice/<int:pk>/print/', permission_required_with_403('RIGS.view_invoice')(finance.InvoicePrint.as_view()),
|
path('invoice/<int:pk>/print/', permission_required_with_403('RIGS.view_invoice')(views.InvoicePrint.as_view()),
|
||||||
name='invoice_print'),
|
name='invoice_print'),
|
||||||
path('invoice/<int:pk>/void/', permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()),
|
path('invoice/<int:pk>/void/', permission_required_with_403('RIGS.change_invoice')(views.InvoiceVoid.as_view()),
|
||||||
name='invoice_void'),
|
name='invoice_void'),
|
||||||
path('invoice/<int:pk>/delete/',
|
path('invoice/<int:pk>/delete/',
|
||||||
permission_required_with_403('RIGS.change_invoice')(finance.InvoiceDelete.as_view()),
|
permission_required_with_403('RIGS.change_invoice')(views.InvoiceDelete.as_view()),
|
||||||
name='invoice_delete'),
|
name='invoice_delete'),
|
||||||
|
|
||||||
path('payment/create/', permission_required_with_403('RIGS.add_payment')(finance.PaymentCreate.as_view()),
|
path('payment/create/', permission_required_with_403('RIGS.add_payment')(views.PaymentCreate.as_view()),
|
||||||
name='payment_create'),
|
name='payment_create'),
|
||||||
path('payment/<int:pk>/delete/', permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()),
|
path('payment/<int:pk>/delete/', permission_required_with_403('RIGS.add_payment')(views.PaymentDelete.as_view()),
|
||||||
name='payment_delete'),
|
name='payment_delete'),
|
||||||
|
|
||||||
# Client event authorisation
|
# Client event authorisation
|
||||||
path('event/<pk>/auth/',
|
path('event/<pk>/auth/',
|
||||||
permission_required_with_403('RIGS.change_event')(rigboard.EventAuthorisationRequest.as_view()),
|
permission_required_with_403('RIGS.change_event')(views.EventAuthorisationRequest.as_view()),
|
||||||
name='event_authorise_request'),
|
name='event_authorise_request'),
|
||||||
path('event/<int:pk>/auth/preview/',
|
path('event/<int:pk>/auth/preview/',
|
||||||
permission_required_with_403('RIGS.change_event')(rigboard.EventAuthoriseRequestEmailPreview.as_view()),
|
permission_required_with_403('RIGS.change_event')(views.EventAuthoriseRequestEmailPreview.as_view()),
|
||||||
name='event_authorise_preview'),
|
name='event_authorise_preview'),
|
||||||
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
|
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', views.EventAuthorise.as_view(),
|
||||||
name='event_authorise'),
|
name='event_authorise'),
|
||||||
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/preview/$', rigboard.EventAuthorise.as_view(preview=True),
|
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/preview/$', views.EventAuthorise.as_view(preview=True),
|
||||||
name='event_authorise_form_preview'),
|
name='event_authorise_form_preview'),
|
||||||
|
|
||||||
# ICS Calendar - API key authentication
|
# ICS Calendar - API key authentication
|
||||||
re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),
|
re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(views.CalendarICS()),
|
||||||
name="ics_calendar"),
|
name="ics_calendar"),
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
5
RIGS/views/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .crud import *
|
||||||
|
from .finance import *
|
||||||
|
from .hs import *
|
||||||
|
from .ical import *
|
||||||
|
from .rigboard import *
|
||||||
@@ -6,7 +6,7 @@ class PersonList(GenericListView):
|
|||||||
model = models.Person
|
model = models.Person
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(PersonList, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['page_title'] = "People"
|
context['page_title'] = "People"
|
||||||
context['create'] = 'person_create'
|
context['create'] = 'person_create'
|
||||||
context['edit'] = 'person_update'
|
context['edit'] = 'person_update'
|
||||||
@@ -19,7 +19,7 @@ class PersonDetail(GenericDetailView):
|
|||||||
model = models.Person
|
model = models.Person
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(PersonDetail, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['history_link'] = 'person_history'
|
context['history_link'] = 'person_history'
|
||||||
context['detail_link'] = 'person_detail'
|
context['detail_link'] = 'person_detail'
|
||||||
context['update_link'] = 'person_update'
|
context['update_link'] = 'person_update'
|
||||||
@@ -49,7 +49,7 @@ class OrganisationList(GenericListView):
|
|||||||
model = models.Organisation
|
model = models.Organisation
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(OrganisationList, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['create'] = 'organisation_create'
|
context['create'] = 'organisation_create'
|
||||||
context['edit'] = 'organisation_update'
|
context['edit'] = 'organisation_update'
|
||||||
context['can_edit'] = self.request.user.has_perm('RIGS.change_organisation')
|
context['can_edit'] = self.request.user.has_perm('RIGS.change_organisation')
|
||||||
@@ -62,7 +62,7 @@ class OrganisationDetail(GenericDetailView):
|
|||||||
model = models.Organisation
|
model = models.Organisation
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(OrganisationDetail, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['history_link'] = 'organisation_history'
|
context['history_link'] = 'organisation_history'
|
||||||
context['detail_link'] = 'organisation_detail'
|
context['detail_link'] = 'organisation_detail'
|
||||||
context['update_link'] = 'organisation_update'
|
context['update_link'] = 'organisation_update'
|
||||||
@@ -92,7 +92,7 @@ class VenueList(GenericListView):
|
|||||||
model = models.Venue
|
model = models.Venue
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(VenueList, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['create'] = 'venue_create'
|
context['create'] = 'venue_create'
|
||||||
context['edit'] = 'venue_update'
|
context['edit'] = 'venue_update'
|
||||||
context['can_edit'] = self.request.user.has_perm('RIGS.change_venue')
|
context['can_edit'] = self.request.user.has_perm('RIGS.change_venue')
|
||||||
@@ -104,7 +104,7 @@ class VenueDetail(GenericDetailView):
|
|||||||
model = models.Venue
|
model = models.Venue
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(VenueDetail, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['history_link'] = 'venue_history'
|
context['history_link'] = 'venue_history'
|
||||||
context['detail_link'] = 'venue_detail'
|
context['detail_link'] = 'venue_detail'
|
||||||
context['update_link'] = 'venue_update'
|
context['update_link'] = 'venue_update'
|
||||||
@@ -24,11 +24,12 @@ class InvoiceIndex(generic.ListView):
|
|||||||
template_name = 'invoice_list.html'
|
template_name = 'invoice_list.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(InvoiceIndex, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
total = 0
|
total = 0
|
||||||
for i in context['object_list']:
|
for i in context['object_list']:
|
||||||
total += i.balance
|
total += i.balance
|
||||||
context['page_title'] = "Outstanding Invoices ({} Events, £{:.2f})".format(len(list(context['object_list'])), total)
|
event_count = len(list(context['object_list']))
|
||||||
|
context['page_title'] = f"Outstanding Invoices ({event_count} Events, £{total:.2f})"
|
||||||
context['description'] = "Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger"
|
context['description'] = "Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -41,8 +42,9 @@ class InvoiceDetail(generic.DetailView):
|
|||||||
template_name = 'invoice_detail.html'
|
template_name = 'invoice_detail.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(InvoiceDetail, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['page_title'] = "Invoice {} ({}) ".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
|
invoice_date = self.object.invoice_date.strftime("%d/%m/%Y")
|
||||||
|
context['page_title'] = f"Invoice {self.object.display_id} ({invoice_date})"
|
||||||
if self.object.void:
|
if self.object.void:
|
||||||
context['page_title'] += "<span class='badge badge-warning float-right'>VOID</span>"
|
context['page_title'] += "<span class='badge badge-warning float-right'>VOID</span>"
|
||||||
elif self.object.is_closed:
|
elif self.object.is_closed:
|
||||||
@@ -58,11 +60,14 @@ class InvoicePrint(generic.View):
|
|||||||
object = invoice.event
|
object = invoice.event
|
||||||
template = get_template('event_print.xml')
|
template = get_template('event_print.xml')
|
||||||
|
|
||||||
|
name = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||||
|
filename = f"Invoice {invoice.display_id} for {object.display_id} {name}.pdf"
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'object': object,
|
'object': object,
|
||||||
'invoice': invoice,
|
'invoice': invoice,
|
||||||
'current_user': request.user,
|
'current_user': request.user,
|
||||||
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
|
'filename': filename
|
||||||
}
|
}
|
||||||
|
|
||||||
rml = template.render(context)
|
rml = template.render(context)
|
||||||
@@ -72,7 +77,7 @@ class InvoicePrint(generic.View):
|
|||||||
pdfData = buffer.read()
|
pdfData = buffer.read()
|
||||||
|
|
||||||
response = HttpResponse(content_type='application/pdf')
|
response = HttpResponse(content_type='application/pdf')
|
||||||
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
|
response['Content-Disposition'] = f'filename="{filename}"'
|
||||||
response.write(pdfData)
|
response.write(pdfData)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -117,38 +122,13 @@ class InvoiceArchive(generic.ListView):
|
|||||||
paginate_by = 25
|
paginate_by = 25
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(InvoiceArchive, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['page_title'] = "Invoice Archive"
|
context['page_title'] = "Invoice Archive"
|
||||||
context['description'] = "This page displays all invoices: outstanding, paid, and void"
|
context['description'] = "This page displays all invoices: outstanding, paid, and void"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
q = self.request.GET.get('q', "")
|
return self.model.objects.search(self.request.GET.get('q')).order_by('-invoice_date')
|
||||||
|
|
||||||
filter = Q(event__name__icontains=q)
|
|
||||||
|
|
||||||
# try and parse an int
|
|
||||||
try:
|
|
||||||
val = int(q)
|
|
||||||
filter = filter | Q(pk=val)
|
|
||||||
filter = filter | Q(event__pk=val)
|
|
||||||
except: # noqa
|
|
||||||
# not an integer
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
if q[0] == "N":
|
|
||||||
val = int(q[1:])
|
|
||||||
filter = Q(event__pk=val) # If string is Nxxxxx then filter by event number
|
|
||||||
elif q[0] == "#":
|
|
||||||
val = int(q[1:])
|
|
||||||
filter = Q(pk=val) # If string is #xxxxx then filter by invoice number
|
|
||||||
except: # noqa
|
|
||||||
pass
|
|
||||||
|
|
||||||
object_list = self.model.objects.filter(filter).order_by('-invoice_date')
|
|
||||||
|
|
||||||
return object_list
|
|
||||||
|
|
||||||
|
|
||||||
class InvoiceWaiting(generic.ListView):
|
class InvoiceWaiting(generic.ListView):
|
||||||
@@ -162,7 +142,7 @@ class InvoiceWaiting(generic.ListView):
|
|||||||
objects = self.get_queryset()
|
objects = self.get_queryset()
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
total += obj.sum_total
|
total += obj.sum_total
|
||||||
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(objects), total)
|
context['page_title'] = f"Events for Invoice ({len(objects)} Events, £{total:.2f})"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@@ -196,7 +176,7 @@ class PaymentCreate(generic.CreateView):
|
|||||||
template_name = 'payment_form.html'
|
template_name = 'payment_form.html'
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
initial = super(generic.CreateView, self).get_initial()
|
initial = super().get_initial()
|
||||||
invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None))
|
invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None))
|
||||||
if invoicepk is None:
|
if invoicepk is None:
|
||||||
raise Http404()
|
raise Http404()
|
||||||