Compare commits

..

200 Commits

Author SHA1 Message Date
ce0e47786f Squash migrations 2021-01-23 19:11:28 +00:00
0ed7dc4834 Small alignment fix in asset list 2021-01-23 17:43:33 +00:00
4ca4ea9812 Should fix calendar breaking in production 2021-01-23 17:41:06 +00:00
fe38d5cf36 Add icons to H&S menu items 2021-01-23 17:25:56 +00:00
72221e46bc Further minor fixes to versioning 2020-12-27 18:48:45 +00:00
0e656b4c41 FIX: Stupid typo in versioning.py 2020-12-27 18:42:24 +00:00
a0c6793cf6 Why does this work
Indeed, it may not
2020-12-27 18:28:02 +00:00
80837c3d9a Fixing tests for new logic etc 2020-12-27 17:50:53 +00:00
c027182962 More moving of event size logic 2020-12-14 21:58:21 +00:00
dd95447008 Javascript required shenanigans for RA power 2020-12-13 17:18:35 +00:00
13fcadaf79 Start move of event size logic to RA from Ec 2020-12-13 16:48:06 +00:00
124b7a8e51 Add space for power/rigging plans to be linked to RAs 2020-12-13 16:26:08 +00:00
dfdbcb651f Test fixes 2020-12-13 16:07:33 +00:00
e0615f318c Implement some of Jonny's suggested changes
TODO:
- Define event size at RA time, pass through to EC
- Have later power questions be context dependent
2020-11-16 18:31:37 +00:00
97b29bc549 Merge branch 'master' into bs4 2020-11-15 16:54:21 +00:00
0aa10d86cd Improvements to profile detail page 2020-11-15 16:53:11 +00:00
06e22b0d61 Update emergency contact number 2020-11-15 16:01:39 +00:00
099a184f2e Update emergency contact number 2020-11-13 09:24:39 +00:00
ac916247a6 Allow H&S for non-events 2020-11-12 12:47:47 +00:00
bbc4d1d390 FEAT: Implement #413 show associated assets on cable type detail pg
Closes #413
2020-10-30 00:44:18 +00:00
4b76b77806 FIX: Do not naively cache event table
Not that easy, it turns out. Duh.
2020-10-25 21:54:13 +00:00
44fef39a2e Fix recent change stream list mutation issue 2020-10-25 18:18:24 +00:00
d06895bf96 Fix one test, break another... 2020-10-25 16:26:05 +00:00
e9a29f9444 Implement Jerb's wording changes 2020-10-24 20:33:14 +01:00
14e12f0fb9 Made radio button focus much more obvious on dark theme 2020-10-24 20:24:40 +01:00
7f9a7bba8b Fix cable test 2020-10-24 19:52:26 +01:00
20d4ddd5cf Rework version name method to avoid blank names on eventchecklist vehicles/crew 2020-10-24 19:38:11 +01:00
e6eed9f2f2 First pass at porting calendar from FC V3 to V5
Two major versions and all they did was rename a bunch of names...TWICE.
2020-10-19 00:13:06 +01:00
320ace1a0e Use borders rather than block colors for coloured tables under darktheme 2020-10-18 23:03:05 +01:00
ab2c2f65f0 Fix event table success logic
Yay for copy paste fails >.>
2020-10-18 12:28:28 +01:00
8a4fcdcecb First pass at making the calendar less crap 2020-10-18 11:56:00 +01:00
529941224e Make version changes badges more readable 2020-10-18 11:23:16 +01:00
59756ab86a Remove animation on prefers-reduced-motion/low referesh rate devices
Also normalises handling of asset list cable table & improves its use of space on large devices
2020-10-18 11:15:04 +01:00
7736a2d3cc Fix horizontal-ness on some forms 2020-10-17 18:59:55 +01:00
30df597144 Remove enforced linebreak on status chips 2020-10-17 18:15:15 +01:00
4bb1c0a2a4 More search and replace for BS changes
Thought I'd got them all. Clearly not!
2020-10-17 18:11:42 +01:00
848e8c8ccd Remove lingering use of 'page-header'
BS removed that style
2020-10-17 17:59:23 +01:00
0fee753284 Put rounded corners back where they belong 2020-10-17 17:45:09 +01:00
bb4d31477e Fix caching 2020-10-15 17:32:18 +01:00
3d7ff435c9 Display note icon on event detail page 2020-10-15 14:57:50 +01:00
a950b941ca Move status color logic back to template
Cause that somehow makes it work better??
2020-10-15 14:57:50 +01:00
f57dc9f765 Fix autocompleter.js to properly disable edit links again 2020-10-14 13:30:23 +01:00
8b2f2a9354 Fix tests / default to headless tests
(fingers crossed)
2020-10-13 09:46:24 +01:00
d255e1f89f First pass at clearer display of asset list filters 2020-10-12 23:03:33 +01:00
fd85d50679 Remove flash of content when loading new rig page 2020-10-12 22:16:09 +01:00
2cb5453b82 H&S Details takes up free space on non-internal events 2020-10-12 21:47:18 +01:00
0f019e26a0 Same perm check for generic details 2020-10-12 21:44:02 +01:00
e926731e67 Generic list only displays edit button if user has perm 2020-10-12 21:38:31 +01:00
b69883cc90 Give keyholders supplier edit perm 2020-10-12 21:18:59 +01:00
a0e1702de4 (probably) fix tests 2020-10-12 10:16:15 +01:00
8c8c580bfb Clearer logic for RA inverted fields 2020-10-10 14:25:15 +01:00
49a2bc83ab Make venue/date editable on EC
For multi venue, multi day events

Defaults to date and venue set on the event. Also made power MIC default to that set in RA
2020-10-10 14:15:26 +01:00
026e2a0b03 Fix migration 2020-10-10 12:39:48 +01:00
3cb0d82130 Initial pass at soop-consult confirmation screen for RAs 2020-10-10 12:35:55 +01:00
5a5bb4328d Minor typo fixes 2020-10-10 10:24:21 +01:00
58b1867a13 Curses! 2020-10-09 17:55:23 +01:00
d7678f6b6f Update polyfill for datetime-local
Bloody Firefox. We love to hate you. Proper CSS of the fill to come, SoonTM.

Closes #391
2020-10-09 17:38:07 +01:00
7f3a169875 FIX: Set duplicated event status to provisional
Closes #398.

Flip flop. Flip flop.
2020-10-09 16:45:33 +01:00
5a99560310 Buggerit millennium hand and shrimp
Knew I was gonna forget to fix the tests
2020-10-09 13:06:25 +01:00
12a60e1f50 Correct handling of spaces in paperwork filenames
Also normalises display of Invoice IDs. Partial fix for #391.
2020-10-09 12:48:59 +01:00
934c07be72 Fix dependency hell
Probably
2020-10-09 11:52:52 +01:00
3602da9203 Fixes fixes fixes 2020-10-09 09:42:15 +01:00
f41064abfa Upgrade dependencies 2020-10-09 09:25:23 +01:00
ee9dbf8944 Display tick/cross rather than true/false in boolean version diffs 2020-10-09 01:37:30 +01:00
565e757758 And now the same for generic forms 2020-10-09 01:26:42 +01:00
5af075946a One of these days I'll remember to test BEFORE pushing... 2020-10-08 23:29:11 +01:00
5d56f4f7b0 Also apply better approach to generic detail pages 2020-10-08 23:15:55 +01:00
3903481b3d Better approach to generic list templates + other deduplication 2020-10-08 22:54:30 +01:00
af7d3c4070 Revert "Prevent creating duplicate revisions on event"
Apparently it was too strong at preventing dupes...

This reverts commit cce0ad0f9f.

# Conflicts:
#	RIGS/models.py
2020-10-07 17:31:17 +01:00
025a31f15a Minor test fixes 2020-10-07 17:29:31 +01:00
2db2cc6659 Template improvements 2020-10-07 17:05:51 +01:00
cce0ad0f9f Prevent creating duplicate revisions on event
Potential fix for #322 - I couldn't reproduce even before this change...
2020-10-07 17:05:40 +01:00
ae13cabe09 Properly handle eventauthorisations in new versioning
It's not great, not terrible...
2020-10-07 16:46:28 +01:00
abf3cfe1ce Fix tests 2020-10-06 23:04:25 +01:00
7c79a6afdd Rigboard Timing display tweaks 2020-10-06 16:53:45 +01:00
350a301b36 Kill off excess whitespace on rigboard 2020-10-06 16:42:36 +01:00
2f8e09906b Switch rigboard new button to primary 2020-10-06 16:18:27 +01:00
50ae3022cd Redo light theme palette 2020-10-06 16:12:23 +01:00
f147f19140 Fix event checklist on mobile 2020-10-06 16:08:38 +01:00
0b751d62df Mobile fixes for search 2020-10-06 16:08:31 +01:00
acf814e49e Rework button tag 2020-09-30 09:08:50 +01:00
f70421b8ca Fix tests 2020-09-30 09:08:20 +01:00
84c4ec03de Minor event detail fixes 2020-09-29 19:24:22 +01:00
813b1dac85 Allow multiple event checklists per event
TODO: Status chip now needs rethinking
2020-09-29 18:08:55 +01:00
0117002b01 Initial work on BS4 button templatetag
Newfeatureitis strikes again
2020-09-29 17:41:40 +01:00
932180f99f Fix rigboard validation tests 2020-09-29 17:41:01 +01:00
70de16ed5c Fix errors being squashed 2020-09-29 17:37:54 +01:00
8424424d49 Fix #326 (again) 2020-09-29 17:17:53 +01:00
a78bb19015 Asset detail template improvements 2020-09-28 10:38:59 +01:00
bea762b12b Begin to improve event checklist on mobile 2020-09-28 10:32:57 +01:00
70cc554094 Begin to change add buttons success -> primary
Also change search primary -> info to avoid clash
2020-09-28 10:32:45 +01:00
1dad8d2ba5 Tweak asset list markup 2020-09-28 10:32:00 +01:00
c38105a76e Use template filter for consistent removal of 'None links'
Plus cleaner template markup! More HTML-in-Python tho, which always feels a bit CSS-in-JS
2020-09-28 10:31:43 +01:00
6936b94ce6 Put power threshold values in a collapse 2020-09-28 10:30:58 +01:00
74066e9484 Remove database ID from generic list 2020-09-28 09:22:10 +01:00
018397d28e Add ability to mark event checklists as Large Event
This just disables the checks to allow the rest of it to be filled out for large events, though I expect paper forms may still be used...
2020-09-25 22:06:06 +01:00
143b654210 Major improvements/fixes to authorisation templates 2020-09-25 21:29:00 +01:00
2a1bb57c74 More fixes 2020-09-19 18:03:24 +01:00
77c82efce6 More tweaks 2020-09-19 13:46:55 +01:00
1ea8090668 Minor fixes 2020-09-19 13:39:58 +01:00
31f63ba5c7 Invoice template improvements 2020-09-18 12:33:10 +01:00
9739af765f Curse youuuuu pep8 2020-09-17 12:04:41 +01:00
b90be708d0 Fix for my fix 2020-09-17 11:31:32 +01:00
dcc0e53062 Various misc fixes 2020-09-17 09:32:57 +01:00
ce5a92dfa8 Add revision history to invoices/payments.
Also patches previously introduced reversion permissions hole.

Supersedes and closes #337.
2020-09-16 11:37:56 +01:00
fd926aef85 Fix broken invoice list template 2020-09-15 10:39:01 +01:00
1ecc508b0d Fix wrong variable name in settings.py 2020-09-15 10:29:28 +01:00
a5516ee350 Cleanup & Squash migrations 2020-09-15 10:01:52 +01:00
5487b73006 Much better coverage of H&S things 2020-09-15 01:23:45 +01:00
6426880708 Pages/start of tests for EventChecklists 2020-09-14 16:26:02 +01:00
aa0184a5dc Initial work on RA tests 2020-09-12 14:00:03 +01:00
eb7d8e49e8 Merge branch 'master' into bs4
# Conflicts:
#	RIGS/templates/RIGS/index.html
#	requirements.txt
2020-09-12 11:19:46 +01:00
b959ca13a5 Formatting... 2020-09-12 10:30:33 +01:00
d8e6f2f9c1 Why does this work
Bloody overzealous autoformatter...
2020-09-12 10:26:47 +01:00
1cf910752f Modify auth test so it doesn't try and test for external authorisations
Cause that's not a thing
2020-09-12 10:14:16 +01:00
cf7ada3d9e Test fixes 2020-09-12 10:08:47 +01:00
ba6dbc6980 Rethink rigboard color logic again
Also revert some broken stuff
2020-09-12 10:07:58 +01:00
6d836ee4dd Fix cable table template 2020-09-12 10:07:31 +01:00
e602058771 Fix list templates
TODO: Sensible place to define the 'expected answer' stuff.
2020-09-03 13:02:30 +01:00
c1182efa54 Use template filter for notes 2020-09-01 15:15:15 +01:00
87e831a468 Event properties internal/authorised always return a explicit boolean rather than sometimes None 2020-09-01 15:02:16 +01:00
1d5429052f Restrict versioning to one level of depth for speed
Also fixed the template for nested changes
2020-09-01 13:41:12 +01:00
6ee9efa39e Audit template fixes 2020-09-01 13:13:44 +01:00
945e521feb Do event table color logic at python level 2020-08-31 13:45:32 +01:00
689124a891 Templating improvements to RA/EC stuff 2020-08-31 13:09:47 +01:00
8842c2c3d9 Worst case points on checklist 2020-08-30 20:50:06 +01:00
f3c2ce2519 Validation of power reqs 2020-08-30 12:26:50 +01:00
bfe80db85e Misc fixes 2020-08-30 10:48:19 +01:00
9198724748 Medium event power stuff done, barring worst case stuff 2020-08-29 23:56:08 +01:00
dbe0d35400 Event checklist crew works
Mostly - its not happy with timezones
2020-08-29 22:01:23 +01:00
1feb9449ed Cleanup 2020-08-29 17:31:41 +01:00
d708207ab9 Versioning module now does magic
Automatic creation of views/urls for anything registered with reversion, with a small amount of hackage to preserve legacy stuff. (and the DAMNED asset IDs!) I would never get distracted...
2020-08-29 16:24:11 +01:00
8ea96674db Initial work on new checklist handling. No more JSON! 2020-08-29 13:56:57 +01:00
de04498517 Insert a divider between still-out dry hires and actually upcoming events on rigboard 2020-08-27 17:38:26 +01:00
828964ecb6 Alter rig_count to not include un-checked-in dry hires 2020-08-27 17:08:51 +01:00
3438489934 Add new line functionality for vehicles/drivers
Might it have been easier to create 'dummy' models like with EventItems? Probably...
2020-08-27 02:20:46 +01:00
9cf081efc7 Proof of concept for JSON parsing/storage
\o/
2020-08-25 16:00:19 +01:00
8bb08724b6 Initial shenanigans on storing my overly fancy EC form 2020-08-24 17:25:25 +01:00
da60cad911 Mooooore status chips, mooore 2020-08-24 13:43:03 +01:00
a6ac55baaf Add a button for creating and instantly voiding invoices
Handy dandy for when you have loads of cancelled events, like say, a pandemic
2020-08-24 12:25:24 +01:00
d3f55523da Start work on event checklist 2020-08-14 17:30:57 +01:00
d3d7c052af Invalidate RA review if it is edited after review 2020-08-14 16:04:27 +01:00
902476ebab Fix Power MIC being lost on RA edit
Why it is subtly different to the Event Update behaviour? Who knows
2020-08-06 10:51:44 +01:00
4514de137a Add reviewing to revision history, fix RA editing not working
Also actually commit all the files, that helps
2020-08-05 22:31:50 +01:00
92377227e0 Start RA 'mark review' feature 2020-08-05 20:51:31 +01:00
b88554a57f Style fixes to asset list 2020-08-04 19:09:47 +01:00
9d6948e326 Fixed search for events 2020-08-04 19:03:38 +01:00
67bf60e246 Properly fixed popover darktheme 2020-08-04 19:00:12 +01:00
2e60c5e7bf Do not use Django 'required' for radio selects
As this requires them to be True, whereas we just need to require that an option be entered.
2020-07-03 17:09:32 +01:00
d09f3994fc Bunch of dark mode fixes from test page 2020-07-03 16:42:40 +01:00
79eb0cbff0 Add bootstrap 4 test page 2020-07-03 14:40:42 +01:00
d800a781a5 Fixes to revisions for RAs 2020-07-02 20:13:24 +01:00
721439d095 More RA fixes 2020-07-02 19:26:27 +01:00
b57b918247 Remove the defaults from the RA fields + make them required 2020-07-02 19:20:20 +01:00
1dfaa4d7a8 Disable mobile event table PoC for now 2020-07-02 19:00:45 +01:00
1138cfcde5 Make supplier detail use the generic template 2020-06-25 19:31:51 +01:00
cd168d93ed Match darktheme palette to forum darktheme palette
Why reinvent the wheel.
2020-06-25 19:15:09 +01:00
a4ef69af3c Dark theme palette shenanigans
I just can't decide
2020-06-25 18:59:24 +01:00
be48dd31f9 Testing something re notes
I wonder if I can make that global, rather than per-template...
2020-06-25 18:59:24 +01:00
a6cee78198 Genercise detail pages 2020-06-25 17:51:00 +01:00
d50d2bd0ad Move dark theme SCSS to separate file, fix inactive pagination styling 2020-06-25 14:40:12 +01:00
3ceb48876d Fix reload loops when CSS/JS is changed 2020-06-25 14:20:36 +01:00
8820ed1e67 Remove the dark header from light theme 2020-06-24 12:23:41 +01:00
86fe8a8e1b further darktheme fixes 2020-06-24 12:15:47 +01:00
8a9eefb722 Fix table colors for dry hires 2020-06-24 12:11:11 +01:00
b28377e1f5 Dark mode colour improvements 2020-06-24 12:05:17 +01:00
141e1c94d7 Fix tests for search improvements 2020-06-19 11:22:06 +01:00
eafb394f55 Move closemodal into PyRIGS 2020-06-19 11:10:01 +01:00
84618deac0 FEAT: Improve 'omni'search
- Partialised template
- Added to assets header
- Added ability to search assets/suppliers
- Improved selection logic
- Have it display current query
2020-06-19 11:07:25 +01:00
e4e8823a1a Fix navbar alignment 2020-06-19 10:38:13 +01:00
cb1a8e1627 Fix for a situation that should be impossible 2020-06-19 10:38:02 +01:00
c65330cb91 Update dependencies 2020-06-19 10:37:47 +01:00
6e7fa267bc Add global ctrl/meta-enter shortcut for form submission
Wants rewriting for better efficiency, but hey, it works!
2020-06-15 16:15:23 +01:00
9e1d146b7a Update dependencies
Mirrors/supersedes 0e67da82e2
2020-06-05 18:24:29 +01:00
dependabot[bot]
0e67da82e2 Build(deps): Bump django from 3.0.3 to 3.0.7 (#411)
Bumps [django](https://github.com/django/django) from 3.0.3 to 3.0.7.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.0.3...3.0.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-05 18:11:21 +01:00
aedb4c24db Use locmem cache in sqlite environments
Otherwise the tests just lock up totally. Should close #162
2020-06-04 23:36:31 +01:00
1239fbf185 Fix lingering naive time 2020-06-04 23:28:33 +01:00
7035731655 Add ability to filter event archive by status
Closes #168.
2020-06-04 22:58:19 +01:00
a7c4b90161 Display venue notes in event detail
Notes are no use if nobody reads them. Not sure on this one.
2020-06-03 15:17:07 +01:00
9e93d895ba Add H&S Details to Event Detail View 2020-06-03 15:15:23 +01:00
d31900c5c3 Refactor RA creation stuff, again 2020-06-03 15:15:02 +01:00
3768f4a613 Start RA list template 2020-05-30 17:19:11 +01:00
6867359146 Initial work on caching activity feed
Server side that is. Ref #162.
2020-05-30 15:50:32 +01:00
e45324f5b4 Refactor activity feed template logic
Yay for removing arbitrary if/else chains!
2020-05-30 14:07:50 +01:00
156e639bac Initial work at coercing activity feed into showing RAs
Also shows Asset/Supplier on the homepage feed.
2020-05-29 15:45:16 +01:00
0e2adf3f0d Use correct view for RA history 2020-05-29 15:18:52 +01:00
eb0c027ff9 Expand detail template 2020-05-29 15:15:37 +01:00
93504997fe FIX: Undo breakage causing autopep8
o.O
2020-05-28 23:19:22 +01:00
d3b3d1c9d7 Move text definitions to somewhere more authoratitive 2020-05-28 23:02:47 +01:00
4cfd83eeb3 Different approach to RA linking 2020-05-28 21:46:39 +01:00
00226e9c22 FIX: Don't set every boolean input to radios 2020-05-28 21:46:16 +01:00
b75b6a6736 Initial work at integrating the risk assessment
#136. No clever database structure as yet...
2020-05-28 18:55:22 +01:00
1b1775d0f5 Fix sample command 2020-05-27 18:49:03 -04:00
14d211326e FIX: CI Locale Issues 2020-05-27 18:38:58 -04:00
4081918598 Fix some asset template things 2020-05-27 17:06:50 +02:00
b742d3f8c4 More dark theme wangling 2020-05-27 17:01:55 +02:00
06d7ef0b79 Merge remote-tracking branch 'origin/bs4' into bs4 2020-05-27 04:26:15 +02:00
06f67f4f32 Very initial work at togglable darktheme.
Dammit @alexdaniel654 just when I had my scope creep kinda under control. It'll be v. nice to have though...!
2020-05-27 04:24:58 +02:00
02e8e8aaf7 Fix risk assessment link 2020-05-11 23:22:11 +01:00
David Taylor
4acd9156d0 Switch to heroku-18 stack (#409)
cedar-14 has been deprectated: https://devcenter.heroku.com/changelog-items/1413
2020-04-15 12:53:44 +01:00
206 changed files with 39799 additions and 4777 deletions

3
.gitignore vendored
View File

@@ -109,5 +109,4 @@ com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
.vscode/
/package-lock.json
screenshots/
screenshots/

View File

@@ -28,6 +28,8 @@ DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True
STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False
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']
if STAGING:
@@ -150,6 +152,27 @@ LOGGING = {
}
}
# Tests lock up SQLite otherwise
if STAGING or CI:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
}
}
elif DEBUG:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
}
}
else:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'cache_table',
}
}
RAVEN_CONFIG = {
'dsn': os.environ.get('RAVEN_DSN'),
# If you are using git, you can also automatically configure the

View File

@@ -26,8 +26,8 @@ def create_browser():
options.add_argument("--disk-cache-size=0")
# God Save The Queen
options.add_argument("--lang=en_GB")
options.add_argument("--headless")
if os.environ.get('CI', False):
options.add_argument("--headless")
options.add_argument("--no-sandbox")
driver = webdriver.Chrome(options=options)
return driver
@@ -89,10 +89,7 @@ def screenshot_failure_cls(cls):
# Checks if animation is done
class animation_is_finished(object):
def __init__(self):
pass
class animation_is_finished():
def __call__(self, driver):
numberAnimating = driver.execute_script('return $(":animated").length')
finished = numberAnimating == 0

View File

@@ -31,6 +31,7 @@ class BasePage(Page):
class FormPage(BasePage):
_errors_selector = (By.CLASS_NAME, "alert-danger")
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
def remove_all_required(self):
self.driver.execute_script(

View File

@@ -6,6 +6,7 @@ from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import NoSuchElementException
import datetime
@@ -126,6 +127,22 @@ class CheckBox(Region):
self.toggle()
class RadioSelect(Region): # Currently only works for yes/no radio selects
def set_value(self, value):
if value:
value = "0"
else:
value = "1"
self.find_element(By.XPATH, "//label[@for='{}_{}']".format(self.root.get_attribute("id"), value)).click()
@property
def value(self):
try:
return parse_bool_from_string(self.find_element(By.CSS_SELECTOR, '.custom-control-input:checked').get_attribute("value").lower())
except NoSuchElementException:
return None
class DatePicker(Region):
@property
def value(self):
@@ -199,7 +216,7 @@ class ErrorPage(Region):
class Modal(Region):
_submit_locator = (By.CSS_SELECTOR, '.btn-primary')
_header_selector = (By.TAG_NAME, 'h3')
_header_selector = (By.TAG_NAME, 'h4')
form_items = {
'name': (TextBox, (By.ID, 'id_name'))

View File

@@ -6,23 +6,31 @@ from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.views.decorators.clickjacking import xframe_options_exempt
from django.contrib.auth.views import LoginView
from registration.backends.default.views import RegistrationView
from django.views.generic import TemplateView
from PyRIGS.decorators import permission_required_with_403
import RIGS
import users
import versioning
from PyRIGS import views
urlpatterns = [
path('', include('users.urls')),
path('', include('versioning.urls')),
path('', include('RIGS.urls')),
path('assets/', include('assets.urls')),
path('', login_required(views.Index.as_view()), name='index'),
# API
path('api/<str:model>/', login_required(views.SecureAPIRequest.as_view()),
name="api_secure"),
path('api/<str:model>/<int:pk>/', login_required(views.SecureAPIRequest.as_view()),
name="api_secure"),
path('closemodal/', views.CloseModal.as_view(), name='closemodal'),
path('search_help/', views.SearchHelp.as_view(), name='search_help'),
path('', include('users.urls')),
path('admin/', admin.site.urls),
]
@@ -32,4 +40,5 @@ if settings.DEBUG:
import debug_toolbar
urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)),
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
] + urlpatterns

View File

