Compare commits
288 Commits
6b19d0e8b8
...
bs4
| Author | SHA1 | Date | |
|---|---|---|---|
| ce0e47786f | |||
| 0ed7dc4834 | |||
| 4ca4ea9812 | |||
| fe38d5cf36 | |||
| 72221e46bc | |||
| 0e656b4c41 | |||
| a0c6793cf6 | |||
| 80837c3d9a | |||
| c027182962 | |||
| dd95447008 | |||
| 13fcadaf79 | |||
| 124b7a8e51 | |||
| dfdbcb651f | |||
| e0615f318c | |||
|
97b29bc549
|
|||
|
0aa10d86cd
|
|||
|
06e22b0d61
|
|||
|
ac916247a6
|
|||
|
bbc4d1d390
|
|||
|
4b76b77806
|
|||
|
44fef39a2e
|
|||
|
d06895bf96
|
|||
|
e9a29f9444
|
|||
|
14e12f0fb9
|
|||
|
7f9a7bba8b
|
|||
|
20d4ddd5cf
|
|||
|
e6eed9f2f2
|
|||
|
320ace1a0e
|
|||
|
ab2c2f65f0
|
|||
|
8a4fcdcecb
|
|||
|
529941224e
|
|||
|
59756ab86a
|
|||
|
7736a2d3cc
|
|||
|
30df597144
|
|||
|
4bb1c0a2a4
|
|||
|
848e8c8ccd
|
|||
|
0fee753284
|
|||
|
bb4d31477e
|
|||
|
3d7ff435c9
|
|||
|
a950b941ca
|
|||
|
f57dc9f765
|
|||
| 8b2f2a9354 | |||
|
d255e1f89f
|
|||
|
fd85d50679
|
|||
|
2cb5453b82
|
|||
|
0f019e26a0
|
|||
|
e926731e67
|
|||
|
b69883cc90
|
|||
|
a0e1702de4
|
|||
|
8c8c580bfb
|
|||
|
49a2bc83ab
|
|||
|
026e2a0b03
|
|||
|
3cb0d82130
|
|||
|
5a5bb4328d
|
|||
|
58b1867a13
|
|||
|
d7678f6b6f
|
|||
|
7f3a169875
|
|||
|
5a99560310
|
|||
|
12a60e1f50
|
|||
|
934c07be72
|
|||
| 3602da9203 | |||
| f41064abfa | |||
|
ee9dbf8944
|
|||
|
565e757758
|
|||
|
5af075946a
|
|||
|
5d56f4f7b0
|
|||
|
3903481b3d
|
|||
|
af7d3c4070
|
|||
|
025a31f15a
|
|||
|
2db2cc6659
|
|||
|
cce0ad0f9f
|
|||
|
ae13cabe09
|
|||
|
abf3cfe1ce
|
|||
|
7c79a6afdd
|
|||
|
350a301b36
|
|||
|
2f8e09906b
|
|||
|
50ae3022cd
|
|||
|
f147f19140
|
|||
|
0b751d62df
|
|||
|
acf814e49e
|
|||
|
f70421b8ca
|
|||
|
84c4ec03de
|
|||
|
813b1dac85
|
|||
|
0117002b01
|
|||
|
932180f99f
|
|||
|
70de16ed5c
|
|||
|
8424424d49
|
|||
|
a78bb19015
|
|||
|
bea762b12b
|
|||
|
70cc554094
|
|||
|
1dad8d2ba5
|
|||
|
c38105a76e
|
|||
|
6936b94ce6
|
|||
|
74066e9484
|
|||
|
018397d28e
|
|||
|
143b654210
|
|||
|
2a1bb57c74
|
|||
|
77c82efce6
|
|||
|
1ea8090668
|
|||
| 31f63ba5c7 | |||
| 9739af765f | |||
| b90be708d0 | |||
| dcc0e53062 | |||
|
ce5a92dfa8
|
|||
|
fd926aef85
|
|||
|
1ecc508b0d
|
|||
|
a5516ee350
|
|||
|
5487b73006
|
|||
|
6426880708
|
|||
|
aa0184a5dc
|
|||
|
eb7d8e49e8
|
|||
|
b959ca13a5
|
|||
|
d8e6f2f9c1
|
|||
|
1cf910752f
|
|||
|
cf7ada3d9e
|
|||
|
ba6dbc6980
|
|||
|
6d836ee4dd
|
|||
|
e602058771
|
|||
|
c1182efa54
|
|||
|
87e831a468
|
|||
|
1d5429052f
|
|||
|
6ee9efa39e
|
|||
|
945e521feb
|
|||
|
689124a891
|
|||
|
8842c2c3d9
|
|||
|
f3c2ce2519
|
|||
|
bfe80db85e
|
|||
|
9198724748
|
|||
|
dbe0d35400
|
|||
|
1feb9449ed
|
|||
|
d708207ab9
|
|||
|
8ea96674db
|
|||
|
de04498517
|
|||
|
828964ecb6
|
|||
|
3438489934
|
|||
| 9cf081efc7 | |||
| 8bb08724b6 | |||
| da60cad911 | |||
| a6ac55baaf | |||
|
d3f55523da
|
|||
|
d3d7c052af
|
|||
|
902476ebab
|
|||
|
4514de137a
|
|||
|
92377227e0
|
|||
|
b88554a57f
|
|||
|
9d6948e326
|
|||
|
67bf60e246
|
|||
| 2e60c5e7bf | |||
| d09f3994fc | |||
| 79eb0cbff0 | |||
| d800a781a5 | |||
| 721439d095 | |||
| b57b918247 | |||
| 1dfaa4d7a8 | |||
|
1138cfcde5
|
|||
|
cd168d93ed
|
|||
|
a4ef69af3c
|
|||
|
be48dd31f9
|
|||
|
a6cee78198
|
|||
|
d50d2bd0ad
|
|||
|
3ceb48876d
|
|||
|
8820ed1e67
|
|||
|
86fe8a8e1b
|
|||
|
8a9eefb722
|
|||
|
b28377e1f5
|
|||
|
141e1c94d7
|
|||
|
eafb394f55
|
|||
|
84618deac0
|
|||
|
e4e8823a1a
|
|||
|
cb1a8e1627
|
|||
|
c65330cb91
|
|||
| 6e7fa267bc | |||
|
9e1d146b7a
|
|||
|
aedb4c24db
|
|||
|
1239fbf185
|
|||
|
7035731655
|
|||
|
a7c4b90161
|
|||
|
9e93d895ba
|
|||
|
d31900c5c3
|
|||
|
3768f4a613
|
|||
|
6867359146
|
|||
|
e45324f5b4
|
|||
| 156e639bac | |||
| 0e2adf3f0d | |||
| eb0c027ff9 | |||
| 93504997fe | |||
| d3b3d1c9d7 | |||
| 4cfd83eeb3 | |||
| 00226e9c22 | |||
| b75b6a6736 | |||
| 1b1775d0f5 | |||
| 14d211326e | |||
| 4081918598 | |||
| b742d3f8c4 | |||
| 06d7ef0b79 | |||
| 06f67f4f32 | |||
|
|
170c2f6d8c | ||
|
|
f37228e058 | ||
|
|
6d47be72fe | ||
| 6ded711dd5 | |||
| 920ea0d058 | |||
| e45e58321c | |||
| 3f78b9f05f | |||
| 0bbc23853d | |||
| 7356d020b2 | |||
| 08f40bce9e | |||
| 1d0e8e14e5 | |||
| b52709f412 | |||
| c4c4291050 | |||
| 07641a2520 | |||
| fd6aee83cf | |||
| 177c37ffbc | |||
| d070f97696 | |||
| 2f7389d8bb | |||
| 18fde7c16a | |||
| f06dc56b40 | |||
| e6d06db2a1 | |||
| 544b6df35c | |||
| c9ea1bb75d | |||
| 928d5cd8e6 | |||
| cc225b2eb7 | |||
| 3c0005ddb0 | |||
|
|
8ffb5ab23e | ||
|
34bf49876b
|
|||
|
813db2c474
|
|||
|
919975e1ba
|
|||
|
0d0c783e07
|
|||
|
c23d18cd45
|
|||
|
5a3547ea74
|
|||
|
e3c1da9d13
|
|||
|
3f48c51aeb
|
|||
|
abb8dc25ec
|
|||
|
70995a0d0b
|
|||
|
5e60675115
|
|||
|
f308a095f3
|
|||
|
2bf643cd7a
|
|||
|
c8d0c0d5d0
|
|||
|
176324ed79
|
|||
|
d216dd4c74
|
|||
|
55e37d8c69
|
|||
|
7aa19cc7ab
|
|||
|
4c40226bcf
|
|||
|
d351d9eb7b
|
|||
|
de210caa36
|
|||
|
0271840f4d
|
|||
|
7a08f2d889
|
|||
|
ee7ba3ea19
|
|||
|
41b0387e49
|
|||
|
50ca782569
|
|||
|
0144bd37fc
|
|||
|
959097286c
|
|||
|
264b306b2f
|
|||
|
e42989637e
|
|||
|
12d8b46f86
|
|||
|
89ddb09459
|
|||
|
f4f8c6b417
|
|||
|
36556dea33
|
|||
|
b9d318e675
|
|||
|
b8931a64c8
|
|||
|
cfe0a264e7
|
|||
|
8fbe9f9026
|
|||
|
fe0e4063d7
|
|||
|
0bd4b281d1
|
|||
|
7de778a57e
|
|||
|
88b34740f6
|
|||
|
e0e4e8d11e
|
|||
|
c1d277be9c
|
|||
|
4a71dd0d95
|
|||
|
2bfecb9c0f
|
|||
|
3814f5abfc
|
|||
|
4c34e4e43e
|
|||
|
3f36f66b8a
|
|||
|
7cef4d03c0
|
|||
|
6970c5c490
|
|||
|
b8ea3d3d42
|
|||
|
366a14408b
|
|||
|
270b1fc5bb
|
|||
|
7786512dc2
|
|||
|
88ac1b93ae
|
|||
|
4d845309c9
|
|||
|
3b8789e49e
|
|||
|
1526a2f22b
|
|||
|
fe71f7640f
|
|||
|
b57716f7fc
|
|||
|
320c43e472
|
|||
|
1e5fcbdba0
|
|||
|
1df1784d02
|
|||
|
6c72f070f2
|
32
.codeclimate.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
engines:
|
||||||
|
csslint:
|
||||||
|
enabled: true
|
||||||
|
duplication:
|
||||||
|
enabled: true
|
||||||
|
config:
|
||||||
|
languages:
|
||||||
|
- ruby
|
||||||
|
- javascript
|
||||||
|
- python
|
||||||
|
- php
|
||||||
|
eslint:
|
||||||
|
enabled: true
|
||||||
|
fixme:
|
||||||
|
enabled: true
|
||||||
|
radon:
|
||||||
|
enabled: true
|
||||||
|
rubocop:
|
||||||
|
enabled: true
|
||||||
|
ratings:
|
||||||
|
paths:
|
||||||
|
- "**.css"
|
||||||
|
- "**.inc"
|
||||||
|
- "**.js"
|
||||||
|
- "**.jsx"
|
||||||
|
- "**.module"
|
||||||
|
- "**.php"
|
||||||
|
- "**.py"
|
||||||
|
- "**.rb"
|
||||||
|
exclude_paths:
|
||||||
|
- config/
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
[run]
|
[run]
|
||||||
omit = */migrations/*
|
source =
|
||||||
*/tests/*
|
./
|
||||||
*/site-packages/*
|
|
||||||
*/distutils/*
|
omit =
|
||||||
|
*/migrations/*
|
||||||
|
|||||||
2
.csslintrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
--exclude-exts=.min.css
|
||||||
|
--ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
|
||||||
1
.eslintignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
**/*{.,-}min.js
|
||||||
213
.eslintrc
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
ecmaFeatures:
|
||||||
|
modules: true
|
||||||
|
jsx: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
amd: true
|
||||||
|
browser: true
|
||||||
|
es6: true
|
||||||
|
jquery: true
|
||||||
|
node: true
|
||||||
|
|
||||||
|
# http://eslint.org/docs/rules/
|
||||||
|
rules:
|
||||||
|
# Possible Errors
|
||||||
|
comma-dangle: [2, never]
|
||||||
|
no-cond-assign: 2
|
||||||
|
no-console: 0
|
||||||
|
no-constant-condition: 2
|
||||||
|
no-control-regex: 2
|
||||||
|
no-debugger: 2
|
||||||
|
no-dupe-args: 2
|
||||||
|
no-dupe-keys: 2
|
||||||
|
no-duplicate-case: 2
|
||||||
|
no-empty: 2
|
||||||
|
no-empty-character-class: 2
|
||||||
|
no-ex-assign: 2
|
||||||
|
no-extra-boolean-cast: 2
|
||||||
|
no-extra-parens: 0
|
||||||
|
no-extra-semi: 2
|
||||||
|
no-func-assign: 2
|
||||||
|
no-inner-declarations: [2, functions]
|
||||||
|
no-invalid-regexp: 2
|
||||||
|
no-irregular-whitespace: 2
|
||||||
|
no-negated-in-lhs: 2
|
||||||
|
no-obj-calls: 2
|
||||||
|
no-regex-spaces: 2
|
||||||
|
no-sparse-arrays: 2
|
||||||
|
no-unexpected-multiline: 2
|
||||||
|
no-unreachable: 2
|
||||||
|
use-isnan: 2
|
||||||
|
valid-jsdoc: 0
|
||||||
|
valid-typeof: 2
|
||||||
|
|
||||||
|
# Best Practices
|
||||||
|
accessor-pairs: 2
|
||||||
|
block-scoped-var: 0
|
||||||
|
complexity: [2, 6]
|
||||||
|
consistent-return: 0
|
||||||
|
curly: 0
|
||||||
|
default-case: 0
|
||||||
|
dot-location: 0
|
||||||
|
dot-notation: 0
|
||||||
|
eqeqeq: 2
|
||||||
|
guard-for-in: 2
|
||||||
|
no-alert: 2
|
||||||
|
no-caller: 2
|
||||||
|
no-case-declarations: 2
|
||||||
|
no-div-regex: 2
|
||||||
|
no-else-return: 0
|
||||||
|
no-empty-label: 2
|
||||||
|
no-empty-pattern: 2
|
||||||
|
no-eq-null: 2
|
||||||
|
no-eval: 2
|
||||||
|
no-extend-native: 2
|
||||||
|
no-extra-bind: 2
|
||||||
|
no-fallthrough: 2
|
||||||
|
no-floating-decimal: 0
|
||||||
|
no-implicit-coercion: 0
|
||||||
|
no-implied-eval: 2
|
||||||
|
no-invalid-this: 0
|
||||||
|
no-iterator: 2
|
||||||
|
no-labels: 0
|
||||||
|
no-lone-blocks: 2
|
||||||
|
no-loop-func: 2
|
||||||
|
no-magic-number: 0
|
||||||
|
no-multi-spaces: 0
|
||||||
|
no-multi-str: 0
|
||||||
|
no-native-reassign: 2
|
||||||
|
no-new-func: 2
|
||||||
|
no-new-wrappers: 2
|
||||||
|
no-new: 2
|
||||||
|
no-octal-escape: 2
|
||||||
|
no-octal: 2
|
||||||
|
no-proto: 2
|
||||||
|
no-redeclare: 2
|
||||||
|
no-return-assign: 2
|
||||||
|
no-script-url: 2
|
||||||
|
no-self-compare: 2
|
||||||
|
no-sequences: 0
|
||||||
|
no-throw-literal: 0
|
||||||
|
no-unused-expressions: 2
|
||||||
|
no-useless-call: 2
|
||||||
|
no-useless-concat: 2
|
||||||
|
no-void: 2
|
||||||
|
no-warning-comments: 0
|
||||||
|
no-with: 2
|
||||||
|
radix: 2
|
||||||
|
vars-on-top: 0
|
||||||
|
wrap-iife: 2
|
||||||
|
yoda: 0
|
||||||
|
|
||||||
|
# Strict
|
||||||
|
strict: 0
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
init-declarations: 0
|
||||||
|
no-catch-shadow: 2
|
||||||
|
no-delete-var: 2
|
||||||
|
no-label-var: 2
|
||||||
|
no-shadow-restricted-names: 2
|
||||||
|
no-shadow: 0
|
||||||
|
no-undef-init: 2
|
||||||
|
no-undef: 0
|
||||||
|
no-undefined: 0
|
||||||
|
no-unused-vars: 0
|
||||||
|
no-use-before-define: 0
|
||||||
|
|
||||||
|
# Node.js and CommonJS
|
||||||
|
callback-return: 2
|
||||||
|
global-require: 2
|
||||||
|
handle-callback-err: 2
|
||||||
|
no-mixed-requires: 0
|
||||||
|
no-new-require: 0
|
||||||
|
no-path-concat: 2
|
||||||
|
no-process-exit: 2
|
||||||
|
no-restricted-modules: 0
|
||||||
|
no-sync: 0
|
||||||
|
|
||||||
|
# Stylistic Issues
|
||||||
|
array-bracket-spacing: 0
|
||||||
|
block-spacing: 0
|
||||||
|
brace-style: 0
|
||||||
|
camelcase: 0
|
||||||
|
comma-spacing: 0
|
||||||
|
comma-style: 0
|
||||||
|
computed-property-spacing: 0
|
||||||
|
consistent-this: 0
|
||||||
|
eol-last: 0
|
||||||
|
func-names: 0
|
||||||
|
func-style: 0
|
||||||
|
id-length: 0
|
||||||
|
id-match: 0
|
||||||
|
indent: 0
|
||||||
|
jsx-quotes: 0
|
||||||
|
key-spacing: 0
|
||||||
|
linebreak-style: 0
|
||||||
|
lines-around-comment: 0
|
||||||
|
max-depth: 0
|
||||||
|
max-len: 0
|
||||||
|
max-nested-callbacks: 0
|
||||||
|
max-params: 0
|
||||||
|
max-statements: [2, 30]
|
||||||
|
new-cap: 0
|
||||||
|
new-parens: 0
|
||||||
|
newline-after-var: 0
|
||||||
|
no-array-constructor: 0
|
||||||
|
no-bitwise: 0
|
||||||
|
no-continue: 0
|
||||||
|
no-inline-comments: 0
|
||||||
|
no-lonely-if: 0
|
||||||
|
no-mixed-spaces-and-tabs: 0
|
||||||
|
no-multiple-empty-lines: 0
|
||||||
|
no-negated-condition: 0
|
||||||
|
no-nested-ternary: 0
|
||||||
|
no-new-object: 0
|
||||||
|
no-plusplus: 0
|
||||||
|
no-restricted-syntax: 0
|
||||||
|
no-spaced-func: 0
|
||||||
|
no-ternary: 0
|
||||||
|
no-trailing-spaces: 0
|
||||||
|
no-underscore-dangle: 0
|
||||||
|
no-unneeded-ternary: 0
|
||||||
|
object-curly-spacing: 0
|
||||||
|
one-var: 0
|
||||||
|
operator-assignment: 0
|
||||||
|
operator-linebreak: 0
|
||||||
|
padded-blocks: 0
|
||||||
|
quote-props: 0
|
||||||
|
quotes: 0
|
||||||
|
require-jsdoc: 0
|
||||||
|
semi-spacing: 0
|
||||||
|
semi: 0
|
||||||
|
sort-vars: 0
|
||||||
|
space-after-keywords: 0
|
||||||
|
space-before-blocks: 0
|
||||||
|
space-before-function-paren: 0
|
||||||
|
space-before-keywords: 0
|
||||||
|
space-in-parens: 0
|
||||||
|
space-infix-ops: 0
|
||||||
|
space-return-throw-case: 0
|
||||||
|
space-unary-ops: 0
|
||||||
|
spaced-comment: 0
|
||||||
|
wrap-regex: 0
|
||||||
|
|
||||||
|
# ECMAScript 6
|
||||||
|
arrow-body-style: 0
|
||||||
|
arrow-parens: 0
|
||||||
|
arrow-spacing: 0
|
||||||
|
constructor-super: 0
|
||||||
|
generator-star-spacing: 0
|
||||||
|
no-arrow-condition: 0
|
||||||
|
no-class-assign: 0
|
||||||
|
no-const-assign: 0
|
||||||
|
no-dupe-class-members: 0
|
||||||
|
no-this-before-super: 0
|
||||||
|
no-var: 0
|
||||||
|
object-shorthand: 0
|
||||||
|
prefer-arrow-callback: 0
|
||||||
|
prefer-const: 0
|
||||||
|
prefer-reflect: 0
|
||||||
|
prefer-spread: 0
|
||||||
|
prefer-template: 0
|
||||||
|
require-yield: 0
|
||||||
58
.github/workflows/django.yml
vendored
@@ -1,58 +0,0 @@
|
|||||||
name: Django CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9.1
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
id: pcache
|
|
||||||
with:
|
|
||||||
path: ~/.local/share/virtualenvs
|
|
||||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pipenv-
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip pipenv
|
|
||||||
pipenv install -d
|
|
||||||
# if: steps.pcache.outputs.cache-hit != 'true'
|
|
||||||
- name: Cache Static Files
|
|
||||||
id: static-cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: 'pipeline/built_assets'
|
|
||||||
key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
|
|
||||||
- uses: bahmutov/npm-install@v1
|
|
||||||
if: steps.static-cache.outputs.cache-hit != 'true'
|
|
||||||
- run: node node_modules/gulp/bin/gulp build
|
|
||||||
if: steps.static-cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Basic Checks
|
|
||||||
run: |
|
|
||||||
pipenv run pycodestyle . --exclude=migrations,node_modules
|
|
||||||
pipenv run python manage.py check
|
|
||||||
pipenv run python manage.py makemigrations --check --dry-run
|
|
||||||
pipenv run python manage.py collectstatic --noinput
|
|
||||||
- name: Run Tests
|
|
||||||
run: pipenv run pytest -n auto -vv --cov
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
name: failure-screenshots ${{ matrix.test-group }}
|
|
||||||
path: screenshots/
|
|
||||||
retention-days: 5
|
|
||||||
- name: Coveralls
|
|
||||||
run: pipenv run coveralls --service=github
|
|
||||||
15
.gitignore
vendored
@@ -26,7 +26,6 @@ var/
|
|||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
node_modules/
|
node_modules/
|
||||||
data/
|
|
||||||
|
|
||||||
# Continer extras
|
# Continer extras
|
||||||
.vagrant
|
.vagrant
|
||||||
@@ -69,9 +68,19 @@ target/
|
|||||||
|
|
||||||
## Directory-based project format:
|
## Directory-based project format:
|
||||||
.idea/
|
.idea/
|
||||||
|
# if you remove the above rule, at least ignore the following:
|
||||||
|
|
||||||
#Built dependencies
|
# User-specific stuff:
|
||||||
pipeline/built_assets
|
# .idea/workspace.xml
|
||||||
|
# .idea/tasks.xml
|
||||||
|
# .idea/dictionaries
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
# .idea/dataSources.ids
|
||||||
|
# .idea/dataSources.xml
|
||||||
|
# .idea/sqlDataSources.xml
|
||||||
|
# .idea/dynamic.xml
|
||||||
|
# .idea/uiDesigner.xml
|
||||||
|
|
||||||
# Gradle:
|
# Gradle:
|
||||||
# .idea/gradle.xml
|
# .idea/gradle.xml
|
||||||
|
|||||||
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PyRIGS
|
||||||
5
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||||
|
</project>
|
||||||
|
|
||||||
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/pyrigs.iml" filepath="$PROJECT_DIR$/.idea/pyrigs.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
5
.idea/scopes/scope_settings.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="DependencyValidationManager">
|
||||||
|
<state>
|
||||||
|
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
7
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
||||||
1156
.rubocop.yml
Normal file
@@ -1,6 +1,7 @@
|
|||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
*.scss
|
||||||
*.md
|
*.md
|
||||||
**/tests
|
*.rb
|
||||||
conftest.py
|
Vagrantfile
|
||||||
pytest.ini
|
config/vagrant/*
|
||||||
Dockerfile
|
config/vagrant.yml
|
||||||
|
|||||||
37
.travis.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
language: python
|
||||||
|
python:
|
||||||
|
"3.8"
|
||||||
|
cache: pip
|
||||||
|
|
||||||
|
addons:
|
||||||
|
chrome: stable
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- export LANGUAGE=en_GB.UTF-8
|
||||||
|
|
||||||
|
install:
|
||||||
|
- |
|
||||||
|
latest=$(wget -qO- https://chromedriver.storage.googleapis.com/LATEST_RELEASE)
|
||||||
|
wget https://chromedriver.storage.googleapis.com/$latest/chromedriver_linux64.zip
|
||||||
|
- unzip chromedriver_linux64.zip
|
||||||
|
- export PATH=$PATH:$(pwd)
|
||||||
|
- chmod +x chromedriver
|
||||||
|
- pip install -r requirements.txt
|
||||||
|
- pip install coveralls codeclimate-test-reporter pycodestyle
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- export PATH=$PATH:/usr/lib/chromium-browser/
|
||||||
|
- python manage.py collectstatic --noinput
|
||||||
|
|
||||||
|
script:
|
||||||
|
- pycodestyle . --exclude=migrations,importer*
|
||||||
|
- python manage.py check
|
||||||
|
- python manage.py makemigrations --check --dry-run
|
||||||
|
- coverage run manage.py test --verbosity=2
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- coveralls
|
||||||
|
- codeclimate-test-reporter
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
webhooks: https://fathomless-fjord-24024.herokuapp.com/notify
|
||||||
96
Pipfile
@@ -1,96 +0,0 @@
|
|||||||
[[source]]
|
|
||||||
url = "https://pypi.python.org/simple"
|
|
||||||
verify_ssl = true
|
|
||||||
name = "pypi"
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
ansicolors = "~=1.1.8"
|
|
||||||
asgiref = "~=3.3.1"
|
|
||||||
"backports.tempfile" = "~=1.0"
|
|
||||||
"backports.weakref" = "~=1.0.post1"
|
|
||||||
beautifulsoup4 = "~=4.9.3"
|
|
||||||
Brotli = "~=1.0.9"
|
|
||||||
cachetools = "~=4.2.1"
|
|
||||||
certifi = "~=2020.12.5"
|
|
||||||
chardet = "~=4.0.0"
|
|
||||||
configparser = "~=5.0.1"
|
|
||||||
contextlib2 = "~=0.6.0.post1"
|
|
||||||
cssselect = "~=1.1.0"
|
|
||||||
cssutils = "~=1.0.2"
|
|
||||||
dj-database-url = "~=0.5.0"
|
|
||||||
dj-static = "~=0.0.6"
|
|
||||||
Django = "~=3.2"
|
|
||||||
django-debug-toolbar = "~=3.2"
|
|
||||||
django-filter = "~=2.4.0"
|
|
||||||
django-ical = "~=1.7.1"
|
|
||||||
django-recurrence = "~=1.10.3"
|
|
||||||
django-registration-redux = "~=2.9"
|
|
||||||
django-reversion = "~=3.0.9"
|
|
||||||
django-toolbelt = "~=0.0.1"
|
|
||||||
django-widget-tweaks = "~=1.4.8"
|
|
||||||
django-htmlmin = "~=0.11.0"
|
|
||||||
envparse = "~=0.2.0"
|
|
||||||
gunicorn = "~=20.0.4"
|
|
||||||
icalendar = "~=4.0.7"
|
|
||||||
idna = "~=2.10"
|
|
||||||
importlib-metadata = "~=3.4.0"
|
|
||||||
lxml = "~=4.6.3"
|
|
||||||
Markdown = "~=3.3.3"
|
|
||||||
msgpack = "~=1.0.2"
|
|
||||||
pep517 = "~=0.9.1"
|
|
||||||
Pillow = "~=8.3.2"
|
|
||||||
premailer = "~=3.7.0"
|
|
||||||
progress = "~=1.5"
|
|
||||||
psutil = "~=5.8.0"
|
|
||||||
psycopg2 = "~=2.8.6"
|
|
||||||
Pygments = "~=2.7.4"
|
|
||||||
pyparsing = "~=2.4.7"
|
|
||||||
PyPDF2 = "~=1.26.0"
|
|
||||||
PyPOM = "~=2.2.0"
|
|
||||||
python-dateutil = "~=2.8.1"
|
|
||||||
pytoml = "~=0.1.21"
|
|
||||||
pytz = "~=2020.5"
|
|
||||||
reportlab = "~=3.5.59"
|
|
||||||
requests = "~=2.25.1"
|
|
||||||
retrying = "~=1.3.3"
|
|
||||||
simplejson = "~=3.17.2"
|
|
||||||
six = "~=1.15.0"
|
|
||||||
soupsieve = "~=2.1"
|
|
||||||
sqlparse = "~=0.4.2"
|
|
||||||
static3 = "~=0.7.0"
|
|
||||||
svg2rlg = "~=0.3"
|
|
||||||
tini = "~=3.0.1"
|
|
||||||
tornado = "~=6.1"
|
|
||||||
urllib3 = "~=1.26.5"
|
|
||||||
whitenoise = "~=5.2.0"
|
|
||||||
yolk = "~=0.4.3"
|
|
||||||
"z3c.rml" = "~=4.1.2"
|
|
||||||
zipp = "~=3.4.0"
|
|
||||||
"zope.component" = "~=4.6.2"
|
|
||||||
"zope.deferredimport" = "~=4.3.1"
|
|
||||||
"zope.deprecation" = "~=4.4.0"
|
|
||||||
"zope.event" = "~=4.5.0"
|
|
||||||
"zope.hookable" = "~=5.0.1"
|
|
||||||
"zope.interface" = "~=5.2.0"
|
|
||||||
"zope.proxy" = "~=4.3.5"
|
|
||||||
"zope.schema" = "~=6.0.1"
|
|
||||||
sentry-sdk = "*"
|
|
||||||
diff-match-patch = "*"
|
|
||||||
python-barcode = "*"
|
|
||||||
django-hCaptcha = "*"
|
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
selenium = "~=3.141.0"
|
|
||||||
pycodestyle = "*"
|
|
||||||
coveralls = "*"
|
|
||||||
django-coverage-plugin = "*"
|
|
||||||
pytest-cov = "*"
|
|
||||||
pytest-django = "*"
|
|
||||||
pluggy = "*"
|
|
||||||
pytest-splinter = "*"
|
|
||||||
pytest = "*"
|
|
||||||
pytest-xdist = {extras = [ "psutil",], version = "*"}
|
|
||||||
PyPOM = {extras = [ "splinter",], version = "*"}
|
|
||||||
|
|
||||||
[requires]
|
|
||||||
python_version = "3.9"
|
|
||||||
1526
Pipfile.lock
generated
@@ -1,7 +1,6 @@
|
|||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
@@ -16,7 +15,11 @@ def get_oembed(login_url, request, oembed_view, kwargs):
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def has_oembed(oembed_view, login_url=settings.LOGIN_URL):
|
def has_oembed(oembed_view, login_url=None):
|
||||||
|
if not login_url:
|
||||||
|
from django.conf import settings
|
||||||
|
login_url = settings.LOGIN_URL
|
||||||
|
|
||||||
def _dec(view_func):
|
def _dec(view_func):
|
||||||
def _checklogin(request, *args, **kwargs):
|
def _checklogin(request, *args, **kwargs):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
|
|||||||
@@ -8,23 +8,27 @@ For the full list of settings and their values, see
|
|||||||
https://docs.djangoproject.com/en/1.7/ref/settings/
|
https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
from pathlib import Path
|
import os
|
||||||
|
import raven
|
||||||
import secrets
|
import secrets
|
||||||
|
import datetime
|
||||||
|
|
||||||
import sentry_sdk
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
from sentry_sdk.integrations.django import DjangoIntegration
|
|
||||||
from envparse import env
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Quick-start development settings - unsuitable for production
|
||||||
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
|
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = env('SECRET_KEY', default='gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e')
|
SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get(
|
||||||
|
'SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e'
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = env('DEBUG', cast=bool, default=True)
|
DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True
|
||||||
STAGING = env('STAGING', cast=bool, default=False)
|
|
||||||
CI = env('CI', cast=bool, default=False)
|
STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False
|
||||||
|
|
||||||
|
CI = bool(int(os.environ.get('CI'))) if os.environ.get('CI') else False
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
||||||
|
|
||||||
@@ -49,7 +53,6 @@ if DEBUG:
|
|||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'whitenoise.runserver_nostatic',
|
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
@@ -61,18 +64,18 @@ INSTALLED_APPS = (
|
|||||||
'users',
|
'users',
|
||||||
'RIGS',
|
'RIGS',
|
||||||
'assets',
|
'assets',
|
||||||
'training',
|
|
||||||
|
|
||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
'registration',
|
'registration',
|
||||||
'reversion',
|
'reversion',
|
||||||
|
'captcha',
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
'hcaptcha',
|
'raven.contrib.django.raven_compat',
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE = (
|
MIDDLEWARE = (
|
||||||
|
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'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',
|
||||||
@@ -81,8 +84,6 @@ MIDDLEWARE = (
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'htmlmin.middleware.HtmlMinifyMiddleware',
|
|
||||||
'htmlmin.middleware.MarkRequestMiddleware',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = 'PyRIGS.urls'
|
ROOT_URLCONF = 'PyRIGS.urls'
|
||||||
@@ -90,10 +91,11 @@ ROOT_URLCONF = 'PyRIGS.urls'
|
|||||||
WSGI_APPLICATION = 'PyRIGS.wsgi.application'
|
WSGI_APPLICATION = 'PyRIGS.wsgi.application'
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': str(BASE_DIR / 'db.sqlite3'),
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,12 +173,12 @@ else:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Error/performance monitoring
|
RAVEN_CONFIG = {
|
||||||
sentry_sdk.init(
|
'dsn': os.environ.get('RAVEN_DSN'),
|
||||||
dsn=env('SENTRY_DSN', default=""),
|
# If you are using git, you can also automatically configure the
|
||||||
integrations=[DjangoIntegration()],
|
# release based on the git info.
|
||||||
traces_sample_rate=1.0,
|
# 'release': raven.fetch_git_sha(os.path.dirname(os.path.dirname(__file__))),
|
||||||
)
|
}
|
||||||
|
|
||||||
# User system
|
# User system
|
||||||
AUTH_USER_MODEL = 'RIGS.Profile'
|
AUTH_USER_MODEL = 'RIGS.Profile'
|
||||||
@@ -187,21 +189,26 @@ LOGOUT_URL = '/user/logout/'
|
|||||||
|
|
||||||
ACCOUNT_ACTIVATION_DAYS = 7
|
ACCOUNT_ACTIVATION_DAYS = 7
|
||||||
|
|
||||||
# CAPTCHA settings
|
# reCAPTCHA settings
|
||||||
HCAPTCHA_SITEKEY = env('HCAPTCHA_SITEKEY', '10000000-ffff-ffff-ffff-000000000001')
|
RECAPTCHA_PUBLIC_KEY = os.environ.get('RECAPTCHA_PUBLIC_KEY',
|
||||||
HCAPTCHA_SECRET = env('HCAPTCHA_SECRET', '0x0000000000000000000000000000000000000000')
|
"6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
|
||||||
|
RECAPTCHA_PRIVATE_KEY = os.environ.get('RECAPTCHA_PRIVATE_KEY',
|
||||||
|
"6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
|
||||||
|
NOCAPTCHA = True
|
||||||
|
|
||||||
|
SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
EMAILER_TEST = False
|
EMAILER_TEST = False
|
||||||
if not DEBUG or EMAILER_TEST:
|
if not DEBUG or EMAILER_TEST:
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
EMAIL_HOST = env('EMAIL_HOST')
|
EMAIL_HOST = os.environ.get('EMAIL_HOST')
|
||||||
EMAIL_PORT = env('EMAIL_PORT', cast=int, default=25)
|
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25))
|
||||||
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
|
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
|
||||||
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
|
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
|
||||||
EMAIL_USE_TLS = env('EMAIL_USE_TLS', cast=bool, default=False)
|
EMAIL_USE_TLS = bool(int(os.environ.get('EMAIL_USE_TLS', 0)))
|
||||||
EMAIL_USE_SSL = env('EMAIL_USE_SSL', cast=bool, default=False)
|
EMAIL_USE_SSL = bool(int(os.environ.get('EMAIL_USE_SSL', 0)))
|
||||||
DEFAULT_FROM_EMAIL = env('EMAIL_FROM')
|
DEFAULT_FROM_EMAIL = os.environ.get('EMAIL_FROM')
|
||||||
else:
|
else:
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
@@ -226,18 +233,18 @@ USE_TZ = True
|
|||||||
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
|
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = str(BASE_DIR / 'static/')
|
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
|
||||||
STATICFILES_DIRS = [
|
STATIC_DIRS = (
|
||||||
str(BASE_DIR / 'pipeline/built_assets'),
|
os.path.join(BASE_DIR, 'static/')
|
||||||
]
|
)
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [
|
'DIRS': [
|
||||||
BASE_DIR / 'templates'
|
os.path.join(BASE_DIR, 'templates')
|
||||||
],
|
],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
@@ -260,5 +267,10 @@ 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'
|
||||||
|
RISK_ASSESSMENT_URL = os.environ.get('RISK_ASSESSMENT_URL') if os.environ.get(
|
||||||
|
'RISK_ASSESSMENT_URL') else "http://example.com"
|
||||||
|
RISK_ASSESSMENT_SECRET = os.environ.get('RISK_ASSESSMENT_SECRET') if os.environ.get(
|
||||||
|
'RISK_ASSESSMENT_SECRET') else secrets.token_hex(15)
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
IMGUR_UPLOAD_CLIENT_ID = os.environ.get('IMGUR_UPLOAD_CLIENT_ID', '')
|
||||||
|
IMGUR_UPLOAD_CLIENT_SECRET = os.environ.get('IMGUR_UPLOAD_CLIENT_SECRET', '')
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import sys
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import pytz
|
|
||||||
from django.conf import settings
|
|
||||||
from django.test import LiveServerTestCase
|
from django.test import LiveServerTestCase
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
|
||||||
|
|
||||||
from RIGS import models as rigsmodels
|
from RIGS import models as rigsmodels
|
||||||
from . import pages
|
from . import pages
|
||||||
|
import os
|
||||||
from pytest_django.asserts import assertContains
|
import pytz
|
||||||
|
from datetime import date, time, datetime, timedelta
|
||||||
|
from django.conf import settings
|
||||||
|
import imgurpython
|
||||||
|
import PyRIGS.settings
|
||||||
|
import sys
|
||||||
|
import pathlib
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
|
||||||
def create_datetime(year, month, day, hour, minute):
|
def create_datetime(year, month, day, hour, min):
|
||||||
tz = pytz.timezone(settings.TIME_ZONE)
|
tz = pytz.timezone(settings.TIME_ZONE)
|
||||||
return tz.localize(datetime(year, month, day, hour, minute)).astimezone(tz)
|
return tz.localize(datetime(year, month, day, hour, min)).astimezone(pytz.utc)
|
||||||
|
|
||||||
|
|
||||||
def create_browser():
|
def create_browser():
|
||||||
options = webdriver.ChromeOptions()
|
options = webdriver.ChromeOptions()
|
||||||
options.add_argument("--window-size=1920,1080")
|
options.add_argument("--window-size=1920,1080")
|
||||||
|
# No caching, please and thank you
|
||||||
|
options.add_argument("--aggressive-cache-discard")
|
||||||
|
options.add_argument("--disk-cache-size=0")
|
||||||
|
# God Save The Queen
|
||||||
|
options.add_argument("--lang=en_GB")
|
||||||
options.add_argument("--headless")
|
options.add_argument("--headless")
|
||||||
if settings.CI:
|
if os.environ.get('CI', False):
|
||||||
options.add_argument("--no-sandbox")
|
options.add_argument("--no-sandbox")
|
||||||
driver = webdriver.Chrome(options=options)
|
driver = webdriver.Chrome(options=options)
|
||||||
return driver
|
return driver
|
||||||
@@ -34,7 +37,6 @@ class BaseTest(LiveServerTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
self.driver = create_browser()
|
self.driver = create_browser()
|
||||||
self.wait = WebDriverWait(self.driver, 15)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
@@ -48,11 +50,10 @@ class AutoLoginTest(BaseTest):
|
|||||||
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True)
|
||||||
self.profile.set_password("EventTestPassword")
|
self.profile.set_password("EventTestPassword")
|
||||||
self.profile.save()
|
self.profile.save()
|
||||||
login_page = pages.LoginPage(self.driver, self.live_server_url).open()
|
loginPage = pages.LoginPage(self.driver, self.live_server_url).open()
|
||||||
login_page.login("EventTest", "EventTestPassword")
|
loginPage.login("EventTest", "EventTestPassword")
|
||||||
|
|
||||||
|
|
||||||
# FIXME Refactor as a pytest fixture
|
|
||||||
def screenshot_failure(func):
|
def screenshot_failure(func):
|
||||||
def wrapper_func(self, *args, **kwargs):
|
def wrapper_func(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
@@ -63,9 +64,20 @@ 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)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
if settings.IMGUR_UPLOAD_CLIENT_ID != "":
|
||||||
|
config = {
|
||||||
|
'album': None,
|
||||||
|
'name': screenshot_name,
|
||||||
|
'title': screenshot_name,
|
||||||
|
'description': ""
|
||||||
|
}
|
||||||
|
client = imgurpython.ImgurClient(settings.IMGUR_UPLOAD_CLIENT_ID, settings.IMGUR_UPLOAD_CLIENT_SECRET)
|
||||||
|
image = client.upload_from_path(screenshot_file, config=config)
|
||||||
|
print("Error in test {} is at url {}".format(screenshot_name, image['link']), file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr)
|
||||||
|
raise e
|
||||||
return wrapper_func
|
return wrapper_func
|
||||||
|
|
||||||
|
|
||||||
@@ -76,30 +88,12 @@ def screenshot_failure_cls(cls):
|
|||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
def assert_times_almost_equal(first_time, second_time):
|
# Checks if animation is done
|
||||||
assert first_time.replace(microsecond=0, second=0) == second_time.replace(microsecond=0, second=0)
|
class animation_is_finished():
|
||||||
|
def __call__(self, driver):
|
||||||
|
numberAnimating = driver.execute_script('return $(":animated").length')
|
||||||
def assert_oembed(alt_event_embed_url, alt_oembed_url, client, event_embed_url, event_url, oembed_url):
|
finished = numberAnimating == 0
|
||||||
# Test the meta tag is in place
|
if finished:
|
||||||
response = client.get(event_url, follow=True, HTTP_HOST='example.com')
|
import time
|
||||||
assertContains(response, 'application/json+oembed')
|
time.sleep(0.1)
|
||||||
assertContains(response, oembed_url)
|
return finished
|
||||||
# Test that the JSON exists
|
|
||||||
response = client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
|
||||||
assert response.status_code == 200
|
|
||||||
assertContains(response, event_embed_url)
|
|
||||||
# Should also work for non-existant events
|
|
||||||
response = client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
|
||||||
assert response.status_code == 200
|
|
||||||
assertContains(response, alt_event_embed_url)
|
|
||||||
|
|
||||||
|
|
||||||
def login(client, django_user_model):
|
|
||||||
pwd = 'testuser'
|
|
||||||
usr = 'TestUser'
|
|
||||||
user = django_user_model.objects.create_user(username=usr, email="TestUser@test.com", password=pwd,
|
|
||||||
is_superuser=True,
|
|
||||||
is_active=True, is_staff=True)
|
|
||||||
assert client.login(username=usr, password=pwd)
|
|
||||||
return user
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from pypom import Page
|
from pypom import Page, Region
|
||||||
from selenium.common.exceptions import NoSuchElementException
|
|
||||||
from selenium.webdriver.common.action_chains import ActionChains
|
from selenium.webdriver.common.action_chains import ActionChains
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver import Chrome
|
||||||
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
from PyRIGS.tests import regions
|
from PyRIGS.tests import regions
|
||||||
|
|
||||||
|
|
||||||
@@ -44,7 +44,6 @@ class FormPage(BasePage):
|
|||||||
submit = self.find_element(*self._submit_locator)
|
submit = self.find_element(*self._submit_locator)
|
||||||
ActionChains(self.driver).move_to_element(submit).perform()
|
ActionChains(self.driver).move_to_element(submit).perform()
|
||||||
submit.click()
|
submit.click()
|
||||||
self.wait.until(animation_is_finished())
|
|
||||||
self.wait.until(lambda x: self.errors != previous_errors or self.success)
|
self.wait.until(lambda x: self.errors != previous_errors or self.success)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -74,13 +73,3 @@ class LoginPage(BasePage):
|
|||||||
password_element.send_keys(password)
|
password_element.send_keys(password)
|
||||||
|
|
||||||
self.find_element(*self._submit_locator).click()
|
self.find_element(*self._submit_locator).click()
|
||||||
|
|
||||||
|
|
||||||
class animation_is_finished():
|
|
||||||
def __call__(self, driver):
|
|
||||||
number_animating = driver.execute_script('return $(":animated").length')
|
|
||||||
finished = number_animating == 0
|
|
||||||
if finished:
|
|
||||||
import time
|
|
||||||
time.sleep(0.1)
|
|
||||||
return finished
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import datetime
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from pypom import Region
|
from pypom import Region
|
||||||
from selenium.common.exceptions import NoSuchElementException
|
from django.utils import timezone
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
|
||||||
from selenium.webdriver.support import expected_conditions
|
from selenium.webdriver.support import expected_conditions
|
||||||
|
from selenium.webdriver.remote.webelement import WebElement
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
from selenium.webdriver.support.select import Select
|
from selenium.webdriver.support.select import Select
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
def parse_bool_from_string(string):
|
def parse_bool_from_string(string):
|
||||||
@@ -18,22 +19,18 @@ def parse_bool_from_string(string):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# 12-Hour vs 24-Hour Time. Affects widget display
|
||||||
|
|
||||||
|
|
||||||
def get_time_format():
|
def get_time_format():
|
||||||
# Default
|
# Default
|
||||||
time_format = "%H%M"
|
time_format = "%H:%M"
|
||||||
if settings.CI: # The CI is American
|
# If system is 12hr
|
||||||
time_format = "%I%M%p"
|
if timezone.now().strftime("%p"):
|
||||||
|
time_format = "%I:%M %p"
|
||||||
return time_format
|
return time_format
|
||||||
|
|
||||||
|
|
||||||
def get_date_format():
|
|
||||||
date_format = "%d%m%Y"
|
|
||||||
if settings.CI: # And try as I might I can't stop it being so
|
|
||||||
date_format = "%m%d%Y"
|
|
||||||
return date_format
|
|
||||||
|
|
||||||
|
|
||||||
class BootstrapSelectElement(Region):
|
class BootstrapSelectElement(Region):
|
||||||
_main_button_locator = (By.CSS_SELECTOR, 'button.dropdown-toggle')
|
_main_button_locator = (By.CSS_SELECTOR, 'button.dropdown-toggle')
|
||||||
_option_box_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu')
|
_option_box_locator = (By.CSS_SELECTOR, 'ul.dropdown-menu')
|
||||||
@@ -71,11 +68,11 @@ class BootstrapSelectElement(Region):
|
|||||||
self.find_element(*self._deselect_all_locator).click()
|
self.find_element(*self._deselect_all_locator).click()
|
||||||
|
|
||||||
def search(self, query):
|
def search(self, query):
|
||||||
# self.wait.until(expected_conditions.visibility_of_element_located(self._status_locator))
|
|
||||||
search_box = self.find_element(*self._search_locator)
|
search_box = self.find_element(*self._search_locator)
|
||||||
self.open()
|
self.open()
|
||||||
search_box.clear()
|
search_box.clear()
|
||||||
search_box.send_keys(query)
|
search_box.send_keys(query)
|
||||||
|
status_text = self.find_element(*self._status_locator)
|
||||||
self.wait.until(expected_conditions.invisibility_of_element_located(self._status_locator))
|
self.wait.until(expected_conditions.invisibility_of_element_located(self._status_locator))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -117,15 +114,6 @@ 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.nextSibling.CodeMirror.getDoc().getValue();", self.root.get_attribute("id"))
|
|
||||||
|
|
||||||
def set_value(self, value):
|
|
||||||
self.driver.execute_script("document.querySelector('#' + arguments[0]).nextSibling.nextSibling.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()
|
||||||
@@ -162,13 +150,13 @@ class DatePicker(Region):
|
|||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.root.clear()
|
self.root.clear()
|
||||||
self.root.send_keys(value.strftime(get_date_format()))
|
self.root.send_keys(value.strftime("%d%m%Y"))
|
||||||
|
|
||||||
|
|
||||||
class TimePicker(Region):
|
class TimePicker(Region):
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
return datetime.datetime.strptime(self.root.get_attribute("value"), "%H:%M")
|
return datetime.datetime.strptime(self.root.get_attribute("value"), get_time_format())
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.root.clear()
|
self.root.clear()
|
||||||
@@ -178,12 +166,12 @@ class TimePicker(Region):
|
|||||||
class DateTimePicker(Region):
|
class DateTimePicker(Region):
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
return datetime.datetime.strptime(self.root.get_attribute("value"), "%Y-%m-%d %H:%M")
|
return datetime.datetime.strptime(self.root.get_attribute("value"), "%Y-%m-%d " + get_time_format())
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.root.clear()
|
self.root.clear()
|
||||||
|
|
||||||
date = value.date().strftime(get_date_format())
|
date = value.date().strftime("%d%m%Y")
|
||||||
time = value.time().strftime(get_time_format())
|
time = value.time().strftime(get_time_format())
|
||||||
|
|
||||||
self.root.send_keys(date)
|
self.root.send_keys(date)
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from django.core.management import call_command
|
|
||||||
from django.template.defaultfilters import striptags
|
|
||||||
from django.urls import URLPattern, URLResolver
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.urls.exceptions import NoReverseMatch
|
|
||||||
from pytest_django.asserts import assertRedirects, assertContains, assertNotContains
|
|
||||||
from pytest_django.asserts import assertTemplateUsed, assertInHTML
|
|
||||||
|
|
||||||
from PyRIGS import urls
|
|
||||||
from RIGS.models import Event
|
|
||||||
from assets.models import Asset
|
|
||||||
from django.db import connection
|
|
||||||
import pytest
|
|
||||||
from django.core.management import call_command
|
|
||||||
from django.template.defaultfilters import striptags
|
|
||||||
from django.urls.exceptions import NoReverseMatch
|
|
||||||
|
|
||||||
from RIGS.models import Event
|
|
||||||
from assets.models import Asset
|
|
||||||
from django.db import connection
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.test.utils import override_settings
|
|
||||||
|
|
||||||
|
|
||||||
def find_urls_recursive(patterns):
|
|
||||||
urls_to_check = []
|
|
||||||
for url in patterns:
|
|
||||||
if isinstance(url, URLResolver):
|
|
||||||
urls_to_check += find_urls_recursive(url.url_patterns)
|
|
||||||
elif isinstance(url, URLPattern):
|
|
||||||
# Skip some things that actually don't need auth (mainly OEmbed JSONs that are essentially just a redirect)
|
|
||||||
if url.name is not None and url.name != "closemodal" and "json" not in str(url):
|
|
||||||
urls_to_check.append(url)
|
|
||||||
return urls_to_check
|
|
||||||
|
|
||||||
|
|
||||||
def get_request_url(url):
|
|
||||||
pattern = str(url.pattern)
|
|
||||||
try:
|
|
||||||
kwargz = {}
|
|
||||||
if ":pk>" in pattern:
|
|
||||||
kwargz['pk'] = 1
|
|
||||||
if ":model>" in pattern:
|
|
||||||
kwargz['model'] = "event"
|
|
||||||
return reverse(url.name, kwargs=kwargz)
|
|
||||||
except NoReverseMatch:
|
|
||||||
print("Couldn't test url " + pattern)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("command", ['generateSampleAssetsData', 'generateSampleRIGSData', 'generateSampleUserData',
|
|
||||||
'deleteSampleData'])
|
|
||||||
def test_production_exception(command):
|
|
||||||
from django.core.management.base import CommandError
|
|
||||||
with pytest.raises(CommandError, match=".*production"):
|
|
||||||
call_command(command)
|
|
||||||
|
|
||||||
|
|
||||||
class TestSampleDataGenerator(TestCase):
|
|
||||||
@override_settings(DEBUG=True)
|
|
||||||
def test_sample_data(self):
|
|
||||||
call_command('generateSampleData')
|
|
||||||
assert Asset.objects.all().count() > 50
|
|
||||||
assert Event.objects.all().count() > 100
|
|
||||||
call_command('deleteSampleData')
|
|
||||||
assert Asset.objects.all().count() == 0
|
|
||||||
assert Event.objects.all().count() == 0
|
|
||||||
|
|
||||||
|
|
||||||
class TestSampleDataGenerator(TestCase):
|
|
||||||
@override_settings(DEBUG=True)
|
|
||||||
def setUp(self):
|
|
||||||
call_command('generateSampleData')
|
|
||||||
|
|
||||||
def test_unauthenticated(self): # Nothing should be available to the unauthenticated
|
|
||||||
for url in find_urls_recursive(urls.urlpatterns):
|
|
||||||
request_url = get_request_url(url)
|
|
||||||
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')
|
|
||||||
assertContains(response, 'Login')
|
|
||||||
if 'application/json+oembed' in response.content.decode():
|
|
||||||
assertTemplateUsed(response, 'login_redirect.html')
|
|
||||||
else:
|
|
||||||
if "embed" in str(url):
|
|
||||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
|
||||||
else:
|
|
||||||
expected_url = "{0}?next={1}".format(reverse('login'), request_url)
|
|
||||||
assertRedirects(response, expected_url)
|
|
||||||
|
|
||||||
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):
|
|
||||||
assert self.client.login(username="basic", password="basic")
|
|
||||||
|
|
||||||
url = reverse('asset_list')
|
|
||||||
response = self.client.get(url)
|
|
||||||
# Check edit and duplicate buttons NOT shown in list
|
|
||||||
assertNotContains(response, 'Edit')
|
|
||||||
assertNotContains(response,
|
|
||||||
'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})
|
|
||||||
response = self.client.get(url)
|
|
||||||
assertNotContains(response, 'Purchase Details')
|
|
||||||
assertNotContains(response, 'View Revision History')
|
|
||||||
|
|
||||||
urlz = {'asset_history', 'asset_update', 'asset_duplicate'}
|
|
||||||
for url_name in urlz:
|
|
||||||
request_url = reverse(url_name, kwargs={'pk': Asset.objects.first().asset_id})
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
assert response.status_code == 403
|
|
||||||
|
|
||||||
request_url = reverse('supplier_create')
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
assert response.status_code == 403
|
|
||||||
|
|
||||||
request_url = reverse('supplier_update', kwargs={'pk': 1})
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
assert response.status_code == 403
|
|
||||||
self.client.logout()
|
|
||||||
|
|
||||||
def test_keyholder_access(self):
|
|
||||||
assert self.client.login(username="keyholder", password="keyholder")
|
|
||||||
|
|
||||||
url = reverse('asset_list')
|
|
||||||
response = self.client.get(url)
|
|
||||||
# Check edit and duplicate buttons shown in list
|
|
||||||
assertContains(response, 'Edit')
|
|
||||||
assertContains(response, 'Duplicate')
|
|
||||||
|
|
||||||
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
|
|
||||||
response = self.client.get(url)
|
|
||||||
assertContains(response, 'Purchase Details')
|
|
||||||
assertContains(response, 'View Revision History')
|
|
||||||
self.client.logout()
|
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
from django.conf import settings
|
|
||||||
from django.conf.urls import include
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.conf import settings
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
|
from django.contrib.auth.views import LoginView
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
from PyRIGS.decorators import permission_required_with_403
|
||||||
|
import RIGS
|
||||||
|
import users
|
||||||
|
import versioning
|
||||||
from PyRIGS import views
|
from PyRIGS import views
|
||||||
|
|
||||||
urlpatterns = [
|
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'),
|
||||||
|
|
||||||
@@ -23,19 +27,18 @@ 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_help/', login_required(views.SearchHelp.as_view()), name='search_help'),
|
path('search_help/', views.SearchHelp.as_view(), name='search_help'),
|
||||||
|
|
||||||
path('', include('users.urls')),
|
path('', include('users.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")),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += staticfiles_urlpatterns()
|
urlpatterns += staticfiles_urlpatterns()
|
||||||
|
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
urlpatterns += [
|
urlpatterns = [
|
||||||
path('__debug__/', include(debug_toolbar.urls)),
|
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||||
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
|
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
|
||||||
]
|
] + urlpatterns
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import datetime
|
|
||||||
import operator
|
|
||||||
from functools import reduce
|
|
||||||
|
|
||||||
import simplejson
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib import messages
|
|
||||||
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.http.response import HttpResponseRedirect
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
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.contrib.auth.views import LoginView
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.core import serializers
|
||||||
|
from django.conf import settings
|
||||||
|
import simplejson
|
||||||
|
from django.contrib import messages
|
||||||
|
import datetime
|
||||||
|
import pytz
|
||||||
|
import operator
|
||||||
|
from registration.views import RegistrationView
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models, forms
|
||||||
from assets import models as asset_models
|
from assets import models as asset_models
|
||||||
from training import models as training_models
|
from functools import reduce
|
||||||
|
|
||||||
|
from django.views.decorators.cache import never_cache, cache_page
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
|
||||||
|
|
||||||
def is_ajax(request):
|
# Displays the current rig count along with a few other bits and pieces
|
||||||
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
|
class Index(generic.TemplateView):
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
@@ -39,8 +41,7 @@ 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,
|
'supplier': asset_models.Supplier
|
||||||
'training_item': training_models.TrainingItem,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
perms = {
|
perms = {
|
||||||
@@ -49,8 +50,7 @@ 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,
|
'supplier': None
|
||||||
'training_item': None, # TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@@ -78,9 +78,6 @@ 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:
|
||||||
@@ -101,9 +98,6 @@ 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)
|
||||||
for filter in filters:
|
|
||||||
q = Q(**{field: True})
|
|
||||||
qs.append(q)
|
|
||||||
queries.append(reduce(operator.or_, qs))
|
queries.append(reduce(operator.or_, qs))
|
||||||
|
|
||||||
# Build the data response list
|
# Build the data response list
|
||||||
@@ -157,7 +151,7 @@ class SecureAPIRequest(generic.View):
|
|||||||
|
|
||||||
class ModalURLMixin:
|
class ModalURLMixin:
|
||||||
def get_close_url(self, update, detail):
|
def get_close_url(self, update, detail):
|
||||||
if is_ajax(self.request):
|
if self.request.is_ajax():
|
||||||
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]))
|
||||||
@@ -176,7 +170,7 @@ class GenericListView(generic.ListView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GenericListView, self).get_context_data(**kwargs)
|
context = super(GenericListView, self).get_context_data(**kwargs)
|
||||||
context['page_title'] = self.model.__name__ + "s"
|
context['page_title'] = self.model.__name__ + "s"
|
||||||
if is_ajax(self.request):
|
if self.request.is_ajax():
|
||||||
context['override'] = "base_ajax.html"
|
context['override'] = "base_ajax.html"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -208,7 +202,7 @@ class GenericDetailView(generic.DetailView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GenericDetailView, self).get_context_data(**kwargs)
|
context = super(GenericDetailView, self).get_context_data(**kwargs)
|
||||||
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name)
|
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name)
|
||||||
if is_ajax(self.request):
|
if self.request.is_ajax():
|
||||||
context['override'] = "base_ajax.html"
|
context['override'] = "base_ajax.html"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -219,7 +213,7 @@ class GenericUpdateView(generic.UpdateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GenericUpdateView, self).get_context_data(**kwargs)
|
context = super(GenericUpdateView, self).get_context_data(**kwargs)
|
||||||
context['page_title'] = "Edit {}".format(self.model.__name__)
|
context['page_title'] = "Edit {}".format(self.model.__name__)
|
||||||
if is_ajax(self.request):
|
if self.request.is_ajax():
|
||||||
context['override'] = "base_ajax.html"
|
context['override'] = "base_ajax.html"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -230,7 +224,7 @@ class GenericCreateView(generic.CreateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GenericCreateView, self).get_context_data(**kwargs)
|
context = super(GenericCreateView, self).get_context_data(**kwargs)
|
||||||
context['page_title'] = "Create {}".format(self.model.__name__)
|
context['page_title'] = "Create {}".format(self.model.__name__)
|
||||||
if is_ajax(self.request):
|
if self.request.is_ajax():
|
||||||
context['override'] = "base_ajax.html"
|
context['override'] = "base_ajax.html"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -239,29 +233,15 @@ class SearchHelp(generic.TemplateView):
|
|||||||
template_name = 'search_help.html'
|
template_name = 'search_help.html'
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Called from a modal window (e.g. when an item is submitted to an event/invoice).
|
||||||
|
May optionally also include some javascript in a success message to cause a load of
|
||||||
|
the new information onto the page.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class CloseModal(generic.TemplateView):
|
class CloseModal(generic.TemplateView):
|
||||||
"""
|
|
||||||
Called from a modal window (e.g. when an item is submitted to an event/invoice).
|
|
||||||
May optionally also include some javascript in a success message to cause a load of
|
|
||||||
the new information onto the page.
|
|
||||||
"""
|
|
||||||
template_name = 'closemodal.html'
|
template_name = 'closemodal.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
return {'messages': messages.get_messages(self.request)}
|
return {'messages': messages.get_messages(self.request)}
|
||||||
|
|
||||||
|
|
||||||
class OEmbedView(generic.View):
|
|
||||||
def get(self, request, pk=None):
|
|
||||||
embed_url = reverse(self.url_name, args=[pk])
|
|
||||||
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
|
|
||||||
'version': '1.0',
|
|
||||||
'type': 'rich',
|
|
||||||
'height': '250'
|
|
||||||
}
|
|
||||||
|
|
||||||
json = simplejson.JSONEncoderForHTML().encode(data)
|
|
||||||
return HttpResponse(json, content_type="application/json")
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# TEC PA & Lighting - PyRIGS #
|
# TEC PA & Lighting - PyRIGS #
|
||||||

|
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
||||||
[](https://coveralls.io/github/nottinghamtec/PyRIGS)
|
[](https://coveralls.io/github/nottinghamtec/PyRIGS)
|
||||||
[](https://codeclimate.com/github/nottinghamtec/PyRIGS/maintainability)
|
|
||||||
|
|
||||||
Welcome to TEC PA & Lighting's PyRIGS program. This is a reimplementation of the previous Rig Information Gathering System (RIGS) that was developed using Ruby on Rails. PyRIGS is our in house app for the centralisation of information on our events and now assets.
|
Welcome to TEC PA & Lighting's PyRIGS program. This is a reimplementation of the previous Rig Information Gathering System (RIGS) that was developed using Ruby on Rails. PyRIGS is our in house app for the centralisation of information on our events and now assets.
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib import messages
|
from RIGS import models, forms
|
||||||
from django.contrib.admin import helpers
|
from users import forms as user_forms
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import Count
|
|
||||||
from django.forms import ModelForm
|
|
||||||
from django.template.response import TemplateResponse
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from reversion import revisions as reversion
|
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
|
|
||||||
from RIGS import models
|
from django.contrib.admin import helpers
|
||||||
from users import forms as user_forms
|
from django.template.response import TemplateResponse
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.db import transaction
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.models import Count
|
||||||
|
from django.forms import ModelForm
|
||||||
|
|
||||||
|
from reversion import revisions as reversion
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
admin.site.register(models.VatRate, 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)
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import reversion
|
|
||||||
from django import forms
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.urls import reverse_lazy
|
||||||
from django.db.models import Q
|
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.template import RequestContext
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.urls import reverse
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
from django.db.models import Q
|
||||||
from z3c.rml import rml2pdf
|
from z3c.rml import rml2pdf
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
import reversion
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
|
||||||
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +37,20 @@ class InvoiceIndex(generic.ListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.model.objects.outstanding_invoices()
|
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
|
||||||
|
sql = "SELECT * FROM " \
|
||||||
|
"(SELECT " \
|
||||||
|
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
|
||||||
|
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
|
||||||
|
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
|
||||||
|
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
|
||||||
|
"AS sub " \
|
||||||
|
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
|
||||||
|
"ORDER BY invoice_date"
|
||||||
|
|
||||||
|
query = self.model.objects.raw(sql)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
class InvoiceDetail(generic.DetailView):
|
class InvoiceDetail(generic.DetailView):
|
||||||
@@ -42,13 +59,7 @@ class InvoiceDetail(generic.DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(InvoiceDetail, self).get_context_data(**kwargs)
|
context = super(InvoiceDetail, self).get_context_data(**kwargs)
|
||||||
context['page_title'] = "Invoice {} ({}) ".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
|
context['page_title'] = "Invoice {} ({})".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
|
||||||
if self.object.void:
|
|
||||||
context['page_title'] += "<span class='badge badge-warning float-right'>VOID</span>"
|
|
||||||
elif self.object.is_closed:
|
|
||||||
context['page_title'] += "<span class='badge badge-success float-right'>PAID</span>"
|
|
||||||
else:
|
|
||||||
context['page_title'] += "<span class='badge badge-info float-right'>OUTSTANDING</span>"
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -60,6 +71,12 @@ class InvoicePrint(generic.View):
|
|||||||
|
|
||||||
context = {
|
context = {
|
||||||
'object': object,
|
'object': object,
|
||||||
|
'fonts': {
|
||||||
|
'opensans': {
|
||||||
|
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
||||||
|
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||||
|
}
|
||||||
|
},
|
||||||
'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': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
|
||||||
@@ -85,8 +102,8 @@ class InvoiceVoid(generic.View):
|
|||||||
object.save()
|
object.save()
|
||||||
|
|
||||||
if object.void:
|
if object.void:
|
||||||
return HttpResponseRedirect(reverse('invoice_list'))
|
return HttpResponseRedirect(reverse_lazy('invoice_list'))
|
||||||
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': object.pk}))
|
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk}))
|
||||||
|
|
||||||
|
|
||||||
class InvoiceDelete(generic.DeleteView):
|
class InvoiceDelete(generic.DeleteView):
|
||||||
@@ -97,14 +114,14 @@ class InvoiceDelete(generic.DeleteView):
|
|||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
if obj.payment_set.all().count() > 0:
|
if obj.payment_set.all().count() > 0:
|
||||||
messages.info(self.request, 'To delete an invoice, delete the payments first.')
|
messages.info(self.request, 'To delete an invoice, delete the payments first.')
|
||||||
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': obj.pk}))
|
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk}))
|
||||||
return super(InvoiceDelete, self).get(pk)
|
return super(InvoiceDelete, self).get(pk)
|
||||||
|
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
if obj.payment_set.all().count() > 0:
|
if obj.payment_set.all().count() > 0:
|
||||||
messages.info(self.request, 'To delete an invoice, delete the payments first.')
|
messages.info(self.request, 'To delete an invoice, delete the payments first.')
|
||||||
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': obj.pk}))
|
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk}))
|
||||||
return super(InvoiceDelete, self).post(pk)
|
return super(InvoiceDelete, self).post(pk)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@@ -159,14 +176,30 @@ class InvoiceWaiting(generic.ListView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
|
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
|
||||||
total = 0
|
total = 0
|
||||||
objects = self.get_queryset()
|
for obj in self.get_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'] = "Events for Invoice ({} Events, £{:.2f})".format(len(self.get_objects()), total)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.model.objects.waiting_invoices()
|
return self.get_objects()
|
||||||
|
|
||||||
|
def get_objects(self):
|
||||||
|
# @todo find a way to select items
|
||||||
|
events = self.model.objects.filter(
|
||||||
|
(
|
||||||
|
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
|
||||||
|
Q(end_date__lte=datetime.date.today()) # Has end date, finishes before
|
||||||
|
) & Q(invoice__isnull=True) & # Has not already been invoiced
|
||||||
|
Q(is_rig=True) # Is a rig (not non-rig)
|
||||||
|
|
||||||
|
).order_by('start_date') \
|
||||||
|
.select_related('person',
|
||||||
|
'organisation',
|
||||||
|
'venue', 'mic') \
|
||||||
|
.prefetch_related('items')
|
||||||
|
|
||||||
|
return events
|
||||||
|
|
||||||
|
|
||||||
class InvoiceEvent(generic.View):
|
class InvoiceEvent(generic.View):
|
||||||
@@ -187,7 +220,7 @@ class InvoiceEvent(generic.View):
|
|||||||
invoice.save()
|
invoice.save()
|
||||||
messages.warning(self.request, 'Invoice voided')
|
messages.warning(self.request, 'Invoice voided')
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': invoice.pk}))
|
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk}))
|
||||||
|
|
||||||
|
|
||||||
class PaymentCreate(generic.CreateView):
|
class PaymentCreate(generic.CreateView):
|
||||||
@@ -213,7 +246,7 @@ class PaymentCreate(generic.CreateView):
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
messages.info(self.request, "location.reload()")
|
messages.info(self.request, "location.reload()")
|
||||||
return reverse('closemodal')
|
return reverse_lazy('closemodal')
|
||||||
|
|
||||||
|
|
||||||
class PaymentDelete(generic.DeleteView):
|
class PaymentDelete(generic.DeleteView):
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import simplejson
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.utils import formats
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.utils import timezone
|
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||||
|
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm
|
||||||
|
from django.db import transaction
|
||||||
|
from registration.forms import RegistrationFormUniqueEmail
|
||||||
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
|
from captcha.fields import ReCaptchaField
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
import simplejson
|
||||||
|
from datetime import datetime
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
|
|||||||
27
RIGS/hs.py
@@ -1,11 +1,11 @@
|
|||||||
from django.contrib import messages
|
from RIGS import models, forms
|
||||||
|
from django.views import generic
|
||||||
|
from django.utils import timezone
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import timezone
|
|
||||||
from django.views import generic
|
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
from django.db.models import AutoField, ManyToOneRel
|
||||||
from RIGS import models, forms
|
from django.contrib import messages
|
||||||
|
|
||||||
|
|
||||||
class EventRiskAssessmentCreate(generic.CreateView):
|
class EventRiskAssessmentCreate(generic.CreateView):
|
||||||
@@ -70,20 +70,12 @@ class EventRiskAssessmentDetail(generic.DetailView):
|
|||||||
model = models.RiskAssessment
|
model = models.RiskAssessment
|
||||||
template_name = 'risk_assessment_detail.html'
|
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):
|
class EventRiskAssessmentList(generic.ListView):
|
||||||
paginate_by = 20
|
paginate_by = 20
|
||||||
model = models.RiskAssessment
|
model = models.RiskAssessment
|
||||||
template_name = 'hs_object_list.html'
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
|
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
|
||||||
context['title'] = 'Risk Assessment'
|
context['title'] = 'Risk Assessment'
|
||||||
@@ -91,6 +83,7 @@ class EventRiskAssessmentList(generic.ListView):
|
|||||||
context['edit'] = 'ra_edit'
|
context['edit'] = 'ra_edit'
|
||||||
context['review'] = 'ra_review'
|
context['review'] = 'ra_review'
|
||||||
context['perm'] = 'perms.RIGS.review_riskassessment'
|
context['perm'] = 'perms.RIGS.review_riskassessment'
|
||||||
|
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -112,7 +105,7 @@ class EventChecklistDetail(generic.DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(EventChecklistDetail, self).get_context_data(**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)
|
context['page_title'] = "Event Checklist for Event {} {}".format(self.object.event.display_id, self.object.event.name)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -187,9 +180,6 @@ class EventChecklistList(generic.ListView):
|
|||||||
model = models.EventChecklist
|
model = models.EventChecklist
|
||||||
template_name = 'hs_object_list.html'
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(EventChecklistList, self).get_context_data(**kwargs)
|
context = super(EventChecklistList, self).get_context_data(**kwargs)
|
||||||
context['title'] = 'Event Checklist'
|
context['title'] = 'Event Checklist'
|
||||||
@@ -197,6 +187,7 @@ class EventChecklistList(generic.ListView):
|
|||||||
context['edit'] = 'ec_edit'
|
context['edit'] = 'ec_edit'
|
||||||
context['review'] = 'ec_review'
|
context['review'] = 'ec_review'
|
||||||
context['perm'] = 'perms.RIGS.review_eventchecklist'
|
context['perm'] = 'perms.RIGS.review_eventchecklist'
|
||||||
|
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -218,7 +209,7 @@ class HSList(generic.ListView):
|
|||||||
template_name = 'hs_list.html'
|
template_name = 'hs_list.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return models.Event.objects.all().exclude(status=models.Event.CANCELLED).order_by('-start_date').select_related('riskassessment').prefetch_related('checklists')
|
return models.Event.objects.all().order_by('-start_date')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(HSList, self).get_context_data(**kwargs)
|
context = super(HSList, self).get_context_data(**kwargs)
|
||||||
|
|||||||
13
RIGS/ical.py
@@ -1,11 +1,12 @@
|
|||||||
import datetime
|
from RIGS import models, forms
|
||||||
|
|
||||||
import pytz
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db.models import Q
|
|
||||||
from django_ical.views import ICalFeed
|
from django_ical.views import ICalFeed
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from RIGS import models
|
import datetime
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
|
||||||
class CalendarICS(ICalFeed):
|
class CalendarICS(ICalFeed):
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
|
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from assets import models
|
|
||||||
from RIGS import models as rigsmodels
|
|
||||||
from training import models as tmodels
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'Deletes testing sample data'
|
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
if not settings.DEBUG:
|
|
||||||
raise CommandError('You cannot run this command in production')
|
|
||||||
|
|
||||||
self.delete_objects(models.AssetCategory)
|
|
||||||
self.delete_objects(models.AssetStatus)
|
|
||||||
self.delete_objects(models.Supplier)
|
|
||||||
self.delete_objects(models.Connector)
|
|
||||||
self.delete_objects(models.Asset)
|
|
||||||
self.delete_objects(rigsmodels.VatRate)
|
|
||||||
self.delete_objects(rigsmodels.Profile)
|
|
||||||
self.delete_objects(rigsmodels.Person)
|
|
||||||
self.delete_objects(rigsmodels.Organisation)
|
|
||||||
self.delete_objects(rigsmodels.Venue)
|
|
||||||
self.delete_objects(Group)
|
|
||||||
self.delete_objects(rigsmodels.Event)
|
|
||||||
self.delete_objects(rigsmodels.EventItem)
|
|
||||||
self.delete_objects(rigsmodels.Invoice)
|
|
||||||
self.delete_objects(rigsmodels.Payment)
|
|
||||||
self.delete_objects(rigsmodels.RiskAssessment)
|
|
||||||
self.delete_objects(rigsmodels.EventChecklist)
|
|
||||||
self.delete_objects(tmodels.TrainingCategory)
|
|
||||||
self.delete_objects(tmodels.TrainingItem)
|
|
||||||
self.delete_objects(tmodels.TrainingLevel)
|
|
||||||
|
|
||||||
def delete_objects(self, model):
|
|
||||||
for obj in model.objects.all():
|
|
||||||
obj.delete()
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
from RIGS import models
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@@ -9,7 +7,5 @@ class Command(BaseCommand):
|
|||||||
can_import_settings = True
|
can_import_settings = True
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
call_command('generateSampleUserData')
|
|
||||||
call_command('generateSampleRIGSData')
|
call_command('generateSampleRIGSData')
|
||||||
call_command('generateSampleAssetsData')
|
call_command('generateSampleAssetsData')
|
||||||
call_command('generateSampleTrainingData')
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.contrib.auth.models import Group, Permission
|
||||||
|
from django.db import transaction
|
||||||
|
from reversion import revisions as reversion
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from django.contrib.auth.models import Group, Permission
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
from django.db import transaction
|
|
||||||
from django.utils import timezone
|
|
||||||
from reversion import revisions as reversion
|
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
|
|
||||||
@@ -17,11 +16,13 @@ class Command(BaseCommand):
|
|||||||
people = []
|
people = []
|
||||||
organisations = []
|
organisations = []
|
||||||
venues = []
|
venues = []
|
||||||
events = []
|
profiles = []
|
||||||
profiles = models.Profile.objects.all()
|
|
||||||
|
keyholder_group = None
|
||||||
|
finance_group = None
|
||||||
|
hs_group = None
|
||||||
|
|
||||||
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):
|
||||||
@@ -32,13 +33,20 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||||
self.setup_people()
|
|
||||||
self.setup_organisations()
|
|
||||||
self.setup_venues()
|
|
||||||
self.setup_events()
|
|
||||||
print("Done generating rigboard data")
|
|
||||||
|
|
||||||
def setup_people(self):
|
self.setupGenericProfiles()
|
||||||
|
|
||||||
|
self.setupPeople()
|
||||||
|
self.setupOrganisations()
|
||||||
|
self.setupVenues()
|
||||||
|
|
||||||
|
self.setupGroups()
|
||||||
|
|
||||||
|
self.setupEvents()
|
||||||
|
|
||||||
|
self.setupUsefulProfiles()
|
||||||
|
|
||||||
|
def setupPeople(self):
|
||||||
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
|
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
|
||||||
"Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore",
|
"Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore",
|
||||||
"Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan",
|
"Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan",
|
||||||
@@ -53,25 +61,25 @@ class Command(BaseCommand):
|
|||||||
"Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa
|
"Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa
|
||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
reversion.set_user(random.choice(models.Profile.objects.all()))
|
reversion.set_user(random.choice(self.profiles))
|
||||||
person = models.Person.objects.create(name=name)
|
|
||||||
|
|
||||||
|
newPerson = models.Person.objects.create(name=name)
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
person.email = "address@person.com"
|
newPerson.email = "address@person.com"
|
||||||
|
|
||||||
if i % 5 == 0:
|
if i % 5 == 0:
|
||||||
person.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||||
|
|
||||||
if i % 7 == 0:
|
if i % 7 == 0:
|
||||||
person.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||||
|
|
||||||
if i % 9 == 0:
|
if i % 9 == 0:
|
||||||
person.phone = "01234 567894"
|
newPerson.phone = "01234 567894"
|
||||||
|
|
||||||
person.save()
|
newPerson.save()
|
||||||
self.people.append(person)
|
self.people.append(newPerson)
|
||||||
|
|
||||||
def setup_organisations(self):
|
def setupOrganisations(self):
|
||||||
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars",
|
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars",
|
||||||
"ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc",
|
"ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc",
|
||||||
"Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp",
|
"Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp",
|
||||||
@@ -100,28 +108,27 @@ class Command(BaseCommand):
|
|||||||
"Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
|
"Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
|
||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
reversion.set_user(random.choice(models.Profile.objects.all()))
|
reversion.set_user(random.choice(self.profiles))
|
||||||
new_organisation = models.Organisation.objects.create(name=name)
|
newOrganisation = models.Organisation.objects.create(name=name)
|
||||||
|
|
||||||
if i % 2 == 0:
|
if i % 2 == 0:
|
||||||
new_organisation.has_su_account = True
|
newOrganisation.has_su_account = True
|
||||||
|
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
new_organisation.email = "address@organisation.com"
|
newOrganisation.email = "address@organisation.com"
|
||||||
|
|
||||||
if i % 5 == 0:
|
if i % 5 == 0:
|
||||||
new_organisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||||
|
|
||||||
if i % 7 == 0:
|
if i % 7 == 0:
|
||||||
new_organisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||||
|
|
||||||
if i % 9 == 0:
|
if i % 9 == 0:
|
||||||
new_organisation.phone = "01234 567894"
|
newOrganisation.phone = "01234 567894"
|
||||||
|
|
||||||
new_organisation.save()
|
newOrganisation.save()
|
||||||
self.organisations.append(new_organisation)
|
self.organisations.append(newOrganisation)
|
||||||
|
|
||||||
def setup_venues(self):
|
def setupVenues(self):
|
||||||
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch",
|
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch",
|
||||||
"The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands",
|
"The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands",
|
||||||
"The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins",
|
"The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins",
|
||||||
@@ -137,27 +144,108 @@ class Command(BaseCommand):
|
|||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
reversion.set_user(random.choice(self.profiles))
|
reversion.set_user(random.choice(self.profiles))
|
||||||
new_venue = models.Venue.objects.create(name=name)
|
newVenue = models.Venue.objects.create(name=name)
|
||||||
|
|
||||||
if i % 2 == 0:
|
if i % 2 == 0:
|
||||||
new_venue.three_phase_available = True
|
newVenue.three_phase_available = True
|
||||||
|
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
new_venue.email = "address@venue.com"
|
newVenue.email = "address@venue.com"
|
||||||
|
|
||||||
if i % 5 == 0:
|
if i % 5 == 0:
|
||||||
new_venue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||||
|
|
||||||
if i % 7 == 0:
|
if i % 7 == 0:
|
||||||
new_venue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||||
|
|
||||||
if i % 9 == 0:
|
if i % 9 == 0:
|
||||||
new_venue.phone = "01234 567894"
|
newVenue.phone = "01234 567894"
|
||||||
|
|
||||||
new_venue.save()
|
newVenue.save()
|
||||||
self.venues.append(new_venue)
|
self.venues.append(newVenue)
|
||||||
|
|
||||||
def setup_events(self):
|
def setupGroups(self):
|
||||||
|
self.keyholder_group = Group.objects.create(name='Keyholders')
|
||||||
|
self.finance_group = Group.objects.create(name='Finance')
|
||||||
|
self.hs_group = Group.objects.create(name='H&S')
|
||||||
|
|
||||||
|
keyholderPerms = ["add_event", "change_event", "view_event",
|
||||||
|
"add_eventitem", "change_eventitem", "delete_eventitem",
|
||||||
|
"add_organisation", "change_organisation", "view_organisation",
|
||||||
|
"add_person", "change_person", "view_person", "view_profile",
|
||||||
|
"add_venue", "change_venue", "view_venue",
|
||||||
|
"add_asset", "change_asset", "delete_asset",
|
||||||
|
"view_asset", "view_supplier", "change_supplier", "asset_finance",
|
||||||
|
"add_supplier", "view_cabletype", "change_cabletype",
|
||||||
|
"add_cabletype", "view_eventchecklist", "change_eventchecklist",
|
||||||
|
"add_eventchecklist", "view_riskassessment", "change_riskassessment",
|
||||||
|
"add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew",
|
||||||
|
"delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle",
|
||||||
|
"change_eventchecklistvehicle",
|
||||||
|
"delete_eventchecklistvehicle", "view_eventchecklistvehicle", ]
|
||||||
|
financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
|
||||||
|
"add_payment", "change_payment", "delete_payment"]
|
||||||
|
hsPerms = keyholderPerms + ["review_riskassessment", "review_eventchecklist"]
|
||||||
|
|
||||||
|
for permId in keyholderPerms:
|
||||||
|
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
|
||||||
|
|
||||||
|
for permId in financePerms:
|
||||||
|
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
|
||||||
|
|
||||||
|
for permId in hsPerms:
|
||||||
|
self.hs_group.permissions.add(Permission.objects.get(codename=permId))
|
||||||
|
|
||||||
|
def setupGenericProfiles(self):
|
||||||
|
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble",
|
||||||
|
"Jack Harkness", "Mickey Smith", "Rose Tyler"]
|
||||||
|
for i, name in enumerate(names):
|
||||||
|
newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0],
|
||||||
|
last_name=name.split(" ")[-1],
|
||||||
|
email=name.replace(" ", "") + "@example.com",
|
||||||
|
initials="".join([j[0].upper() for j in name.split()]))
|
||||||
|
if i % 2 == 0:
|
||||||
|
newProfile.phone = "01234 567894"
|
||||||
|
|
||||||
|
newProfile.save()
|
||||||
|
self.profiles.append(newProfile)
|
||||||
|
|
||||||
|
def setupUsefulProfiles(self):
|
||||||
|
superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User",
|
||||||
|
initials="SU",
|
||||||
|
email="superuser@example.com", is_superuser=True, is_active=True,
|
||||||
|
is_staff=True)
|
||||||
|
superUser.set_password('superuser')
|
||||||
|
superUser.save()
|
||||||
|
|
||||||
|
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
|
||||||
|
initials="FU",
|
||||||
|
email="financeuser@example.com", is_active=True, is_approved=True)
|
||||||
|
financeUser.groups.add(self.finance_group)
|
||||||
|
financeUser.groups.add(self.keyholder_group)
|
||||||
|
financeUser.set_password('finance')
|
||||||
|
financeUser.save()
|
||||||
|
|
||||||
|
hsUser = models.Profile.objects.create(username="hs", first_name="HS", last_name="User",
|
||||||
|
initials="HSU",
|
||||||
|
email="hsuser@example.com", is_active=True, is_approved=True)
|
||||||
|
hsUser.groups.add(self.hs_group)
|
||||||
|
hsUser.groups.add(self.keyholder_group)
|
||||||
|
hsUser.set_password('hs')
|
||||||
|
hsUser.save()
|
||||||
|
|
||||||
|
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
|
||||||
|
initials="KU",
|
||||||
|
email="keyholderuser@example.com", is_active=True, is_approved=True)
|
||||||
|
keyholderUser.groups.add(self.keyholder_group)
|
||||||
|
keyholderUser.set_password('keyholder')
|
||||||
|
keyholderUser.save()
|
||||||
|
|
||||||
|
basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU",
|
||||||
|
email="basicuser@example.com", is_active=True, is_approved=True)
|
||||||
|
basicUser.set_password('basic')
|
||||||
|
basicUser.save()
|
||||||
|
|
||||||
|
def setupEvents(self):
|
||||||
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball",
|
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball",
|
||||||
"Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event",
|
"Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event",
|
||||||
"End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show",
|
"End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show",
|
||||||
@@ -168,7 +256,7 @@ class Command(BaseCommand):
|
|||||||
notes = ["The client came into the office at some point", "Who knows if this will happen",
|
notes = ["The client came into the office at some point", "Who knows if this will happen",
|
||||||
"Probably should check this event", "Maybe not happening", "Run away!"]
|
"Probably should check this event", "Maybe not happening", "Run away!"]
|
||||||
|
|
||||||
item_options = [
|
itemOptions = [
|
||||||
{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2,
|
{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2,
|
||||||
'cost': 200.00},
|
'cost': 200.00},
|
||||||
{'name': 'Projector',
|
{'name': 'Projector',
|
||||||
@@ -185,7 +273,7 @@ class Command(BaseCommand):
|
|||||||
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
|
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
|
||||||
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
|
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
|
||||||
|
|
||||||
day_delta = -120 # start adding events from 4 months ago
|
dayDelta = -120 # start adding events from 4 months ago
|
||||||
|
|
||||||
for i in range(150): # Let's add 100 events
|
for i in range(150): # Let's add 100 events
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
@@ -193,100 +281,65 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
name = names[i % len(names)]
|
name = names[i % len(names)]
|
||||||
|
|
||||||
start_date = datetime.date.today() + datetime.timedelta(days=day_delta)
|
startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
|
||||||
day_delta = day_delta + random.randint(0, 3)
|
dayDelta = dayDelta + random.randint(0, 3)
|
||||||
|
|
||||||
new_event = models.Event.objects.create(name=name, start_date=start_date)
|
newEvent = models.Event.objects.create(name=name, start_date=startDate)
|
||||||
|
|
||||||
if random.randint(0, 2) > 1: # 1 in 3 have a start time
|
if random.randint(0, 2) > 1: # 1 in 3 have a start time
|
||||||
new_event.start_time = datetime.time(random.randint(15, 20))
|
newEvent.start_time = datetime.time(random.randint(15, 20))
|
||||||
if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day
|
if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day
|
||||||
new_event.end_time = datetime.time(random.randint(21, 23))
|
newEvent.end_time = datetime.time(random.randint(21, 23))
|
||||||
elif random.randint(0, 1) > 0: # half of the others finish early the next day
|
elif random.randint(0, 1) > 0: # half of the others finish early the next day
|
||||||
new_event.end_date = new_event.start_date + datetime.timedelta(days=1)
|
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1)
|
||||||
new_event.end_time = datetime.time(random.randint(0, 5))
|
newEvent.end_time = datetime.time(random.randint(0, 5))
|
||||||
elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead
|
elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead
|
||||||
new_event.end_date = new_event.start_date + datetime.timedelta(days=random.randint(1, 4))
|
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4))
|
||||||
|
|
||||||
if random.randint(0, 6) > 0: # 5 in 6 have MIC
|
if random.randint(0, 6) > 0: # 5 in 6 have MIC
|
||||||
new_event.mic = random.choice(self.profiles)
|
newEvent.mic = random.choice(self.profiles)
|
||||||
|
|
||||||
if random.randint(0, 6) > 0: # 5 in 6 have organisation
|
if random.randint(0, 6) > 0: # 5 in 6 have organisation
|
||||||
new_event.organisation = random.choice(self.organisations)
|
newEvent.organisation = random.choice(self.organisations)
|
||||||
|
|
||||||
if random.randint(0, 6) > 0: # 5 in 6 have person
|
if random.randint(0, 6) > 0: # 5 in 6 have person
|
||||||
new_event.person = random.choice(self.people)
|
newEvent.person = random.choice(self.people)
|
||||||
|
|
||||||
if random.randint(0, 6) > 0: # 5 in 6 have venue
|
if random.randint(0, 6) > 0: # 5 in 6 have venue
|
||||||
new_event.venue = random.choice(self.venues)
|
newEvent.venue = random.choice(self.venues)
|
||||||
|
|
||||||
# Could have any status, equally weighted
|
# Could have any status, equally weighted
|
||||||
new_event.status = random.choice(
|
newEvent.status = random.choice(
|
||||||
[models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED])
|
[models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED])
|
||||||
|
|
||||||
new_event.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
|
newEvent.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
|
||||||
|
|
||||||
if random.randint(0, 1) > 0: # 1 in 2 have description
|
if random.randint(0, 1) > 0: # 1 in 2 have description
|
||||||
new_event.description = random.choice(descriptions)
|
newEvent.description = random.choice(descriptions)
|
||||||
|
|
||||||
if random.randint(0, 1) > 0: # 1 in 2 have notes
|
if random.randint(0, 1) > 0: # 1 in 2 have notes
|
||||||
new_event.notes = random.choice(notes)
|
newEvent.notes = random.choice(notes)
|
||||||
|
|
||||||
new_event.save()
|
newEvent.save()
|
||||||
|
|
||||||
# Now add some items
|
# Now add some items
|
||||||
for j in range(random.randint(1, 5)):
|
for j in range(random.randint(1, 5)):
|
||||||
item_data = item_options[random.randint(0, len(item_options) - 1)]
|
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
|
||||||
new_item = models.EventItem.objects.create(event=new_event, order=j, **item_data)
|
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
||||||
new_item.save()
|
newItem.save()
|
||||||
|
|
||||||
while new_event.sum_total < 0:
|
while newEvent.sum_total < 0:
|
||||||
item_data = item_options[random.randint(0, len(item_options) - 1)]
|
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
|
||||||
new_item = models.EventItem.objects.create(event=new_event, order=j, **item_data)
|
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
||||||
new_item.save()
|
newItem.save()
|
||||||
|
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
reversion.set_user(random.choice(self.profiles))
|
reversion.set_user(random.choice(self.profiles))
|
||||||
if new_event.start_date < datetime.date.today(): # think about adding an invoice
|
if newEvent.start_date < datetime.date.today(): # think about adding an invoice
|
||||||
if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury
|
if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury
|
||||||
new_invoice = models.Invoice.objects.create(event=new_event)
|
newInvoice = models.Invoice.objects.create(event=newEvent)
|
||||||
if new_event.status is models.Event.CANCELLED: # void cancelled events
|
if newEvent.status is models.Event.CANCELLED: # void cancelled events
|
||||||
new_invoice.void = True
|
newInvoice.void = True
|
||||||
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
|
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
|
||||||
models.Payment.objects.create(invoice=new_invoice, amount=new_invoice.balance,
|
models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance,
|
||||||
date=datetime.date.today())
|
date=datetime.date.today())
|
||||||
if i == 1 or random.randint(0, 5) > 0: # Event 1 and 1 in 5 have a RA
|
|
||||||
models.RiskAssessment.objects.create(event=new_event, supervisor_consulted=bool(random.getrandbits(1)),
|
|
||||||
nonstandard_equipment=bool(random.getrandbits(1)),
|
|
||||||
nonstandard_use=bool(random.getrandbits(1)),
|
|
||||||
contractors=bool(random.getrandbits(1)),
|
|
||||||
other_companies=bool(random.getrandbits(1)),
|
|
||||||
crew_fatigue=bool(random.getrandbits(1)),
|
|
||||||
big_power=bool(random.getrandbits(1)),
|
|
||||||
generators=bool(random.getrandbits(1)),
|
|
||||||
other_companies_power=bool(random.getrandbits(1)),
|
|
||||||
nonstandard_equipment_power=bool(random.getrandbits(1)),
|
|
||||||
multiple_electrical_environments=bool(random.getrandbits(1)),
|
|
||||||
noise_monitoring=bool(random.getrandbits(1)),
|
|
||||||
known_venue=bool(random.getrandbits(1)),
|
|
||||||
safe_loading=bool(random.getrandbits(1)),
|
|
||||||
safe_storage=bool(random.getrandbits(1)),
|
|
||||||
area_outside_of_control=bool(random.getrandbits(1)),
|
|
||||||
barrier_required=bool(random.getrandbits(1)),
|
|
||||||
nonstandard_emergency_procedure=bool(random.getrandbits(1)),
|
|
||||||
special_structures=bool(random.getrandbits(1)),
|
|
||||||
suspended_structures=bool(random.getrandbits(1)),
|
|
||||||
outside=bool(random.getrandbits(1)))
|
|
||||||
if i == 0 or random.randint(0, 1) > 0: # Event 1 and 1 in 10 have a Checklist
|
|
||||||
models.EventChecklist.objects.create(event=new_event, power_mic=random.choice(self.profiles),
|
|
||||||
safe_parking=bool(random.getrandbits(1)),
|
|
||||||
safe_packing=bool(random.getrandbits(1)),
|
|
||||||
exits=bool(random.getrandbits(1)),
|
|
||||||
trip_hazard=bool(random.getrandbits(1)),
|
|
||||||
warning_signs=bool(random.getrandbits(1)),
|
|
||||||
ear_plugs=bool(random.getrandbits(1)),
|
|
||||||
hs_location="Locked away safely",
|
|
||||||
extinguishers_location="Somewhere, I forgot",
|
|
||||||
earthing=bool(random.getrandbits(1)),
|
|
||||||
pat=bool(random.getrandbits(1)),
|
|
||||||
date=timezone.now(), venue=random.choice(self.venues))
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Generated by Django 2.0.13 on 2020-01-11 18:29
|
# Generated by Django 2.0.13 on 2020-01-11 18:29
|
||||||
# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved
|
# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
def approve_legacy(apps, schema_editor):
|
def approve_legacy(apps, schema_editor):
|
||||||
@@ -15,5 +15,5 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(approve_legacy, migrations.RunPython.noop)
|
migrations.RunPython(approve_legacy)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
# Generated by Django 3.1.7 on 2021-03-02 11:48
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def postgres_migration_prep(apps, schema_editor):
|
|
||||||
model = apps.get_model("RIGS", "Event")
|
|
||||||
for field in ["auth_request_to", "collector", "description", "notes", "purchase_order"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "EventAuthorisation")
|
|
||||||
for field in ["account_code", "uni_id"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "EventChecklist")
|
|
||||||
for field in ["extinguishers_location", "hs_location", "w1_description", "w2_description", "w3_description"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "EventItem")
|
|
||||||
for field in ["description"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "Organisation")
|
|
||||||
for field in ["address", "email", "notes", "phone"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "Payment")
|
|
||||||
for field in ["method"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "Person")
|
|
||||||
for field in ["address", "email", "notes", "phone"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "Profile")
|
|
||||||
for field in ["phone"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "RiskAssessment")
|
|
||||||
for field in ["general_notes", "persons_responsible_structures", "power_notes", "rigging_plan", "sound_notes"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
model = apps.get_model("RIGS", "Venue")
|
|
||||||
for field in ["address", "email", "notes", "phone"]:
|
|
||||||
filter_param = {"{}__isnull".format(field): True}
|
|
||||||
update_param = {field: ""}
|
|
||||||
model.objects.filter(**filter_param).update(**update_param)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('RIGS', '0039_auto_20210123_1910'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(postgres_migration_prep, migrations.RunPython.noop)
|
|
||||||
]
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
# Generated by Django 3.1.7 on 2021-03-02 12:04
|
|
||||||
|
|
||||||
import RIGS.models
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('RIGS', '0040_auto_20210302_1148'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='event',
|
|
||||||
name='meet_info',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='event',
|
|
||||||
name='payment_method',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='event',
|
|
||||||
name='payment_received',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='profile',
|
|
||||||
name='dark_theme',
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='auth_request_to',
|
|
||||||
field=models.EmailField(blank=True, default='', max_length=254),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='collector',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=255, verbose_name='collected by'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='notes',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='purchase_order',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=255, verbose_name='PO'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventauthorisation',
|
|
||||||
name='account_code',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=50),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventauthorisation',
|
|
||||||
name='uni_id',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=10, verbose_name='University ID'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='extinguishers_location',
|
|
||||||
field=models.CharField(blank=True, default='', help_text='Location of fire extinguishers', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='hs_location',
|
|
||||||
field=models.CharField(blank=True, default='', help_text='Location of Safety Bag/Box', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='w1_description',
|
|
||||||
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='w2_description',
|
|
||||||
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='w3_description',
|
|
||||||
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventitem',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='organisation',
|
|
||||||
name='address',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='organisation',
|
|
||||||
name='email',
|
|
||||||
field=models.EmailField(blank=True, default='', max_length=254),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='organisation',
|
|
||||||
name='notes',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='organisation',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=15),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='payment',
|
|
||||||
name='method',
|
|
||||||
field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('SU', 'SU Core'), ('T', 'TEC Adjustment')], default='', max_length=2),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='person',
|
|
||||||
name='address',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='person',
|
|
||||||
name='email',
|
|
||||||
field=models.EmailField(blank=True, default='', max_length=254),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='person',
|
|
||||||
name='notes',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='person',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=15),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='profile',
|
|
||||||
name='api_key',
|
|
||||||
field=models.CharField(blank=True, default='', editable=False, max_length=40),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='profile',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=13),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='riskassessment',
|
|
||||||
name='general_notes',
|
|
||||||
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='riskassessment',
|
|
||||||
name='persons_responsible_structures',
|
|
||||||
field=models.TextField(blank=True, default='', help_text='Who are the persons on site responsible for their use?'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='riskassessment',
|
|
||||||
name='power_notes',
|
|
||||||
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='riskassessment',
|
|
||||||
name='power_plan',
|
|
||||||
field=models.URLField(blank=True, default='', help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[RIGS.models.validate_url]),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='riskassessment',
|
|
||||||
name='rigging_plan',
|
|
||||||
field=models.URLField(blank=True, default='', help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[RIGS.models.validate_url]),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='riskassessment',
|
|
||||||
name='sound_notes',
|
|
||||||
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='venue',
|
|
||||||
name='address',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='venue',
|
|
||||||
name='email',
|
|
||||||
field=models.EmailField(blank=True, default='', max_length=254),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='venue',
|
|
||||||
name='notes',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='venue',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=15),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Generated by Django 3.1.13 on 2021-10-07 22:38
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('RIGS', '0041_auto_20210302_1204'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='fd_earth_fault',
|
|
||||||
field=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'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='w1_earth_fault',
|
|
||||||
field=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'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='w2_earth_fault',
|
|
||||||
field=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'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventchecklist',
|
|
||||||
name='w3_earth_fault',
|
|
||||||
field=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'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# 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),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
208
RIGS/models.py
@@ -1,34 +1,34 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import random
|
|
||||||
import string
|
|
||||||
from collections import Counter
|
|
||||||
from decimal import Decimal
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
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
|
||||||
|
import string
|
||||||
|
|
||||||
|
import random
|
||||||
|
from collections import Counter
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
class Profile(AbstractUser):
|
class Profile(AbstractUser):
|
||||||
initials = models.CharField(max_length=5, null=True, blank=False)
|
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
||||||
phone = models.CharField(max_length=13, blank=True, default='')
|
phone = models.CharField(max_length=13, null=True, blank=True)
|
||||||
api_key = models.CharField(max_length=40, blank=True, editable=False, default='')
|
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
|
||||||
is_approved = models.BooleanField(default=False)
|
is_approved = models.BooleanField(default=False)
|
||||||
# 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,
|
||||||
last_emailed = models.DateTimeField(blank=True, null=True)
|
null=True) # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
|
||||||
dark_theme = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
reversion_hide = True
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_api_key(cls):
|
def make_api_key(cls):
|
||||||
@@ -54,7 +54,7 @@ class Profile(AbstractUser):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_events(self):
|
def latest_events(self):
|
||||||
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic', 'riskassessment', 'invoice').prefetch_related('checklists')
|
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def admins(cls):
|
def admins(cls):
|
||||||
@@ -67,6 +67,8 @@ class Profile(AbstractUser):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
# TODO move to versioning - currently get import errors with that
|
||||||
|
|
||||||
|
|
||||||
class RevisionMixin(object):
|
class RevisionMixin(object):
|
||||||
@property
|
@property
|
||||||
@@ -103,12 +105,12 @@ class RevisionMixin(object):
|
|||||||
|
|
||||||
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, null=True)
|
||||||
email = models.EmailField(blank=True, default='')
|
email = models.EmailField(blank=True, null=True)
|
||||||
|
|
||||||
address = models.TextField(blank=True, default='')
|
address = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
notes = models.TextField(blank=True, default='')
|
notes = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
string = self.name
|
string = self.name
|
||||||
@@ -134,17 +136,17 @@ class Person(models.Model, RevisionMixin):
|
|||||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('person_detail', kwargs={'pk': self.pk})
|
return reverse_lazy('person_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class Organisation(models.Model, RevisionMixin):
|
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, null=True)
|
||||||
email = models.EmailField(blank=True, default='')
|
email = models.EmailField(blank=True, null=True)
|
||||||
|
|
||||||
address = models.TextField(blank=True, default='')
|
address = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
notes = models.TextField(blank=True, default='')
|
notes = models.TextField(blank=True, null=True)
|
||||||
union_account = models.BooleanField(default=False)
|
union_account = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -171,7 +173,7 @@ class Organisation(models.Model, RevisionMixin):
|
|||||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('organisation_detail', kwargs={'pk': self.pk})
|
return reverse_lazy('organisation_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class VatManager(models.Manager):
|
class VatManager(models.Manager):
|
||||||
@@ -179,6 +181,7 @@ class VatManager(models.Manager):
|
|||||||
return self.find_rate(timezone.now())
|
return self.find_rate(timezone.now())
|
||||||
|
|
||||||
def find_rate(self, date):
|
def find_rate(self, date):
|
||||||
|
# return self.filter(startAt__lte=date).latest()
|
||||||
try:
|
try:
|
||||||
return self.filter(start_at__lte=date).latest()
|
return self.filter(start_at__lte=date).latest()
|
||||||
except VatRate.DoesNotExist:
|
except VatRate.DoesNotExist:
|
||||||
@@ -211,12 +214,12 @@ class VatRate(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
class Venue(models.Model, RevisionMixin):
|
class Venue(models.Model, RevisionMixin):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
phone = models.CharField(max_length=15, blank=True, default='')
|
phone = models.CharField(max_length=15, blank=True, null=True)
|
||||||
email = models.EmailField(blank=True, default='')
|
email = models.EmailField(blank=True, null=True)
|
||||||
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, null=True)
|
||||||
|
|
||||||
address = models.TextField(blank=True, default='')
|
address = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
string = self.name
|
string = self.name
|
||||||
@@ -229,23 +232,24 @@ class Venue(models.Model, RevisionMixin):
|
|||||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('venue_detail', kwargs={'pk': self.pk})
|
return reverse_lazy('venue_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class EventManager(models.Manager):
|
class EventManager(models.Manager):
|
||||||
def current_events(self):
|
def current_events(self):
|
||||||
events = self.filter(
|
events = self.filter(
|
||||||
(models.Q(start_date__gte=timezone.now(), end_date__isnull=True, dry_hire=False) & ~models.Q(
|
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Starts after with no end
|
status=Event.CANCELLED)) | # Starts after with no end
|
||||||
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(
|
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Ends after
|
status=Event.CANCELLED)) | # Ends after
|
||||||
(models.Q(dry_hire=True, start_date__gte=timezone.now()) & ~models.Q(
|
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Active dry hire
|
status=Event.CANCELLED)) | # Active dry hire
|
||||||
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
|
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
|
||||||
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
||||||
models.Q(status=Event.CANCELLED, start_date__gte=timezone.now()) # Canceled but not started
|
models.Q(status=Event.CANCELLED, start_date__gte=timezone.now().date()) # Canceled but not started
|
||||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
|
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
||||||
|
'organisation',
|
||||||
|
'venue', 'mic')
|
||||||
return events
|
return events
|
||||||
|
|
||||||
def events_in_bounds(self, start, end):
|
def events_in_bounds(self, start, end):
|
||||||
@@ -268,29 +272,16 @@ class EventManager(models.Manager):
|
|||||||
|
|
||||||
def rig_count(self):
|
def rig_count(self):
|
||||||
event_count = self.filter(
|
event_count = self.filter(
|
||||||
(models.Q(start_date__gte=timezone.now(), end_date__isnull=True, dry_hire=False,
|
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False,
|
||||||
is_rig=True) & ~models.Q(
|
is_rig=True) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Starts after with no end
|
status=Event.CANCELLED)) | # Starts after with no end
|
||||||
(models.Q(end_date__gte=timezone.now(), dry_hire=False, is_rig=True) & ~models.Q(
|
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Ends after
|
status=Event.CANCELLED)) | # Ends after
|
||||||
(models.Q(dry_hire=True, start_date__gte=timezone.now(), is_rig=True) & ~models.Q(
|
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q(
|
||||||
status=Event.CANCELLED)) # Active dry hire
|
status=Event.CANCELLED)) # Active dry hire
|
||||||
).count()
|
).count()
|
||||||
return event_count
|
return event_count
|
||||||
|
|
||||||
def waiting_invoices(self):
|
|
||||||
events = self.filter(
|
|
||||||
(
|
|
||||||
models.Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
|
|
||||||
models.Q(end_date__lte=datetime.date.today()) # Or has end date, finishes before
|
|
||||||
) & models.Q(invoice__isnull=True) & # Has not already been invoiced
|
|
||||||
models.Q(is_rig=True) # Is a rig (not non-rig)
|
|
||||||
).order_by('start_date') \
|
|
||||||
.select_related('person', 'organisation', 'venue', 'mic') \
|
|
||||||
.prefetch_related('items')
|
|
||||||
|
|
||||||
return events
|
|
||||||
|
|
||||||
|
|
||||||
@reversion.register(follow=['items'])
|
@reversion.register(follow=['items'])
|
||||||
class Event(models.Model, RevisionMixin):
|
class Event(models.Model, RevisionMixin):
|
||||||
@@ -310,8 +301,8 @@ class Event(models.Model, RevisionMixin):
|
|||||||
person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE)
|
person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE)
|
||||||
organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE)
|
organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE)
|
||||||
venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE)
|
venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE)
|
||||||
description = models.TextField(blank=True, default='')
|
description = models.TextField(blank=True, null=True)
|
||||||
notes = models.TextField(blank=True, default='')
|
notes = models.TextField(blank=True, null=True)
|
||||||
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
||||||
dry_hire = models.BooleanField(default=False)
|
dry_hire = models.BooleanField(default=False)
|
||||||
is_rig = models.BooleanField(default=True)
|
is_rig = models.BooleanField(default=True)
|
||||||
@@ -325,6 +316,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
end_time = models.TimeField(blank=True, null=True)
|
end_time = models.TimeField(blank=True, null=True)
|
||||||
access_at = models.DateTimeField(blank=True, null=True)
|
access_at = models.DateTimeField(blank=True, null=True)
|
||||||
meet_at = models.DateTimeField(blank=True, null=True)
|
meet_at = models.DateTimeField(blank=True, null=True)
|
||||||
|
meet_info = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
|
||||||
# Crew management
|
# Crew management
|
||||||
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True,
|
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True,
|
||||||
@@ -333,23 +325,22 @@ class Event(models.Model, RevisionMixin):
|
|||||||
verbose_name="MIC", on_delete=models.CASCADE)
|
verbose_name="MIC", on_delete=models.CASCADE)
|
||||||
|
|
||||||
# Monies
|
# Monies
|
||||||
purchase_order = models.CharField(max_length=255, blank=True, default='', verbose_name='PO')
|
payment_method = models.CharField(max_length=255, blank=True, null=True)
|
||||||
collector = models.CharField(max_length=255, blank=True, default='', verbose_name='collected by')
|
payment_received = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO')
|
||||||
|
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
|
||||||
|
|
||||||
# Authorisation request details
|
# Authorisation request details
|
||||||
auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
|
auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
|
||||||
auth_request_at = models.DateTimeField(null=True, blank=True)
|
auth_request_at = models.DateTimeField(null=True, blank=True)
|
||||||
auth_request_to = models.EmailField(blank=True, default='')
|
auth_request_to = models.EmailField(null=True, blank=True)
|
||||||
|
|
||||||
@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 str("N%05d" % self.pk)
|
|
||||||
else:
|
|
||||||
return self.pk
|
|
||||||
else:
|
else:
|
||||||
return "????"
|
return self.pk
|
||||||
|
|
||||||
# Calculated values
|
# Calculated values
|
||||||
"""
|
"""
|
||||||
@@ -358,7 +349,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def sum_total(self):
|
def sum_total(self):
|
||||||
total = self.items.aggregate(
|
total = EventItem.objects.filter(event=self).aggregate(
|
||||||
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
||||||
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||||
)['sum_total']
|
)['sum_total']
|
||||||
@@ -372,9 +363,6 @@ class Event(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def vat(self):
|
def vat(self):
|
||||||
# No VAT is owed on internal transfers
|
|
||||||
if self.internal:
|
|
||||||
return 0
|
|
||||||
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01'))
|
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01'))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -471,7 +459,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
objects = EventManager()
|
objects = EventManager()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('event_detail', kwargs={'pk': self.pk})
|
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}: {}".format(self.display_id, self.name)
|
return "{}: {}".format(self.display_id, self.name)
|
||||||
@@ -505,7 +493,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
class EventItem(models.Model, RevisionMixin):
|
class EventItem(models.Model, RevisionMixin):
|
||||||
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
|
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
description = models.TextField(blank=True, default='')
|
description = models.TextField(blank=True, null=True)
|
||||||
quantity = models.IntegerField()
|
quantity = models.IntegerField()
|
||||||
cost = models.DecimalField(max_digits=10, decimal_places=2)
|
cost = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
order = models.IntegerField()
|
order = models.IntegerField()
|
||||||
@@ -520,7 +508,7 @@ 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 str(self.event.pk) + "." + str(self.order) + ": " + self.event.name + " | " + self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
@@ -532,36 +520,19 @@ class EventAuthorisation(models.Model, RevisionMixin):
|
|||||||
event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE)
|
event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE)
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
uni_id = models.CharField(max_length=10, blank=True, default='', verbose_name="University ID")
|
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
|
||||||
account_code = models.CharField(max_length=50, default='', blank=True)
|
account_code = models.CharField(max_length=50, blank=True, null=True)
|
||||||
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
|
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
|
||||||
sent_by = models.ForeignKey('Profile', on_delete=models.CASCADE)
|
sent_by = models.ForeignKey('Profile', on_delete=models.CASCADE)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('event_detail', kwargs={'pk': self.event_id})
|
return reverse_lazy('event_detail', kwargs={'pk': self.event.pk})
|
||||||
|
|
||||||
@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 "{} (requested by {})".format(self.event.display_id, self.sent_by.initials)
|
||||||
|
|
||||||
|
|
||||||
class InvoiceManager(models.Manager):
|
|
||||||
def outstanding_invoices(self):
|
|
||||||
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
|
|
||||||
sql = "SELECT * FROM " \
|
|
||||||
"(SELECT " \
|
|
||||||
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
|
|
||||||
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
|
|
||||||
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
|
|
||||||
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
|
|
||||||
"AS sub " \
|
|
||||||
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
|
|
||||||
"ORDER BY invoice_date"
|
|
||||||
|
|
||||||
query = self.raw(sql)
|
|
||||||
return query
|
|
||||||
|
|
||||||
|
|
||||||
@reversion.register(follow=['payment_set'])
|
@reversion.register(follow=['payment_set'])
|
||||||
class Invoice(models.Model, RevisionMixin):
|
class Invoice(models.Model, RevisionMixin):
|
||||||
event = models.OneToOneField('Event', on_delete=models.CASCADE)
|
event = models.OneToOneField('Event', on_delete=models.CASCADE)
|
||||||
@@ -570,8 +541,6 @@ class Invoice(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
reversion_perm = 'RIGS.view_invoice'
|
reversion_perm = 'RIGS.view_invoice'
|
||||||
|
|
||||||
objects = InvoiceManager()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sum_total(self):
|
def sum_total(self):
|
||||||
return self.event.sum_total
|
return self.event.sum_total
|
||||||
@@ -596,11 +565,11 @@ class Invoice(models.Model, RevisionMixin):
|
|||||||
return self.balance == 0 or self.void
|
return self.balance == 0 or self.void
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('invoice_detail', kwargs={'pk': self.pk})
|
return reverse_lazy('invoice_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return "#{} for Event {}".format(self.display_id, self.event.display_id)
|
return "#{} for Event {}".format(self.display_id, "N%05d" % self.event.pk)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
||||||
@@ -631,7 +600,7 @@ class Payment(models.Model, RevisionMixin):
|
|||||||
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
|
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
|
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
|
||||||
method = models.CharField(max_length=2, choices=METHODS, default='', blank=True)
|
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)
|
||||||
|
|
||||||
reversion_hide = True
|
reversion_hide = True
|
||||||
|
|
||||||
@@ -666,9 +635,10 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
|
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
|
||||||
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
|
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
|
||||||
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
|
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
|
||||||
general_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
general_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||||
|
|
||||||
# Power
|
# Power
|
||||||
|
# event_size = models.IntegerField(blank=True, null=True, choices=SIZES)
|
||||||
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
|
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...
|
# 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,
|
||||||
@@ -678,12 +648,12 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
|
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
|
||||||
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
|
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
|
||||||
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
|
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
|
||||||
power_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
power_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||||
power_plan = models.URLField(blank=True, default='', help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
power_plan = models.URLField(blank=True, null=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
||||||
|
|
||||||
# Sound
|
# Sound
|
||||||
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
|
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
|
||||||
sound_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
sound_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||||
|
|
||||||
# Site
|
# Site
|
||||||
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
|
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
|
||||||
@@ -696,8 +666,8 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
# Structures
|
# Structures
|
||||||
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
|
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
|
||||||
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
|
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
|
||||||
persons_responsible_structures = models.TextField(blank=True, default='', help_text="Who are the persons on site responsible for their use?")
|
persons_responsible_structures = models.TextField(blank=True, null=True, help_text="Who are the persons on site responsible for their use?")
|
||||||
rigging_plan = models.URLField(blank=True, default='', help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
rigging_plan = models.URLField(blank=True, null=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
||||||
|
|
||||||
# Blimey that was a lot of options
|
# Blimey that was a lot of options
|
||||||
|
|
||||||
@@ -741,10 +711,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
|
||||||
@@ -760,7 +726,7 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
return str(self.event)
|
return str(self.event)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('ra_detail', kwargs={'pk': self.pk})
|
return reverse_lazy('ra_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%i - %s" % (self.pk, self.event)
|
return "%i - %s" % (self.pk, self.event)
|
||||||
@@ -783,8 +749,8 @@ class EventChecklist(models.Model, RevisionMixin):
|
|||||||
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
|
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
|
||||||
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
|
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
|
||||||
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
|
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
|
||||||
hs_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of Safety Bag/Box")
|
hs_location = models.CharField(blank=True, null=True, 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, null=True, max_length=255, help_text="Location of fire extinguishers")
|
||||||
|
|
||||||
# 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?")
|
||||||
@@ -802,21 +768,21 @@ 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.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||||
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
|
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
|
||||||
# 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, null=True, 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")
|
||||||
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.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||||
w2_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
|
w2_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
|
||||||
w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
w2_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")
|
||||||
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.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||||
w3_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
|
w3_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
|
||||||
w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
|
w3_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")
|
||||||
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.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
|
||||||
|
|
||||||
all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>")
|
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>")
|
||||||
@@ -833,16 +799,12 @@ class EventChecklist(models.Model, RevisionMixin):
|
|||||||
('review_eventchecklist', 'Can review Event Checklists')
|
('review_eventchecklist', 'Can review Event Checklists')
|
||||||
]
|
]
|
||||||
|
|
||||||
@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 activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return str(self.event)
|
return str(self.event)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('ec_detail', kwargs={'pk': self.pk})
|
return reverse_lazy('ec_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%i - %s" % (self.pk, self.event)
|
return "%i - %s" % (self.pk, self.event)
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
import copy
|
from io import BytesIO
|
||||||
import datetime
|
import urllib.request
|
||||||
import re
|
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
import premailer
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
import simplejson
|
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||||
from PyPDF2 import PdfFileMerger, PdfFileReader
|
from django.views import generic
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.staticfiles import finders
|
|
||||||
from django.core import signing
|
|
||||||
from django.core.exceptions import SuspiciousOperation
|
|
||||||
from django.core.mail import EmailMultiAlternatives
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.template import RequestContext
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import timezone
|
from django.core import signing
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.core.exceptions import SuspiciousOperation
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.contrib import messages
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import generic
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.utils import timezone
|
||||||
from z3c.rml import rml2pdf
|
from z3c.rml import rml2pdf
|
||||||
|
from PyPDF2 import PdfFileMerger, PdfFileReader
|
||||||
|
import simplejson
|
||||||
|
import premailer
|
||||||
|
|
||||||
from PyRIGS import decorators
|
|
||||||
from PyRIGS.views import OEmbedView, is_ajax
|
|
||||||
from RIGS import models, forms
|
from RIGS import models, forms
|
||||||
|
from PyRIGS import decorators
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
import copy
|
||||||
|
|
||||||
__author__ = 'ghost'
|
__author__ = 'ghost'
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ class RigboardIndex(generic.TemplateView):
|
|||||||
context = super(RigboardIndex, self).get_context_data(**kwargs)
|
context = super(RigboardIndex, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
# call out method to get current events
|
# call out method to get current events
|
||||||
context['events'] = models.Event.objects.current_events().select_related('riskassessment', 'invoice').prefetch_related('checklists')
|
context['events'] = models.Event.objects.current_events()
|
||||||
context['page_title'] = "Rigboard"
|
context['page_title'] = "Rigboard"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -60,24 +62,29 @@ class EventDetail(generic.DetailView):
|
|||||||
template_name = 'event_detail.html'
|
template_name = 'event_detail.html'
|
||||||
model = models.Event
|
model = models.Event
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(EventDetail, self).get_context_data(**kwargs)
|
class EventOembed(generic.View):
|
||||||
title = "{} | {}".format(self.object.display_id, self.object.name)
|
model = models.Event
|
||||||
if self.object.dry_hire:
|
|
||||||
title += " <span class='badge badge-secondary'>Dry Hire</span>"
|
def get(self, request, pk=None):
|
||||||
context['page_title'] = title
|
embed_url = reverse('event_embed', args=[pk])
|
||||||
return context
|
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
|
||||||
|
'version': '1.0',
|
||||||
|
'type': 'rich',
|
||||||
|
'height': '250'
|
||||||
|
}
|
||||||
|
|
||||||
|
json = simplejson.JSONEncoderForHTML().encode(data)
|
||||||
|
return HttpResponse(json, content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
class EventEmbed(EventDetail):
|
class EventEmbed(EventDetail):
|
||||||
template_name = 'event_embed.html'
|
template_name = 'event_embed.html'
|
||||||
|
|
||||||
|
|
||||||
class EventOEmbed(OEmbedView):
|
|
||||||
model = models.Event
|
|
||||||
url_name = 'event_embed'
|
|
||||||
|
|
||||||
|
|
||||||
class EventCreate(generic.CreateView):
|
class EventCreate(generic.CreateView):
|
||||||
model = models.Event
|
model = models.Event
|
||||||
form_class = forms.EventForm
|
form_class = forms.EventForm
|
||||||
@@ -151,10 +158,9 @@ class EventDuplicate(EventUpdate):
|
|||||||
# Clear checked in by if it's a dry hire
|
# Clear checked in by if it's a dry hire
|
||||||
if new.dry_hire is True:
|
if new.dry_hire is True:
|
||||||
new.checked_in_by = None
|
new.checked_in_by = None
|
||||||
new.collector = None
|
|
||||||
|
|
||||||
# Remove all the authorisation information from the new event
|
# Remove all the authorisation information from the new event
|
||||||
new.auth_request_to = ''
|
new.auth_request_to = None
|
||||||
new.auth_request_by = None
|
new.auth_request_by = None
|
||||||
new.auth_request_at = None
|
new.auth_request_at = None
|
||||||
|
|
||||||
@@ -182,9 +188,15 @@ class EventPrint(generic.View):
|
|||||||
|
|
||||||
context = {
|
context = {
|
||||||
'object': object,
|
'object': object,
|
||||||
|
'fonts': {
|
||||||
|
'opensans': {
|
||||||
|
'regular': 'static/fonts/OPENSANS-REGULAR.TTF',
|
||||||
|
'bold': 'static/fonts/OPENSANS-BOLD.TTF',
|
||||||
|
}
|
||||||
|
},
|
||||||
'quote': True,
|
'quote': True,
|
||||||
'current_user': request.user,
|
'current_user': request.user,
|
||||||
'filename': 'Event_{}_{}_{}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
|
'filename': 'Event {} {} {}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
|
||||||
}
|
}
|
||||||
|
|
||||||
rml = template.render(context)
|
rml = template.render(context)
|
||||||
@@ -275,7 +287,6 @@ class EventArchive(generic.ListView):
|
|||||||
class EventAuthorise(generic.UpdateView):
|
class EventAuthorise(generic.UpdateView):
|
||||||
template_name = 'eventauthorisation_form.html'
|
template_name = 'eventauthorisation_form.html'
|
||||||
success_template = 'eventauthorisation_success.html'
|
success_template = 'eventauthorisation_success.html'
|
||||||
preview = False
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.object = form.save()
|
self.object = form.save()
|
||||||
@@ -283,7 +294,7 @@ class EventAuthorise(generic.UpdateView):
|
|||||||
self.template_name = self.success_template
|
self.template_name = self.success_template
|
||||||
messages.add_message(self.request, messages.SUCCESS,
|
messages.add_message(self.request, messages.SUCCESS,
|
||||||
'Success! Your event has been authorised. ' +
|
'Success! Your event has been authorised. ' +
|
||||||
'You will also receive email confirmation to %s.' % self.object.email)
|
'You will also receive email confirmation to %s.' % (self.object.email))
|
||||||
return self.render_to_response(self.get_context_data())
|
return self.render_to_response(self.get_context_data())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -303,7 +314,6 @@ class EventAuthorise(generic.UpdateView):
|
|||||||
context['page_title'] = "{}: {}".format(self.event.display_id, self.event.name)
|
context['page_title'] = "{}: {}".format(self.event.display_id, self.event.name)
|
||||||
if self.event.dry_hire:
|
if self.event.dry_hire:
|
||||||
context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>'
|
context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>'
|
||||||
context['preview'] = self.preview
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
@@ -352,7 +362,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
|||||||
return self.get_object()
|
return self.get_object()
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
if is_ajax(self.request):
|
if self.request.is_ajax():
|
||||||
url = reverse_lazy('closemodal')
|
url = reverse_lazy('closemodal')
|
||||||
messages.info(self.request, "location.reload()")
|
messages.info(self.request, "location.reload()")
|
||||||
else:
|
else:
|
||||||
@@ -390,7 +400,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
|||||||
to=[email],
|
to=[email],
|
||||||
reply_to=[self.request.user.email],
|
reply_to=[self.request.user.email],
|
||||||
)
|
)
|
||||||
css = finders.find('css/email.css')
|
css = staticfiles_storage.path('css/email.css')
|
||||||
html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context),
|
html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context),
|
||||||
external_styles=css).transform()
|
external_styles=css).transform()
|
||||||
msg.attach_alternative(html, 'text/html')
|
msg.attach_alternative(html, 'text/html')
|
||||||
@@ -405,7 +415,8 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
|||||||
model = models.Event
|
model = models.Event
|
||||||
|
|
||||||
def render_to_response(self, context, **response_kwargs):
|
def render_to_response(self, context, **response_kwargs):
|
||||||
css = finders.find('css/email.css')
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
css = staticfiles_storage.path('css/email.css')
|
||||||
response = super(EventAuthoriseRequestEmailPreview, self).render_to_response(context, **response_kwargs)
|
response = super(EventAuthoriseRequestEmailPreview, self).render_to_response(context, **response_kwargs)
|
||||||
assert isinstance(response, HttpResponse)
|
assert isinstance(response, HttpResponse)
|
||||||
response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
|
response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
|
||||||
@@ -419,5 +430,4 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
|||||||
'sent_by': self.request.user.pk,
|
'sent_by': self.request.user.pk,
|
||||||
})
|
})
|
||||||
context['to_name'] = self.request.GET.get('to_name', None)
|
context['to_name'] = self.request.GET.get('to_name', None)
|
||||||
context['target'] = 'event_authorise_form_preview'
|
|
||||||
return context
|
return context
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
import urllib.request
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
from django.db.models.signals import post_save
|
||||||
from PyPDF2 import PdfFileReader, PdfFileMerger
|
from PyPDF2 import PdfFileReader, PdfFileMerger
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.staticfiles import finders
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
from django.core.cache import cache
|
|
||||||
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
||||||
from django.db.models.signals import post_save
|
from django.core.cache import cache
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from premailer import Premailer
|
|
||||||
from registration.signals import user_activated
|
from registration.signals import user_activated
|
||||||
from reversion import revisions as reversion
|
from premailer import Premailer
|
||||||
from z3c.rml import rml2pdf
|
from z3c.rml import rml2pdf
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
from reversion import revisions as reversion
|
||||||
|
|
||||||
|
|
||||||
def send_eventauthorisation_success_email(instance):
|
def send_eventauthorisation_success_email(instance):
|
||||||
# Generate PDF first to prevent context conflicts
|
# Generate PDF first to prevent context conflicts
|
||||||
context = {
|
context = {
|
||||||
'object': instance.event,
|
'object': instance.event,
|
||||||
|
'fonts': {
|
||||||
|
'opensans': {
|
||||||
|
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
||||||
|
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
||||||
|
}
|
||||||
|
},
|
||||||
'receipt': True,
|
'receipt': True,
|
||||||
'current_user': False,
|
'current_user': False,
|
||||||
}
|
}
|
||||||
@@ -63,7 +70,7 @@ def send_eventauthorisation_success_email(instance):
|
|||||||
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
||||||
)
|
)
|
||||||
|
|
||||||
css = finders.find('css/email.css')
|
css = staticfiles_storage.path('css/email.css')
|
||||||
html = Premailer(get_template("eventauthorisation_client_success.html").render(context),
|
html = Premailer(get_template("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')
|
||||||
@@ -121,7 +128,7 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
|
|||||||
to=[admin.email],
|
to=[admin.email],
|
||||||
reply_to=[user.email],
|
reply_to=[user.email],
|
||||||
)
|
)
|
||||||
css = finders.find('css/email.css')
|
css = staticfiles_storage.path('css/email.css')
|
||||||
html = Premailer(get_template("admin_awaiting_approval.html").render(context),
|
html = Premailer(get_template("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')
|
||||||
|
|||||||
28
RIGS/static/css/ajax-bootstrap-select.css
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*!
|
||||||
|
* Ajax Bootstrap Select
|
||||||
|
*
|
||||||
|
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
|
||||||
|
*
|
||||||
|
* @version 1.4.5
|
||||||
|
* @author Adam Heim - https://github.com/truckingsim
|
||||||
|
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||||
|
* @copyright 2019 Adam Heim
|
||||||
|
* @license Released under the MIT license.
|
||||||
|
*
|
||||||
|
* Contributors:
|
||||||
|
* Mark Carver - https://github.com/markcarver
|
||||||
|
*
|
||||||
|
* Last build: 2019-04-23 12:18:56 PM EDT
|
||||||
|
*/
|
||||||
|
.bootstrap-select .status {
|
||||||
|
background: #f0f0f0;
|
||||||
|
clear: both;
|
||||||
|
color: #999;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
padding: 10px 20px; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztFQWVFO0FBQ0Y7RUFDRSxtQkFBbUI7RUFDbkIsV0FBVztFQUNYLFdBQVc7RUFDWCxlQUFlO0VBQ2Ysa0JBQWtCO0VBQ2xCLGdCQUFnQjtFQUNoQixjQUFjO0VBQ2QsbUJBQW1CO0VBQ25CLGtCQUFrQixFQUFBIiwiZmlsZSI6ImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIvKiFcbiAqIEFqYXggQm9vdHN0cmFwIFNlbGVjdFxuICpcbiAqIEV4dGVuZHMgZXhpc3RpbmcgW0Jvb3RzdHJhcCBTZWxlY3RdIGltcGxlbWVudGF0aW9ucyBieSBhZGRpbmcgdGhlIGFiaWxpdHkgdG8gc2VhcmNoIHZpYSBBSkFYIHJlcXVlc3RzIGFzIHlvdSB0eXBlLiBPcmlnaW5hbGx5IGZvciBDUk9TQ09OLlxuICpcbiAqIEB2ZXJzaW9uIDEuNC41XG4gKiBAYXV0aG9yIEFkYW0gSGVpbSAtIGh0dHBzOi8vZ2l0aHViLmNvbS90cnVja2luZ3NpbVxuICogQGxpbmsgaHR0cHM6Ly9naXRodWIuY29tL3RydWNraW5nc2ltL0FqYXgtQm9vdHN0cmFwLVNlbGVjdFxuICogQGNvcHlyaWdodCAyMDE5IEFkYW0gSGVpbVxuICogQGxpY2Vuc2UgUmVsZWFzZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlLlxuICpcbiAqIENvbnRyaWJ1dG9yczpcbiAqICAgTWFyayBDYXJ2ZXIgLSBodHRwczovL2dpdGh1Yi5jb20vbWFya2NhcnZlclxuICpcbiAqIExhc3QgYnVpbGQ6IDIwMTktMDQtMjMgMTI6MTg6NTYgUE0gRURUXG4gKi9cbi5ib290c3RyYXAtc2VsZWN0IC5zdGF0dXMge1xuICBiYWNrZ3JvdW5kOiAjZjBmMGYwO1xuICBjbGVhcjogYm90aDtcbiAgY29sb3I6ICM5OTk7XG4gIGZvbnQtc2l6ZTogMTFweDtcbiAgZm9udC1zdHlsZTogaXRhbGljO1xuICBmb250LXdlaWdodDogNTAwO1xuICBsaW5lLWhlaWdodDogMTtcbiAgbWFyZ2luLWJvdHRvbTogLTVweDtcbiAgcGFkZGluZzogMTBweCAyMHB4O1xufVxuIl19 */
|
||||||
28
RIGS/static/css/ajax-bootstrap-select.min.css
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*!
|
||||||
|
* Ajax Bootstrap Select
|
||||||
|
*
|
||||||
|
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
|
||||||
|
*
|
||||||
|
* @version 1.4.5
|
||||||
|
* @author Adam Heim - https://github.com/truckingsim
|
||||||
|
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||||
|
* @copyright 2019 Adam Heim
|
||||||
|
* @license Released under the MIT license.
|
||||||
|
*
|
||||||
|
* Contributors:
|
||||||
|
* Mark Carver - https://github.com/markcarver
|
||||||
|
*
|
||||||
|
* Last build: 2019-04-23 12:18:56 PM EDT
|
||||||
|
*/
|
||||||
|
.bootstrap-select .status {
|
||||||
|
background: #f0f0f0;
|
||||||
|
clear: both;
|
||||||
|
color: #999;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
padding: 10px 20px; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5taW4uY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7RUFlRTtBQUFDO0VBQTBCLG1CQUFrQjtFQUFDLFdBQVU7RUFBQyxXQUFVO0VBQUMsZUFBYztFQUFDLGtCQUFpQjtFQUFDLGdCQUFlO0VBQUMsY0FBYTtFQUFDLG1CQUFrQjtFQUFDLGtCQUFpQixFQUFBIiwiZmlsZSI6ImFqYXgtYm9vdHN0cmFwLXNlbGVjdC5taW4uY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLyohXG4gKiBBamF4IEJvb3RzdHJhcCBTZWxlY3RcbiAqXG4gKiBFeHRlbmRzIGV4aXN0aW5nIFtCb290c3RyYXAgU2VsZWN0XSBpbXBsZW1lbnRhdGlvbnMgYnkgYWRkaW5nIHRoZSBhYmlsaXR5IHRvIHNlYXJjaCB2aWEgQUpBWCByZXF1ZXN0cyBhcyB5b3UgdHlwZS4gT3JpZ2luYWxseSBmb3IgQ1JPU0NPTi5cbiAqXG4gKiBAdmVyc2lvbiAxLjQuNVxuICogQGF1dGhvciBBZGFtIEhlaW0gLSBodHRwczovL2dpdGh1Yi5jb20vdHJ1Y2tpbmdzaW1cbiAqIEBsaW5rIGh0dHBzOi8vZ2l0aHViLmNvbS90cnVja2luZ3NpbS9BamF4LUJvb3RzdHJhcC1TZWxlY3RcbiAqIEBjb3B5cmlnaHQgMjAxOSBBZGFtIEhlaW1cbiAqIEBsaWNlbnNlIFJlbGVhc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZS5cbiAqXG4gKiBDb250cmlidXRvcnM6XG4gKiAgIE1hcmsgQ2FydmVyIC0gaHR0cHM6Ly9naXRodWIuY29tL21hcmtjYXJ2ZXJcbiAqXG4gKiBMYXN0IGJ1aWxkOiAyMDE5LTA0LTIzIDEyOjE4OjU2IFBNIEVEVFxuICovLmJvb3RzdHJhcC1zZWxlY3QgLnN0YXR1c3tiYWNrZ3JvdW5kOiNmMGYwZjA7Y2xlYXI6Ym90aDtjb2xvcjojOTk5O2ZvbnQtc2l6ZToxMXB4O2ZvbnQtc3R5bGU6aXRhbGljO2ZvbnQtd2VpZ2h0OjUwMDtsaW5lLWhlaWdodDoxO21hcmdpbi1ib3R0b206LTVweDtwYWRkaW5nOjEwcHggMjBweH0iXX0= */
|
||||||
23
RIGS/static/css/autocomplete.css
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
.autocomplete {
|
||||||
|
background: white;
|
||||||
|
z-index: 1000;
|
||||||
|
font: 14px/22px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid rgba(50, 50, 50, 0.6); }
|
||||||
|
|
||||||
|
.autocomplete * {
|
||||||
|
font: inherit; }
|
||||||
|
|
||||||
|
.autocomplete > div {
|
||||||
|
padding: 0 4px; }
|
||||||
|
|
||||||
|
.autocomplete .group {
|
||||||
|
background: #eee; }
|
||||||
|
|
||||||
|
.autocomplete > div:hover:not(.group),
|
||||||
|
.autocomplete > div.selected {
|
||||||
|
background: #81ca91;
|
||||||
|
cursor: pointer; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImF1dG9jb21wbGV0ZS5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0E7RUFDSSxpQkFBaUI7RUFDakIsYUFBYTtFQUNiLDRHQUE0RztFQUM1RyxjQUFjO0VBQ2Qsc0JBQXNCO0VBQ3RCLHVDQUF1QyxFQUFBOztBQUczQztFQUNJLGFBQWEsRUFBQTs7QUFHakI7RUFDSSxjQUFjLEVBQUE7O0FBR2xCO0VBQ0ksZ0JBQWdCLEVBQUE7O0FBR3BCOztFQUVJLG1CQUFtQjtFQUNuQixlQUFlLEVBQUEiLCJmaWxlIjoiYXV0b2NvbXBsZXRlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIlxyXG4uYXV0b2NvbXBsZXRlIHtcclxuICAgIGJhY2tncm91bmQ6IHdoaXRlO1xyXG4gICAgei1pbmRleDogMTAwMDtcclxuICAgIGZvbnQ6IDE0cHgvMjJweCBcIi1hcHBsZS1zeXN0ZW1cIiwgQmxpbmtNYWNTeXN0ZW1Gb250LCBcIlNlZ29lIFVJXCIsIFJvYm90bywgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBBcmlhbCwgc2Fucy1zZXJpZjtcclxuICAgIG92ZXJmbG93OiBhdXRvO1xyXG4gICAgYm94LXNpemluZzogYm9yZGVyLWJveDtcclxuICAgIGJvcmRlcjogMXB4IHNvbGlkIHJnYmEoNTAsIDUwLCA1MCwgMC42KTtcclxufVxyXG5cclxuLmF1dG9jb21wbGV0ZSAqIHtcclxuICAgIGZvbnQ6IGluaGVyaXQ7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgPiBkaXYge1xyXG4gICAgcGFkZGluZzogMCA0cHg7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgLmdyb3VwIHtcclxuICAgIGJhY2tncm91bmQ6ICNlZWU7XHJcbn1cclxuXHJcbi5hdXRvY29tcGxldGUgPiBkaXY6aG92ZXI6bm90KC5ncm91cCksXHJcbi5hdXRvY29tcGxldGUgPiBkaXYuc2VsZWN0ZWQge1xyXG4gICAgYmFja2dyb3VuZDogIzgxY2E5MTtcclxuICAgIGN1cnNvcjogcG9pbnRlcjtcclxufVxyXG5cclxuIl19 */
|
||||||
248
RIGS/static/css/bootstrap-datetimepicker.min.css
vendored
Normal file
455
RIGS/static/css/bootstrap-select.css
vendored
Normal file
6650
RIGS/static/css/dark_screen.css
Normal file
39
RIGS/static/css/email.css
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
body {
|
||||||
|
margin: 0px; }
|
||||||
|
|
||||||
|
.main-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse; }
|
||||||
|
|
||||||
|
.client-header {
|
||||||
|
background-image: url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");
|
||||||
|
background-color: #222;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 28px; }
|
||||||
|
.client-header .logos {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 640px; }
|
||||||
|
.client-header img {
|
||||||
|
height: 110px; }
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
width: 100%; }
|
||||||
|
.content-container .content {
|
||||||
|
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left; }
|
||||||
|
.content-container .content .button-container {
|
||||||
|
width: 100%; }
|
||||||
|
.content-container .content .button-container .button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: #357ebf;
|
||||||
|
border-radius: 4px; }
|
||||||
|
.content-container .content .button-container .button a {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImVtYWlsLnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7RUFDRSxXQUFXLEVBQUE7O0FBR2I7RUFDRSxXQUFXO0VBQ1gseUJBQXlCLEVBQUE7O0FBSTNCO0VBQ0UsMkVBQTJFO0VBQzNFLHNCQUFzQjtFQUN0Qiw0QkFBNEI7RUFDNUIsMkJBQTJCO0VBRTNCLFdBQVc7RUFFWCxtQkFBbUIsRUFBQTtFQVJyQjtJQVdJLFdBQVc7SUFDWCxnQkFBZ0IsRUFBQTtFQVpwQjtJQWdCSSxhQUFhLEVBQUE7O0FBSWpCO0VBQ0UsV0FBVyxFQUFBO0VBRGI7SUFJSSx3RUFBd0U7SUFFeEUsV0FBVztJQUNYLGdCQUFnQjtJQUNoQixhQUFhO0lBQ2IsZ0JBQWdCLEVBQUE7SUFUcEI7TUFZTSxXQUFXLEVBQUE7TUFaakI7UUFlUSxpQkFBaUI7UUFDakIseUJBaERjO1FBaURkLGtCQUFrQixFQUFBO1FBakIxQjtVQW9CVSxXQUFXO1VBQ1gscUJBQXFCLEVBQUEiLCJmaWxlIjoiZW1haWwuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiJGJ1dHRvbl9jb2xvcjogIzM1N2ViZjtcblxuYm9keXtcbiAgbWFyZ2luOiAwcHg7XG59XG5cbi5tYWluLXRhYmxle1xuICB3aWR0aDogMTAwJTtcbiAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTtcblxufVxuXG4uY2xpZW50LWhlYWRlciB7XG4gIGJhY2tncm91bmQtaW1hZ2U6IHVybChcImh0dHBzOi8vd3d3Lm5vdHRpbmdoYW10ZWMuY28udWsvaW1ncy93b2YyMDE0LTEuanBnXCIpO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMjIyO1xuICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0O1xuICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiBjZW50ZXI7XG5cbiAgd2lkdGg6IDEwMCU7XG5cbiAgbWFyZ2luLWJvdHRvbTogMjhweDtcblxuICAubG9nb3N7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgbWF4LXdpZHRoOiA2NDBweDtcbiAgfVxuXG4gIGltZyB7XG4gICAgaGVpZ2h0OiAxMTBweDtcbiAgfVxufVxuXG4uY29udGVudC1jb250YWluZXJ7XG4gIHdpZHRoOiAxMDAlO1xuXG4gIC5jb250ZW50IHtcbiAgICBmb250LWZhbWlseTogXCJPcGVuIFNhbnNcIiwgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmO1xuXG4gICAgd2lkdGg6IDEwMCU7XG4gICAgbWF4LXdpZHRoOiA2MDBweDtcbiAgICBwYWRkaW5nOiAxMHB4O1xuICAgIHRleHQtYWxpZ246IGxlZnQ7XG5cbiAgICAuYnV0dG9uLWNvbnRhaW5lcntcbiAgICAgIHdpZHRoOiAxMDAlO1xuXG4gICAgICAuYnV0dG9uIHtcbiAgICAgICAgcGFkZGluZzogNnB4IDEycHg7XG4gICAgICAgIGJhY2tncm91bmQtY29sb3I6ICRidXR0b25fY29sb3I7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDRweDtcblxuICAgICAgICBhIHtcbiAgICAgICAgICBjb2xvcjogI2ZmZjtcbiAgICAgICAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gICAgICAgIH1cblxuICAgICAgfVxuXG4gICAgfVxuXG4gIH1cbn1cblxuIl19 */
|
||||||
788
RIGS/static/css/flatpickr.css
Normal file
1282
RIGS/static/css/fullcalendar.css
Normal file
178
RIGS/static/css/fullcalendar.print.css
Normal file
2
RIGS/static/css/ie.css
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJpZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6W119 */
|
||||||
269
RIGS/static/css/main.css
Normal file
1047
RIGS/static/css/main.min.css
vendored
Normal file
7223
RIGS/static/css/print.css
Normal file
14066
RIGS/static/css/screen.css
Normal file
BIN
RIGS/static/fonts/OPENSANS-BOLDITALIC.TTF
Normal file
BIN
RIGS/static/fonts/OPENSANS-ITALIC.TTF
Normal file
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.3 MiB |
|
Before Width: | Height: | Size: 852 KiB |
|
Before Width: | Height: | Size: 164 KiB |
22
RIGS/static/js/ajax-bootstrap-select.js
Normal file
6
RIGS/static/js/alert.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap alert.js v4.5.2 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/
|
||||||
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("jquery"),require("./util.js")):"function"==typeof define&&define.amd?define(["jquery","./util.js"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).Alert=t(e.jQuery,e.Util)}(this,(function(e,t){"use strict";function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e,t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t;var r=e.fn.alert,o=function(){function r(e){this._element=e}var o,l,i,a=r.prototype;return a.close=function(e){var t=this._element;e&&(t=this._getRootElement(e)),this._triggerCloseEvent(t).isDefaultPrevented()||this._removeElement(t)},a.dispose=function(){e.removeData(this._element,"bs.alert"),this._element=null},a._getRootElement=function(n){var r=t.getSelectorFromElement(n),o=!1;return r&&(o=document.querySelector(r)),o||(o=e(n).closest(".alert")[0]),o},a._triggerCloseEvent=function(t){var n=e.Event("close.bs.alert");return e(t).trigger(n),n},a._removeElement=function(n){var r=this;if(e(n).removeClass("show"),e(n).hasClass("fade")){var o=t.getTransitionDurationFromElement(n);e(n).one(t.TRANSITION_END,(function(e){return r._destroyElement(n,e)})).emulateTransitionEnd(o)}else this._destroyElement(n)},a._destroyElement=function(t){e(t).detach().trigger("closed.bs.alert").remove()},r._jQueryInterface=function(t){return this.each((function(){var n=e(this),o=n.data("bs.alert");o||(o=new r(this),n.data("bs.alert",o)),"close"===t&&o[t](this)}))},r._handleDismiss=function(e){return function(t){t&&t.preventDefault(),e.close(this)}},o=r,i=[{key:"VERSION",get:function(){return"4.5.2"}}],(l=null)&&n(o.prototype,l),i&&n(o,i),r}();return e(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',o._handleDismiss(new o)),e.fn.alert=o._jQueryInterface,e.fn.alert.Constructor=o,e.fn.alert.noConflict=function(){return e.fn.alert=r,o._jQueryInterface},o}));
|
||||||
5
RIGS/static/js/all.js
Normal file
1
RIGS/static/js/asteroids.min.js
vendored
Normal file
1
RIGS/static/js/autocompleter.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
function changeSelectedValue(e,t,a,r){e.find("option").remove(),e.append($("<option></option>").attr("value",t).text(a).data("update_url",r)),e.selectpicker("render"),e.selectpicker("refresh"),e.selectpicker("val",t),e.change()}function refreshUpdateHref(e){targetObject=$("#"+e.attr("id")+"-update"),update_url=$("option:selected",e).data("update_url"),""==update_url?(targetObject.removeAttr("href"),targetObject.addClass("disabled")):(targetObject.prop("href",update_url),targetObject.removeClass("disabled"))}function initPicker(e){var t={ajax:{url:e.data("sourceurl"),type:"GET",dataType:"json",data:{term:"{{{q}}}"}},locale:{emptyTitle:""},clearOnEmpty:!1,preprocessData:function(e){var t,a=e.length,r=[];if(r.push({text:clearSelectionLabel,value:"",data:{update_url:"",subtext:""}}),a)for(t=0;t<a;t++)r.push($.extend(!0,e[t],{text:e[t].label,value:e[t].pk,data:{update_url:e[t].update,subtext:""}}));return r}};e.prepend($("<option></option>").attr("value","").text(clearSelectionLabel).data("update_url","")),e.selectpicker().ajaxSelectPicker(t),e.change((function(){refreshUpdateHref(e)})),refreshUpdateHref(e)}$(document).ready((function(){clearSelectionLabel="(no selection)",$(".selectpicker").each((function(){initPicker($(this))})),$("#modal").on("hide.bs.modal",(function(e){null!=modaltarget&&""!=modalobject&&changeSelectedValue($(modaltarget),modalobject[0].pk,modalobject[0].fields.name,modalobject[0].update_url)}))}));
|
||||||
1
RIGS/static/js/bootstrap-datetimepicker.js
vendored
Normal file
7
RIGS/static/js/bootstrap-select.js
vendored
Normal file
7
RIGS/static/js/clipboard.min.js
vendored
Normal file
6
RIGS/static/js/collapse.js
Normal file
1
RIGS/static/js/dark-mode-switch.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!function(){var t,e=document.getElementById("darkSwitch");e&&(t=null!==localStorage.getItem("darkSwitch")&&"dark"===localStorage.getItem("darkSwitch"),(e.checked=t)?document.body.setAttribute("data-theme","dark"):document.body.removeAttribute("data-theme"),e.addEventListener("change",(function(t){e.checked?(document.body.setAttribute("data-theme","dark"),localStorage.setItem("darkSwitch","dark")):(document.body.removeAttribute("data-theme"),localStorage.removeItem("darkSwitch"))})))}();
|
||||||
1
RIGS/static/js/datetime-fix.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
$(document).ready((function(){var t;(t=document.createElement("input")).setAttribute("type","datetime-local"),("text"===t.type||navigator.userAgent.toLowerCase().indexOf("firefox")>-1)&&($("<link>").appendTo("head").attr({type:"text/css",rel:"stylesheet"}).attr("href",'{% static "css/flatpickr.css" %}'),$.when($.getScript('{% static "js/flatpickr.min.js" %}'),$.Deferred((function(t){$(t.resolve)}))).done((function(){$("input[type=datetime-local]").attr("type","text").flatpickr({dateFormat:"Y-m-dTH:m",enableTime:!0,altInput:!0,altFormat:"d/m/y H:m"})})))}));
|
||||||
6
RIGS/static/js/dropdown.js
Normal file
2
RIGS/static/js/flatpickr.min.js
vendored
Normal file
6
RIGS/static/js/fullcalendar.js
Normal file
1
RIGS/static/js/interaction.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
function setupItemTable(t){objectitems=JSON.parse(t),$.each(objectitems,(function(t,e){objectitems[t]=JSON.parse(e)})),newitem=-1}function nl2br(t,e){return(t+"").replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g,"$1"+(e||void 0===e?"<br />":"<br>")+"$2")}function escapeHtml(t){return $("<div/>").text(t).html()}function updatePrices(){var t=0;for(var e in objectitems){var i=objectitems[e].fields,a=i.cost*i.quantity;$("#item-"+e+" .sub-total").html(parseFloat(a).toFixed(2)).data("subtotal",a),t+=Number(a)}$("#sumtotal").text(parseFloat(t).toFixed(2));var o=t*Number($("#vat-rate").data("rate"));$("#vat").text(parseFloat(o).toFixed(2)),$("#total").text(parseFloat(t+o).toFixed(2))}$("#item-table").on("click",".item-delete",(function(){delete objectitems[$(this).data("pk")],$("#item-"+$(this).data("pk")).remove(),updatePrices()})),$("#item-table").on("click",".item-add",(function(){$("#item-form").data("pk",newitem),$("#item_name").val(""),$("#item_description").val(""),$("#item_quantity").val(""),$("#item_cost").val(""),$($(this).data("target")).modal("show")})),$("#item-table").on("click",".item-edit",(function(){var t=$(this).data("pk");$("#item-form").data("pk",t);var e=objectitems[t].fields;$("#item_name").val(e.name),$("#item_description").val(e.description),$("#item_quantity").val(e.quantity),$("#item_cost").val(e.cost),$($(this).data("target")).modal("show")})),$("body").on("submit","#item-form",(function(t){t.preventDefault();var e,i=$(this).data("pk");if($("#itemModal").modal("hide"),i==newitem--){(e=new Object).name=$("#item_name").val(),e.description=$("#item_description").val(),e.cost=$("#item_cost").val(),e.quantity=$("#item_quantity").val();var a=0;for(item in objectitems)a++;e.order=a,objectitems[i]=new Object,objectitems[i].fields=e,$("#new-item-row").clone().attr("id","item-"+i).data("pk",i).appendTo("#item-table-body"),$("#item-"+i+" .item-delete, #item-"+i+" .item-edit").data("pk",i)}else(e=objectitems[i].fields).name=$("#item_name").val(),e.description=$("#item_description").val(),e.cost=$("#item_cost").val(),e.quantity=$("#item_quantity").val(),objectitems[i].fields=e;$row=$("#item-"+i),$row.find(".name").html(escapeHtml(e.name)),$row.find(".description").html(nl2br(escapeHtml(e.description))),$row.find(".cost").html(parseFloat(e.cost).toFixed(2)),$row.find(".quantity").html(e.quantity),updatePrices()})),$("body").on("submit",".itemised_form",(function(t){$("#id_items_json").val(JSON.stringify(objectitems))}));var fixHelper=function(t,e){return e.children().each((function(){$(this).width($(this).width())})),e};$("#item-table tbody").sortable({helper:fixHelper,update:function(t,e){info=$(this).sortable("toArray"),itemorder=new Array,$.each(info,(function(t,e){pk=$("#"+e).data("pk"),objectitems[pk].fields.order=t}))}});
|
||||||
186
RIGS/static/js/jquery-ui.js
vendored
Normal file
25
RIGS/static/js/jquery.js
vendored
Normal file
1
RIGS/static/js/konami.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
var Konami=function(t){var e={addEvent:function(t,e,n,o){t.addEventListener?t.addEventListener(e,n,!1):t.attachEvent&&(t["e"+e+n]=n,t[e+n]=function(){t["e"+e+n](window.event,o)},t.attachEvent("on"+e,t[e+n]))},removeEvent:function(t,e,n){t.removeEventListener?t.removeEventListener(e,n):t.attachEvent&&t.detachEvent(e)},input:"",pattern:"38384040373937396665",keydownHandler:function(t,n){if(n&&(e=n),e.input+=t?t.keyCode:event.keyCode,e.input.length>e.pattern.length&&(e.input=e.input.substr(e.input.length-e.pattern.length)),e.input===e.pattern)return e.code(e._currentLink),e.input="",t.preventDefault(),!1},load:function(t){this._currentLink=t,this.addEvent(document,"keydown",this.keydownHandler,this),this.iphone.load(t)},unload:function(){this.removeEvent(document,"keydown",this.keydownHandler),this.iphone.unload()},code:function(t){window.location=t},iphone:{start_x:0,start_y:0,stop_x:0,stop_y:0,tap:!1,capture:!1,orig_keys:"",keys:["UP","UP","DOWN","DOWN","LEFT","RIGHT","LEFT","RIGHT","TAP","TAP"],input:[],code:function(t){e.code(t)},touchmoveHandler:function(t){if(1===t.touches.length&&!0===e.iphone.capture){var n=t.touches[0];e.iphone.stop_x=n.pageX,e.iphone.stop_y=n.pageY,e.iphone.tap=!1,e.iphone.capture=!1,e.iphone.check_direction()}},touchendHandler:function(){if(e.iphone.input.push(e.iphone.check_direction()),e.iphone.input.length>e.iphone.keys.length&&e.iphone.input.shift(),e.iphone.input.length===e.iphone.keys.length){for(var t=!0,n=0;n<e.iphone.keys.length;n++)e.iphone.input[n]!==e.iphone.keys[n]&&(t=!1);t&&e.iphone.code(e._currentLink)}},touchstartHandler:function(t){e.iphone.start_x=t.changedTouches[0].pageX,e.iphone.start_y=t.changedTouches[0].pageY,e.iphone.tap=!0,e.iphone.capture=!0},load:function(t){this.orig_keys=this.keys,e.addEvent(document,"touchmove",this.touchmoveHandler),e.addEvent(document,"touchend",this.touchendHandler,!1),e.addEvent(document,"touchstart",this.touchstartHandler)},unload:function(){e.removeEvent(document,"touchmove",this.touchmoveHandler),e.removeEvent(document,"touchend",this.touchendHandler),e.removeEvent(document,"touchstart",this.touchstartHandler)},check_direction:function(){return x_magnitude=Math.abs(this.start_x-this.stop_x),y_magnitude=Math.abs(this.start_y-this.stop_y),x=this.start_x-this.stop_x<0?"RIGHT":"LEFT",y=this.start_y-this.stop_y<0?"DOWN":"UP",result=x_magnitude>y_magnitude?x:y,result=!0===this.tap?"TAP":result,result}}};return"string"==typeof t&&e.load(t),"function"==typeof t&&(e.code=t,e.load()),e};"undefined"!=typeof module&&void 0!==module.exports?module.exports=Konami:"function"==typeof define&&define.amd?define([],(function(){return Konami})):window.Konami=Konami;
|
||||||
6
RIGS/static/js/main.js
Normal file
6
RIGS/static/js/main.min.js
vendored
Normal file
6
RIGS/static/js/marked.min.js
vendored
6
RIGS/static/js/modal.js
Normal file
8
RIGS/static/js/moment.js
Normal file
6
RIGS/static/js/popover.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap popover.js v4.5.2 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/
|
||||||
|
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery"),require("./tooltip.js")):"function"==typeof define&&define.amd?define(["jquery","./tooltip.js"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).Popover=e(t.jQuery,t.Tooltip)}(this,(function(t,e){"use strict";function n(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function o(){return(o=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t[o]=n[o])}return t}).apply(this,arguments)}t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t,e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e;var r="popover",i=".bs.popover",s=t.fn[r],p=new RegExp("(^|\\s)bs-popover\\S+","g"),u=o({},e.Default,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'}),a=o({},e.DefaultType,{content:"(string|element|function)"}),l={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},c=function(e){var o,s;function c(){return e.apply(this,arguments)||this}s=e,(o=c).prototype=Object.create(s.prototype),o.prototype.constructor=o,o.__proto__=s;var f,h,d,y=c.prototype;return y.isWithContent=function(){return this.getTitle()||this._getContent()},y.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},y.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},y.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(".popover-header"),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(".popover-body"),n),e.removeClass("fade show")},y._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},y._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(p);null!==n&&n.length>0&&e.removeClass(n.join(""))},c._jQueryInterface=function(e){return this.each((function(){var n=t(this).data("bs.popover"),o="object"==typeof e?e:null;if((n||!/dispose|hide/.test(e))&&(n||(n=new c(this,o),t(this).data("bs.popover",n)),"string"==typeof e)){if(void 0===n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},f=c,d=[{key:"VERSION",get:function(){return"4.5.2"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return r}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return l}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return a}}],(h=null)&&n(f.prototype,h),d&&n(f,d),c}(e);return t.fn[r]=c._jQueryInterface,t.fn[r].Constructor=c,t.fn[r].noConflict=function(){return t.fn[r]=s,c._jQueryInterface},c}));
|
||||||