@@ -20,6 +20,19 @@ from RIGS import models, forms
from assets import models as asset_models
from functools import reduce
from django.views.decorators.cache import never_cache, cache_page
from django.utils.decorators import method_decorator
# Displays the current rig count along with a few other bits and pieces
class Index(generic.TemplateView):
template_name = 'index.html'
def get_context_data(self, **kwargs):
context = super(Index, self).get_context_data(**kwargs)
context['rig_count'] = models.Event.objects.rig_count()
return context
class SecureAPIRequest(generic.View):
models = {
@@ -136,9 +149,31 @@ class SecureAPIRequest(generic.View):
return HttpResponse(model)
class ModalURLMixin:
def get_close_url(self, update, detail):
if self.request.is_ajax():
url = reverse_lazy('closemodal')
update_url = str(reverse_lazy(update, kwargs={'pk': self.object.pk}))
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
messages.info(self.request, "modalobject[0]['update_url']='" + update_url + "'")
else:
url = reverse_lazy(detail, kwargs={
'pk': self.object.pk,
})
return url
class GenericListView(generic.ListView):
template_name = 'generic_list.html'
paginate_by = 20
def get_context_data(self, **kwargs):
context = super(GenericListView, self).get_context_data(**kwargs)
context['page_title'] = self.model.__name__ + "s"
if self.request.is_ajax():
context['override'] = "base_ajax.html"
return context
def get_queryset(self):
q = self.request.GET.get('q', "")
@@ -159,3 +194,54 @@ class GenericListView(generic.ListView):
if orderBy != "":
object_list = object_list.order_by(orderBy)
return object_list
class GenericDetailView(generic.DetailView):
template_name = "generic_detail.html"
def get_context_data(self, **kwargs):
context = super(GenericDetailView, self).get_context_data(**kwargs)
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name)
if self.request.is_ajax():
context['override'] = "base_ajax.html"
return context
class GenericUpdateView(generic.UpdateView):
template_name = "generic_form.html"
def get_context_data(self, **kwargs):
context = super(GenericUpdateView, self).get_context_data(**kwargs)
context['page_title'] = "Edit {}".format(self.model.__name__)
if self.request.is_ajax():
context['override'] = "base_ajax.html"
return context
class GenericCreateView(generic.CreateView):
template_name = "generic_form.html"
def get_context_data(self, **kwargs):
context = super(GenericCreateView, self).get_context_data(**kwargs)
context['page_title'] = "Create {}".format(self.model.__name__)
if self.request.is_ajax():
context['override'] = "base_ajax.html"
return context
class SearchHelp(generic.TemplateView):
template_name = 'search_help.html'
"""
Called from a modal window (e.g. when an item is submitted to an event/invoice).
May optionally also include some javascript in a success message to cause a load of
the new information onto the page.
"""
class CloseModal(generic.TemplateView):
template_name = 'closemodal.html'
def get_context_data(self, **kwargs):
return {'messages': messages.get_messages(self.request)}

114
README.md
View File

@@ -1,111 +1,17 @@
# TEC PA & Lighting - PyRIGS #
[![Build Status](https://travis-ci.org/nottinghamtec/PyRIGS.svg)](https://travis-ci.org/nottinghamtec/PyRIGS)
[![Build Status](https://travis-ci.org/nottinghamtec/PyRIGS.svg?branch=master)](https://travis-ci.org/nottinghamtec/PyRIGS)
[![Coverage Status](https://coveralls.io/repos/github/nottinghamtec/PyRIGS/badge.svg)](https://coveralls.io/github/nottinghamtec/PyRIGS)
Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.
Welcome to TEC PA & Lighting's PyRIGS program. This is a reimplementation of the previous Rig Information Gathering System (RIGS) that was developed using Ruby on Rails. PyRIGS is our in house app for the centralisation of information on our events and now assets.
The purpose of this project is to make the system more compatible and easier to understand such that should future changes be needed they can be made without having to understand the intricacies of Rails.
For setup information and other such helpful stuff check the [Wiki](https://github.com/nottinghamtec/PyRIGS/wiki)
### What is this repository for? ###
When a significant feature is developed on a branch, raise a pull request and it can be reviewed before being put into production.
Most of the documents here assume a basic knowledge of how Python and Django work (hint, if I don't say something, Google it, you will find 10000's of answers). The documentation is purely to be specific to TEC's application of the framework.
### Editing ###
It is recommended that you use the PyCharm IDE by JetBrains. Whilst other editors are available, this is the best for integration with Django as it can automatically manage all the pesky admin commands that frequently need running, as well as nice integration with git.
For the more experienced developer/somebody who doesn't want a full IDE and wants it to open in less than the age of the universe, I can strongly recommend [Sublime Text](http://www.sublimetext.com/). It has a bit of a steeper learning curve, and won't manage anything Django/git related out of the box, but once you get the hang of it is by far the fastest and most powerful editor I have used (for any type of project).
Please contact TJP for details on how to acquire these.
### Python Environment ###
Whilst the Python version used is not critical to the running of the application, using the same version usually helps avoid a lot of issues. Orginally written with the C implementation of Python 2 (CPython 2, specifically the Python 2.7 standard), the application now runs in Python 3.
Once you have your Python distribution installed, go ahead an follow the steps to set up a virtualenv, which will isolate the project from the system environment.
#### PyCharm ####
If you are using the prefered PyCharm IDE, then this should be quite easy.
1. Select "File/Settings" -> "Project Interpreter"
2. Click the small cog in the top right
3. Select "Create VirtualEnv"
4. Enter a name and a location. This doesn't matter where, just make sure it makes sense and you remember it incase you need it later (I recommend calling it "pyrigs" in "~/.virtualenvs/pyrigs")
5. Select the base interpreter to your Python 3 base interpreter (Python 2 will work, just be careful)
6. Click OK, you *don't* want to inherit global packages or make it available to all projects.
7. Open a file such as manage.py. PyCharm should winge that dependances aren't installed. This might take a while to register, but give it change. When it does, click the button to install them and let it do it's thing. If for some reason PyCharm should decide that it doesn't want to help you here, see below for the console instructions on how to do this manually.
To run the Django application follow these steps
1. Select "Run/Edit Configurations"
2. Create a new "Django server", give it a sensible name for when you need it later.
3. You might need to set the interpreter to be your virtualenv.
4. Click "OK"
5. Run the application
#### Console Based ####
If you aren't using PyCharm, or want to use a console for some reason, this is really easy, there is even [virtualenvwrapper](https://virtualenvwrapper.readthedocs.org/en/latest/) to help things along. Simply run
```
virtualenv <dir>
```
Where dir is the directory you wish to create the virtualenv in.
Next activate the virtualenv.
```
Windows
<virtualenv_dir>/Scripts/activate.bat
Unix
source <virtualenv_dir>/bin/activate
```
Finally install the requirements using pip
```
cd <pyrigs project directory>
pip install -r requirements.txt
```
This might take a while, but be patient and you should then be ready to go.
To run the server under normal conditions when you are already in the virtualenv (see above)
```
python manage.py runserver
```
Please refer to Django documentation for a full list of options available here.
### Development using docker
```
docker build . -t pyrigs
docker run -it --rm -p=8000:8000 -v $(pwd):/app pyrigs
```
### Sample Data ###
Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty:
```
python manage.py flush
```
Then load the sample data using the command:
```
python manage.py generateSampleData
```
4 user accounts are created for convenience:
|Username |Password |
|---------|---------|
|superuser|superuser|
|finance |finance |
|keyholder|keyholder|
|basic |basic |
### Testing ###
Tests are contained in 3 files. `RIGS/test_models.py` contains tests for logic within the data models. `RIGS/test_unit.py` contains "Live server" tests, using raw web requests. `RIGS/test_integration.py` contains user interface tests which take control of a web browser. For automated Travis tests, we use [Sauce Labs](https://saucelabs.com). When debugging locally, ensure that you have the latest version of Google Chrome installed, then install [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) and ensure it is on the `PATH`.
You can run the entire test suite, or you can run specific sections individually. For example, in order of specificity:
```
python manage.py test
python manage.py test RIGS.test_models
python manage.py test RIGS.test_models.EventTestCase
python manage.py test RIGS.test_models.EventTestCase.test_current_events
```
# Apps
- PyRIGS: Base app, stores 'global' information
- RIGS: Rigboard stuff - event calendar etc
- assets: Database of our kit, testing data etc
- versioning: Our custom logic built on top of django-reversion. Semi-modular.
- users: Our custom logic for registration and profiles. Semi-modular.
- training: SoonTM
[![forthebadge](https://forthebadge.com/images/badges/built-with-resentment.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://forthebadge.com)

View File

@@ -19,8 +19,7 @@ from reversion import revisions as reversion
admin.site.register(models.VatRate, VersionAdmin)
admin.site.register(models.Event, VersionAdmin)
admin.site.register(models.EventItem, VersionAdmin)
admin.site.register(models.Invoice)
admin.site.register(models.Payment)
admin.site.register(models.Invoice, VersionAdmin)
def approve_user(modeladmin, request, queryset):
@@ -125,3 +124,13 @@ class VenueAdmin(AssociateAdmin):
class OrganisationAdmin(AssociateAdmin):
list_display = ('id', 'name', 'phone', 'email', 'number_of_events')
merge_fields = ['name', 'phone', 'email', 'address', 'notes', 'union_account']
@admin.register(models.RiskAssessment)
class RiskAssessmentAdmin(VersionAdmin):
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')
@admin.register(models.EventChecklist)
class EventChecklistAdmin(VersionAdmin):
list_display = ('id', 'event', 'reviewed_at', 'reviewed_by')

View File

@@ -13,6 +13,9 @@ from django.db.models import Q
from z3c.rml import rml2pdf
from django.db.models import Q
from django.db import transaction
import reversion
from RIGS import models
from django import forms
@@ -22,15 +25,15 @@ forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
class InvoiceIndex(generic.ListView):
model = models.Invoice
template_name = 'invoice_list_active.html'
template_name = 'invoice_list.html'
def get_context_data(self, **kwargs):
context = super(InvoiceIndex, self).get_context_data(**kwargs)
total = 0
for i in context['object_list']:
total += i.balance
context['total'] = total
context['count'] = len(list(context['object_list']))
context['page_title'] = "Outstanding Invoices ({} Events, £{:.2f})".format(len(list(context['object_list'])), total)
context['description'] = "Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger"
return context
def get_queryset(self):
@@ -54,6 +57,11 @@ class InvoiceDetail(generic.DetailView):
model = models.Invoice
template_name = 'invoice_detail.html'
def get_context_data(self, **kwargs):
context = super(InvoiceDetail, self).get_context_data(**kwargs)
context['page_title'] = "Invoice {} ({})".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
return context
class InvoicePrint(generic.View):
def get(self, request, pk):
@@ -71,6 +79,7 @@ class InvoicePrint(generic.View):
},
'invoice': invoice,
'current_user': request.user,
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
}
rml = template.render(context)
@@ -79,11 +88,8 @@ class InvoicePrint(generic.View):
pdfData = buffer.read()
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = "filename=Invoice %05d - N%05d | %s.pdf" % (
invoice.pk, invoice.event.pk, escapedEventName)
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
response.write(pdfData)
return response
@@ -127,6 +133,12 @@ class InvoiceArchive(generic.ListView):
template_name = 'invoice_list_archive.html'
paginate_by = 25
def get_context_data(self, **kwargs):
context = super(InvoiceArchive, self).get_context_data(**kwargs)
context['page_title'] = "Invoice Archive"
context['description'] = "This page displays all invoices: outstanding, paid, and void"
return context
def get_queryset(self):
q = self.request.GET.get('q', "")
@@ -166,8 +178,7 @@ class InvoiceWaiting(generic.ListView):
total = 0
for obj in self.get_objects():
total += obj.sum_total
context['total'] = total
context['count'] = len(self.get_objects())
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(self.get_objects()), total)
return context
def get_queryset(self):
@@ -192,7 +203,10 @@ class InvoiceWaiting(generic.ListView):
class InvoiceEvent(generic.View):
@transaction.atomic()
@reversion.create_revision()
def get(self, *args, **kwargs):
reversion.set_user(self.request.user)
epk = kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
invoice, created = models.Invoice.objects.get_or_create(event=event)
@@ -201,6 +215,11 @@ class InvoiceEvent(generic.View):
invoice.invoice_date = datetime.date.today()
messages.success(self.request, 'Invoice created successfully')
if kwargs.get('void'):
invoice.void = not invoice.void
invoice.save()
messages.warning(self.request, 'Invoice voided')
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk}))
@@ -218,6 +237,13 @@ class PaymentCreate(generic.CreateView):
initial.update({'invoice': invoice})
return initial
@transaction.atomic()
@reversion.create_revision()
def form_valid(self, form, *args, **kwargs):
reversion.add_to_revision(form.cleaned_data['invoice'])
reversion.set_comment("Payment added")
return super().form_valid(form, *args, **kwargs)
def get_success_url(self):
messages.info(self.request, "location.reload()")
return reverse_lazy('closemodal')
@@ -225,6 +251,14 @@ class PaymentCreate(generic.CreateView):
class PaymentDelete(generic.DeleteView):
model = models.Payment
template_name = 'payment_confirm_delete.html'
@transaction.atomic()
@reversion.create_revision()
def delete(self, *args, **kwargs):
reversion.add_to_revision(self.get_object().invoice)
reversion.set_comment("Payment removed")
return super().delete(*args, **kwargs)
def get_success_url(self):
return self.request.POST.get('next')

View File

@@ -4,10 +4,14 @@ from django.conf import settings
from django.core import serializers
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm
from 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
import simplejson
from datetime import datetime
from django.utils import timezone
from RIGS import models
@@ -18,8 +22,6 @@ forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'
# Events Shit
class EventForm(forms.ModelForm):
datetime_input_formats = list(settings.DATETIME_INPUT_FORMATS)
meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
@@ -153,3 +155,129 @@ class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
class EventAuthorisationRequestForm(forms.Form):
email = forms.EmailField(required=True, label='Authoriser Email')
class EventRiskAssessmentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(EventRiskAssessmentForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
if str(name) == 'supervisor_consulted':
field.widget = forms.CheckboxInput()
elif field.__class__ == forms.BooleanField:
field.widget = forms.RadioSelect(choices=[
(True, 'Yes'),
(False, 'No')
], attrs={'class': 'custom-control-input', 'required': 'true'})
def clean(self):
# Check expected values
unexpected_values = []
for field, value in models.RiskAssessment.expected_values.items():
if self.cleaned_data.get(field) != value:
unexpected_values.append("<li>{}</li>".format(self._meta.model._meta.get_field(field).help_text))
if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'):
raise forms.ValidationError("Your answers to these questions: <ul>{}</ul> require consulting with a supervisor.".format(''.join([str(elem) for elem in unexpected_values])), code='unusual_answers')
return super(EventRiskAssessmentForm, self).clean()
class Meta:
model = models.RiskAssessment
fields = '__all__'
exclude = ['reviewed_at', 'reviewed_by']
class EventChecklistForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(EventChecklistForm, self).__init__(*args, **kwargs)
self.fields['date'].widget.format = '%Y-%m-%d'
for name, field in self.fields.items():
if field.__class__ == forms.NullBooleanField:
# Only display yes/no to user, the 'none' is only ever set in the background
field.widget = forms.CheckboxInput()
# Parsed from incoming form data by clean, then saved into models when the form is saved
items = {}
related_models = {
'venue': models.Venue,
'power_mic': models.Profile,
}
# Two possible formats
def parsedatetime(self, date_string):
try:
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S'))
except ValueError:
return timezone.make_aware(datetime.strptime(date_string, '%Y-%m-%dT%H:%M'))
# There's probably a thousand better ways to do this, but this one is mine
def clean(self):
vehicles = {key: val for key, val in self.data.items()
if key.startswith('vehicle')}
for key in vehicles:
pk = int(key.split('_')[1])
driver_key = 'driver_' + str(pk)
if(self.data[driver_key] == ''):
raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
else:
try:
item = models.EventChecklistVehicle.objects.get(pk=pk)
except models.EventChecklistVehicle.DoesNotExist:
item = models.EventChecklistVehicle()
item.vehicle = vehicles['vehicle_' + str(pk)]
item.driver = models.Profile.objects.get(pk=self.data[driver_key])
item.full_clean('checklist')
# item does not have a database pk yet as it isn't saved
self.items['v' + str(pk)] = item
crewmembers = {key: val for key, val in self.data.items()
if key.startswith('crewmember')}
other_fields = ['start', 'role', 'end']
for key in crewmembers:
pk = int(key.split('_')[1])
for field in other_fields:
value = self.data['{}_{}'.format(field, pk)]
if value == '':
raise forms.ValidationError('Add a {} to crewmember {}'.format(field, pk), code='{}_mismatch'.format(field))
try:
item = models.EventChecklistCrew.objects.get(pk=pk)
except models.EventChecklistCrew.DoesNotExist:
item = models.EventChecklistCrew()
item.crewmember = models.Profile.objects.get(pk=self.data['crewmember_' + str(pk)])
item.start = self.parsedatetime(self.data['start_' + str(pk)])
item.role = self.data['role_' + str(pk)]
item.end = self.parsedatetime(self.data['end_' + str(pk)])
item.full_clean('checklist')
# item does not have a database pk yet as it isn't saved
self.items['c' + str(pk)] = item
return super(EventChecklistForm, self).clean()
def save(self, commit=True):
checklist = super(EventChecklistForm, self).save(commit=False)
if (commit):
# Remove all existing, to be recreated from the form
checklist.vehicles.all().delete()
checklist.crew.all().delete()
checklist.save()
for key in self.items:
item = self.items[key]
reversion.add_to_revision(item)
# finish and save new database items
item.checklist = checklist
item.full_clean()
item.save()
self.items.clear()
return checklist
class Meta:
model = models.EventChecklist
fields = '__all__'
exclude = ['reviewed_at', 'reviewed_by']

217
RIGS/hs.py Normal file
View File

@@ -0,0 +1,217 @@
from RIGS import models, forms
from django.views import generic
from django.utils import timezone
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from reversion import revisions as reversion
from django.db.models import AutoField, ManyToOneRel
from django.contrib import messages
class EventRiskAssessmentCreate(generic.CreateView):
model = models.RiskAssessment
template_name = 'risk_assessment_form.html'
form_class = forms.EventRiskAssessmentForm
def get(self, *args, **kwargs):
epk = kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
# Check if RA exists
ra = models.RiskAssessment.objects.filter(event=event).first()
if ra is not None:
return HttpResponseRedirect(reverse_lazy('ra_edit', kwargs={'pk': ra.pk}))
return super(EventRiskAssessmentCreate, self).get(self)
def get_form(self, **kwargs):
form = super(EventRiskAssessmentCreate, self).get_form(**kwargs)
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
form.instance.event = event
return form
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentCreate, self).get_context_data(**kwargs)
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
context['event'] = event
context['page_title'] = 'Create Risk Assessment for Event {}'.format(event.display_id)
return context
def get_success_url(self):
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
class EventRiskAssessmentEdit(generic.UpdateView):
model = models.RiskAssessment
template_name = 'risk_assessment_form.html'
form_class = forms.EventRiskAssessmentForm
def get_success_url(self):
ra = self.get_object()
ra.reviewed_by = None
ra.reviewed_at = None
ra.save()
return reverse_lazy('ra_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentEdit, self).get_context_data(**kwargs)
rpk = self.kwargs.get('pk')
ra = models.RiskAssessment.objects.get(pk=rpk)
context['event'] = ra.event
context['edit'] = True
context['page_title'] = 'Edit Risk Assessment for Event {}'.format(ra.event.display_id)
return context
class EventRiskAssessmentDetail(generic.DetailView):
model = models.RiskAssessment
template_name = 'risk_assessment_detail.html'
class EventRiskAssessmentList(generic.ListView):
paginate_by = 20
model = models.RiskAssessment
template_name = 'hs_object_list.html'
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
context['title'] = 'Risk Assessment'
context['view'] = 'ra_detail'
context['edit'] = 'ra_edit'
context['review'] = 'ra_review'
context['perm'] = 'perms.RIGS.review_riskassessment'
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
return context
class EventRiskAssessmentReview(generic.View):
def get(self, *args, **kwargs):
rpk = kwargs.get('pk')
ra = models.RiskAssessment.objects.get(pk=rpk)
with reversion.create_revision():
reversion.set_user(self.request.user)
ra.reviewed_by = self.request.user
ra.reviewed_at = timezone.now()
ra.save()
return HttpResponseRedirect(reverse_lazy('ra_list'))
class EventChecklistDetail(generic.DetailView):
model = models.EventChecklist
template_name = 'event_checklist_detail.html'
def get_context_data(self, **kwargs):
context = super(EventChecklistDetail, self).get_context_data(**kwargs)
context['page_title'] = "Event Checklist for Event {} {}".format(self.object.event.display_id, self.object.event.name)
return context
class EventChecklistEdit(generic.UpdateView):
model = models.EventChecklist
template_name = 'event_checklist_form.html'
form_class = forms.EventChecklistForm
def get_success_url(self):
ec = self.get_object()
ec.reviewed_by = None
ec.reviewed_at = None
ec.save()
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(EventChecklistEdit, self).get_context_data(**kwargs)
pk = self.kwargs.get('pk')
ec = models.EventChecklist.objects.get(pk=pk)
context['event'] = ec.event
context['edit'] = True
context['page_title'] = 'Edit Event Checklist for Event {}'.format(ec.event.display_id)
form = context['form']
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
for field, model in form.related_models.items():
value = form[field].value()
if value is not None and value != '':
context[field] = model.objects.get(pk=value)
return context
class EventChecklistCreate(generic.CreateView):
model = models.EventChecklist
template_name = 'event_checklist_form.html'
form_class = forms.EventChecklistForm
# From both business logic and programming POVs, RAs must exist before ECs!
def get(self, *args, **kwargs):
epk = kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
# Check if RA exists
ra = models.RiskAssessment.objects.filter(event=event).first()
if ra is None:
messages.error(self.request, 'A Risk Assessment must exist prior to creating any Event Checklists for {}! Please create one now.'.format(event))
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
return super(EventChecklistCreate, self).get(self)
def get_form(self, **kwargs):
form = super(EventChecklistCreate, self).get_form(**kwargs)
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
form.instance.event = event
return form
def get_context_data(self, **kwargs):
context = super(EventChecklistCreate, self).get_context_data(**kwargs)
epk = self.kwargs.get('pk')
event = models.Event.objects.get(pk=epk)
context['event'] = event
context['page_title'] = 'Create Event Checklist for Event {}'.format(event.display_id)
return context
def get_success_url(self):
return reverse_lazy('ec_detail', kwargs={'pk': self.object.pk})
class EventChecklistList(generic.ListView):
paginate_by = 20
model = models.EventChecklist
template_name = 'hs_object_list.html'
def get_context_data(self, **kwargs):
context = super(EventChecklistList, self).get_context_data(**kwargs)
context['title'] = 'Event Checklist'
context['view'] = 'ec_detail'
context['edit'] = 'ec_edit'
context['review'] = 'ec_review'
context['perm'] = 'perms.RIGS.review_eventchecklist'
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
return context
class EventChecklistReview(generic.View):
def get(self, *args, **kwargs):
rpk = kwargs.get('pk')
ec = models.EventChecklist.objects.get(pk=rpk)
with reversion.create_revision():
reversion.set_user(self.request.user)
ec.reviewed_by = self.request.user
ec.reviewed_at = timezone.now()
ec.save()
return HttpResponseRedirect(reverse_lazy('ec_list'))
class HSList(generic.ListView):
paginate_by = 20
model = models.Event
template_name = 'hs_list.html'
def get_queryset(self):
return models.Event.objects.all().order_by('-start_date')
def get_context_data(self, **kwargs):
context = super(HSList, self).get_context_data(**kwargs)
context['page_title'] = 'H&S Overview'
return context

View File

@@ -102,7 +102,7 @@ class CalendarICS(ICalFeed):
return item.earliest_time
def item_end_datetime(self, item):
if type(item.latest_time) == datetime.date: # Ical end_datetime is non-inclusive, so add a day
if isinstance(item.latest_time, datetime.date): # Ical end_datetime is non-inclusive, so add a day
return item.latest_time + datetime.timedelta(days=1)
return item.latest_time

View File

@@ -20,6 +20,7 @@ class Command(BaseCommand):
keyholder_group = None
finance_group = None
hs_group = None
def handle(self, *args, **options):
from django.conf import settings
@@ -165,6 +166,7 @@ class Command(BaseCommand):
def setupGroups(self):
self.keyholder_group = Group.objects.create(name='Keyholders')
self.finance_group = Group.objects.create(name='Finance')
self.hs_group = Group.objects.create(name='H&S')
keyholderPerms = ["add_event", "change_event", "view_event",
"add_eventitem", "change_eventitem", "delete_eventitem",
@@ -172,10 +174,17 @@ class Command(BaseCommand):
"add_person", "change_person", "view_person", "view_profile",
"add_venue", "change_venue", "view_venue",
"add_asset", "change_asset", "delete_asset",
"asset_finance", "view_asset", "view_supplier", "asset_finance",
"add_supplier"]
"view_asset", "view_supplier", "change_supplier", "asset_finance",
"add_supplier", "view_cabletype", "change_cabletype",
"add_cabletype", "view_eventchecklist", "change_eventchecklist",
"add_eventchecklist", "view_riskassessment", "change_riskassessment",
"add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew",
"delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle",
"change_eventchecklistvehicle",
"delete_eventchecklistvehicle", "view_eventchecklistvehicle", ]
financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
"add_payment", "change_payment", "delete_payment"]
hsPerms = keyholderPerms + ["review_riskassessment", "review_eventchecklist"]
for permId in keyholderPerms:
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
@@ -183,6 +192,9 @@ class Command(BaseCommand):
for permId in financePerms:
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
for permId in hsPerms:
self.hs_group.permissions.add(Permission.objects.get(codename=permId))
def setupGenericProfiles(self):
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble",
"Jack Harkness", "Mickey Smith", "Rose Tyler"]
@@ -207,21 +219,29 @@ class Command(BaseCommand):
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
initials="FU",
email="financeuser@example.com", is_active=True)
email="financeuser@example.com", is_active=True, is_approved=True)
financeUser.groups.add(self.finance_group)
financeUser.groups.add(self.keyholder_group)
financeUser.set_password('finance')
financeUser.save()
hsUser = models.Profile.objects.create(username="hs", first_name="HS", last_name="User",
initials="HSU",
email="hsuser@example.com", is_active=True, is_approved=True)
hsUser.groups.add(self.hs_group)
hsUser.groups.add(self.keyholder_group)
hsUser.set_password('hs')
hsUser.save()
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
initials="KU",
email="keyholderuser@example.com", is_active=True)
email="keyholderuser@example.com", is_active=True, is_approved=True)
keyholderUser.groups.add(self.keyholder_group)
keyholderUser.set_password('keyholder')
keyholderUser.save()
basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU",
email="basicuser@example.com", is_active=True)
email="basicuser@example.com", is_active=True, is_approved=True)
basicUser.set_password('basic')
basicUser.save()

View File

@@ -0,0 +1,190 @@
# Generated by Django 3.1.2 on 2021-01-23 19:10
import RIGS.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0038_auto_20200306_2000'),
]
operations = [
migrations.CreateModel(
name='EventChecklist',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField()),
('safe_parking', models.BooleanField(blank=True, help_text='Vehicles parked safely?<br><small>(does not obstruct venue access)</small>', null=True)),
('safe_packing', models.BooleanField(blank=True, help_text='Equipment packed away safely?<br><small>(including flightcases)</small>', null=True)),
('exits', models.BooleanField(blank=True, help_text='Emergency exits clear?', null=True)),
('trip_hazard', models.BooleanField(blank=True, help_text='Appropriate barriers around kit and cabling secured?', null=True)),
('warning_signs', models.BooleanField(blank=True, help_text='Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>')),
('ear_plugs', models.BooleanField(blank=True, help_text='Ear plugs issued to crew where needed?', null=True)),
('hs_location', models.CharField(blank=True, help_text='Location of Safety Bag/Box', max_length=255, null=True)),
('extinguishers_location', models.CharField(blank=True, help_text='Location of fire extinguishers', max_length=255, null=True)),
('rcds', models.BooleanField(blank=True, help_text='RCDs installed where needed and tested?', null=True)),
('supply_test', models.BooleanField(blank=True, help_text='Electrical supplies tested?<br><small>(using socket tester)</small>', null=True)),
('earthing', models.BooleanField(blank=True, help_text='Equipment appropriately earthed?<br><small>(truss, stage, generators etc)</small>', null=True)),
('pat', models.BooleanField(blank=True, help_text='All equipment in PAT period?', null=True)),
('source_rcd', models.BooleanField(blank=True, help_text='Source RCD protected?<br><small>(if cable is more than 3m long) </small>', null=True)),
('labelling', models.BooleanField(blank=True, help_text='Appropriate and clear labelling on distribution and cabling?', null=True)),
('fd_voltage_l1', models.IntegerField(blank=True, help_text='L1 - N', null=True, verbose_name='First Distro Voltage L1-N')),
('fd_voltage_l2', models.IntegerField(blank=True, help_text='L2 - N', null=True, verbose_name='First Distro Voltage L2-N')),
('fd_voltage_l3', models.IntegerField(blank=True, help_text='L3 - N', null=True, verbose_name='First Distro Voltage L3-N')),
('fd_phase_rotation', models.BooleanField(blank=True, help_text='Phase Rotation<br><small>(if required)</small>', null=True, verbose_name='Phase Rotation')),
('fd_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True, verbose_name='Earth Fault Loop Impedance')),
('fd_pssc', models.IntegerField(blank=True, help_text='Prospective Short Circuit Current', null=True, verbose_name='PSCC')),
('w1_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
('w1_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
('w1_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
('w1_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
('w2_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
('w2_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
('w2_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
('w2_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
('w3_description', models.CharField(blank=True, help_text='Description', max_length=255, null=True)),
('w3_polarity', models.BooleanField(blank=True, help_text='Polarity Checked?', null=True)),
('w3_voltage', models.IntegerField(blank=True, help_text='Voltage', null=True)),
('w3_earth_fault', models.IntegerField(blank=True, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', null=True)),
('all_rcds_tested', models.BooleanField(blank=True, help_text='All circuit RCDs tested?<br><small>(using test button)</small>', null=True)),
('public_sockets_tested', models.BooleanField(blank=True, help_text='Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>', null=True)),
('reviewed_at', models.DateTimeField(null=True)),
],
options={
'ordering': ['event'],
'permissions': [('review_eventchecklist', 'Can review Event Checklists')],
},
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='EventChecklistCrew',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(max_length=255)),
('start', models.DateTimeField()),
('end', models.DateTimeField()),
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')),
],
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='EventChecklistVehicle',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vehicle', models.CharField(max_length=255)),
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')),
],
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='RiskAssessment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nonstandard_equipment', models.BooleanField(help_text="Does the event require any hired in equipment or use of equipment that is not covered by <a href='https://nottinghamtec.sharepoint.com/:f:/g/HealthAndSafety/Eo4xED_DrqFFsfYIjKzMZIIB6Gm_ZfR-a8l84RnzxtBjrA?e=Bf0Haw'>TEC's standard risk assessments and method statements?</a>")),
('nonstandard_use', models.BooleanField(help_text='Are TEC using their equipment in a way that is abnormal?<br><small>i.e. Not covered by TECs standard health and safety documentation</small>')),
('contractors', models.BooleanField(help_text='Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>')),
('other_companies', models.BooleanField(help_text='Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>')),
('crew_fatigue', models.BooleanField(help_text='Is crew fatigue likely to be a risk at any point during this event?')),
('general_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
('big_power', models.BooleanField(help_text='Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?')),
('outside', models.BooleanField(help_text='Is the event outdoors?')),
('generators', models.BooleanField(help_text='Will generators be used?')),
('other_companies_power', models.BooleanField(help_text='Will TEC be supplying power to any other companies?')),
('nonstandard_equipment_power', models.BooleanField(help_text='Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?')),
('multiple_electrical_environments', models.BooleanField(help_text='Will the electrical installation occupy more than one electrical environment?')),
('power_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
('power_plan', models.URLField(blank=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", null=True, validators=[RIGS.models.validate_url])),
('noise_monitoring', models.BooleanField(help_text='Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?')),
('sound_notes', models.TextField(blank=True, help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?', null=True)),
('known_venue', models.BooleanField(help_text='Is this venue new to you (the MIC) or new to TEC?')),
('safe_loading', models.BooleanField(help_text='Are there any issues preventing a safe load in or out? (e.g. sufficient lighting, flat, not in a crowded area etc.)')),
('safe_storage', models.BooleanField(help_text='Are there any problems with safe and secure equipment storage?')),
('area_outside_of_control', models.BooleanField(help_text="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?")),
('barrier_required', models.BooleanField(help_text='Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?')),
('nonstandard_emergency_procedure', models.BooleanField(help_text="Does the emergency procedure for the event differ from TEC's standard procedures?")),
('special_structures', models.BooleanField(help_text='Does the event require use of winch stands, motors, MPT Towers, or staging?')),
('suspended_structures', models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")),
('persons_responsible_structures', models.TextField(blank=True, help_text='Who are the persons on site responsible for their use?', null=True)),
('rigging_plan', models.URLField(blank=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", null=True, validators=[RIGS.models.validate_url])),
('reviewed_at', models.DateTimeField(null=True)),
('supervisor_consulted', models.BooleanField(null=True)),
],
options={
'ordering': ['event'],
'permissions': [('review_riskassessment', 'Can review Risk Assessments')],
},
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.RemoveField(
model_name='eventcrew',
name='event',
),
migrations.RemoveField(
model_name='eventcrew',
name='user',
),
migrations.DeleteModel(
name='RIGSVersion',
),
migrations.RemoveField(
model_name='event',
name='risk_assessment_edit_url',
),
migrations.AlterField(
model_name='profile',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
migrations.DeleteModel(
name='EventCrew',
),
migrations.AddField(
model_name='riskassessment',
name='event',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='RIGS.event'),
),
migrations.AddField(
model_name='riskassessment',
name='power_mic',
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='power_mic', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
),
migrations.AddField(
model_name='riskassessment',
name='reviewed_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
),
migrations.AddField(
model_name='eventchecklistvehicle',
name='driver',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='eventchecklistcrew',
name='crewmember',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crewed', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='eventchecklist',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to='RIGS.event'),
),
migrations.AddField(
model_name='eventchecklist',
name='power_mic',
field=models.ForeignKey(blank=True, help_text='Who is the Power MIC?', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='checklists', to=settings.AUTH_USER_MODEL, verbose_name='Power MIC'),
),
migrations.AddField(
model_name='eventchecklist',
name='reviewed_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Reviewer'),
),
migrations.AddField(
model_name='eventchecklist',
name='venue',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='RIGS.venue'),
),
]

View File

@@ -1,16 +0,0 @@
# Generated by Django 3.0.3 on 2020-03-18 00:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0038_auto_20200306_2000'),
]
operations = [
migrations.DeleteModel(
name='EventCrew',
),
]

View File

@@ -1,16 +0,0 @@
# Generated by Django 3.0.3 on 2020-04-15 18:40
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0039_delete_eventcrew'),
]
operations = [
migrations.DeleteModel(
name='RIGSVersion',
),
]

View File

@@ -1,8 +1,8 @@
import datetime
import hashlib
import datetime
import pytz
from django import forms
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.conf import settings
@@ -19,6 +19,8 @@ from decimal import Decimal
from django.core.exceptions import ValidationError
from django.urls import reverse_lazy
from urllib.parse import urlparse
class Profile(AbstractUser):
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
@@ -65,8 +67,15 @@ class Profile(AbstractUser):
def __str__(self):
return self.name
# TODO move to versioning - currently get import errors with that
class RevisionMixin(object):
@property
def is_first_version(self):
versions = Version.objects.get_for_object(self)
return len(versions) == 1
@property
def current_version(self):
version = Version.objects.get_for_object(self).select_related('revision').first()
@@ -189,6 +198,8 @@ class VatRate(models.Model, RevisionMixin):
objects = VatManager()
reversion_hide = True
@property
def as_percent(self):
return self.rate * 100
@@ -267,9 +278,7 @@ class EventManager(models.Manager):
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True, is_rig=True) & (
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) # Active dry hire GT
status=Event.CANCELLED)) # Active dry hire
).count()
return event_count
@@ -326,8 +335,12 @@ class Event(models.Model, RevisionMixin):
auth_request_at = models.DateTimeField(null=True, blank=True)
auth_request_to = models.EmailField(null=True, blank=True)
# Risk assessment info
risk_assessment_edit_url = models.CharField(verbose_name="risk assessment", max_length=255, blank=True, null=True)
@property
def display_id(self):
if self.is_rig:
return str("N%05d" % self.pk)
else:
return self.pk
# Calculated values
"""
@@ -336,17 +349,6 @@ class Event(models.Model, RevisionMixin):
@property
def sum_total(self):
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
# if connection.vendor == 'postgresql':
# sql = "SELECT SUM(quantity * cost) AS sum_total FROM \"RIGS_eventitem\" WHERE event_id=%i" % self.id
# else:
# sql = "SELECT id, SUM(quantity * cost) AS sum_total FROM RIGS_eventitem WHERE event_id=%i" % self.id
# total = self.items.raw(sql)[0]
# if total.sum_total:
# return total.sum_total
# total = 0.0
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
# total += item.sum
total = EventItem.objects.filter(event=self).aggregate(
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
output_field=models.DecimalField(max_digits=10, decimal_places=2))
@@ -380,8 +382,8 @@ class Event(models.Model, RevisionMixin):
return (self.status == self.BOOKED or self.status == self.CONFIRMED)
@property
def authorised(self):
return not self.internal and self.purchase_order or self.authorisation.amount == self.total
def hs_done(self):
return self.riskassessment is not None and len(self.checklists.all()) > 0
@property
def has_start_time(self):
@@ -445,7 +447,14 @@ class Event(models.Model, RevisionMixin):
@property
def internal(self):
return self.organisation and self.organisation.union_account
return bool(self.organisation and self.organisation.union_account)
@property
def authorised(self):
if self.internal:
return self.authorisation.amount == self.total
else:
return bool(self.purchase_order)
objects = EventManager()
@@ -453,22 +462,26 @@ class Event(models.Model, RevisionMixin):
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
def __str__(self):
return str(self.pk) + ": " + self.name
return "{}: {}".format(self.display_id, self.name)
def clean(self):
errdict = {}
if self.end_date and self.start_date > self.end_date:
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
errdict['end_date'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
startEndSameDay = not self.end_date or self.end_date == self.start_date
hasStartAndEnd = self.has_start_time and self.has_end_time
if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time:
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
errdict['end_time'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.']
if self.access_at is not None:
if self.access_at.date() > self.start_date:
raise ValidationError('Regardless of what some clients might think, access time cannot be after the event has started.')
errdict['access_at'] = ['Regardless of what some clients might think, access time cannot be after the event has started.']
elif self.start_time is not None and self.start_date == self.access_at.date() and self.access_at.time() > self.start_time:
raise ValidationError('Regardless of what some clients might think, access time cannot be after the event has started.')
errdict['access_at'] = ['Regardless of what some clients might think, access time cannot be after the event has started.']
if errdict != {}: # If there was an error when validation
raise ValidationError(errdict)
def save(self, *args, **kwargs):
"""Call :meth:`full_clean` before saving."""
@@ -476,7 +489,8 @@ class Event(models.Model, RevisionMixin):
super(Event, self).save(*args, **kwargs)
class EventItem(models.Model):
@reversion.register
class EventItem(models.Model, RevisionMixin):
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
@@ -484,6 +498,8 @@ class EventItem(models.Model):
cost = models.DecimalField(max_digits=10, decimal_places=2)
order = models.IntegerField()
reversion_hide = True
@property
def total_cost(self):
return self.cost * self.quantity
@@ -494,6 +510,10 @@ class EventItem(models.Model):
def __str__(self):
return str(self.event.pk) + "." + str(self.order) + ": " + self.event.name + " | " + self.name
@property
def activity_feed_string(self):
return str("item {}".format(self.name))
@reversion.register
class EventAuthorisation(models.Model, RevisionMixin):
@@ -510,15 +530,17 @@ class EventAuthorisation(models.Model, RevisionMixin):
@property
def activity_feed_string(self):
return str("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')')
return "{} (requested by {})".format(self.event.display_id, self.sent_by.initials)
@reversion.register(follow=['payment_set'])
class Invoice(models.Model):
class Invoice(models.Model, RevisionMixin):
event = models.OneToOneField('Event', on_delete=models.CASCADE)
invoice_date = models.DateField(auto_now_add=True)
void = models.BooleanField(default=False)
reversion_perm = 'RIGS.view_invoice'
@property
def sum_total(self):
return self.event.sum_total
@@ -542,14 +564,26 @@ class Invoice(models.Model):
def is_closed(self):
return self.balance == 0 or self.void
def get_absolute_url(self):
return reverse_lazy('invoice_detail', kwargs={'pk': self.pk})
@property
def activity_feed_string(self):
return "#{} for Event {}".format(self.display_id, "N%05d" % self.event.pk)
def __str__(self):
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
@property
def display_id(self):
return "{:05d}".format(self.pk)
class Meta:
ordering = ['-invoice_date']
class Payment(models.Model):
@reversion.register
class Payment(models.Model, RevisionMixin):
CASH = 'C'
INTERNAL = 'I'
EXTERNAL = 'E'
@@ -568,5 +602,239 @@ class Payment(models.Model):
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)
reversion_hide = True
def __str__(self):
return "%s: %d" % (self.get_method_display(), self.amount)
@property
def activity_feed_string(self):
return str("payment of £{}".format(self.amount))
def validate_url(value):
if not value:
return # Required error is done the field
obj = urlparse(value)
if obj.hostname not in ('nottinghamtec.sharepoint.com'):
raise ValidationError('URL must point to a location on the TEC Sharepoint')
@reversion.register
class RiskAssessment(models.Model, RevisionMixin):
SMALL = (0, 'Small')
MEDIUM = (1, 'Medium')
LARGE = (2, 'Large')
SIZES = (SMALL, MEDIUM, LARGE)
event = models.OneToOneField('Event', on_delete=models.CASCADE)
# General
nonstandard_equipment = models.BooleanField(help_text="Does the event require any hired in equipment or use of equipment that is not covered by <a href='https://nottinghamtec.sharepoint.com/:f:/g/HealthAndSafety/Eo4xED_DrqFFsfYIjKzMZIIB6Gm_ZfR-a8l84RnzxtBjrA?e=Bf0Haw'>"
"TEC's standard risk assessments and method statements?</a>")
nonstandard_use = models.BooleanField(help_text="Are TEC using their equipment in a way that is abnormal?<br><small>i.e. Not covered by TECs standard health and safety documentation</small>")
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
general_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
# Power
# event_size = models.IntegerField(blank=True, null=True, choices=SIZES)
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
# If yes to the above two, you must answer...
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)")
outside = models.BooleanField(help_text="Is the event outdoors?")
generators = models.BooleanField(help_text="Will generators be used?")
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
power_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
power_plan = models.URLField(blank=True, null=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
# Sound
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
sound_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
# Site
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
safe_loading = models.BooleanField(help_text="Are there any issues preventing a safe load in or out? (e.g. sufficient lighting, flat, not in a crowded area etc.)")
safe_storage = models.BooleanField(help_text="Are there any problems with safe and secure equipment storage?")
area_outside_of_control = models.BooleanField(help_text="Is any part of the work area out of TEC's direct control or openly accessible during the build or breakdown period?")
barrier_required = models.BooleanField(help_text="Is there a requirement for TEC to provide any barrier for security or protection of persons/equipment?")
nonstandard_emergency_procedure = models.BooleanField(help_text="Does the emergency procedure for the event differ from TEC's standard procedures?")
# Structures
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
persons_responsible_structures = models.TextField(blank=True, null=True, help_text="Who are the persons on site responsible for their use?")
rigging_plan = models.URLField(blank=True, null=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
# Blimey that was a lot of options
reviewed_at = models.DateTimeField(null=True)
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
verbose_name="Reviewer", on_delete=models.CASCADE)
supervisor_consulted = models.BooleanField(null=True)
expected_values = {
'nonstandard_equipment': False,
'nonstandard_use': False,
'contractors': False,
'other_companies': False,
'crew_fatigue': False,
'big_power': False,
'generators': False,
'other_companies_power': False,
'nonstandard_equipment_power': False,
'multiple_electrical_environments': False,
'noise_monitoring': False,
'known_venue': False,
'safe_loading': False,
'safe_storage': False,
'area_outside_of_control': False,
'barrier_required': False,
'nonstandard_emergency_procedure': False,
'special_structures': False,
'suspended_structures': False,
}
inverted_fields = {key: value for (key, value) in expected_values.items() if not value}.keys()
def clean(self):
# Check for idiots
if not self.outside and self.generators:
raise forms.ValidationError("Engage brain, please. <strong>No generators indoors!(!)</strong>")
class Meta:
ordering = ['event']
permissions = [
('review_riskassessment', 'Can review Risk Assessments')
]
@property
def event_size(self):
# Confirm event size. Check all except generators, since generators entails outside
if self.outside or self.other_companies_power or self.nonstandard_equipment_power or self.multiple_electrical_environments:
return self.LARGE[0]
elif self.big_power:
return self.MEDIUM[0]
else:
return self.SMALL[0]
@property
def activity_feed_string(self):
return str(self.event)
def get_absolute_url(self):
return reverse_lazy('ra_detail', kwargs={'pk': self.pk})
def __str__(self):
return "%i - %s" % (self.pk, self.event)
@reversion.register(follow=['vehicles', 'crew'])
class EventChecklist(models.Model, RevisionMixin):
event = models.ForeignKey('Event', related_name='checklists', on_delete=models.CASCADE)
# General
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='checklists',
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC?")
venue = models.ForeignKey('Venue', on_delete=models.CASCADE)
date = models.DateField()
# Safety Checks
safe_parking = models.BooleanField(blank=True, null=True, help_text="Vehicles parked safely?<br><small>(does not obstruct venue access)</small>")
safe_packing = models.BooleanField(blank=True, null=True, help_text="Equipment packed away safely?<br><small>(including flightcases)</small>")
exits = models.BooleanField(blank=True, null=True, help_text="Emergency exits clear?")
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
hs_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of Safety Bag/Box")
extinguishers_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of fire extinguishers")
# Small Electrical Checks
rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?")
supply_test = models.BooleanField(blank=True, null=True, help_text="Electrical supplies tested?<br><small>(using socket tester)</small>")
# Shared electrical checks
earthing = models.BooleanField(blank=True, null=True, help_text="Equipment appropriately earthed?<br><small>(truss, stage, generators etc)</small>")
pat = models.BooleanField(blank=True, null=True, help_text="All equipment in PAT period?")
# Medium Electrical Checks
source_rcd = models.BooleanField(blank=True, null=True, help_text="Source RCD protected?<br><small>(if cable is more than 3m long) </small>")
labelling = models.BooleanField(blank=True, null=True, help_text="Appropriate and clear labelling on distribution and cabling?")
# First Distro
fd_voltage_l1 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L1-N", help_text="L1 - N")
fd_voltage_l2 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L2-N", help_text="L2 - N")
fd_voltage_l3 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L3-N", help_text="L3 - N")
fd_phase_rotation = models.BooleanField(blank=True, null=True, verbose_name="Phase Rotation", help_text="Phase Rotation<br><small>(if required)</small>")
fd_earth_fault = models.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
# Worst case points
w1_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w1_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w1_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w2_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w2_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w3_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w3_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>")
public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>")
reviewed_at = models.DateTimeField(null=True)
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
verbose_name="Reviewer", on_delete=models.CASCADE)
inverted_fields = []
class Meta:
ordering = ['event']
permissions = [
('review_eventchecklist', 'Can review Event Checklists')
]
@property
def activity_feed_string(self):
return str(self.event)
def get_absolute_url(self):
return reverse_lazy('ec_detail', kwargs={'pk': self.pk})
def __str__(self):
return "%i - %s" % (self.pk, self.event)
@reversion.register
class EventChecklistVehicle(models.Model, RevisionMixin):
checklist = models.ForeignKey('EventChecklist', related_name='vehicles', blank=True, on_delete=models.CASCADE)
vehicle = models.CharField(max_length=255)
driver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='vehicles', on_delete=models.CASCADE)
reversion_hide = True
def __str__(self):
return "{} driven by {}".format(self.vehicle, str(self.driver))
@reversion.register
class EventChecklistCrew(models.Model, RevisionMixin):
checklist = models.ForeignKey('EventChecklist', related_name='crew', blank=True, on_delete=models.CASCADE)
crewmember = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='crewed', on_delete=models.CASCADE)
role = models.CharField(max_length=255)
start = models.DateTimeField()
end = models.DateTimeField()
reversion_hide = True
def clean(self):
if self.start > self.end:
raise ValidationError('Unless you\'ve invented time travel, crew can\'t finish before they have started.')
def __str__(self):
return "{} ({})".format(str(self.crewmember), self.role)

View File

@@ -6,12 +6,13 @@ import urllib.parse
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.views import generic
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.template import RequestContext
from django.template.loader import get_template
from django.conf import settings
from django.urls import reverse
from django.urls import reverse_lazy
from django.core import signing
from django.http import HttpResponse
from django.core.exceptions import SuspiciousOperation
@@ -19,6 +20,7 @@ from django.db.models import Q
from django.contrib import messages
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone
from z3c.rml import rml2pdf
from PyPDF2 import PdfFileMerger, PdfFileReader
import simplejson
@@ -42,6 +44,7 @@ class RigboardIndex(generic.TemplateView):
# call out method to get current events
context['events'] = models.Event.objects.current_events()
context['page_title'] = "Rigboard"
return context
@@ -82,26 +85,6 @@ class EventEmbed(EventDetail):
template_name = 'event_embed.html'
class EventRA(generic.base.RedirectView):
permanent = False
def get_redirect_url(self, *args, **kwargs):
event = get_object_or_404(models.Event, pk=kwargs['pk'])
if event.risk_assessment_edit_url:
return event.risk_assessment_edit_url
params = {
'entry.708610078': f'N{event.pk:05}',
'entry.905899507': event.name,
'entry.139491562': event.venue.name if event.venue else '',
'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + (
(' - ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''),
'entry.902421165': event.mic.name if event.mic else ''
}
return settings.RISK_ASSESSMENT_URL + "?" + urllib.parse.urlencode(params)
class EventCreate(generic.CreateView):
model = models.Event
form_class = forms.EventForm
@@ -109,11 +92,12 @@ class EventCreate(generic.CreateView):
def get_context_data(self, **kwargs):
context = super(EventCreate, self).get_context_data(**kwargs)
context['page_title'] = "New Event"
context['edit'] = True
context['currentVAT'] = models.VatRate.objects.current_rate()
form = context['form']
if re.search(r'"-\d+"', form['items_json'].value()):
if hasattr(form, 'items_json') and re.search(r'"-\d+"', form['items_json'].value()):
messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.")
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
@@ -134,6 +118,7 @@ class EventUpdate(generic.UpdateView):
def get_context_data(self, **kwargs):
context = super(EventUpdate, self).get_context_data(**kwargs)
context['page_title'] = "Event {}".format(self.object.display_id)
context['edit'] = True
form = context['form']
@@ -147,7 +132,7 @@ class EventUpdate(generic.UpdateView):
return context
def render_to_response(self, context, **response_kwargs):
if not hasattr(context, 'duplicate'):
if hasattr(context, 'duplicate') and not context['duplicate']:
# If this event has already been emailed to a client, show a warning
if self.object.auth_request_at is not None:
messages.info(self.request,
@@ -155,7 +140,7 @@ class EventUpdate(generic.UpdateView):
if hasattr(self.object, 'authorised'):
messages.warning(self.request,
'This event has already been authorised by client, any changes to price will require reauthorisation.')
'This event has already been authorised by the client, any changes to the price will require reauthorisation.')
return super(EventUpdate, self).render_to_response(context, **response_kwargs)
def get_success_url(self):
@@ -168,6 +153,7 @@ class EventDuplicate(EventUpdate):
new = copy.copy(old) # Make a copy of the object in memory
new.based_on = old # Make the new event based on the old event
new.purchase_order = None # Remove old PO
new.status = new.PROVISIONAL # Return status to provisional
# Clear checked in by if it's a dry hire
if new.dry_hire is True:
@@ -188,6 +174,7 @@ class EventDuplicate(EventUpdate):
def get_context_data(self, **kwargs):
context = super(EventDuplicate, self).get_context_data(**kwargs)
context['page_title'] = "Duplicate of Event {}".format(self.object.display_id)
context["duplicate"] = True
return context
@@ -209,6 +196,7 @@ class EventPrint(generic.View):
},
'quote': True,
'current_user': request.user,
'filename': 'Event {} {} {}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
}
rml = template.render(context)
@@ -223,10 +211,7 @@ class EventPrint(generic.View):
merger.write(merged)
response = HttpResponse(content_type='application/pdf')
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
response['Content-Disposition'] = "filename=N%05d | %s.pdf" % (object.pk, escapedEventName)
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
response.write(merged.getvalue())
return response
@@ -242,6 +227,8 @@ class EventArchive(generic.ListView):
context['start'] = self.request.GET.get('start', None)
context['end'] = self.request.GET.get('end', datetime.date.today().strftime('%Y-%m-%d'))
context['statuses'] = models.Event.EVENT_STATUS_CHOICES
context['page_title'] = 'Event Archive'
return context
def get_queryset(self):
@@ -281,6 +268,11 @@ class EventArchive(generic.ListView):
filter &= qfilter
status = self.request.GET.getlist('status', "")
if len(status) > 0:
filter &= Q(status__in=status)
qs = self.model.objects.filter(filter).order_by('-start_date')
# Preselect related for efficiency
@@ -318,8 +310,10 @@ class EventAuthorise(generic.UpdateView):
def get_context_data(self, **kwargs):
context = super(EventAuthorise, self).get_context_data(**kwargs)
context['event'] = self.event
context['tos_url'] = settings.TERMS_OF_HIRE_URL
context['page_title'] = "{}: {}".format(self.event.display_id, self.event.name)
if self.event.dry_hire:
context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>'
return context
def get(self, request, *args, **kwargs):
@@ -382,7 +376,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
email = form.cleaned_data['email']
event = self.object
event.auth_request_by = self.request.user
event.auth_request_at = datetime.datetime.now()
event.auth_request_at = timezone.now()
event.auth_request_to = email
event.save()
@@ -437,27 +431,3 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
})
context['to_name'] = self.request.GET.get('to_name', None)
return context
@method_decorator(csrf_exempt, name='dispatch')
class LogRiskAssessment(generic.View):
http_method_names = ["post"]
def post(self, request, **kwargs):
data = request.POST
shared_secret = data.get("secret")
edit_url = data.get("editUrl")
rig_number = data.get("rigNum")
if shared_secret is None or edit_url is None or rig_number is None:
return HttpResponse(status=422)
if shared_secret != settings.RISK_ASSESSMENT_SECRET:
return HttpResponse(status=403)
rig_number = int(re.sub("[^0-9]", "", rig_number))
event = get_object_or_404(models.Event, pk=rig_number)
event.risk_assessment_edit_url = edit_url
event.save()
return HttpResponse(status=200)

View File

@@ -10,6 +10,7 @@ from PyPDF2 import PdfFileReader, PdfFileMerger
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.core.cache import cache
from django.template.loader import get_template
from django.urls import reverse
from django.utils import timezone
@@ -18,6 +19,7 @@ from premailer import Premailer
from z3c.rml import rml2pdf
from RIGS import models
from reversion import revisions as reversion
def send_eventauthorisation_success_email(instance):
@@ -138,3 +140,11 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
user_activated.connect(send_admin_awaiting_approval_email)
def update_cache(sender, instance, created, **kwargs):
cache.clear()
for model in reversion.get_registered_models():
post_save.connect(update_cache, sender=model)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

269
RIGS/static/css/main.css Normal file

File diff suppressed because one or more lines are too long

1047
RIGS/static/css/main.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
/*!
* Bootstrap alert.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* 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=e||self).Alert=t(e.jQuery,e.Util)}(this,(function(e,t){"use strict";function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}e=e&&e.hasOwnProperty("default")?e.default:e,t=t&&t.hasOwnProperty("default")?t.default:t;var r=e.fn.alert,o={CLOSE:"close.bs.alert",CLOSED:"closed.bs.alert",CLICK_DATA_API:"click.bs.alert.data-api"},i="alert",l="fade",s="show",a=function(){function r(e){this._element=e}var a,u,f,c=r.prototype;return c.close=function(e){var t=this._element;e&&(t=this._getRootElement(e)),this._triggerCloseEvent(t).isDefaultPrevented()||this._removeElement(t)},c.dispose=function(){e.removeData(this._element,"bs.alert"),this._element=null},c._getRootElement=function(n){var r=t.getSelectorFromElement(n),o=!1;return r&&(o=document.querySelector(r)),o||(o=e(n).closest("."+i)[0]),o},c._triggerCloseEvent=function(t){var n=e.Event(o.CLOSE);return e(t).trigger(n),n},c._removeElement=function(n){var r=this;if(e(n).removeClass(s),e(n).hasClass(l)){var o=t.getTransitionDurationFromElement(n);e(n).one(t.TRANSITION_END,(function(e){return r._destroyElement(n,e)})).emulateTransitionEnd(o)}else this._destroyElement(n)},c._destroyElement=function(t){e(t).detach().trigger(o.CLOSED).remove()},r._jQueryInterface=function(t){return this.each((function(){var n=e(this),o=n.data("bs.alert");o||(o=new r(this),n.data("bs.alert",o)),"close"===t&&o[t](this)}))},r._handleDismiss=function(e){return function(t){t&&t.preventDefault(),e.close(this)}},a=r,f=[{key:"VERSION",get:function(){return"4.4.1"}}],(u=null)&&n(a.prototype,u),f&&n(a,f),r}();return e(document).on(o.CLICK_DATA_API,'[data-dismiss="alert"]',a._handleDismiss(new a)),e.fn.alert=a._jQueryInterface,e.fn.alert.Constructor=a,e.fn.alert.noConflict=function(){return e.fn.alert=r,a._jQueryInterface},a}));
!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}));

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
$(document).ready((function(){function e(e){targetObject=$("#"+e.attr("id")+"-update"),update_url=$("option:selected",e).data("update_url"),""==update_url?targetObject.attr("disabled",!0):(targetObject.attr("href",update_url),targetObject.attr("disabled",!1))}clearSelectionLabel="(no selection)",$(".selectpicker").each((function(){var t={ajax:{url:$(this).data("sourceurl"),type:"GET",dataType:"json",data:{term:"{{{q}}}"}},locale:{emptyTitle:""},clearOnEmpty:!1,preprocessData:function(e){var t,a=e.length,l=[];if(l.push({text:clearSelectionLabel,value:"",data:{update_url:"",subtext:""}}),a)for(t=0;t<a;t++)l.push($.extend(!0,e[t],{text:e[t].label,value:e[t].pk,data:{update_url:e[t].update,subtext:""}}));return l}};$(this).prepend($("<option></option>").attr("value","").text(clearSelectionLabel).data("update_url","")),$(this).selectpicker().ajaxSelectPicker(t),$(this).change((function(){e($(this))})),e($(this))})),$("#modal").on("hide.bs.modal",(function(e){null!=modaltarget&&""!=modalobject&&function(e,t,a,l){e.find("option").remove(),e.append($("<option></option>").attr("value",t).text(a).data("update_url",l)),e.selectpicker("render"),e.selectpicker("refresh"),e.selectpicker("val",t),e.change()}($(modaltarget),modalobject[0].pk,modalobject[0].fields.name,modalobject[0].update_url)}))}));
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)}))}));

7
RIGS/static/js/clipboard.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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"))})))}();

View 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"})})))}));

File diff suppressed because one or more lines are too long

2
RIGS/static/js/flatpickr.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
RIGS/static/js/main.js Normal file

File diff suppressed because one or more lines are too long

6
RIGS/static/js/main.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
/*!
* Bootstrap popover.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* 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(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("jquery"),require("./tooltip.js")):"function"==typeof define&&define.amd?define(["jquery","./tooltip.js"],t):(e=e||self).Popover=t(e.jQuery,e.Tooltip)}(this,(function(e,t){"use strict";function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}e=e&&e.hasOwnProperty("default")?e.default:e,t=t&&t.hasOwnProperty("default")?t.default:t;var u="popover",s=".bs.popover",c=e.fn[u],p=new RegExp("(^|\\s)bs-popover\\S+","g"),f=i({},t.Default,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'}),a=i({},t.DefaultType,{content:"(string|element|function)"}),l="fade",h="show",y=".popover-header",d=".popover-body",v={HIDE:"hide"+s,HIDDEN:"hidden"+s,SHOW:"show"+s,SHOWN:"shown"+s,INSERTED:"inserted"+s,CLICK:"click"+s,FOCUSIN:"focusin"+s,FOCUSOUT:"focusout"+s,MOUSEENTER:"mouseenter"+s,MOUSELEAVE:"mouseleave"+s},g=function(t){var r,o;function i(){return t.apply(this,arguments)||this}o=t,(r=i).prototype=Object.create(o.prototype),r.prototype.constructor=r,r.__proto__=o;var c,g,b,O=i.prototype;return O.isWithContent=function(){return this.getTitle()||this._getContent()},O.addAttachmentClass=function(t){e(this.getTipElement()).addClass("bs-popover-"+t)},O.getTipElement=function(){return this.tip=this.tip||e(this.config.template)[0],this.tip},O.setContent=function(){var t=e(this.getTipElement());this.setElementContent(t.find(y),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(t.find(d),n),t.removeClass(l+" "+h)},O._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},O._cleanTipClass=function(){var t=e(this.getTipElement()),n=t.attr("class").match(p);null!==n&&n.length>0&&t.removeClass(n.join(""))},i._jQueryInterface=function(t){return this.each((function(){var n=e(this).data("bs.popover"),r="object"==typeof t?t:null;if((n||!/dispose|hide/.test(t))&&(n||(n=new i(this,r),e(this).data("bs.popover",n)),"string"==typeof t)){if(void 0===n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},c=i,b=[{key:"VERSION",get:function(){return"4.4.1"}},{key:"Default",get:function(){return f}},{key:"NAME",get:function(){return u}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return v}},{key:"EVENT_KEY",get:function(){return s}},{key:"DefaultType",get:function(){return a}}],(g=null)&&n(c.prototype,g),b&&n(c,b),i}(t);return e.fn[u]=g._jQueryInterface,e.fn[u].Constructor=g,e.fn[u].noConflict=function(){return e.fn[u]=c,g._jQueryInterface},g}));
!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}));

View File

@@ -1,41 +1,37 @@
$(document).ready(function() {
clearSelectionLabel = '(no selection)';
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
//console.log('Changing selected value');
obj.find('option').remove(); //Remove all the available options
obj.append( //Add the new option
$("<option></option>")
.attr("value",pk)
.text(text)
.data('update_url',update_url)
);
obj.selectpicker('render'); //Re-render the UI
obj.selectpicker('refresh'); //Re-render the UI
obj.selectpicker('val', pk); //Set the new value to be selected
obj.change(); //Trigger the change function manually
.attr("value",pk)
.text(text)
.data('update_url',update_url)
);
obj.selectpicker('render'); //Re-render the UI
obj.selectpicker('refresh'); //Re-render the UI
obj.selectpicker('val', pk); //Set the new value to be selected
obj.change(); //Trigger the change function manually
}
function refreshUpdateHref(obj) {
//console.log('Refreshing Update URL');
//console.log('Refreshing Update URL');
targetObject = $('#'+obj.attr('id')+'-update');
update_url = $('option:selected', obj).data('update_url');
update_url = $('option:selected', obj).data('update_url');
if (update_url=="") { //Probably "clear selection" has been chosen
// console.log('Trying to disable');
targetObject.attr('disabled', true);
} else {
targetObject.attr('href', update_url);
targetObject.attr('disabled', false);
}
if (update_url=="") { //Probably "clear selection" has been chosen
//console.log('Trying to disable');
targetObject.removeAttr('href');
targetObject.addClass('disabled');
} else {
targetObject.prop('href', update_url);
targetObject.removeClass('disabled');
}
}
$(".selectpicker").each(function() {
function initPicker(obj) {
var options = {
ajax: {
url: $(this).data('sourceurl'),
url: obj.data('sourceurl'),
type: 'GET',
dataType: 'json',
// Use "{{{q}}}" as a placeholder and Ajax Bootstrap Select will
@@ -76,29 +72,31 @@ $(".selectpicker").each(function() {
}
};
$(this).prepend($("<option></option>")
obj.prepend($("<option></option>")
.attr("value",'')
.text(clearSelectionLabel)
.data('update_url','')); //Add "clear selection" option
$(this).selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
$(this).change(function(){ //on change, update the edit button href
// console.log('Selectbox Changed');
refreshUpdateHref($(this));
obj.selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
obj.change(function(){ //on change, update the edit button href
//console.log('Selectbox Changed');
refreshUpdateHref(obj);
});
refreshUpdateHref($(this)); //Ensure href is correct at the beginning
refreshUpdateHref(obj); //Ensure href is correct at the beginning
}
});
$(document).ready(function() {
clearSelectionLabel = '(no selection)';
//When update/edit modal box submitted
$(".selectpicker").each(function(){initPicker($(this))});
//When update/edit modal box submitted
$('#modal').on('hide.bs.modal', function (e) {
if (modaltarget != undefined && modalobject != "") {
//Update the selector with new values
changeSelectedValue($(modaltarget),modalobject[0]['pk'],modalobject[0]['fields']['name'],modalobject[0]['update_url']);
}
});
});

View File

@@ -137,4 +137,3 @@ $("#item-table tbody").sortable({
}
});

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
/*!
* Bootstrap util.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Bootstrap util.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")):"function"==typeof define&&define.amd?define(["jquery"],e):(t=t||self).Util=e(t.jQuery)}(this,(function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;function e(e){var r=this,o=!1;return t(this).one(n.TRANSITION_END,(function(){o=!0})),setTimeout((function(){o||n.triggerTransitionEnd(r)}),e),this}var n={TRANSITION_END:"bsTransitionEnd",getUID:function(t){do{t+=~~(1e6*Math.random())}while(document.getElementById(t));return t},getSelectorFromElement:function(t){var e=t.getAttribute("data-target");if(!e||"#"===e){var n=t.getAttribute("href");e=n&&"#"!==n?n.trim():""}try{return document.querySelector(e)?e:null}catch(t){return null}},getTransitionDurationFromElement:function(e){if(!e)return 0;var n=t(e).css("transition-duration"),r=t(e).css("transition-delay"),o=parseFloat(n),i=parseFloat(r);return o||i?(n=n.split(",")[0],r=r.split(",")[0],1e3*(parseFloat(n)+parseFloat(r))):0},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(e){t(e).trigger("transitionend")},supportsTransitionEnd:function(){return Boolean("transitionend")},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,r){for(var o in r)if(Object.prototype.hasOwnProperty.call(r,o)){var i=r[o],a=e[o],u=a&&n.isElement(a)?"element":(s=a,{}.toString.call(s).match(/\s([a-z]+)/i)[1].toLowerCase());if(!new RegExp(i).test(u))throw new Error(t.toUpperCase()+': Option "'+o+'" provided type "'+u+'" but expected type "'+i+'".')}var s},findShadowRoot:function(t){if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){var e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?n.findShadowRoot(t.parentNode):null},jQueryDetection:function(){if(void 0===t)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1===e[0]&&9===e[1]&&e[2]<1||e[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};return n.jQueryDetection(),t.fn.emulateTransitionEnd=e,t.event.special[n.TRANSITION_END]={bindType:"transitionend",delegateType:"transitionend",handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}},n}));
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).Util=e(t.jQuery)}(this,(function(t){"use strict";t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t;function e(e){var r=this,o=!1;return t(this).one(n.TRANSITION_END,(function(){o=!0})),setTimeout((function(){o||n.triggerTransitionEnd(r)}),e),this}var n={TRANSITION_END:"bsTransitionEnd",getUID:function(t){do{t+=~~(1e6*Math.random())}while(document.getElementById(t));return t},getSelectorFromElement:function(t){var e=t.getAttribute("data-target");if(!e||"#"===e){var n=t.getAttribute("href");e=n&&"#"!==n?n.trim():""}try{return document.querySelector(e)?e:null}catch(t){return null}},getTransitionDurationFromElement:function(e){if(!e)return 0;var n=t(e).css("transition-duration"),r=t(e).css("transition-delay"),o=parseFloat(n),i=parseFloat(r);return o||i?(n=n.split(",")[0],r=r.split(",")[0],1e3*(parseFloat(n)+parseFloat(r))):0},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(e){t(e).trigger("transitionend")},supportsTransitionEnd:function(){return Boolean("transitionend")},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,r){for(var o in r)if(Object.prototype.hasOwnProperty.call(r,o)){var i=r[o],a=e[o],u=a&&n.isElement(a)?"element":null==(s=a)?""+s:{}.toString.call(s).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(i).test(u))throw new Error(t.toUpperCase()+': Option "'+o+'" provided type "'+u+'" but expected type "'+i+'".')}var s},findShadowRoot:function(t){if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){var e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?n.findShadowRoot(t.parentNode):null},jQueryDetection:function(){if(void 0===t)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1===e[0]&&9===e[1]&&e[2]<1||e[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};return n.jQueryDetection(),t.fn.emulateTransitionEnd=e,t.event.special[n.TRANSITION_END]={bindType:"transitionend",delegateType:"transitionend",handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}},n}));

View File

@@ -1,11 +1,11 @@
$font-family-sans-serif: "Open Sans", sans-serif;
//Make it look less primary school
$font-size-base: 0.875rem;
$font-size-base: 0.85rem;
$theme-colors: (
"yellow": #ffd351,
"success": #3b7743,
"warning": #D3963B,
"danger": #A94447,
"info": #B8FAFF,
"primary": #4CB8F1
);
"success": #5cb85c,
"warning": #f0ad4e,
"danger": #d9534f,
"info": #5bc0de,
"primary": #3A52A2
) !default;
$enable-shadows: true;

View File

@@ -0,0 +1,114 @@
[data-theme='dark'] {
$theme-colors: (
"success": #3AB54A,
"warning": #FFE89B,
"danger": #BF1E2E,
"info": #25AAE2,
"primary": #3A52A2
);
@import "custom-variables";
@import 'node_modules/@forevolve/bootstrap-dark/scss/bootstrap-dark.scss';
background: #222;
color: $gray-100;
$darktheme: #121416;
.navbar {
background-color: #111111 !important;
}
.dropdown-menu {
box-shadow: 0 0.5rem 1rem black;
color: $gray-100;
background: $darktheme;
}
.form-control:not(.btn) {
background-color: $darktheme;
color: $gray-100;
}
.btn-light, .popover, .popover-header, .popover-body, .status {
background-color: $darktheme !important;
color: $gray-100 !important;
border-color: $darktheme;
}
.bs-popover-right > .arrow::after {
border-right-color: $darktheme;
}
.bs-popover-left > .arrow::after {
border-left-color: $darktheme;
}
.bs-popover-top > .arrow::after {
border-top-color: $darktheme;
}
.bs-popover-bottom > .arrow::after {
border-bottom-color: $darktheme;
}
a {
color: $blue;
}
.badge, .btn-success, .bg-warning {
color: black;
}
.badge-dark, .badge-secondary, .btn-primary {
color: $gray-100;
}
.input-group-text {
border-color: $darktheme;
}
.btn-secondary, .btn-info {
color: white;
}
.page-item.disabled {
opacity: 0.5;
}
.bg-light {
color: black;
background-color: $gray-200 !important;
border-radius: 0.2em;
}
.fc-day-today {
background-color: transparent !important;
border: 0.2em solid $info !important;
}
.fc-daygrid-dot-event {
color: white !important;
}
.table {
border-collapse: separate !important;
border-spacing: 0;
}
.table tr th {
border-right: 0 !important;
}
.table tr td {
border-left: 0 !important;
}
.table tr td:not(:last-child) {
border-right: 0 !important;
}
@each $color, $value in $theme-colors {
.table-#{$color} {
> td,th {
border: 0.3em solid theme-color-level($color, -6) !important;
}
> * {
color: white !important;
background-color: #222 !important;
}
}
}
del {
color: black;
background-color: $danger;
border-radius: 3px;
}
ins {
color: black;
background-color: $success;
border-radius: 3px;
}
pre {
background: $gray-600;
color: white;
}
.custom-control-input:focus ~ .custom-control-label::before {
box-shadow: 0 0 0 $input-focus-width rgba($primary, 0.7) !important;
}
}

View File

@@ -1,6 +1,37 @@
@import "dark_screen";
@import "custom-variables";
@import "node_modules/bootstrap/scss/bootstrap";
@media screen and
(prefers-reduced-motion: reduce),
(update: slow) {
* {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}
.fc-event-main {
color: black !important;
}
.fc-daygrid-dot-event {
color: black !important;
}
.fc-timeGridDay-view {
.fc-event-main {
font-size: large !important;
}
}
.fc-timeGridWeek-view {
.fc-event-main {
font-size: 0.9rem;
}
}
#content {
padding: 40px 15px;
}
@@ -34,6 +65,7 @@ textarea {
.event-mic-photo {
max-width: 2em;
border-radius: 0.25em;
}
.item-description {
@@ -63,11 +95,13 @@ textarea {
del {
background-color: #f2dede;
border-radius: 3px;
padding: 0.1em;
}
ins {
background-color: #dff0d8;
border-radius: 3px;
padding: 0.1em;
}
.nav-link {
@@ -93,6 +127,23 @@ ins {
transform: translateY(45px); /* TODO Remove absolute positioning */
}
.errorlist > li {
list-style-type: none;
}
pre {
page-break-inside: avoid;
font-family: monospace;
font-size: 15px;
line-height: 1.6;
margin-bottom: 1.6em;
max-width: 100%;
overflow: auto;
padding: 1em 1.5em;
display: block;
word-wrap: break-word;
}
svg {
display: inline;
white-space: no-wrap;

View File

@@ -30,6 +30,18 @@
{% endif %}
</div>
</li>
{% if perms.RIGS.view_riskassessment %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownHS" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
H&S
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdownHS">
<a class="dropdown-item" href="{% url 'hs_list' %}"><span class="fas fa-eye"></span> Overview</a>
<a class="dropdown-item" href="{% url 'ra_list' %}"><span class="fas fa-file-medical"></span> Risk Assessments</a>
<a class="dropdown-item" href="{% url 'ec_list' %}"><span class="fas fa-tasks"></span> Event Checklists</a>
</div>
</li>
{% endif %}
{% if perms.RIGS.view_invoice %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownInvoices" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@@ -53,51 +65,14 @@
{% if perms.RIGS.view_venue %}
<li class="nav-item"><a class="nav-link" href="{% url 'venue_list' %}">Venues</a></li>
{% endif %}
<form id="searchForm" class="form-inline flex-nowrap mx-3" role="form" method="GET">
<div class="input-group input-group-sm flex-nowrap">
<div class="input-group-prepend">
<input id="id_search_input" type="search" name="q" class="form-control form-control-sm" placeholder="Search..." />
</div>
<select id="search-options" class="custom-select form-control">
<option selected data-action="{% url 'event_archive' %}" href="#">Events</option>
<option data-action="{% url 'person_list' %}" href="#">People</option>
<option data-action="{% url 'organisation_list' %}" href="#">Organisations</option>
<option data-action="{% url 'venue_list' %}" href="#">Venues</option>
{% if perms.RIGS.view_invoice %}
<option data-action="{% url 'invoice_archive' %}" href="#">Invoices</option>
{% endif %}
</select>
</div>
<button class="btn btn-primary form-control form-control-sm btn-sm">Search</button>
<a href="{% url 'search_help' %}" class="nav-link modal-href btn-sm"><span class="fas fa-question-circle"></span></a>
</form>
{% endif %}
<li class="nav-item dropdown" id="user">
{% if user.is_authenticated %}
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Hi {{ user.first_name }}
</a>
<ul class="dropdown-menu p-3" id="userdropdown">
<li class="media">
<a href="{% url 'profile_detail' %}">
<img src="{{ request.user.profile_picture }}" class="media-object"/>
<div class="media-body">
<b>{{ request.user.first_name }} {{ request.user.last_name }}</b>
<p class="muted">{{ request.user.email }}</p>
</div>
</a>
</li>
<li>
<a href="{% url 'logout' %}" class="btn btn-primary align"><i class="fas fa-sign-out-alt"></i> Logout</a>
</li>
</ul>
{% else %}
<a class="nav-link" href="{% url 'login' %}">
Login
</a>
{% endif %}
</li>
{% endblock %}
{% block titleelements_right %}
{% include 'partials/search.html' %}
{% include 'partials/navbar_user.html' %}
{% endblock %}
{% block js %}
<script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/popover.js' %}"></script>
@@ -106,17 +81,4 @@
$('[data-toggle="tooltip"]').tooltip();
})
</script>
<script>
$(document).ready(function(){
$('#search-options option').click(function(){
$('#searchForm').attr('action', $(this).data('action')).submit();
});
$('#id_search_input').keypress(function (e) {
if (e.which == 13) {
$('#searchForm').attr('action', $('#search-options option').first().data('action')).submit();
return false;
}
});
});
</script>
{% endblock %}

View File

@@ -1,17 +1,15 @@
{% extends 'base_rigs.html' %}
{% load static %}
{% block title %}Calendar{% endblock %}
{% block css %}
<link href="{% static "css/fullcalendar.css" %}" rel='stylesheet' />
<link href="{% static "css/fullcalendar.print.css" %}" rel='stylesheet' media='print' />
<link href="{% static 'css/main.min.css' %}" rel='stylesheet' />
{% endblock %}
{% block js %}
<script src="{% static 'js/moment.js' %}"></script>
<script src="{% static 'js/fullcalendar.js' %}"></script>
<script src="{% static 'js/main.min.js' %}"></script>
<script>
function getUrlVars() {
var vars = {};
@@ -21,57 +19,50 @@
return vars;
}
$(document).ready(function() {
viewToUrl = {
'agendaWeek':'week',
'agendaDay':'day',
'month':'month'
}
viewFromUrl = {
'week':'agendaWeek',
'day':'agendaDay',
'month':'month'
}
$('#calendar').fullCalendar({
editable: false,
eventLimit: true, // allow "more" link when too many events
firstDay: 1,
aspectRatio: 1.5,
timeFormat: 'HH:mm',
views: {
basic: {
// options apply to basicWeek and basicDay views
},
agenda: {
// options apply to agendaWeek and agendaDay views
},
week: {
columnFormat:'ddd D/M'
},
day: {
// options apply to basicDay and agendaDay views
}
},
header:false,
events: function(start_moment, end_moment, timezone, callback) {
viewToUrl = {
'timeGridWeek':'week',
'timeGridDay':'day',
'dayGridMonth':'month'
}
viewFromUrl = {
'week':'timeGridWeek',
'day':'timeGridDay',
'month':'dayGridMonth'
}
var calendar; //Need to access it from jquery ready
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
calendar = new FullCalendar.Calendar(calendarEl, {
themeSystem: 'bootstrap',
//defaultView: 'dayGridMonth', This is now default
aspectRatio: 1.5,
eventTimeFormat: {
'hour': '2-digit',
'minute': '2-digit',
'hour12': false
},
//nowIndicator: true,
//firstDay: 1,
headerToolbar: false,
editable: false,
dayMaxEventRows: true, // allow "more" link when too many events
events: function(fetchInfo, successCallback, failureCallback) {
$.ajax({
url: '/api/event',
dataType: 'json',
data: {
start: moment(start_moment).format("YYYY-MM-DD[T]HH:mm:ss"),
end: moment(end_moment).format("YYYY-MM-DD[T]HH:mm:ss")
start: moment(fetchInfo.startStr).format("YYYY-MM-DD[T]HH:mm:ss"),
end: moment(fetchInfo.endStr).format("YYYY-MM-DD[T]HH:mm:ss")
},
success: function(doc) {
var events = [];
colours = {'Provisional': '#f0ad4e',
'Confirmed': '#5cb85c' ,
'Booked': '#5cb85c' ,
colours = {
'Provisional': '#FFE89B',
'Confirmed': '#3AB54A' ,
'Booked': '#3AB54A' ,
'Cancelled': 'grey' ,
'non-rig': '#5bc0de'
'non-rig': '#25AAE2'
};
$(doc).each(function() {
end = $(this).attr('latest')
@@ -92,20 +83,19 @@
}else{
thisEvent['color'] = colours['non-rig'];
}
events.push(thisEvent);
});
callback(events);
successCallback(events);
}
});
},
viewRender: function(view, element){
datesSet: function(info) {
var view = info.view;
// Set the title of the view
$('#calendar-header').text(view.title);
// Enable/Disable "Today" button as required
if(moment().isBetween(view.intervalStart, view.intervalEnd)){
if(moment().isBetween(view.currentStart, view.currentEnd)){
//Today is within the current view
$('#today-button').prop('disabled', true);
}else{
@@ -113,74 +103,65 @@
}
// Set active view select button
switch(view.name){
case 'month':
switch(view.type){
case 'dayGridMonth':
$('#month-button').addClass('active');
$('#week-button').removeClass('active');
$('#day-button').removeClass('active');
break;
case 'agendaWeek':
case 'timeGridWeek':
$('#month-button').removeClass('active');
$('#week-button').addClass('active');
$('#day-button').removeClass('active');
break;
case 'agendaDay':
case 'timeGridDay':
$('#month-button').removeClass('active');
$('#week-button').removeClass('active');
$('#day-button').addClass('active');
break;
}
history.replaceState(null,null,'{% url 'web_calendar' %}'+viewToUrl[view.name]+'/'+view.intervalStart.format('YYYY-MM-DD')+'/');
history.replaceState(null,null,"{% url 'web_calendar' %}"+viewToUrl[view.type]+'/'+moment(view.currentStart).format('YYYY-MM-DD')+'/');
}
});
calendar.render();
});
$(document).ready(function() {
// set some button listeners
$('#next-button').click(function(){ $('#calendar').fullCalendar('next') });
$('#prev-button').click(function(){ $('#calendar').fullCalendar('prev') });
$('#today-button').click(function(){ $('#calendar').fullCalendar('today') });
$('#month-button').click(function(){ $('#calendar').fullCalendar('changeView','month') });
$('#week-button').click(function(){ $('#calendar').fullCalendar('changeView','agendaWeek') });
$('#day-button').click(function(){ $('#calendar').fullCalendar('changeView','agendaDay') });
$('#next-button').click(function(){ calendar.next(); });
$('#prev-button').click(function(){ calendar.prev(); });
$('#today-button').click(function(){ calendar.today(); });
$('#month-button').click(function(){ calendar.changeView('dayGridMonth'); });
$('#week-button').click(function(){ calendar.changeView('timeGridWeek'); });
$('#day-button').click(function(){ calendar.changeView('timeGridDay'); });
$('#go-to-date-input').change(function(){
if( moment($('#go-to-date-input').val()).isValid() ){
if(moment($('#go-to-date-input').val()).isValid()){
$('#go-to-date-button').prop('disabled', false);
}else{
} else{
$('#go-to-date-button').prop('disabled', true);
}
});
$('#go-to-date-button').click(function(){
day = moment($('#go-to-date-input').val()) ;
day = moment($('#go-to-date-input').val());
if(day.isValid()){
$('#calendar').fullCalendar( 'gotoDate', day);
}else{
calendar.gotoDate(day.format("YYYY-MM-DD"));
} else{
alert('Invalid Date');
}
});
{% if view and date %}
// Go to the initial settings, if they're valid
view = viewFromUrl['{{view}}'];
$('#calendar').fullCalendar( 'changeView', view);
calendar.changeView(view);
day = moment('{{date}}');
if(day.isValid()){
$('#calendar').fullCalendar( 'gotoDate', day);
}else{
calendar.gotoDate(day.format("YYYY-MM-DD"));
} else{
console.log('Supplied date is invalid - using default')
}
{% endif %}
});
</script>
{% endblock %}

View File

@@ -1,78 +0,0 @@
<div class="row my-3">
<div class="col-sm-6">
<div class="card mb-3">
<div class="card-header">Contact Details</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-5">Person</dt>
<dd class="col-sm-7">
{% if event.person %}
{{ event.person.name }}
{% endif %}
</dd>
<dt class="col-sm-5">Email</dt>
<dd class="col-sm-7">
<span class="overflow-ellipsis">{{ event.person.email }}</span>
</dd>
<dt class="col-sm-5">Phone Number</dt>
<dd class="col-sm-7">{{ event.person.phone }}</dd>
</dl>
</div>
</div>
{% if event.organisation %}
<div class="card mt-3">
<div class="card-header">Organisation Details</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-5">Organisation</dt>
<dd class="col-sm-7">
{{ event.organisation.name }}
</dd>
<dt class="col-sm-5">Phone Number</dt>
<dd class="col-sm-7">{{ object.organisation.phone }}</dd>
</dl>
</div>
</div>
{% endif %}
</div>
<div class="col-sm-6">
<div class="card border-info">
<div class="card-header">Event Info</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-5">Event Venue</dt>
<dd class="col-sm-7">
{% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
</a>
{% endif %}
</dd>
<dt class="col-sm-5">Status</dt>
<dd class="col-sm-7">{{ event.get_status_display }}</dd>
<dd class="col-sm-12">&nbsp;</dd>
<dt class="col-sm-5">Access From</dt>
<dd class="col-sm-7">{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
<dt class="col-sm-5">Event Starts</dt>
<dd class="col-sm-7">{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
<dt class="col-sm-5">Event Ends</dt>
<dd class="col-sm-7">{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
<dd class="col-sm-12">&nbsp;</dd>
<dt class="col-sm-5">Event Description</dt>
<dd class="col-sm-12">{{ event.description|linebreaksbr }}</dd>
</dl>
</div>
</div>
</div>
</div>

View File

@@ -1,48 +1,56 @@
{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% load get_list from filters %}
{% load button from filters %}
{% load static %}
{% block title %}Event Archive{% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
{% endblock %}
{% block preload_js %}
<script src="{% static 'js/bootstrap-select.js' %}"></script>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h2>Event Archive</h2>
</div>
<div class="col-sm-12 py-2">
<form class="form-inline">
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text">Start</span>
</div>
<input type="date" name="start" id="start" value="{{ start|default_if_none:"" }}" placeholder="Start" class="form-control" />
<div class="row">
<div class="col-sm-12 py-2">
<form class="form-inline" method="GET">
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text">Start</span>
</div>
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text">End</span>
</div>
<input type="date" name="end" id="end" value="{{ end|default_if_none:"" }}" placeholder="End" class="form-control" />
<input type="date" name="start" id="start" value="{{ start|default_if_none:'' }}" placeholder="Start" class="form-control" />
</div>
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text">End</span>
</div>
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text">Keyword</span>
</div>
<input type="search" name="q" placeholder="Keyword" value="{{ request.GET.q }}" class="form-control" />
<input type="date" name="end" id="end" value="{{ end|default_if_none:'' }}" placeholder="End" class="form-control" />
</div>
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text">Keyword</span>
</div>
<input type="submit" class="btn btn-primary" value="Search"/>
</form>
</div>
<input type="search" name="q" placeholder="Keyword" value="{{ request.GET.q }}" class="form-control" />
</div>
<select class="selectpicker pr-3" multiple data-actions-box="true" data-none-selected-text="Status" data-actions-box="true" id="status" name="status">
{% for status in statuses %}
<option value="{{status.0}}" {% if status.0|safe in request.GET|get_list:'status' %}selected=""{% endif %}>{{status.1}}</option>
{% endfor %}
</select>
{% button 'search' %}
</form>
</div>
<div class="row">
<div class="col-sm-12">
{% with object_list as events %}
{% include 'event_table.html' %}
{% endwith %}
</div>
</div>
<div class="row">
<div class="col-sm-12">
{% with object_list as events %}
{% include 'partials/event_table.html' %}
{% endwith %}
</div>
</div>
{% paginator %}
{% if is_paginated %}
<div class="row justify-content-center">
{% paginator %}
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,247 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load help_text from filters %}
{% load profile_by_index from filters %}
{% load yesnoi from filters %}
{% load button from filters %}
{% block content %}
<div class="row">
<div class="col-12 text-right my-3">
{% button 'edit' url='ec_edit' pk=object.pk %}
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-12">
<div class="card mb-3">
<div class="card-header">General</div>
<div class="card-body">
<dl class="row">
<dt class="col-6">Date</dt>
<dd class="col-6">
{{ object.date }}
</dd>
<dt class="col-6">Venue</dt>
<dd class="col-6">
{% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
</a>
{% endif %}
</dd>
<dt class="col-6">{{ object|help_text:'power_mic' }}</dt>
<dd class="col-6">
<a href="{% url 'profile_detail' object.power_mic.pk %}">{{ object.power_mic.name }}</a>
</dd>
</dl>
<p>List vehicles and their drivers</p>
<ul>
{% for i in object.vehicles.all %}
<li>{{i}}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<div class="col-md-6 col-sm-12">
<div class="card mb-3">
<div class="card-header">Safety Checks</div>
<div class="card-body">
<dl class="row">
<dt class="col-10">{{ object|help_text:'safe_parking'|safe }}</dt>
<dd class="col-2">
{{ object.safe_parking|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'safe_packing'|safe }}</dt>
<dd class="col-2">
{{ object.safe_packing|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'exits'|safe }}</dt>
<dd class="col-2">
{{ object.exits|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'trip_hazard'|safe }}</dt>
<dd class="col-2">
{{ object.trip_hazard|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'warning_signs'|safe }}</dt>
<dd class="col-2">
{{ object.warning_signs|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'ear_plugs'|safe }}</dt>
<dd class="col-2">
{{ object.ear_plugs|yesnoi }}
</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">Crew Record</div>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Crewmember</th>
<th scope="col">Start Time</th>
<th scope="col">Role</th>
<th scope="col">End Time</th>
</tr>
</thead>
<tbody id="crewmemberst">
{% for crew in object.crew.all %}
<tr>
<td>{{crew.crewmember}}</td>
<td>{{crew.start}}</td>
<td>{{crew.role}}</td>
<td>{{crew.end}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card mb-3">
<div class="card-header">Power {% include 'partials/event_size.html' with object=object.event.riskassessment %}</div>
{% if object.event.riskassessment.event_size != 2 %}
<div class="card-body">
{% if object.event.riskassessment.event_size == 1 %}
<dl class="row">
<dt class="col-10">{{ object|help_text:'source_rcd'|safe }}</dt>
<dd class="col-2">
{{ object.source_rcd|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'labelling'|safe }}</dt>
<dd class="col-2">
{{ object.labelling|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'earthing'|safe }}</dt>
<dd class="col-2">
{{ object.earthing|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'pat'|safe }}</dt>
<dd class="col-2">
{{ object.pat|yesnoi }}
</dd>
</dl>
<hr>
<p>Tests at first distro</p>
<table class="table table-bordered">
<thead>
<tr>
<th scope="col" class="text-center">Test</th>
<th scope="col" colspan="3" class="text-center">Value</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row" rowspan="2">Voltage<br><small>(cube meter)</small></th>
<th>{{ object|help_text:'fd_voltage_l1' }}</th>
<th>{{ object|help_text:'fd_voltage_l2' }}</th>
<th>{{ object|help_text:'fd_voltage_l3' }}</th>
</tr>
<tr>
<td>{{ object.fd_voltage_l1 }}</td>
<td>{{ object.fd_voltage_l2 }}</td>
<td>{{ object.fd_voltage_l3 }}</td>
</tr>
<tr>
<th scope="row">{{ object|help_text:'fd_phase_rotation'|safe }}</th>
<td colspan="3">{{ object.fd_phase_rotation|yesnoi }}</td>
</tr>
<tr>
<th scope="row">{{ object|help_text:'fd_earth_fault'|safe}}</th>
<td colspan="3">{{ object.fd_earth_fault }}</td>
</tr>
<tr>
<th scope="row">{{ object|help_text:'fd_pssc'}}</th>
<td colspan="3">{{ object.fd_pssc }}</td>
</tr>
</tbody>
</table>
<hr>
<p>Tests at 'Worst Case' points (at least 1 point required)</p>
<table class="table table-bordered">
<thead>
<tr>
<th scope="col" class="text-center">Test</th>
<th scope="col" class="text-center">Point 1</th>
<th scope="col" class="text-center">Point 2</th>
<th scope="col" class="text-center">Point 3</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{ object|help_text:'w1_description'|safe}}</th>
<td>{{ object.w1_description }}</td>
<td>{{ object.w2_description|default:'' }}</td>
<td>{{ object.w3_description|default:'' }}</td>
</tr>
<tr>
<th scope="row">{{ object|help_text:'w1_polarity'|safe}}</th>
<td>{{ object.w1_polarity|yesnoi }}</td>
<td>{{ object.w2_polarity|default:''|yesnoi }}</td>
<td>{{ object.w3_polarity|default:''|yesnoi }}</td>
</tr>
<tr>
<th scope="row">{{ object|help_text:'w1_voltage'|safe}}</th>
<td>{{ object.w1_voltage }}</td>
<td>{{ object.w2_voltage|default:'' }}</td>
<td>{{ object.w3_voltage|default:'' }}</td>
</tr>
<tr>
<th scope="row">{{ object|help_text:'w1_earth_fault'|safe}}</th>
<td>{{ object.w1_earth_fault }}</td>
<td>{{ object.w2_earth_fault|default:'' }}</td>
<td>{{ object.w3_earth_fault|default:'' }}</td>
</tr>
</tbody>
</table>
<hr>
<dl class="row">
<dt class="col-10">{{ object|help_text:'all_rcds_tested'|safe }}</dt>
<dd class="col-2">
{{ object.all_rcds_tested|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'public_sockets_tested'|safe }}</dt>
<dd class="col-2">
{{ object.public_sockets_tested|yesnoi }}
</dd>
</dl>
<hr>
{% include 'partials/ec_power_info.html' %}
{% else %}
<dl class="row">
<dt class="col-10">{{ object|help_text:'rcds'|safe }}</dt>
<dd class="col-2">
{{ object.rcds|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'supply_test'|safe }}</dt>
<dd class="col-2">
{{ object.supply_test|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'earthing'|safe }}</dt>
<dd class="col-2">
{{ object.earthing|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'pat'|safe }}</dt>
<dd class="col-2">
{{ object.pat|yesnoi }}
</dd>
</dl>
{% endif %}
</div>
{% endif %}
</div>
</div>
<div class="col-12 text-right">
{% button 'edit' url='ec_edit' pk=object.pk %}
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
</div>
<div class="col-12 text-right">
{% include 'partials/last_edited.html' with target="eventchecklist_history" %}
</div>
{% endblock %}

View File

@@ -0,0 +1,367 @@
{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %}
{% load static %}
{% load help_text from filters %}
{% load profile_by_index from filters %}
{% load button from filters %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
{% endblock %}
{% block js %}
{{ block.super }}
<script src="{% static 'js/jquery-ui.js' %}"></script><!--TODO optimise-->
<script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/modal.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/autocompleter.js' %}"></script>
{% include 'partials/datetime-fix.html' %}
<script>
$(document).ready(function () {
$('button[data-action=add]').on('click', function (event) {
event.preventDefault();
var target = $($(this).attr('data-target'));
var newID = Number(target.attr('data-pk'));
var newRow = $($(this).attr('data-clone'))
.clone().attr('style', "")
.attr('id', function(i, val){
return val.split("_")[0] + '_' + newID;
})
.appendTo(target);
newRow.find('select,input').attr('name', function(i, val){
return val.split("_")[0] + '_' + newID;
})//Disabled is to prevent the hidden row being sent to the form
.removeAttr('disabled');
newRow.find('button[data-action=delete]').attr('data-id', newID);
newRow.find('select').addClass('selectpicker');
newRow.find('.selectpicker').selectpicker('refresh');
$(".selectpicker").each(function(){initPicker($(this))});
initDatetime();
$(target).attr('data-pk', newID - 1);
});
$(document).on('click', 'button[data-action=delete]', function(event) {
event.preventDefault();
$(this).closest('tr').remove();
});
//Somewhat rudimentary way of ensuring people fill in completely (if it hits the database validation the whole table row disappears when the page reloads...)
//the not is to avoid adding it to some of bootstrap-selects extra crap
$('#vehiclest,#crewmemberst').on('change', 'select,input', function () {
$(this).closest('tr').find("select,input").not(':input[type=search]').attr('required', 'true');
});
});
</script>
{% endblock %}
{% block content %}
<div class="col-12">
{% include 'form_errors.html' %}
{% if edit %}
<form role="form" method="POST" action="{% url 'ec_edit' pk=object.pk %}">
{% else %}
<form role="form" method="POST" action="{% url 'event_ec' pk=event.pk %}">
{% endif %}
<input type="hidden" name="{{ form.event.name }}" id="{{ form.event.id_for_label }}"
value="{{event.pk}}"/>
{% csrf_token %}
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">Event Information</div>
<div class="card-body">
<dl class="row">
<dt class="col-4">Event Date</dt>
<dd class="col-8">{{ event.start_date}}{%if event.end_date %}-{{ event.end_date}}{%endif%}</dd>
<dt class="col-4">Event Name</dt>
<dd class="col-8">{{ event.name }}</dd>
<dt class="col-4">Client</dt>
<dd class="col-8">{{ event.person }}</dd>
<dt class="col-4">Event Size</dt>
<dd class="col-8">{% include 'partials/event_size.html' with object=event.riskassessment %}</dd>
</dl>
<div class="form-group form-row">
<label for="{{ form.date.id_for_label }}"
class="col-4 col-form-label">{{ form.date.label }}</label>
{% if not form.date.value %}
{% render_field form.date class+="form-control col-8" value=event.start_date %}
{% else %}
{% render_field form.date class+="form-control col-8" %}
{% endif %}
</div>
<div class="form-group form-row" id="{{ form.venue.id_for_label }}-group">
<label for="{{ form.venue.id_for_label }}"
class="col-4 col-form-label">{{ form.venue.label }}</label>
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker col-8" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
{% if venue %}
<option value="{{venue.pk}}" selected="selected">{{ venue.name }}</option>
{% elif event.venue %}
<option value="{{event.venue.pk}}" selected="selected">{{ event.venue.name }}</option>
{% endif %}
</select>
</div>
<div class="form-group form-row" id="{{ form.power_mic.id_for_label }}-group">
<label for="{{ form.power_mic.id_for_label }}"
class="col-4 col-form-label">{{ form.power_mic.help_text }}</label>
<select id="{{ form.power_mic.id_for_label }}" name="{{ form.power_mic.name }}" class="form-control selectpicker col-8" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required="true">
{% if power_mic %}
<option value="{{power_mic.pk}}" selected="selected">{{ power_mic.name }}</option>
{% elif event.riskassessment.power_mic %}
<option value="{{event.riskassessment.power_mic.pk}}" selected="selected">{{ event.riskassessment.power_mic.name }}</option>
{% endif %}
</select>
</div>
<p class="pt-3 font-weight-bold">List vehicles and their drivers</p>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Vehicle</th>
<th scope="col">Driver</th>
<th scope="col"></th>
</tr>
</thead>
<tbody id="vehiclest" data-pk="-1">
<tr id="vehicles_new" style="display: none;">
<td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td>
<td><select class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" name="driver_new" disabled="true"></select></td>
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button</td>
</tr>
{% for i in object.vehicles.all %}
<tr id="vehicles_{{i.pk}}">
<td><input name="vehicle_{{i.pk}}" type="text" class="form-control" value="{{ i.vehicle }}"/></td>
<td>
<select name="driver_{{i.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if i.driver != '' %}
<option value="{{i.driver.pk}}" selected="selected">{{ i.driver.name }}</option>
{% endif %}
</select>
</td>
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-id='{{i.pk}}' data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="text-right">
<button type="button" class="btn btn-secondary" id="vehicle-add" data-action='add' data-target='#vehiclest' data-clone='#vehicles_new'><span class="fas fa-plus"></span> Add Vehicle</button>
</div>
</div>
</div>
</div>
</div>
<div class="row my-3">
<div class="col-12">
<div class="card">
<div class="card-header">Safety Checks</div>
<div class="card-body">
{% include 'partials/checklist_checkbox.html' with formitem=form.safe_parking %}
{% include 'partials/checklist_checkbox.html' with formitem=form.safe_packing %}
{% include 'partials/checklist_checkbox.html' with formitem=form.exits %}
{% include 'partials/checklist_checkbox.html' with formitem=form.trip_hazard %}
{% include 'partials/checklist_checkbox.html' with formitem=form.warning_signs %}
{% include 'partials/checklist_checkbox.html' with formitem=form.ear_plugs %}
<div class="row pt-3">
<label class="col-5" for="{{ form.hs_location.id_for_label }}">{{ form.hs_location.help_text }}</label>
{% render_field form.hs_location class+="form-control col-7 col-md-4" %}
</div>
<div class="row pt-1">
<label class="col-5" for="{{ form.extinguishers_location.id_for_label }}">{{ form.extinguishers_location.help_text }}</label>
{% render_field form.extinguishers_location class+="form-control col-7 col-md-4" %}
</div>
</div>
</div>
</div>
</div>
<div class="row my-3">
<div class="col-12">
<div class="card">
<div class="card-header">Crew Record</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Person</th>
<th scope="col">Start Time</th>
<th scope="col">Role</th>
<th scope="col">End Time</th>
<th scope="col"></th>
</tr>
</thead>
<tbody id="crewmemberst" data-pk="-1">
<tr id="crew_new" style="display: none;">
<td>
<select name="crewmember_new" class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" disabled="true"></select>
</td>
<td style="min-width: 15ch"><input name="start_new" type="datetime-local" class="form-control" value="{{ i.start }}" disabled="true"/></td>
<td style="min-width: 15ch"><input name="role_new" type="text" class="form-control" value="{{ i.role }}" disabled="true"/></td>
<td style="min-width: 15ch"><input name="end_new" type="datetime-local" class="form-control" value="{{ i.end }}" disabled="true" /></td>
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-id='{{crew.pk}}' data-action='delete' data-target='#crewmember'><span class="fas fa-times"></span></button</td>
</tr>
{% for crew in object.crew.all %}
<tr id="crew_{{crew.pk}}">
<td>
<select name="crewmember_{{crew.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if crew.crewmember != '' %}
<option value="{{crew.crewmember.pk}}" selected="selected">{{ crew.crewmember.name }}</option>
{% endif %}
</select>
</td>
<td><input name="start_{{crew.pk}}" type="datetime-local" class="form-control" value="{{ crew.start|date:'Y-m-d' }}T{{ crew.start|date:'H:i:s' }}"/></td>
<td><input name="role_{{crew.pk}}" type="text" class="form-control" value="{{ crew.role }}"/></td>
<td><input name="end_{{crew.pk}}" type="datetime-local" class="form-control" value="{{ crew.end|date:'Y-m-d' }}T{{ crew.end|date:'H:i:s' }}"/></td>
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-id='{{crew.pk}}' data-action='delete' data-target='#crewmember'><span class="fas fa-times"></span></button</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card-footer">
<div class="text-right">
<button type="button" class="btn btn-secondary" data-action='add' data-target='#crewmemberst' data-clone='#crew_new'><span class="fas fa-plus"></span> Add Crewmember</button>
</div>
</div>
</div>
</div>
</div>
{% if event.riskassessment.event_size == 0 %}
<div class="row my-3" id="size-0">
<div class="col-12">
<div class="card border-success">
<div class="card-header">Electrical Checks <small>for Small TEC Events <6kVA (aprox. 26A)</small></div>
<div class="card-body">
{% include 'partials/checklist_checkbox.html' with formitem=form.rcds %}
{% include 'partials/checklist_checkbox.html' with formitem=form.supply_test %}
{% include 'partials/checklist_checkbox.html' with formitem=form.earthing %}
{% include 'partials/checklist_checkbox.html' with formitem=form.pat %}
</div>
</div>
</div>
</div>
{% elif event.riskassessment.event_size == 1 %}
<div class="row my-3" id="size-1">
<div class="col-12">
<div class="card border-warning">
<div class="card-header">Electrical Checks <small>for Medium TEC Events </small></div>
<div class="card-body">
{% include 'partials/checklist_checkbox.html' with formitem=form.source_rcd %}
{% include 'partials/checklist_checkbox.html' with formitem=form.labelling %}
{% include 'partials/checklist_checkbox.html' with formitem=form.earthing %}
{% include 'partials/checklist_checkbox.html' with formitem=form.pat %}
<hr>
<p>Tests at first distro</p>
<div class="table-responsive">
<table class="table table-bordered table-sm">
<thead>
<tr>
<th scope="col" class="text-center">Test</th>
<th scope="col" colspan="3" class="text-center">Value</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row" rowspan="2">Voltage<br><small>(cube meter)</small></th>
<th class="text-center">{{ form.fd_voltage_l1.help_text }}</th>
<th class="text-center">{{ form.fd_voltage_l2.help_text }}</th>
<th class="text-center">{{ form.fd_voltage_l3.help_text }}</th>
</tr>
<tr>
<td>{% render_field form.fd_voltage_l1 class+="form-control" style="min-width: 5rem;" %}</td>
<td>{% render_field form.fd_voltage_l2 class+="form-control" style="min-width: 5rem;" %}</td>
<td>{% render_field form.fd_voltage_l3 class+="form-control" style="min-width: 5rem;" %}</td>
</tr>
<tr>
<th scope="row">{{form.fd_phase_rotation.help_text|safe}}</th>
<td colspan="3">{% include 'partials/checklist_checkbox.html' with formitem=form.fd_phase_rotation %}</td>
</tr>
<tr>
<th scope="row">{{form.fd_earth_fault.help_text|safe}}</th>
<td colspan="3">{% render_field form.fd_earth_fault class+="form-control" %}</td>
</tr>
<tr>
<th scope="row">{{form.fd_pssc.help_text|safe}}</th>
<td colspan="3">{% render_field form.fd_pssc class+="form-control" %}</td>
</tr>
</tbody>
</table>
</div>
<hr>
<p>Tests at 'Worst Case' points (at least 1 point required)</p>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col" class="text-center">Test</th>
<th scope="col" class="text-center">Point 1</th>
<th scope="col" class="text-center">Point 2</th>
<th scope="col" class="text-center">Point 3</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{form.w1_description.help_text|safe}}</th>
<td>{% render_field form.w1_description class+="form-control" style="min-width: 5rem;" %}</td>
<td>{% render_field form.w2_description class+="form-control" style="min-width: 5rem;" %}</td>
<td>{% render_field form.w3_description class+="form-control" style="min-width: 5rem;" %}</td>
</tr>
<tr>
<th scope="row">{{form.w1_polarity.help_text|safe}}</th>
<td>{% render_field form.w1_polarity %}</td>
<td>{% render_field form.w2_polarity %}</td>
<td>{% render_field form.w3_polarity %}</td>
</tr>
<tr>
<th scope="row">{{form.w1_voltage.help_text|safe}}</th>
<td>{% render_field form.w1_voltage class+="form-control" %}</td>
<td>{% render_field form.w2_voltage class+="form-control" %}</td>
<td>{% render_field form.w3_voltage class+="form-control" %}</td>
</tr>
<tr>
<th scope="row">{{form.w1_earth_fault.help_text|safe}}</th>
<td>{% render_field form.w1_earth_fault class+="form-control" %}</td>
<td>{% render_field form.w2_earth_fault class+="form-control" %}</td>
<td>{% render_field form.w3_earth_fault class+="form-control" %}</td>
</tr>
</tbody>
</table>
</div>
<hr>
{% include 'partials/checklist_checkbox.html' with formitem=form.all_rcds_tested %}
{% include 'partials/checklist_checkbox.html' with formitem=form.public_sockets_tested %}
{% include 'partials/ec_power_info.html' %}
</div>
</div>
</div>
</div>
{% else %}
<div class="row my-3" id="size-2">
<div class="col-12">
<div class="card border-danger">
<div class="card-header">Electrical Checks <small>for Large TEC Events</small></div>
<div class="card-body">
<p>Outside the scope of this assessment. <strong>I really hope you checked with a supervisor...</strong></p>
</div>
</div>
</div>
</div>
{% endif %}
<div class="row mt-3">
<div class="col-sm-12 text-right">
{% button 'submit' %}
</div>
</div>
</form>
</div>
{% endblock %}

View File

@@ -1,4 +1,7 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load linkornone from filters %}
{% load namewithnotes from filters %}
{% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %}
{% block content %}
@@ -18,30 +21,28 @@
{% endif %}
{% if object.is_rig and perms.RIGS.view_event %}
{# only need contact details for a rig #}
<div class="col-sm">
<div class="col-md-6">
{% if event.person %}
<div class="card card-default mb-3">
<div class="card-header">Contact Details</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-6">Person</dt>
<dd class="col-sm-6"
<dd class="col-sm-6">
{% if object.person %}
<a href="{% url 'person_detail' object.person.pk %}" class="modal-href">
{{ object.person }}
{{ object.person|namewithnotes:'person_detail' }}
</a>
{% endif %}
</dd>
<dt class="col-sm-6">Email</dt>
<dd class="col-sm-6">
<a href="mailto:{{object.person.email}}" target="_blank">
<span class="overflow-ellipsis">{{ object.person.email }}</span>
</a>
</dd>
<dd class="col-sm-6">{{ object.person.email|linkornone:'mailto' }}</dd>
<dt class="col-sm-6">Phone Number</dt>
<dd class="col-sm-6"><a href="tel:{{object.person.phone}}">{{ object.person.phone }}</a></dd>
<dd class="col-sm-6">{{ object.person.phone|linkornone:'tel' }}</a></dd>
</dl>
</div>
</div>
{% endif %}
{% if event.organisation %}
<div class="card card-default">
<div class="card-header">Organisation</div>
@@ -51,16 +52,14 @@
<dd class="col-sm-6">
{% if object.organisation %}
<a href="{% url 'organisation_detail' object.organisation.pk %}" class="modal-href">
{{ object.organisation }}
{{ object.organisation|namewithnotes:'organisation_detail' }}
</a>
{% endif %}
</dd>
<dt class="col-sm-6">Email</dt>
<dd class="col-sm-6">{{ object.organisation.email|linkornone:'mailto' }}</dd>
<dt class="col-sm-6">Phone Number</dt>
<dd class="col-sm-6">
<a href="tel:{{object.person.phone}}">
{{ object.organisation.phone }}
</a>
</dd>
<dd class="col-sm-6">{{ object.organisation.phone|linkornone:'tel' }}</a></dd>
<dt class="col-sm-6">Has SU Account</dt>
<dd class="col-sm-6">{{ event.organisation.union_account|yesno|capfirst }}</dd>
</dl>
@@ -69,13 +68,20 @@
{% endif %}
</div>
{% endif %}
<div class="col-sm">
<div class="col-md-6">
{% include 'partials/event_details.html' %}
</div>
{% if event.is_rig and event.internal and perms.RIGS.view_event %}
<div class="col-sm-12 py-3">
{ include 'partials/auth_details.html' %}
<div>
{% if not event.dry_hire %}
<div class="col {% if event.is_rig %}py-3{%endif %}">
{% include 'partials/hs_details.html' %}
</div>
{% endif %}
{% if event.is_rig %}
{% if event.is_rig and event.internal and perms.RIGS.view_event %}
<div class="col-md-8 py-3">
{% include 'partials/auth_details.html' %}
</div>
{% endif %}
{% endif %}
{% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right">

View File

@@ -1,25 +1,10 @@
{% load button from filters %}
<div class="btn-group py-3">
<a href="{% url 'event_update' event.pk %}" class="btn btn-secondary"><span
class="fas fa-edit"></span> <span
class="hidden-xs">Edit</span></a>
{% button 'edit' 'event_update' event.pk %}
{% if event.is_rig %}
{% if not event.dry_hire %}
<a href="{% url 'event_ra' event.pk %}" class="btn
{% if event.risk_assessment_edit_url %}
btn-success
{% else %}
btn-warning
{% endif %}
"><i class="fas fa-paperclip"></i> <span
class="hidden-xs">RA</span></a>
{% endif %}
<a href="{% url 'event_print' event.pk %}" target="_blank" class="btn btn-primary"><i
class="fas fa-print"></i> <span
class="hidden-xs">Print</span></a>
{% button 'print' 'event_print' event.pk %}
{% endif %}
<a href="{% url 'event_duplicate' event.pk %}" class="btn btn-secondary" title="Duplicate Rig"><span
class="fas fa-copy"></span> <span
class="hidden-xs">Duplicate</span></a>
{% button 'duplicate' 'event_duplicate' event.pk %}
{% if event.is_rig %}
{% if event.internal %}
<a class="btn item-add modal-href event-authorise-request
@@ -29,11 +14,13 @@
btn-warning
{% elif event.auth_request_to %}
btn-info
{% else %}
btn-secondary
{% endif %}
"
href="{% url 'event_authorise_request' object.pk %}">
<i class="fas fa-paper-plane"></i>
<span class="hidden-xs">
<span class="d-none d-sm-inline">
{% if event.authorised %}
Authorised
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
@@ -58,7 +45,7 @@
{% endif %}
" title="Invoice Rig"><span
class="fas fa-pound-sign"></span>
<span class="hidden-xs">Invoice</span></a>
<span class="d-none d-sm-inline">Invoice</span></a>
{% endif %}
{% endif %}
</div>

View File

@@ -13,7 +13,7 @@
<span class="pull-right">
{% if object.mic %}
<div class="text-center">
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo img-rounded"/>
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo rounded"/>
</div>
{% elif object.is_rig %}
<span class="fas fa-exclamation-sign"></span>

View File

@@ -3,17 +3,13 @@
{% load widget_tweaks %}
{% load static %}
{% load multiply from filters %}
{% block title %}
{% if object.pk %}
Event {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
{% else %}New Event{% endif %}
{% endblock %}
{% load button from filters %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/flatpickr.css' %}"/>
{% endblock %}
{% block preload_js %}
@@ -31,6 +27,8 @@
<script src="{% static 'js/autocompleter.js' %}"></script>
{% include 'partials/datetime-fix.html' %}
<script>
function setTime23Hours() {
$('#{{ form.end_time.id_for_label }}').val('23:00');
@@ -47,76 +45,37 @@
}
$('#'+id_end_time).val('02:00');
}
const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches;
$(document).ready(function () {
dur = matches ? 0 : 500;
{% if not object.pk and not form.errors %}
$('.form-hws').slideUp(function () {
$('.form-is_rig').slideUp();
$('.form-hws').slideUp(dur, function () {
$('.form-is_rig').slideUp(dur);
});
{% elif not object.pk and form.errors %}
if ($('#{{form.is_rig.auto_id}}').attr('checked') != 'checked') {
$('.form-is_rig').hide();
}
{% endif %}
{% if not object.pk %}
$('#is_rig-selector button').on('click', function () {
$('.form-non_rig').slideDown();
$('.form-non_rig').slideDown(dur);
if ($(this).data('is_rig') == 1) {
$('#{{form.is_rig.auto_id}}').prop('checked', true);
if ($('.form-non_rig').is(':hidden')) {
$('.form-is_rig').show();
} else {
$('.form-is_rig').slideDown();
$('.form-is_rig').slideDown(dur);
}
$('.form-hws, .form-hws .form-is_rig').css('overflow', 'visible');
} else {
$('#{{form.is_rig.auto_id}}').prop('checked', false);
$('.form-is_rig').slideUp();
$('.form-is_rig').slideUp(dur);
}
});
{% endif %}
function supportsDate() {
//return false; //for development
var input = document.createElement('input');
input.setAttribute('type','date');
var notADateValue = 'not-a-date';
input.setAttribute('value', notADateValue);
return !(input.value === notADateValue);
}
if(supportsDate()){
//Good, we'll use the browser implementation
}else{
//Rubbish browser - do JQuery backup
$('<link>')
.appendTo('head')
.attr({type : 'text/css', rel : 'stylesheet'})
.attr('href', '{% static "css/bootstrap-datetimepicker.min.css" %}');
$.when(
$.getScript( "{% static "js/moment.js" %}" ),
$.getScript( "{% static "js/bootstrap-datetimepicker.js" %}" ),
$.Deferred(function( deferred ){
$( deferred.resolve );
})
).done(function(){
$('input[type=date]').attr('type','text').datetimepicker({
format: 'YYYY-MM-DD',
});
$('input[type=time]').attr('type','text').datetimepicker({
format: 'HH:mm',
});
$('input[type=datetime-local]').attr('type','text').datetimepicker({
format: 'YYYY-MM-DD[T]HH:mm',
sideBySide: true,
});
});
}
});
$(document).ready(function () {
setupItemTable($("#{{ form.items_json.id_for_label }}").val());
});
@@ -124,59 +83,285 @@
$('[data-toggle="tooltip"]').tooltip();
})
</script>
<noscript>
<style>
.form-hws {
display: inherit !important;
}
</style>
</noscript>
{% endblock %}
{% block content %}
{% include 'item_modal.html' %}
<form class="form-horizontal itemised_form" role="form" method="POST">
<form class=" itemised_form" role="form" method="POST">
{% csrf_token %}
<div class="row">
<div class="col-sm-12">
<h2>
{% if duplicate %}
Duplicate of Event N{{ object.pk|stringformat:"05d" }}
{% elif object.pk %}
Event N{{ object.pk|stringformat:"05d" }}
{% else %}
New Event
{% endif %}
</h2>
<div class="col-12">
{% include 'form_errors.html' %}
</div>
{% include 'form_errors.html' %}
{% render_field form.is_rig style="display: none" %}
<input type="hidden" name="{{ form.items_json.name }}" id="{{ form.items_json.id_for_label }}"
value="{{ form.items_json.value }}"/>
{# New rig buttons #}
{% if not object.pk %}
<div class="col-sm-12">
<div class="card row align-items-center">
<div class="card-body" id="is_rig-selector">
<span data-toggle="tooltip"
title="Anything that involves TEC kit, crew, or otherwise us providing a service to anyone.">
<button type="button" class="btn btn-primary" data-is_rig="1">Rig</button>
</span>
<span data-toggle="tooltip"
title="Things that aren't service-based, like training, meetings and site visits.">
<button type="button" class="btn btn-info" data-is_rig="0">Non-Rig</button>
</span>
<span data-toggle="tooltip" title="Coming soon...">
<button type="button" class="btn btn-warning" data-is_rig="-1">Subhire</button>
</span>
</div>
<div class="col-sm-12">
<div class="card text-center" id="is_rig-selector">
<div class="card-body">
<span data-toggle="tooltip"
title="Anything that involves TEC kit, crew, or otherwise us providing a service to anyone.">
<button type="button" class="btn btn-primary w-25" data-is_rig="1">Rig</button>
</span>
<span data-toggle="tooltip"
title="Things that aren't service-based, like training, meetings and site visits.">
<button type="button" class="btn btn-info w-25" data-is_rig="0">Non-Rig</button>
</span>
</div>
</div>
</div>
{% endif %}
{# Contact details #}
{% include 'partials/contact_details_form.html' %}
<div class="col-md-6 mt-3">
<div class="card form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %} mb-3" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
<div class="card-header">Contact Details</div>
<div class="card-body">
<div class="form-group" data-toggle="tooltip" title="The main contact for the event, can be left blank if purely an organisation">
<label for="{{ form.person.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.person.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-8">
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='person' %}">
{% if person %}
<option value="{{form.person.value}}" selected="selected" data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'person_create' %}" class="btn btn-success modal-href"
data-target="#{{ form.person.id_for_label }}">
<span class="fas fa-plus"></span>
</a>
<a {% if form.person.value %}href="{% url 'person_update' form.person.value %}"{% endif %} class="btn btn-warning modal-href" id="{{ form.person.id_for_label }}-update" data-target="#{{ form.person.id_for_label }}">
<span class="fas fa-user-edit"></span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="form-group" data-toggle="tooltip" title="The client organisation, leave blank if client is an individual">
<label for="{{ form.organisation.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.organisation.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-8">
<select id="{{ form.organisation.id_for_label }}" name="{{ form.organisation.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='organisation' %}" >
{% if organisation %}
<option value="{{form.organisation.value}}" selected="selected" data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'organisation_create' %}" class="btn btn-success modal-href"
data-target="#{{ form.organisation.id_for_label }}">
<span class="fas fa-plus"></span>
</a>
<a {% if form.organisation.value %}href="{% url 'organisation_update' form.organisation.value %}"{% endif %} class="btn btn-warning modal-href" id="{{ form.organisation.id_for_label }}-update" data-target="#{{ form.organisation.id_for_label }}">
<span class="fas fa-edit"></span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card form-hws form-non_rig mb-3" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
<div class="card-header">Event Description</div>
<div class="card-body">
<div class="form-group" data-toggle="tooltip" title="A short description of the event, shown on rigboard and on paperwork">
<label for="{{ form.description.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.description.label }}</label>
<div class="col-sm-8">
{% render_field form.description class+="form-control" %}
</div>
</div>
</div>
</div>
</div>
{# Event details #}
{% include 'partials/event_details_form.html' %}
<div class="col-md-6 my-3">
<div class="card card-default form-hws form-non_rig" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
<div class="card-header">Event Details</div>
<div class="card-body">
<div id="form-hws">
<div class="form-group" data-toggle="tooltip" title="Name of the event, displays on rigboard and on paperwork">
<label for="{{ form.name.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.name.label }}</label>
<div class="col-sm-8">
{% render_field form.name class+="form-control" %}
</div>
</div>
<div class="form-group" data-toggle="tooltip" title="The venue for the rig, leave blank if unknown (e.g. for a dry hire)">
<label for="{{ form.venue.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.venue.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-8">
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
{% if venue %}
<option value="{{form.venue.value}}" selected="selected" data-update_url="{% url 'venue_update' form.venue.value %}">{{ venue }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'venue_create' %}" class="btn btn-success modal-href"
data-target="#{{ form.venue.id_for_label }}">
<span class="fas fa-plus"></span>
</a>
<a href="{% if object.venue %}{% url 'venue_update' object.venue.pk %}{% endif %}" class="btn btn-warning modal-href" id="{{ form.venue.id_for_label }}-update" data-target="#{{ form.venue.id_for_label }}">
<span class="fas fa-edit"></span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="{{ form.start_date.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.start_date.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="Start date for event, required">
{% render_field form.start_date class+="form-control" %}
</div>
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="Start time of event, can be left blank">
{% render_field form.start_time class+="form-control" step="60" %}
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="{{ form.end_date.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.end_date.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="End date of event, leave blank if unknown or same as start date">
{% render_field form.end_date class+="form-control" %}
</div>
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="End time of event, leave blank if unknown">
{% render_field form.end_time class+="form-control" step="60" %}
</div>
</div>
</div>
</div>
{# Rig only information #}
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="form-group" data-toggle="tooltip" title="The date/time at which TEC have access to the venue">
<label for="{{ form.access_at.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.access_at.label }}</label>
<div class="col-sm-8">
{% render_field form.access_at class+="form-control" step="60" %}
</div>
</div>
<div class="form-group" data-toggle="tooltip" title="The date/time at which crew should meet for this event">
<label for="{{ form.meet_at.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.meet_at.label }}</label>
<div class="col-sm-8">
{% render_field form.meet_at class+="form-control" step="60" %}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div class="checkbox">
<label data-toggle="tooltip" title="Mark this event as a dry-hire, so it needs to be checked in at the end">
{% render_field form.dry_hire %}{{ form.dry_hire.label }}
</label>
</div>
</div>
</div>
</div>
{# Status is needed on all events types and it looks good here in the form #}
<div class="form-group" data-toggle="tooltip" title="The current status of the event. Only mark as booked once paperwork is received">
<label for="{{ form.status.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.status.label }}</label>
<div class="col-sm-8">
{% render_field form.status class+="form-control" %}
</div>
</div>
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="form-group" data-toggle="tooltip" title="The Member in Charge of this event">
<label for="{{ form.mic.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.mic.label }}</label>
<div class="col-sm-8">
<select id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if mic %}
<option value="{{form.mic.value}}" selected="selected" >{{ mic.name }}</option>
{% endif %}
</select>
</div>
</div>
{% if object.dry_hire %}
<div class="form-group" data-toggle="tooltip" title="The person who checked-in this dry hire">
<label for="{{ form.checked_in_by.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.checked_in_by.label }}</label>
<div class="col-sm-8">
<select id="{{ form.checked_in_by.id_for_label }}" name="{{ form.checked_in_by.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if checked_in_by %}
<option value="{{form.checked_in_by.value}}" selected="selected" >{{ checked_in_by.name }}</option>
{% endif %}
</select>
</div>
</div>
{% endif %}
<div class="form-group" data-toggle="tooltip" title="The student ID of the client who collected the dry-hire">
<label for="{{ form.collector.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.collector.label }}</label>
<div class="col-sm-8">
{% render_field form.collector class+="form-control" %}
</div>
</div>
<div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)">
<label for="{{ form.purchase_order.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.purchase_order.label }}</label>
<div class="col-sm-8">
{% render_field form.purchase_order class+="form-control" %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{# Notes and item shit #}
<div class="col-sm-12">
<div class="card card-default form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="card card-default form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
<div class="card-body">
<div class="col-sm-12">
<div class="form-group" data-toggle="tooltip" title="Notes on the event. This is only visible to keyholders, and is not displayed on the paperwork">
@@ -188,12 +373,8 @@
</div>
</div>
</div>
<div class="col-sm-12 text-right form-hws form-non_rig my-3">
<div class="btn-group">
<button type="submit" class="btn btn-primary" title="Save"><i
class="fas fa-save"></i> Save
</button>
</div>
<div class="col-sm-12 text-right form-hws form-non_rig my-3" {% if not object.pk and not form.errors %}style="display: none;"{% endif%}>
{% button 'submit' %}
</div>
</div>
</form>

View File

@@ -3,7 +3,7 @@
{% load static %}
<!DOCTYPE document SYSTEM "rml.dtd">
<document filename="Event {{ object.id }} - {{ object.name }} - {{ object.start_date }}.pdf">
<document filename="{{filename}}">
<docinit>
<registerTTFont faceName="OpenSans" fileName="{{ fonts.opensans.regular }}"/>
<registerTTFont faceName="OpenSans-Bold" fileName="{{ fonts.opensans.bold }}"/>
@@ -97,8 +97,6 @@
<drawString x="265" y="746">info@nottinghamtec.co.uk</drawString>
<drawString x="137" y="732">Phone: (0115) 846 8720</drawString>
<setFont name="OpenSans" size="10" />
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
<setFont name="OpenSans" size="7" />

View File

@@ -314,7 +314,7 @@
</td>
</tr>
<tr>
<td>24 Hour Emergency Contacts: 07825 065681 and 07825 065678</td>
<td>General Enquires and 24 Hour Emergency Contact: 0115 84 68720</td>
</tr>
{% else %}
<tr>

View File

@@ -1,104 +0,0 @@
<div class="d-none d-md-block">
<div class="table-responsive">
<table class="table mb-0">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Event Date</th>
<th scope="col">Event Details</th>
<th scope="col">MIC</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr {% include 'partials/event_table_colour.html' %} id="event_row">
<!---Number-->
<th scope="row" id="event_number">{{ event.pk }}</th>
<!--Dates-->
<td id="event_dates">
<div><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></div>
{% if event.end_date and event.end_date != event.start_date %}
<div><strong>{{ event.end_date|date:"D d/m/Y" }}</strong></div>
{% endif %}
<!---Times-->
{% if not event.cancelled %}
<dl class="dl-horizontal">
{% if event.meet_at %}
<dt>Crew meet</dt>
<dd>{{ event.meet_at|date:"H:i" }}<br/>{{ event.meet_at|date:"(Y-m-d)" }}</dd>
{% endif %}
{% if event.has_start_time %}
<dt>Event starts</dt>
<dd>
{{ event.start_time|date:"H:i" }}<br/>
{{ event.start_date|date:"(Y-m-d)" }}<br/>
</dd>
{% endif %}
{% if event.has_end_time%}{% if event.start_date != event.end_date or event.start_time != event.end_time %}
<dt>Event ends</dt>
<dd>
{{ event.end_time|date:"H:i" }}<br/>
{{ event.end_date|date:"(Y-m-d)" }}
</dd>
{% endif %}{% endif %}
</dl>
{% endif %}
</td>
<!---Details-->
<td id="event_details">
<h4>
<a href="{% url 'event_detail' event.pk %}">
{{ event.name }}
</a>
{% if event.venue %}
<small>at {{ event.venue }}</small>
{% endif %}
{% if event.dry_hire %}
<span class="badge badge-secondary">Dry Hire</span>
{% endif %}
</h4>
{% if event.is_rig and not event.cancelled %}
<h5>
{{ event.person.name }}
{% if event.organisation %}
for {{ event.organisation.name }}
{% endif %}
</h5>
{% endif %}
{% if not event.cancelled and event.description %}
<p>{{ event.description|linebreaksbr }}</p>
{% endif %}
{% include 'partials/event_status.html' %}
</td>
<!---MIC-->
<td id="event_mic">
{% if event.mic %}
<div class="media">
{% if perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{% endif %}
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo mr-3"/>
{% if perms.RIGS.view_profile %}
</a>
{% endif %}
<div class="media-body">
<p>{{ event.mic.initials }}</p>
</div>
</div>
{% elif event.is_rig %}
<span class="fas fa-exclamation"></span>
{% endif %}
</td>
</tr>
{% empty %}
<tr class="bg-warning">
<td colspan="6">No events found</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="d-xs-block d-sm-block d-md-none">
{% include 'event_table_mobile.html' %}
</div>

View File

@@ -1,41 +0,0 @@
<div class="card">
{% for event in events %}
<div class="card-header {% if event.cancelled %}
text-muted bg-secondary
{% elif event.authorised and event.risk_assessment_edit_url and event.mic %}
bg-success
{% elif not event.is_rig %}
bg-info
{% else %}
bg-warning
{% endif %}
">
<a href="{% url 'event_detail' event.pk %}">{{ event.pk }} | {{ event.name }}</a>
{% if event.dry_hire %}
<span class="badge badge-pill badge-secondary">Dry Hire</span>
{% endif %}
</div>
<div class="card-body">
{% include 'partials/event_status.html' %}
<h6 class="pt-2"><strong>{{ event.start_date|date:"D d/m/Y" }}</strong>
{% if event.end_date and event.end_date != event.start_date %}
<strong> {{ event.end_date|date:"D d/m/Y" }}</strong></h4>
{% else %}
</h6>
{% endif %}
<ul class="list-group list-group-flush pb-3">
<li class="list-group-item">Venue: {{ event.venue }}</li>
<li class="list-group-item">Client: {{ event.person }}</li>
<li class="list-group-item">Organisation: {{ event.organisation }}</li>
</ul>
{% if not event.cancelled and event.description %}
<p>{{ event.description|linebreaksbr }}</p>
{% endif %}
</div>
<div class="card-footer">MIC: {% if event.mic %}<p>{{ event.mic.initials }}</p>{% elif event.is_rig %}<i class="fas fa-exclamation"></i>{% endif %}</div>
{% empty %}
<div class="card-body bg-warning">
<p>No events found</p>
</div>
{% endfor %}
</div>

View File

@@ -0,0 +1,37 @@
{% extends 'base_client.html' %}
{% load widget_tweaks %}
{% load static %}
{% block js %}
<script src="{% static 'js/tooltip.js' %}"></script>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip();
});
$('form').on('submit', function () {
$('#loading-modal').modal({
backdrop: 'static',
show: true
});
});
</script>
{% endblock %}
{% block content %}
<div class="row my-3">
{% include 'partials/client_eventdetails.html' %}
</div>
<div class="row mb-3">
<div class="col-sm-12">
<div class="card">
{% with object=event auth=True %}
{% include 'item_table.html' %}
{% endwith %}
</div>
</div>
</div>
{% block authorisation %}
{% endblock %}
{% endblock %}

View File

@@ -9,13 +9,7 @@
by <b>{{ object.name }}</b> as of <b>{{ object.event.last_edited_at }}</b>.
</p>
<p>
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
Your event is now fully booked and payment will be processed by the finance department automatically.
{% else %}{# external #}
Your event is now fully booked and our finance department will be contact to arrange payment.
{% endif %}
</p>
<p>Your event is now fully booked and payment will be processed by the finance department automatically.</p>
<p>TEC PA &amp; Lighting</p>
{% endblock %}

View File

@@ -2,10 +2,6 @@ Hi {{ to_name|default_if_none:"there" }},
Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.event.last_edited_at}}.
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
Your event is now fully booked and payment will be processed by the finance department automatically.
{% else %}{# external #}
Your event is now fully booked and our finance department will be contact to arrange payment.
{% endif %}
TEC PA & Lighting

View File

@@ -1,130 +1,99 @@
{% extends 'base_client.html' %}
{% extends 'eventauthorisation.html' %}
{% load widget_tweaks %}
{% load static %}
{% block js %}
<script src="{% static 'js/tooltip.js' %}"></script>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip();
});
$('form').on('submit', function () {
$('#loading-modal').modal({
backdrop: 'static',
show: true
});
});
</script>
{% endblock %}
{% block title %}
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }}
{% endblock %}
{% block authorisation %}
<div class="row">
<div class="col-sm-12">
<div class="card border-primary">
<div class="card-header" id="eventauth">Event Authorisation</div>
{% block content %}
<div class="page-header my-3">
<h1>
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %}
| {{ event.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
</h1>
</div>
<div class="card-body">
<form class=" itemised_form" role="form" method="POST" action="#eventauth">
{% csrf_token %}
{% include 'form_errors.html' %}
<div class="row">
<div class="col-sm-12">
<p>
I agree that I am authorised to approve this event. I agree that I am the
<strong>President/Treasurer or account holder</strong> of the hirer, or that I
have the written permission of the
<strong>President/Treasurer or account holder</strong> of the hirer stating that
I can authorise this event.
</p>
</div>
<div class="row">
{% include 'client_eventdetails.html' %}
</div>
<div class="col-sm-12 col-md-6">
<div class="col-sm-12 form-group form-row" data-toggle="tooltip"
title="Your name as the person authorising the event.">
<label for="{{ form.name.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.name.label }}</label>
<div class="row">
<div class="col-sm-12">
{% with object=event %}
{% include 'item_table.html' %}
{% endwith %}
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="card border-primary">
<div class="card-header" id="eventauth">Event Authorisation</div>
<div class="card-body">
<form class="form-horizontal itemised_form" role="form" method="POST" action="#eventauth">
{% csrf_token %}
{% include 'form_errors.html' %}
<div class="row">
<div class="col-sm-12">
<p>
I agree that I am authorised to approve this event. I agree that I am the
<strong>President/Treasurer or account holder</strong> of the hirer, or that I
have the written permission of the
<strong>President/Treasurer or account holder</strong> of the hirer stating that
I can authorise this event.
</p>
</div>
<div class="col-sm-12 col-md-6">
<div class="col-sm-12 form-group" data-toggle="tooltip"
title="Your name as the person authorising the event.">
<label for="{{ form.name.id_for_label }}"
class="col-sm-4 control-label">{{ form.name.label }}</label>
<div class="col-sm-8">
{% render_field form.name class+="form-control" %}
</div>
</div>
<div class="col-sm-12 form-group" data-toggle="tooltip"
title="Your Student ID or Staff username as the person authorising the event.">
<label for="{{ form.uni_id.id_for_label }}"
class="col-sm-4 control-label">{{ form.uni_id.label }}</label>
<div class="col-sm-8">
{% render_field form.uni_id class+="form-control" %}
</div>
<div class="col-sm-8">
{% render_field form.name class+="form-control" %}
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="col-sm-12 form-group" data-toggle="tooltip"
title="The Students' Union account code you wish this event to be charged to.">
<label for="{{ form.account_code.id_for_label }}"
class="col-sm-4 control-label">{{ form.account_code.label }}</label>
<div class="col-sm-8">
{% render_field form.account_code class+="form-control" %}
</div>
<div class="col-sm-12 form-group form-row" data-toggle="tooltip"
title="Your Student ID or Staff username as the person authorising the event.">
<label for="{{ form.uni_id.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.uni_id.label }}</label>
<div class="col-sm-8">
{% render_field form.uni_id class+="form-control" %}
</div>
</div>
</div>
<div class="col-sm-12 form-group" data-toggle="tooltip"
title="The full amount chargable for this event as displayed above, including VAT.">
<label for="{{ form.amount.id_for_label }}"
class="col-sm-4 control-label">{{ form.amount.label }}</label>
<div class="col-sm-8">
{% render_field form.amount class+="form-control" %}
</div>
<div class="col-sm-12 col-md-6">
<div class="col-sm-12 form-group form-row" data-toggle="tooltip"
title="The Students' Union account code you wish this event to be charged to.">
<label for="{{ form.account_code.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.account_code.label }}</label>
<div class="col-sm-8">
{% render_field form.account_code class+="form-control" %}
</div>
</div>
<div class="col-sm-12">
<div class="col-sm-12 col-md-8 form-group">
<div class="col-sm-offset-4 col-sm-8 col-md-offset-3" data-toggle="tooltip"
title="In order to book an event you must agree to the TEC Terms of Hire.">
<div class="checkbox">
<label>
{% render_field form.tos %} I have read and agree to the TEC
<a href="{{ tos_url }}">Terms of Hire</a>. E&OE.
</label>
</div>
</div>
</div>
<div class="col-sm-12 col-md-4 text-right">
<div class="btn-group">
<button class="btn btn-primary btn-lg" type="submit">Authorise</button>
<div class="col-sm-12 form-group form-row" data-toggle="tooltip"
title="The full amount chargable for this event as displayed above, including VAT.">
<label for="{{ form.amount.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.amount.label }}</label>
<div class="col-sm-8">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">£</span>
</div>
{% render_field form.amount class+="form-control" %}
</div>
</div>
</div>
</div>
</form>
</div>
<div class="col-sm-12">
<div class="col-sm-12 col-md-8 form-group">
<div class="col-sm-offset-4 col-sm-8 col-md-offset-3" data-toggle="tooltip"
title="In order to book an event you must agree to the TEC Terms of Hire.">
<div class="checkbox">
<label>
{% render_field form.tos %} I have read and agree to the TEC
<a href="{{ tos_url }}">Terms of Hire</a>. E&OE.
</label>
</div>
</div>
</div>
<div class="text-right">
<div class="btn-group">
<button class="btn btn-primary btn-lg" type="submit">Authorise</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,64 +1,42 @@
{% extends 'base_client.html' %}
{% extends 'eventauthorisation.html' %}
{% load widget_tweaks %}
{% block title %}
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }}
{% block js %}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>
{% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %}
| {{ event.name }} {% if event.dry_hire %}<span class="badge">Dry Hire</span>{% endif %}
</h1>
</div>
</div>
{% block authorisation %}
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header">Event Authorisation</div>
{% include 'client_eventdetails.html' %}
<div class="card-body">
<div class="row">
<div class="col-sm-12 col-md-6">
<dl class="dl row">
<dt class="col-6">Name</dt>
<dd class="col-6">{{ object.name }}</dd>
<div class="row">
<div class="col-sm-12">
{% with object=event %}
{% include 'item_table.html' %}
{% endwith %}
</div>
</div>
<dt class="col-6">Email</dt>
<dd class="col-6">{{ object.email }}</dd>
<div class="row">
<div class="col-sm-12">
<div class="card card-default">
<div class="card-header">Event Authorisation</div>
<dt class="col-6">University ID</dt>
<dd class="col-6">{{ object.uni_id }}</dd>
</dl>
</div>
<div class="card-body">
<div class="row">
<div class="col-sm-12 col-md-6">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ object.name }}</dd>
<div class="col-sm-12 col-md-6">
<dl class="dl row">
<dt class="col-6">Account code</dt>
<dd class="col-6">{{ object.account_code }}</dd>
<dt>Email</dt>
<dd>{{ object.email }}</dd>
{% if internal %}
<dt>University ID</dt>
<dd>{{ object.uni_id }}</dd>
{% endif %}
</dl>
</div>
<div class="col-sm-12 col-md-6">
<dl class="dl-horizontal">
<dt>Account code</dt>
<dd>{{ object.account_code }}</dd>
<dt>Authorised amount</dt>
<dd>£ {{ object.amount|floatformat:2 }}</dd>
</dl>
</div>
<dt class="col-6">Authorised amount</dt>
<dd class="col-6">£ {{ object.amount|floatformat:2 }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,50 @@
{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% load button from filters %}
{% block content %}
<div class="table-responsive">
<table class="table mb-0">
<thead>
<tr>
<th scope="col">Event</th>
<th scope="col">Dates</th>
<th scope="col">RA</th>
<th scope="col">Checklists</th>
</tr>
</thead>
<tbody>
{% for event in object_list %}
<tr id="event_row">
<th scope="row" id="event_number"><a href="{% url 'event_detail' event.pk %}">{{ event }}</a></th>
<!--Dates-->
<td id="event_dates">
<span><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></span>
{% if event.end_date and event.end_date != event.start_date %}
<br><span><strong>{{ event.end_date|date:"D d/m/Y" }}</strong></span>
{% endif %}
</td>
<td>{% include 'partials/hs_status.html' with event=event object=event.riskassessment view='ra_detail' edit='ra_edit' create='event_ra' review='ra_review' perm=perms.RIGS.review_riskassessment %}</td>
<td>
{% for checklist in event.checklists.all %}
{% include 'partials/hs_status.html' with event=event object=checklist view='ec_detail' edit='ec_edit' create='event_ec' review='ec_review' perm=perms.RIGS.review_eventchecklist %}
<br>
{% endfor %}
<a href="{% url 'event_ec' event.pk %}" class="btn btn-info"><span class="fas fa-paperclip"></span> <span
class="d-none d-sm-inline">Create</span></a>
</td>
</tr>
{% empty %}
<tr class="bg-warning text-dark">
<td colspan="6">No events found</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<div class="row justify-content-center">
{% paginator %}
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% load help_text from filters %}
{% load verbose_name from filters %}
{% load get_field from filters %}
{% block title %}{{ title }} List{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<h2>{{title}} List</h2>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="table-responsive">
<table class="table mb-0">
<thead>
<tr>
<th scope="col">Event</th>
{# mmm hax #}
{% if object_list.0 != None %}
{% for field in fields %}
<th scope="col">{{ object_list.0|verbose_name:field|title }}</th>
{% endfor %}
{% endif %}
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr class="{% if object.reviewed_by %}table-success{%endif%}">
{# General #}
<th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a></th>
{% for field in fields %}
<td>{{ object|get_field:field }}</td>
{% endfor %}
{# Buttons #}
<td>
{% include 'partials/hs_status.html' %}
</td>
</tr>
{% empty %}
<tr class="bg-warning">
<td colspan="6">Nothing found</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% if is_paginated %}
<div class="row justify-content-center">
{% paginator %}
</div>
{% endif %}
{% endblock %}

View File

@@ -1,35 +1,20 @@
{% extends 'base_rigs.html' %}
{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %}
{% block title %}Delete Invoice {{ object.invoice.pk }}{% endblock %}
{% block content %}
<div class="col-sm-offset-2 col-sm-8">
<div class="alert alert-danger" role="alert">
<h2>Delete invoice {{ object.pk }}</h2>
<div class="alert alert-danger" role="alert">
<h2>Delete Invoice {{ object.pk }}</h2>
<p>Are you sure you wish to delete invoice {{ object.pk }}?</p>
<p>Are you sure you wish to delete invoice {{ object.pk }}?</p>
<p class="text-center"><strong>This action cannot be undone!</strong></p>
<div class="row">
<div class="col-sm-12">
<form action="{{ action_link }}" method="post">{% csrf_token %}
<input type="hidden" name="next" value="{% url 'invoice_list' %}"/>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<input type="submit" value="Yes" class="btn btn-danger col-sm-1"/>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default col-sm-1">No</a>
</form>
</div>
</div>
</div>
<div class="text-right">
<form action="{{ action_link }}" method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'invoice_list' %}"/>
<input type="submit" value="Yes" class="btn btn-danger col-sm-1"/>
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-success col-sm-1">No</a>
</form>
</div>
{% endblock %}
</div>
{% endblock %}

View File

@@ -1,27 +1,20 @@
{% extends 'base_rigs.html' %}
{% block title %}Invoice {{ object.pk }}{% endblock %}
{% load button from filters %}
{% block content %}
<div class="col-sm-12">
<div class="row">
<div class="col-sm-8">
<h2>Invoice {{ object.pk }} ({{ object.invoice_date|date:"d/m/Y"}})</h2>
</div>
<div class="row justify-content-end py-3">
<div class="col-sm-4 text-right">
<div class="btn-group btn-page">
<a href="{% url 'invoice_delete' object.pk %}" class="btn btn-danger" title="Delete Invoice">
<span class="fas fa-times"></span> <span
class="hidden-xs">Delete</span>
</a>
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-warning" title="Void Invoice">
<span class="fas fa-ban"></span> <span
class="hidden-xs">Void</span>
</a>
<a href="{% url 'invoice_print' object.pk %}" target="_blank" title="Print Invoice" class="btn btn-primary"><span
class="fas fa-print"></span> <span
class="hidden-xs">Print</span></a>
<a href="{% url 'invoice_delete' object.pk %}" class="btn btn-danger" title="Delete Invoice">
<span class="fas fa-times"></span> <span
class="d-none d-sm-inline">Delete</span>
</a>
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-warning" title="Void Invoice">
<span class="fas fa-ban"></span> <span
class="d-none d-sm-inline">Void</span>
</a>
{% button 'print' url='invoice_print' pk=object.pk %}
</div>
</div>
</div>
@@ -53,13 +46,13 @@
{% endif %}
</div>
<div class="row">
<div class="row py-4">
<div class="col-sm-6">
<div class="card card-default">
<div class="card-body">
<div class="pull-right">
<div class="text-right py-3">
<a href="{% url 'payment_create' %}?invoice={{ object.pk }}"
class="btn btn-default modal-href"
class="btn btn-success modal-href"
data-target="#{{ form.person.id_for_label }}">
<span class="fas fa-plus"></span> Add
</a>
@@ -96,14 +89,14 @@
</div>
<div class="col-sm-6">
<div class="card card-default">
<div class="card-body">
{% with object.event as object %}
{% include 'item_table.html' %}
{% endwith %}
</div>
<div class="card">
{% with object.event as object %}
{% include 'item_table.html' %}
{% endwith %}
</div>
</div>
</div>
<div class="col-12 text-right">
{% include 'partials/last_edited.html' with target="invoice_history" %}
</div>
</div>

View File

@@ -1,17 +1,15 @@
{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% load button from filters %}
{% load static %}
{% block title %}Invoices{% endblock %}
{% block content %}
<div class="col-sm-12">
<h2>{% block heading %}Invoices{% endblock %}</h2>
{% block description %}{% endblock %}
{{ description }}
{% block search %}{% endblock %}
<div class="table-responsive col-sm-12">
<table class="table table-hover">
<thead class="thead-dark">
<div class="table-responsive">
<table class="table table-hover table-sm">
<thead>
<tr>
<th scope="col">Invoice #</th>
<th scope="col">Event</th>
@@ -25,7 +23,7 @@
<tbody>
{% for invoice in invoice_list %}
<tr class="table-{% if invoice.is_closed %}success{% else %}warning{% endif %}">
<th scope="row">{{ invoice.pk }}<br>
<th scope="row">{{ invoice.display_id }}<br>
<span class="text-muted">{% if invoice.void %}(VOID){% elif invoice.is_closed %}(PAID){% else %}(O/S){% endif %}</span></th>
<td><a href="{% url 'event_detail' invoice.event.pk %}">N{{ invoice.event.pk|stringformat:"05d" }}</a>: {{ invoice.event.name }} <br>
<span class="text-muted">{{ invoice.event.get_status_display }}{% if not invoice.event.mic %}, No MIC{% endif %}
@@ -51,9 +49,7 @@
{% endif %}
</td>
<td class="text-right">
<a href="{% url 'invoice_detail' invoice.pk %}" class="btn btn-primary">
<i class="fas fa-edit"></i>
</a>
{% button 'view' url='invoice_detail' pk=invoice.pk %}
</td>
</tr>
{% endfor %}

View File

@@ -1,13 +0,0 @@
{% extends 'invoice_list.html' %}
{% block title %}
Outstanding Invoices
{% endblock %}
{% block heading %}
Outstanding Invoices ({{ count }} Events, £{{ total|floatformat:2 }})
{% endblock %}
{% block description %}
<p>Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger</p>
{% endblock %}

View File

@@ -1,24 +1,7 @@
{% extends 'invoice_list.html' %}
{% block title %}
Invoice Archive
{% endblock %}
{% block heading %}
All Invoices
{% endblock %}
{% block description %}
<p>This page displays all invoices: outstanding, paid, and void</p>
{% endblock %}
{% block search %}
<div class="col-sm-3 col-sm-offset-9">
<form class="form form-horizontal col-sm-12">
<div class="form-group">
<input type="search" name="q" placeholder="Search" value="{{ request.GET.q }}"
class="form-control"/>
</div>
</form>
<div class="py-3">
{% include 'partials/list_search.html' %}
</div>
{% endblock %}

View File

@@ -2,8 +2,6 @@
{% load paginator from filters %}
{% load static %}
{% block title %}Events for Invoice{% endblock %}
{% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script>
@@ -15,11 +13,10 @@
{% block content %}
<div class="col-sm-12">
<h2>Events for Invoice ({{count}} Events, £{{ total|floatformat:2 }})</h2>
<p>These events have happened, but paperwork has not yet been sent to treasury</p>
<div class="table-responsive col-sm-12">
<div class="table-responsive">
<table class="table table-hover">
<thead class="thead-dark">
<thead>
<tr>
<th scope="col">Event #</th>
<th scope="col">Start Date</th>
@@ -32,8 +29,8 @@
</thead>
<tbody>
{% for event in object_list %}
<tr {% include 'partials/event_table_colour.html' %}>
<th scope="row"><a href="{% url 'event_detail' event.pk %}">N{{ event.pk|stringformat:"05d" }}</a><br>
<tr class="{{event.status_color}}">
<th scope="row"><a href="{% url 'event_detail' event.pk %}">{{ event.display_id }}</a><br>
<span class="text-muted">{{ event.get_status_display }}</span></th>
<td>{{ event.start_date }}</td>
<td>
@@ -69,12 +66,15 @@
{% endif %}
</td>
<td class="text-right">
<a href="{% url 'invoice_event' event.pk %}"
class="btn btn-primary"
data-toggle="tooltip"
title="'Invoice' this event - click this when paperwork has been sent to treasury">
<i class="fas fa-pound-sign"></i> Paperwork Sent
</a>
<div class="btn-group">
<a href="{% url 'invoice_event' event.pk %}"
class="btn btn-primary">
<span class="fas fa-pound-sign"></span> Create Invoice
</a>
<a href="{% url 'invoice_event_void' event.pk %}"
class="btn btn-warning">& Void
</a>
</div>
</td>
</tr>
{% endfor %}

View File

@@ -3,18 +3,21 @@
<div class="modal-content">
<div class="modal-header">
<h4>{{ object.name|default:"New Event" }}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="item-form">
<div class="modal-body">
<div class="form-group form-row">
<label for="item_name" class="col-sm-2 control-label">Item Name</label>
<label for="item_name" class="col-sm-2 col-form-label">Item Name</label>
<div class="col-sm-10">
<input type="text" placeholder="Item Name" class="form-control" required maxlength="255"
id="item_name"/>
</div>
</div>
<div class="form-group form-row">
<label for="item_description" class="col-sm-2 control-label">Description</label>
<label for="item_description" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-10">
<textarea type="text" placeholder="Description" class="form-control"
id="item_description" rows="8"></textarea>
@@ -23,7 +26,7 @@
<div class="form-row">
<div class="col-sm-6">
<div class="form-group form-row">
<label for="item_quantity" class="col-sm-4 control-label">Quantity</label>
<label for="item_quantity" class="col-sm-4 col-form-label">Quantity</label>
<div class="col-sm-8">
<input type="number" placeholder="Quantity" class="form-control" required
min="1" id="item_quantity"/>
@@ -32,7 +35,7 @@
</div>
<div class="col-sm-6">
<div class="form-group form-row">
<label for="item_cost" class="col-sm-4 control-label">Cost</label>
<label for="item_cost" class="col-sm-4 col-form-label">Cost</label>
<div class="col-sm-8">
<div class="input-group">

View File

@@ -15,12 +15,12 @@
{% if edit %}
<td class="vert-align text-right">
<div class="btn-group" role="group" aria-label="Action buttons">
<button type="button" class="item-edit btn btn-xs btn-warning"
<button type="button" class="item-edit btn btn-sm btn-warning"
data-pk="{{item.pk}}"
data-toggle="modal" data-target="#itemModal">
<span class="fas fa-edit"></span>
</button>
<button type="button" class="item-delete btn btn-xs btn-danger"
<button type="button" class="item-delete btn btn-sm btn-danger"
data-pk="{{item.pk}}">
<span class="fas fa-times-circle"></span>
</button>

View File

@@ -12,7 +12,7 @@
{% endif %}
{% if edit %}
<th scope="col" class="text-right align-self-start">
<button type="button" class="btn btn-success btn-xs item-add"
<button type="button" class="btn btn-success btn-sm item-add"
data-toggle="modal"
data-target="#itemModal">
<i class="fas fa-plus"></i> Add Item
@@ -26,7 +26,7 @@
{% include 'item_row.html' %}
{% endfor %}
</tbody>
{% if perms.RIGS.view_event %}
{% if auth or perms.RIGS.view_event %}
<tfoot>
<tr>
<td rowspan="3" colspan="2"></td>
@@ -65,12 +65,12 @@
{% if edit %}
<td class="vert-align text-right">
<div class="btn-group" role="group" aria-label="Action buttons">
<button type="button" class="item-edit btn btn-xs btn-warning"
<button type="button" class="item-edit btn btn-sm btn-warning"
data-pk="{{item.pk}}"
data-toggle="modal" data-target="#itemModal">
<span class="fas fa-edit"></span>
</button>
<button type="button" class="item-delete btn btn-xs btn-danger"
<button type="button" class="item-delete btn btn-sm btn-danger"
data-pk="{{item.pk}}">
<span class="fas fa-times-circle"></span>
</button>

View File

@@ -1,4 +0,0 @@
{% load to_class_name from filters %}
{# pass in variable "object" to this template #}
<a title="{% if object.is_rig == False %}Non-rig{% elif object.dry_hire %}Dry Hire{% elif object.is_rig %}Rig{%else%}{{object|to_class_name}}{% endif %} | '{{object.name}}'" href="{{ object.get_absolute_url }}">{% if object.is_rig == False %}Non-rig{% elif object.dry_hire %}Dry Hire{% elif object.is_rig %}Rig{%else%}{{object|to_class_name}}{% endif %} | '{{ object.activity_feed_string|default:object.name }}'</a>

View File

@@ -1,92 +0,0 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load widget_tweaks %}
{% block title %}Organisation | {{ object.name }}{% endblock %}
{% block content %}
<div class="row">
{% if not request.is_ajax %}
<div class="col-sm-12">
<h1>Organisation | {{ object.name }}</h1>
</div>
{% endif %}
<div class="col-sm">
<div class="card bg-info">
<div class="card-header">Organisation Details</div>
<div class="card-body">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{ object.name }}</dd>
<dt>Phone</dt>
<dd><a href="tel:{{ object.phone }}">{{ object.phone }}</a></dd>
<dt>Email</dt>
<dd><a href="mailto:{{ object.email }}"><span class="overflow-ellipsis">{{ object.email }}</span></a></dd>
<dt>Address</dt>
<dd>{{ object.address|linebreaksbr }}</dd>
<dt>Notes</dt>
<dd>{{ object.notes|linebreaksbr }}</dd>
<dt>Union Account</dt>
<dd>{{ object.union_account|yesno|capfirst }}</dd>
</dl>
</div>
</div>
</div>
<div class="col-sm">
<div class="card">
<div class="card-header">Associated People</div>
<div class="card-body">
<div class="list-group">
{% for person,count in object.persons %}
<a class="list-group-item" href="{% url 'person_detail' person.pk %}">{{ person.pk|stringformat:"05d" }} | {{ person.name }} <span class="badge" title="{{count}} events with {{person.name}} for {{object.name}}">{{count}}</span></a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-sm-12">
<div class="card">
<div class="card-header">Associated Events</div>
{% with object.latest_events as events %}
{% include 'event_table.html' %}
{% endwith %}
</div>
</div>
</div>
{% if not request.is_ajax %}
<div class="row">
<div class="col-sm-12 text-right">
<div class="btn-group">
<a href="{% url 'organisation_update' object.pk %}" class="btn btn-default"><span
class="fas fa-edit"></span> Edit</a>
</div>
{% include 'partials/last_edited.html' with target="organisation_history" %}
</div>
</div>
{% endif %}
{% endblock %}
{% if request.is_ajax %}
{% block footer %}
<div class="row">
<div class="col-sm-12 text-right">
<div class="btn-group">
<a href="{% url 'organisation_detail' object.pk %}" class="btn btn-primary"><span
class="fas fa-eye"></span> Open Page</a>
<a href="{% url 'organisation_update' object.pk %}" class="btn btn-warning"><span
class="fas fa-edit"></span> Edit</a>
</div>
{% include 'partials/last_edited.html' with target="organisation_history" %}
</div>
</div>
{% endblock %}
{% endif %}

View File

@@ -1,78 +0,0 @@
{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}{% if object.pk %}Edit {{ object.name }}{% else %}Add Organisation{% endif %}{% endblock %}
{% block content %}
<div class="col-sm-offset-1 col-sm-10">
<h3>{{ object.pk|yesno:"Edit,Add" }} Organisation</h3>
<form action="{{ form.action|default:request.path }}" method="post" class="form-horizontal">{% csrf_token %}
<div class="row">
<div class="col-md-6">
{% include 'form_errors.html' %}
<div class="form-group">
<label for="{{ form.name.id_for_label }}"
class="col-sm-2 control-label">{{ form.name.label }}</label>
<div class="col-sm-10">
{% render_field form.name class+="form-control" placeholder=form.name.label %}
</div>
</div>
<div class="form-group">
<label for="{{ form.phone.id_for_label }}"
class="col-sm-2 control-label">{{ form.phone.label }}</label>
<div class="col-sm-10">
{% render_field form.phone class+="form-control" type="tel" placeholder=form.phone.label %}
</div>
</div>
<div class="form-group">
<label for="{{ form.email.id_for_label }}"
class="col-sm-2 control-label">{{ form.email.label }}</label>
<div class="col-sm-10">
{% render_field form.email class+="form-control" type="email" placeholder=form.email.label %}
</div>
</div>
<div class="form-group">
<label for="{{ form.address.id_for_label }}"
class="col-sm-2 control-label">{{ form.address.label }}</label>
<div class="col-sm-10">
{% render_field form.address class+="form-control" placeholder=form.address.label %}
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="{{ form.notes.id_for_label }}"
class="col-sm-2 control-label">{{ form.notes.label }}</label>
<div class="col-sm-10">
{% render_field form.notes class+="form-control" placeholder=form.notes.label %}
</div>
</div>
<div class="form-group">
<div class="col-sm-10 col-sm-offset-2">
<div class="checkbox">
<label>
{% render_field form.union_account %} {{ form.union_account.label }}
</label>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<input class="btn btn-primary pull-right" type="submit"/>
</div>
</form>
</div>
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load widget_tweaks %}
{% block title %}Organisations{% endblock %}
{% block content %}
<div class="row">
<h3>Organisations</h3>
</div>
{% include 'partials/generic_list.html' with edit="organisation_update" detail="organisation_detail" create="organisation_create" union_account=True %}
{% endblock %}

View File

@@ -0,0 +1,8 @@
<div class="row">
<div class="col-sm-12 py-3">
<div class="card">
<div class="card-header">Associated Events</div>
{% include 'partials/event_table.html' with events=object.latest_events %}
</div>
</div>
</div>

View File

@@ -0,0 +1,10 @@
<div class="col-sm">
<div class="card">
<div class="card-header">Associated Organisations</div>
<ul class="list-group list-group-flush">
{% for organisation,count in object.organisations %}
<a class="list-group-item list-group-item-action" href="{% url 'organisation_detail' organisation.pk %}">{{ organisation.pk|stringformat:"05d" }} | {{ organisation.name }} <span class="badge badge-secondary" title="{{count}} events with {{object.name}} for {{organisation.name}}">{{count}}</span></a>
{% endfor %}
</ul>
</div>
</div>

View File

@@ -0,0 +1,12 @@
<div class="col-sm">
<div class="card">
<div class="card-header">Associated People</div>
<div class="list-group list-group-flush">
{% for person,count in object.persons %}
<a class="list-group-item list-group-item-action" href="{% url 'person_detail' person.pk %}">{{ person.pk|stringformat:"05d" }} | {{ person.name }} <span class="badge badge-secondary" title="{{count}} events with {{person.name}} for {{object.name}}">{{count}}</span></a>
{% empty %}
<div class="card-body">None</div>
{% endfor %}
</div>
</div>
</div>

View File

@@ -8,8 +8,8 @@
{% endif %}
">
<div class="card-header">Client Authorisation</div>
<div class="card-body">
<dl class="col-sm-6">
<div class="card-body row">
<dl class="col-md-6">
<dt>Authorisation Request</dt>
<dd>{{ object.auth_request_to|yesno:"Yes,No" }}</dd>
@@ -22,8 +22,8 @@
<dt>To</dt>
<dd>{{ object.auth_request_to }}</dd>
</dl>
<dd class="visible-xs">&nbsp;</dd>
<dl class="col-sm-6">
<dd class="d-block d-sm-none">&nbsp;</dd>
<dl class="col-md-6">
<dt>Authorised</dt>
<dd>{{ object.authorised|yesno:"Yes,No" }}</dd>

View File

@@ -0,0 +1,6 @@
{% load widget_tweaks %}
{% load help_text from filters %}
<div class="form-check">
{% render_field formitem class+="form-check-input" %}
<label class="form-check-label" for="{{ formitem.id_for_label }}">{{formitem.help_text|safe}}</label>
</div>

View File

@@ -0,0 +1,76 @@
<div class="col-sm-6">
<div class="card mb-3">
<div class="card-header">Contact Details</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-5">Person</dt>
<dd class="col-sm-7">
{% if event.person %}
{{ event.person.name }}
{% endif %}
</dd>
<dt class="col-sm-5">Email</dt>
<dd class="col-sm-7">
<span class="overflow-ellipsis">{{ event.person.email }}</span>
</dd>
<dt class="col-sm-5">Phone Number</dt>
<dd class="col-sm-7">{{ event.person.phone }}</dd>
</dl>
</div>
</div>
{% if event.organisation %}
<div class="card mt-3">
<div class="card-header">Organisation Details</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-5">Organisation</dt>
<dd class="col-sm-7">
{{ event.organisation.name }}
</dd>
<dt class="col-sm-5">Phone Number</dt>
<dd class="col-sm-7">{{ object.organisation.phone }}</dd>
</dl>
</div>
</div>
{% endif %}
</div>
<div class="col-sm-6">
<div class="card border-info">
<div class="card-header">Event Info</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-5">Event Venue</dt>
<dd class="col-sm-7">
{% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
</a>
{% endif %}
</dd>
<dt class="col-sm-5">Status</dt>
<dd class="col-sm-7">{{ event.get_status_display }}</dd>
<dd class="col-sm-12">&nbsp;</dd>
<dt class="col-sm-5">Access From</dt>
<dd class="col-sm-7">{{ event.access_at|date:"D d M Y H:i"|default:"" }}</dd>
<dt class="col-sm-5">Event Starts</dt>
<dd class="col-sm-7">{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}</dd>
<dt class="col-sm-5">Event Ends</dt>
<dd class="col-sm-7">{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}</dd>
<dd class="col-sm-12">&nbsp;</dd>
<dt class="col-sm-5">Event Description</dt>
<dd class="col-sm-12">{{ event.description|linebreaksbr }}</dd>
</dl>
</div>
</div>
</div>

View File

@@ -1,74 +0,0 @@
{% load widget_tweaks %}
<div class="col-md-6 mt-3">
<div class="card form-hws form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %} mb-3">
<div class="card-header">Contact Details</div>
<div class="card-body">
<div class="form-group" data-toggle="tooltip" title="The main contact for the event, can be left blank if purely an organisation">
<label for="{{ form.person.id_for_label }}"
class="col-sm-4 control-label">{{ form.person.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-8">
<select id="{{ form.person.id_for_label }}" name="{{ form.person.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='person' %}">
{% if person %}
<option value="{{form.person.value}}" selected="selected" data-update_url="{% url 'person_update' form.person.value %}">{{ person }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'person_create' %}" class="btn btn-success modal-href"
data-target="#{{ form.person.id_for_label }}">
<span class="fas fa-plus"></span>
</a>
<a href="{% if form.person.value %}{% url 'person_update' form.person.value %}{% endif %}" class="btn btn-warning modal-href" id="{{ form.person.id_for_label }}-update" data-target="#{{ form.person.id_for_label }}">
<span class="fas fa-user-edit"></span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="form-group" data-toggle="tooltip" title="The client organisation, leave blank if client is an individual">
<label for="{{ form.organisation.id_for_label }}"
class="col-sm-4 control-label">{{ form.organisation.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-8">
<select id="{{ form.organisation.id_for_label }}" name="{{ form.organisation.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='organisation' %}" >
{% if organisation %}
<option value="{{form.organisation.value}}" selected="selected" data-update_url="{% url 'organisation_update' form.organisation.value %}">{{ organisation }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'organisation_create' %}" class="btn btn-success modal-href"
data-target="#{{ form.organisation.id_for_label }}">
<span class="fas fa-plus"></span>
</a>
<a href="{% if form.organisation.value %}{% url 'organisation_update' form.organisation.value %}{% endif %}" class="btn btn-warning modal-href" id="{{ form.organisation.id_for_label }}-update" data-target="#{{ form.organisation.id_for_label }}">
<span class="fas fa-edit"></span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card form-hws form-non_rig mb-3">
<div class="card-header">Event Description</div>
<div class="card-body">
<div class="form-group" data-toggle="tooltip" title="A short description of the event, shown on rigboard and on paperwork">
<label for="{{ form.description.id_for_label }}"
class="col-sm-4 control-label">{{ form.description.label }}</label>
<div class="col-sm-8">
{% render_field form.description class+="form-control" %}
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,51 @@
<h5 class="py-3"><a class="btn btn-info" data-toggle="collapse" href="#values" aria-expanded="false" aria-controls="values">View Threshold Values</a></h5>
<div class="row collapse" id="values">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th scope="row">RCD Value (mA)</th>
<th scope="row">Max Z<small>S</small> (Ohms)</th>
</tr>
</thead>
<tbody>
<tr>
<td>30</td>
<td>1667</td>
</tr>
<tr>
<td>100</td>
<td>500</td>
</tr>
<tr>
<td>300</td>
<td>167</td>
<tr>
<td>500</td>
<td>100</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6 col-sm-12">
<table class="table table-bordered">
<thead>
<tr>
<th scope="row">Distro</th>
<th scope="row">Max PSSC (kA)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Intel & Toblerone distros</td>
<td>6</td>
</tr>
<tr>
<td>All other distros</td>
<td>10</td>
</tr>
</tbody>
</table>
<p><strong>Voltage Drop on Circuit:</strong> 5% (approx. 12v)</p>
</div>
</div>

View File

@@ -1,3 +1,4 @@
{% load namewithnotes from filters %}
<div class="card card-info">
<div class="card-header">Event Info</div>
<div class="card-body">
@@ -6,10 +7,16 @@
<dd class="col-sm-6">
{% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
{{ object.venue|namewithnotes:'venue_detail' }}
</a>
{% endif %}
</dd>
{% if object.venue %}
<dt class="col-sm-6">Venue Notes</dt>
<dd class="col-sm-6">
{{ object.venue.notes }}{% if object.venue.three_phase_available %}<br>(Three phase available){%endif%}
</dd>
{% endif %}
{% if event.is_rig %}
<dt class="col-sm-6">Event MIC</dt>

View File

@@ -1,164 +0,0 @@
{% load widget_tweaks %}
{% load l10n %}
<div class="col-md-6 my-3">
<div class="card card-default form-hws form-non_rig">
<div class="card-header">Event Details</div>
<div class="card-body">
<div id="form-hws">
<div class="form-group" data-toggle="tooltip" title="Name of the event, displays on rigboard and on paperwork">
<label for="{{ form.name.id_for_label }}"
class="col-sm-4 control-label">{{ form.name.label }}</label>
<div class="col-sm-8">
{% render_field form.name class+="form-control" %}
</div>
</div>
<div class="form-group" data-toggle="tooltip" title="The venue for the rig, leave blank if unknown (e.g. for a dry hire)">
<label for="{{ form.venue.id_for_label }}"
class="col-sm-4 control-label">{{ form.venue.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-8">
<select id="{{ form.venue.id_for_label }}" name="{{ form.venue.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='venue' %}">
{% if venue %}
<option value="{{form.venue.value}}" selected="selected" data-update_url="{% url 'venue_update' form.venue.value %}">{{ venue }}</option>
{% endif %}
</select>
</div>
<div class="col-sm-3 col-md-5 col-lg-4 align-right">
<div class="btn-group">
<a href="{% url 'venue_create' %}" class="btn btn-success modal-href"
data-target="#{{ form.venue.id_for_label }}">
<span class="fas fa-plus"></span>
</a>
<a href="{% if object.venue %}{% url 'venue_update' object.venue.pk %}{% endif %}" class="btn btn-warning modal-href" id="{{ form.venue.id_for_label }}-update" data-target="#{{ form.venue.id_for_label }}">
<span class="fas fa-edit"></span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="{{ form.start_date.id_for_label }}"
class="col-sm-4 control-label">{{ form.start_date.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="Start date for event, required">
{% render_field form.start_date class+="form-control" %}
</div>
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="Start time of event, can be left blank">
{% render_field form.start_time class+="form-control" step="60" %}
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="{{ form.end_date.id_for_label }}"
class="col-sm-4 control-label">{{ form.end_date.label }}</label>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-12 col-md-7" data-toggle="tooltip" title="End date of event, leave blank if unknown or same as start date">
{% render_field form.end_date class+="form-control" %}
</div>
<div class="col-sm-12 col-md-5" data-toggle="tooltip" title="End time of event, leave blank if unknown">
{% render_field form.end_time class+="form-control" step="60" %}
</div>
</div>
</div>
</div>
{# Rig only information #}
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="form-group" data-toggle="tooltip" title="The date/time at which TEC have access to the venue">
<label for="{{ form.access_at.id_for_label }}"
class="col-sm-4 control-label">{{ form.access_at.label }}</label>
<div class="col-sm-8">
{% render_field form.access_at class+="form-control" step="60" %}
</div>
</div>
<div class="form-group" data-toggle="tooltip" title="The date/time at which crew should meet for this event">
<label for="{{ form.meet_at.id_for_label }}"
class="col-sm-4 control-label">{{ form.meet_at.label }}</label>
<div class="col-sm-8">
{% render_field form.meet_at class+="form-control" step="60" %}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div class="checkbox">
<label data-toggle="tooltip" title="Mark this event as a dry-hire, so it needs to be checked in at the end">
{% render_field form.dry_hire %}{{ form.dry_hire.label }}
</label>
</div>
</div>
</div>
</div>
{# Status is needed on all events types and it looks good here in the form #}
<div class="form-group" data-toggle="tooltip" title="The current status of the event. Only mark as booked once paperwork is received">
<label for="{{ form.status.id_for_label }}"
class="col-sm-4 control-label">{{ form.status.label }}</label>
<div class="col-sm-8">
{% render_field form.status class+="form-control" %}
</div>
</div>
<div class="form-is_rig {% if object.pk and not object.is_rig %}hidden{% endif %}">
<div class="form-group" data-toggle="tooltip" title="The Member in Charge of this event">
<label for="{{ form.mic.id_for_label }}"
class="col-sm-4 control-label">{{ form.mic.label }}</label>
<div class="col-sm-8">
<select id="{{ form.mic.id_for_label }}" name="{{ form.mic.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if mic %}
<option value="{{form.mic.value}}" selected="selected" >{{ mic.name }}</option>
{% endif %}
</select>
</div>
</div>
{% if object.dry_hire %}
<div class="form-group" data-toggle="tooltip" title="The person who checked-in this dry hire">
<label for="{{ form.checked_in_by.id_for_label }}"
class="col-sm-4 control-label">{{ form.checked_in_by.label }}</label>
<div class="col-sm-8">
<select id="{{ form.checked_in_by.id_for_label }}" name="{{ form.checked_in_by.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if checked_in_by %}
<option value="{{form.checked_in_by.value}}" selected="selected" >{{ checked_in_by.name }}</option>
{% endif %}
</select>
</div>
</div>
{% endif %}
<div class="form-group" data-toggle="tooltip" title="The student ID of the client who collected the dry-hire">
<label for="{{ form.collector.id_for_label }}"
class="col-sm-4 control-label">{{ form.collector.label }}</label>
<div class="col-sm-8">
{% render_field form.collector class+="form-control" %}
</div>
</div>
<div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)">
<label for="{{ form.purchase_order.id_for_label }}"
class="col-sm-4 control-label">{{ form.purchase_order.label }}</label>
<div class="col-sm-8">
{% render_field form.purchase_order class+="form-control" %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,7 @@
{% if object.event_size == 2 %}
<span class="badge badge-danger p-2 my-3">Large Event</span>
{% elif object.event_size == 1 %}
<span class="badge badge-warning p-2 my-3">Medium Event</span>
{%else%}
<span class="badge badge-success p-2 my-3">Small Event</span>
{%endif%}

View File

@@ -1,17 +1,44 @@
<h5>
<span class="badge badge-{% if event.confirmed %}success{% elif event.cancelled %}dark{% else %}warning{% endif %}">Status: {{ event.get_status_display }}</span>
{% if event.is_rig %}
{% if event.purchase_order %}
<span class="badge badge-success">PO: {{ event.purchase_order }}</i></span>
<span class="badge badge-success">PO: {{ event.purchase_order }}</span>
{% elif event.authorised %}
<span class="badge badge-success">Payment: Authorised <i class="fas fa-check"></i></span>
<span class="badge badge-success">Authorisation: Complete <span class="fas fa-check"></span></span>
{% else %}
<span class="badge badge-danger">Payment: <i class="fas fa-times"></i></span>
<span class="badge badge-danger">Authorisation: <span class="fas fa-times"></span></span>
{% endif %}
<!-- TODO show invoice stuff here -->
{% if event.risk_assessment_edit_url %}
<span class="badge badge-success">RA: <i class="fas fa-check"></i></span>
{% if not event.dry_hire %}
{% if event.riskassessment %}
<span class="badge badge-success">RA: <span class="fas fa-check"></span>{%if event.riskassessment.reviewed_by%}<span class="fas fa-check"></span>{%endif%}</span>
{% else %}
<span class="badge badge-danger">RA: <span class="fas fa-times"></span></span>
{% endif %}
{% else %}
<span class="badge badge-danger">RA: <i class="fas fa-times"></i></span>
<span class="badge badge-secondary">RA: N/A</span>
{% endif %}
{% if not event.dry_hire %}
{% if event.hs_done %}
{# TODO Display status of all checklists #}
<span class="badge badge-success">Checklist: <span class="fas fa-check"></span></span>
{% else %}
<span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span>
{% endif %}
{% else %}
<span class="badge badge-secondary">Checklist: N/A</span>
{% endif %}
{% if perms.RIGS.view_invoice %}
{% if event.invoice %}
{% if event.invoice.void %}
<span class="badge badge-secondary">Invoice: Void</span>
{% elif event.invoice.is_closed %}
<span class="badge badge-success">Invoice: Paid</span>
{% else %}
<span class="badge badge-warning">Invoice: Outstanding</span>
{% endif %}
{% else %}
<span class="badge badge-info">Invoice: Not Generated</span>
{% endif %}
{% endif %}
{% endif %}
</h5>

View File

@@ -0,0 +1,104 @@
{% load namewithnotes from filters %}
<div class="table-responsive">
<table class="table mb-0" id="event_table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Dates & Times</th>
<th scope="col">Event Details</th>
<th scope="col">MIC</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr class="{% if event.cancelled %}
table-secondary
{% elif not event.is_rig %}
table-info
{% elif not event.mic %}
table-danger
{% elif event.confirmed and event.authorised %}
{% if event.dry_hire or event.riskassessment %}
table-success
{% else %}
table-warning
{% endif %}
{% else %}
table-warning
{% endif %}" id="event_row">
<!---Number-->
<th scope="row" id="event_number">{{ event.display_id }}</th>
<!--Dates & Times-->
<td id="event_dates">
<span class="text-nowrap">Start: <strong>{{ event.start_date|date:"D d/m/Y" }}</strong>
{% if event.has_start_time %}
{{ event.start_time|date:"H:i" }}
{% endif %}
</span>
{% if event.end_date %}
<br>
<span class="text-nowrap">End: {% if event.end_date != event.start_date %}<strong>{{ event.end_date|date:"D d/m/Y" }}</strong>{% endif %}
{% if event.has_end_time %}
{{ event.end_time|date:"H:i" }}
{% endif %}
</span>
{% endif %}
{% if not event.cancelled %}
{% if event.meet_at %}
<br><span>Crew meet: <strong>{{ event.meet_at|date:"H:i" }}</strong> {{ event.meet_at|date:"(d/m/Y)" }}</span>
{% endif %}
{% if event.access_at %}
<br><span>Access at: <strong>{{ event.access_at|date:"H:i" }}</strong> {{ event.access_at|date:"(d/m/Y)" }}</span>
{% endif %}
{% endif %}
</td>
<!---Details-->
<td id="event_details" class="w-100">
<h4>
<a href="{% url 'event_detail' event.pk %}">
{{ event.name }}
</a>
{% if event.venue %}
<small>at {{ event.venue|namewithnotes:'venue_detail' }}</small>
{% endif %}
{% if event.dry_hire %}
<span class="badge badge-secondary">Dry Hire</span>
{% endif %}
</h4>
{% if event.is_rig and not event.cancelled %}
<h5>
{{ event.person.name }}
{% if event.organisation %}
for {{ event.organisation.name }}
{% endif %}
</h5>
{% endif %}
{% if not event.cancelled and event.description %}
<p>{{ event.description|linebreaksbr }}</p>
{% endif %}
{% include 'partials/event_status.html' %}
</td>
<!---MIC-->
<td id="event_mic" class="text-nowrap">
{% if event.mic %}
{% if perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{% endif %}
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo"/>
{{ event.mic }}
{% if perms.RIGS.view_profile %}
</a>
{% endif %}
{% elif event.is_rig %}
<span class="fas fa-exclamation"></span>
{% endif %}
</td>
</tr>
{% empty %}
<tr class="bg-warning">
<td colspan="4">No events found</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

View File

@@ -1,9 +0,0 @@
class="{% if event.cancelled %}
text-muted table-secondary
{% elif event.authorised and event.risk_assessment_edit_url and event.mic %}
table-success
{% elif not event.is_rig %}
table-info
{% else %}
table-warning
{% endif %}"

View File

@@ -0,0 +1,19 @@
<div class="card {% if event.hs_done %}
border-success
{% else %}
border-warning
{% endif %}">
<div class="card-header">Health &amp; Safety Details</div>
<div class="card-body">
<h5>Risk Assessment:</h5>
{% include 'partials/hs_status.html' with event=event object=event.riskassessment view='ra_detail' edit='ra_edit' create='event_ra' review='ra_review' perm=perms.RIGS.review_riskassessment %}
<hr>
<h5>Event Checklists:</h5>
{% for checklist in event.checklists.all %}
{% include 'partials/hs_status.html' with event=event object=checklist view='ec_detail' edit='ec_edit' create='event_ec' review='ec_review' perm=perms.RIGS.review_eventchecklist %}
<br/>
{% endfor %}
<a href="{% url 'event_ec' event.pk %}" class="btn btn-info mt-2"><span class="fas fa-paperclip"></span> <span
class="hidden-xs">Create</span></a>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More