Compare commits

...

108 Commits

Author SHA1 Message Date
dependabot[bot]
7eed29ff4f Bump urllib3 from 1.26.20 to 2.6.3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.20 to 2.6.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.20...2.6.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-17 16:02:41 +00:00
Joe Banks
2d2df68c07 Merge pull request #630 from jamesatjaminit/master
Remove missed async attributes
2025-10-26 01:29:11 +00:00
James Cook
2e9b7e351a remove missed async's 2025-10-25 22:20:42 +01:00
James Herbert
6fd8f17094 Merge pull request #629 from jamesatjaminit/master 2025-10-06 21:28:54 +01:00
James Cook
7f6b15c154 Merge branch 'master' of github.com:jamesatjaminit/PyRIGS 2025-10-06 21:18:07 +01:00
James Cook
bb84bbab77 remove async from select.js script 2025-10-06 21:17:55 +01:00
Joe Banks
fa60c4421f Merge pull request #627 from nottinghamtec/jb3/pipenv-to-uv
Migrate from pipenv to uv
2025-09-28 19:35:06 +01:00
Joe Banks
97b3663909 Add .venv to pycodestyle ignore 2025-09-28 19:28:54 +01:00
Joe Banks
50f6ff4bfd Migrate CI flows to using UV 2025-09-28 19:20:24 +01:00
Joe Banks
0945a893d5 Add uv files 2025-09-28 19:20:11 +01:00
Joe Banks
bc3611db9e Remove pipenv files 2025-09-28 19:20:03 +01:00
Joe Banks
a6c76ee24f no harm in a cheeky CSS animation 2025-09-25 23:29:56 +01:00
Joe Banks
a4ab2992a4 FrankenRIGS 2025-09-25 23:18:42 +01:00
Joe Banks
33ac604d10 Add override functionality to database url via FRANKENRIGS_DATABASE_URL 2025-09-25 23:07:55 +01:00
Joe Banks
9f25fe7bf0 Upgrade psycopg2 and switch to psycopg2-binary 2025-09-25 20:49:56 +01:00
Joe Banks
68b28a6df2 Supposedly fix the zope.interface problem 2025-09-24 20:52:01 +01:00
Joe Banks
b022a0541e Attempt to improve list stylings 2025-09-24 20:25:39 +01:00
Joe Banks
51deadc192 Merge pull request #624 from jamesatjaminit/master
fix: use earliest time instead of start time
2025-09-22 00:59:35 +01:00
James Cook
444f64ddc1 fix: use earliest time instead of start time 2025-09-21 22:40:44 +01:00
Joe Banks
3f38ce77e0 Filter Rigboard context data with new cancelled query parameter 2025-03-30 15:34:21 +01:00
Joe Banks
39a2401ec9 Add button to toggle cancelled event filtering 2025-03-30 15:34:02 +01:00
Joe Banks
032768d3b0 Add day of week to rigboard 2025-03-27 01:16:08 +00:00
Joe Banks
007571fab6 Attempt to fix some of the failing tests 2025-03-17 00:16:44 +00:00
Joe Banks
311189470d Remove erroneous whitespace 2025-03-16 22:59:21 +00:00
Joe Banks
db387c768a Potentially support cmd+P on macOS for printing events 2025-03-16 22:58:16 +00:00
Joe Banks
3c4901f86a Hide MIC when not required 2025-03-16 22:55:17 +00:00
Joe Banks
385a3bd979 Update colours for rigboard 2025-03-16 22:55:07 +00:00
Joe Banks
ded8925ef5 Add needs_mic property to events 2025-03-16 22:54:58 +00:00
Joe Banks
0bb7418f17 Merge pull request #621 from nottinghamtec/jb3/bunch-o-fixes
Bunch o' Fixes
2025-03-16 22:25:05 +00:00
Joe Banks
30a053b813 Update some locked NPM packages (npm audit) 2025-03-16 22:17:49 +00:00
Joe Banks
bd5af7c390 Update interaction tests for only MIC filter 2025-03-16 22:11:00 +00:00
Joe Banks
22bba7e8af Introduce new rigboard template 2025-03-16 22:06:06 +00:00
Joe Banks
dcf19ff773 Don't show full POs in event status 2025-03-16 22:05:54 +00:00
Joe Banks
6e1ac4e203 Create template for each row of new rigboard 2025-03-16 22:05:45 +00:00
Joe Banks
899f958aa3 Store legacy rigboard in new template file 2025-03-16 22:05:35 +00:00
Joe Banks
ee83fd2064 Add link to legacy rigboard on rigboard template 2025-03-16 22:05:27 +00:00
Joe Banks
87dd32da6c Add shortcut to event page for opening PDF with Ctrl+P
Closes #457. You can still use browser default print if you are so
inclined (however it looks trash). This is a quick way to export the
PDF.
2025-03-16 20:05:03 +00:00
Joe Banks
25593f3e6c Surface only_mic in frontend ICS URl generator 2025-03-16 20:00:24 +00:00
Joe Banks
89d50fbd49 Update ICS feed with ?only_mic filter 2025-03-16 19:59:24 +00:00
Joe Banks
7313c905ea Attach user object when using api_key_required decorator 2025-03-16 19:58:56 +00:00
Joe Banks
074d40ea03 Select current user for supervisor field
(Closes #615 and #546)
2025-03-16 19:47:58 +00:00
Joe Banks
1496a75826 Add .venv to gitignore 2025-03-16 19:33:16 +00:00
Joe Banks
1f22470bfd Add missing endif 2025-01-25 23:47:57 +00:00
Joe Banks
47babff6d4 Notes on power test export 2025-01-25 23:43:39 +00:00
Joe Banks
ad8ffa69de Fix indentation of crew list partial
VSCode auto-format and DTL do not play nicely
2024-12-13 23:29:57 +00:00
Joe Banks
99fdca6a2d Show crew list on event checklist view (#613)
* Split crew list into partial

* Use new crew list partial on event checklist
2024-12-13 16:43:12 +00:00
Joe Banks
245a92f8e6 Fix vertical centering of navbar logo (#614) 2024-12-08 21:46:20 +00:00
Joe Banks
89d7c5140e Add fields to rig info for additional access requirements (#611)
* Add fields to rig info for additional access requirements

* Add form field for additional access requirements

* Display access requirements in rig info

* Make access requirements field non-required

Oops...

* Add event access field to risk assessment

* Allow for modification and display risk assessment venue access item

* Correct tests for RAs with new parking fields

* Add note to new venue access component of RA

* Correct div boundaries for non-rig access requirements

* Fill parking and access field in sample data generator

* Set parking and access field to false in RA creation test

* Hopefully the final correction of the RA test suite
2024-11-20 22:44:11 +00:00
Joe Banks
6b4a1ce6bf Add quick link to log session on homepage for supervisors 2024-11-14 01:58:53 +00:00
Joe Banks
93762fe198 Allow sorting and filtering by Profile.date_joined in Django admin (#606) 2024-11-01 23:59:22 +00:00
Joe Banks
2559c92927 Power test export refactoring (#607) 2024-11-01 23:49:17 +00:00
3afa4deb52 Tweak finance dashboard to ignore 'non-rigs' when counting 'total events'
You can't invoice for a 'non-rig' after all.
2024-10-30 18:00:32 +00:00
Joe Banks
9f910af7eb Change ANTIALIAS to LANCZOS in asset label generator (#604) 2024-10-29 22:48:19 +00:00
Joe Banks
6c32db3998 Fix issues caused by thousands separator (#602)
* Disable thousands separation in locale settings that caused issues updating rigs

* Update invoice dashboard to use "g" suffixed floatformats for thousands separation
2024-10-27 15:09:04 +00:00
Joe Banks
c2ef469d5d Finance dashboard (#600)
* Enable commas for float thousands separation

* Add new invoice dashboard template

* Add new view controller for finance dashboard

* Add finance dashboard to dropdown

* Update finance URLs to put dashboard at index route

* Add payment methods to generated sample data

* Flip 'outstanding' and 'waiting' cards on dashboard to match order in dropdown

Also made them link to their respective lists and fixed low text contrast

---------

Co-authored-by: FreneticScribbler <aj@aronajones.com>
2024-10-27 13:11:42 +00:00
Joe Banks
e5c7e24941 Link to event details from check-in notice (#595) 2024-10-26 17:54:51 +01:00
be7b595edb Add warning when creating event with access time more than a week before start.
Yes. I know it's a dirty solution. >:D

Closes #593
2024-10-20 19:56:40 +01:00
6d53df0c8b Should fix logo leaking out of navbar 2024-10-20 19:19:57 +01:00
732a6e5c1e Fix contrast and width issues on "now" alert 2024-10-20 19:05:51 +01:00
Joe Banks
c6823bb9ac Colour cancelled invoices red (#599) 2024-10-17 22:10:23 +01:00
Joe Banks
ec000beee8 Update CI build version to 3.10 (#598)
* Update CI build version to 3.10

* Wrap Python version in string to avoid decimalisation

* Update requests for CVE mitigation

* Install cairo
2024-10-17 21:33:46 +01:00
Joe Banks
6c8eb380fd Generate PDFs from Power Test Records (#594)
* Add new block to base_print.xml for additional styles from downstream templates

* Fix page totals on exports by adding <namedString> element

* Add new print template for power test records

* Add a generated name property to power tests to allow for export

* Add new routes for print export for power tests

* Add print button to power test records view page

* Address linting errors
2024-10-15 22:48:47 +01:00
3123d3899c Fix regression in autocompleter - when adding new people/orgs/venues through modal they are not autoselected
Ah the lovely fragile javascript ecosystem, I can't even pin down when or why it broke.
2024-07-21 16:55:14 +01:00
dependabot[bot]
c93c04ec6e Bump urllib3 from 1.26.18 to 1.26.19 (#586)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.18 to 1.26.19.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/1.26.19/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.18...1.26.19)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 13:10:51 +01:00
6bf8d56ce8 Update CI workflow components 2024-07-03 13:10:34 +01:00
8246071b8c Stop gulp corrupting all the font files.
I bloody love the javascript ecosystem, I do. Really.

>.<
2024-07-01 19:29:45 +01:00
06fa1a3b1b Stop asteroids breaking the buildchain.
Game's a bit broken but I've better things to do than fix an easter egg
2024-07-01 19:18:56 +01:00
70abfaf2ae Bulk update JS dependences
May fix #587, either incidentally or properly, the font files presently being served seem to be corrupted in some odd manner.
2024-07-01 18:38:45 +01:00
02a5a94fbb Potential fix for #573
None of the test cases appear to reproduce the issue so live testing it is.
2024-07-01 18:10:41 +01:00
dependabot[bot]
ddce9752c6 Bump braces and gulp (#585)
Bumps [braces](https://github.com/micromatch/braces) to 3.0.3 and updates ancestor dependency [gulp](https://github.com/gulpjs/gulp). These dependencies need to be updated together.


Updates `braces` from 3.0.2 to 3.0.3
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

Updates `gulp` from 4.0.2 to 5.0.0
- [Release notes](https://github.com/gulpjs/gulp/releases)
- [Changelog](https://github.com/gulpjs/gulp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/gulp/compare/v4.0.2...v5.0.0)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
- dependency-name: gulp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 12:28:46 +01:00
dependabot[bot]
5a16e06bed --- (#583)
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 16:45:52 +02:00
dependabot[bot]
5160a61a62 Build(deps): Bump gunicorn from 20.0.4 to 22.0.0 (#581)
Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 20.0.4 to 22.0.0.
- [Release notes](https://github.com/benoitc/gunicorn/releases)
- [Commits](https://github.com/benoitc/gunicorn/compare/20.0.4...22.0.0)

---
updated-dependencies:
- dependency-name: gunicorn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-18 17:41:53 +01:00
dependabot[bot]
607f282ef6 Build(deps): Bump sqlparse from 0.4.4 to 0.5.0 (#580)
Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.4.4 to 0.5.0.
- [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG)
- [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.4...0.5.0)

---
updated-dependencies:
- dependency-name: sqlparse
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 17:26:44 +01:00
dependabot[bot]
d71bb81edf Build(deps): Bump idna from 2.10 to 3.7 (#579)
Bumps [idna](https://github.com/kjd/idna) from 2.10 to 3.7.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v2.10...v3.7)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-15 16:27:48 +01:00
dependabot[bot]
f46915233e Build(deps): Bump tar from 6.1.14 to 6.2.1 (#578)
Bumps [tar](https://github.com/isaacs/node-tar) from 6.1.14 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.1.14...v6.2.1)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 18:32:46 +01:00
Alex Daniel
0c900d2447 Fix typo in PSCC (#577)
Circuit is spelt Circuit, not Sircuit
2024-04-06 19:18:09 +01:00
953b691cc2 Switch magic flexy rigboard layout to use *container* width queries rather than *screen* width queries
This resolves the overflow seen in the profile detail view in the most overkill way possible. Nerf this!
2024-04-06 19:14:23 +01:00
17fa447861 Reimplement .dl-horizontal, dropped in Bootstrap 3 and apparently still used in our templates in some places 2024-04-06 18:13:32 +01:00
bobbinz
409125c8a3 PSSC Values (Three Phase) (#576)
* Update ec_power_info.html

Added three phase column and made tables look the same

* Update ec_power_info.html

Fixed spelling booboo
2024-03-26 19:25:29 +00:00
dependabot[bot]
03d996eb66 Build(deps-dev): Bump follow-redirects from 1.15.4 to 1.15.6 (#574)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 20:19:20 +00:00
dependabot[bot]
1e40916a94 Build(deps): Bump es5-ext from 0.10.62 to 0.10.64 (#572)
Bumps [es5-ext](https://github.com/medikoo/es5-ext) from 0.10.62 to 0.10.64.
- [Release notes](https://github.com/medikoo/es5-ext/releases)
- [Changelog](https://github.com/medikoo/es5-ext/blob/main/CHANGELOG.md)
- [Commits](https://github.com/medikoo/es5-ext/compare/v0.10.62...v0.10.64)

---
updated-dependencies:
- dependency-name: es5-ext
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 17:14:37 +00:00
dependabot[bot]
eae3f762b7 Build(deps): Bump ip from 2.0.0 to 2.0.1 (#571)
Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1.
- [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 17:14:29 +00:00
dependabot[bot]
7a387e8724 Build(deps): Bump axios and browser-sync (#570)
Removes [axios](https://github.com/axios/axios). It's no longer used after updating ancestor dependency [browser-sync](https://github.com/BrowserSync/browser-sync). These dependencies need to be updated together.


Removes `axios`

Updates `browser-sync` from 2.29.1 to 3.0.2
- [Release notes](https://github.com/BrowserSync/browser-sync/releases)
- [Changelog](https://github.com/BrowserSync/browser-sync/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BrowserSync/browser-sync/compare/v2.29.1...v3.0.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
- dependency-name: browser-sync
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 17:14:20 +00:00
dependabot[bot]
4f42219821 Build(deps-dev): Bump follow-redirects from 1.15.2 to 1.15.4 (#569)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-21 19:44:08 +00:00
d7d2f93295 Fix typo in non-event display 2024-01-21 19:43:47 +00:00
2a2ce742b0 Reimplement rigboard page in a fully responsive manner (#567)
* Revive this concept for 2023

(cherry picked from commit b3939d8426)

# Conflicts:
#	pipeline/source_assets/scss/dark_screen.scss

* Update app.json

* Updates to all three layouts
2023-12-17 18:01:49 +00:00
dependabot[bot]
68d3605230 Build(deps): Bump urllib3 from 1.26.17 to 1.26.18 (#566)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.17 to 1.26.18.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.17...1.26.18)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-21 16:59:35 +01:00
1fff150566 Fix ID10T error 2023-10-21 16:57:55 +01:00
240ff25c63 Don't automatically deactivate anyone that's never logged in
That's bloody stupid
2023-10-21 16:28:29 +01:00
e265ad58a6 Tweak awaiting approval count to better ignore inactive users 2023-10-11 21:10:08 +01:00
1a32ef424b Make calendar first day Monday, not Sunday 2023-10-10 20:16:39 +01:00
dependabot[bot]
44c92fc859 Build(deps): Bump urllib3 from 1.26.16 to 1.26.17 (#563)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.16 to 1.26.17.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.16...1.26.17)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-06 23:04:13 +01:00
dependabot[bot]
1af182eaa1 Build(deps): Bump postcss from 8.4.23 to 8.4.31 (#565)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.23 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.23...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-05 16:03:39 +01:00
dependabot[bot]
425a697743 Build(deps): Bump pillow from 9.3.0 to 10.0.1 (#564)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.3.0 to 10.0.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/9.3.0...10.0.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-05 16:03:25 +01:00
dependabot[bot]
b7011e368b Build(deps): Bump tornado from 6.3.2 to 6.3.3 (#561)
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.3.2 to 6.3.3.
- [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.3.2...v6.3.3)

---
updated-dependencies:
- dependency-name: tornado
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Arona Jones <aj@aronajones.com>
2023-09-23 19:04:36 +01:00
7d2f8d2dc8 Partial revert 769d983 which deleted more than it meant to 2023-09-23 18:54:09 +01:00
5a54092771 Update power_detail.html - fix copy paste error 2023-08-30 18:41:45 +00:00
dependabot[bot]
e8a8f2bf0d Build(deps): Bump semver from 5.7.1 to 5.7.2 (#557)
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-01 10:15:23 +01:00
dependabot[bot]
3984c17605 Build(deps): Bump certifi from 2023.5.7 to 2023.7.22 (#559)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.5.7 to 2023.7.22.
- [Commits](https://github.com/certifi/python-certifi/compare/2023.05.07...2023.07.22)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-01 10:07:48 +01:00
dependabot[bot]
b2209eef4e Build(deps): Bump pygments from 2.7.4 to 2.15.0 (#558)
Bumps [pygments](https://github.com/pygments/pygments) from 2.7.4 to 2.15.0.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](https://github.com/pygments/pygments/compare/2.7.4...2.15.0)

---
updated-dependencies:
- dependency-name: pygments
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-01 10:07:37 +01:00
e7c4f7f73d Move flaky test out of class so that it can properly xfail 2023-07-09 23:08:37 +01:00
769d983e3d Remove ability to delete level requirements from level detail
To return in future in a special 'edit mode' page, but easiest to just nuke them entirely for now as it's not something we often need to do
2023-07-09 22:37:36 +01:00
303005a32b Mark problematic test as expected to fail 2023-07-09 22:34:53 +01:00
c722773586 Add explanations to payment methods, remove 'SU Core' method as now obsolete 2023-07-09 22:24:36 +01:00
dependabot[bot]
0c2e677786 Build(deps): Bump tough-cookie and node-sass (#556)
Removes [tough-cookie](https://github.com/salesforce/tough-cookie). It's no longer used after updating ancestor dependency [node-sass](https://github.com/sass/node-sass). These dependencies need to be updated together.


Removes `tough-cookie`

Updates `node-sass` from 7.0.3 to 9.0.0
- [Release notes](https://github.com/sass/node-sass/releases)
- [Changelog](https://github.com/sass/node-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/node-sass/compare/v7.0.3...v9.0.0)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
- dependency-name: node-sass
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-09 18:05:52 +01:00
dependabot[bot]
9719581977 Build(deps): Bump django from 3.2.19 to 3.2.20 (#554)
Bumps [django](https://github.com/django/django) from 3.2.19 to 3.2.20.
- [Commits](https://github.com/django/django/compare/3.2.19...3.2.20)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-07 15:52:05 +01:00
James Herbert
b838dde30b Clarify check-in banner (#553) 2023-07-01 10:32:28 +01:00
387fa56442 Exclude non-rigs from the 'now' checkin list 2023-06-29 12:32:48 +01:00
73 changed files with 6003 additions and 9596 deletions

View File

@@ -14,40 +14,46 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PYTHONDONTWRITEBYTECODE: 1
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.9
cache: 'pipenv'
- name: Install Dependencies
- uses: actions/checkout@v4
- name: Install build dependencies
run: |
python3 -m pip install --upgrade pip pipenv
pipenv install -d
# if: steps.pcache.outputs.cache-hit != 'true'
sudo apt-get install libcairo2-dev
- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version-file: ".python-version"
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Install Dependencies
run: uv sync --locked --all-extras --dev
- name: Cache Static Files
id: static-cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: 'pipeline/built_assets'
key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
- uses: bahmutov/npm-install@v1
if: steps.static-cache.outputs.cache-hit != 'true'
- run: node node_modules/gulp/bin/gulp build
if: steps.static-cache.outputs.cache-hit != 'true'
- name: Basic Checks
run: |
pipenv run pycodestyle . --exclude=migrations,node_modules
pipenv run python3 manage.py check
pipenv run python3 manage.py makemigrations --check --dry-run
pipenv run python3 manage.py collectstatic --noinput
uv run pycodestyle . --exclude=.venv,migrations,node_modules
uv run python3 manage.py check
uv run python3 manage.py makemigrations --check --dry-run
uv run python3 manage.py collectstatic --noinput
- name: Run Tests
run: pipenv run pytest -n auto --cov
- uses: actions/upload-artifact@v3
run: uv run pytest -n auto --cov
- uses: actions/upload-artifact@v4
if: failure()
with:
name: failure-screenshots ${{ matrix.test-group }}
path: screenshots/
retention-days: 5
- name: Coveralls
run: pipenv run coveralls --service=github
run: uv run coveralls --service=github

3
.gitignore vendored
View File

@@ -101,3 +101,6 @@ crashlytics.properties
crashlytics-build.properties
.vscode/
screenshots/
# Virutal Environments
.venv/

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.10

104
Pipfile
View File

@@ -1,104 +0,0 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
ansicolors = "~=1.1.8"
asgiref = "~=3.3.1"
"backports.tempfile" = "~=1.0"
"backports.weakref" = "~=1.0.post1"
beautifulsoup4 = "~=4.9.3"
Brotli = "~=1.0.9"
cachetools = "~=4.2.1"
chardet = "~=4.0.0"
configparser = "~=5.0.1"
contextlib2 = "~=0.6.0.post1"
cssselect = "~=1.1.0"
cssutils = "~=1.0.2"
dj-database-url = "~=0.5.0"
dj-static = "~=0.0.6"
Django = "~=3.2"
django-debug-toolbar = "~=4.0.0"
django-filter = "~=2.4.0"
django-ical = "~=1.7.1"
django-recurrence = "~=1.10.3"
django-registration-redux = "~=2.9"
django-reversion = "~=3.0.9"
django-widget-tweaks = "~=1.4.8"
django-htmlmin = "~=0.11.0"
envparse = "*"
gunicorn = "~=20.0.4"
icalendar = "~=4.0.7"
idna = "~=2.10"
Markdown = "~=3.3.3"
msgpack = "~=1.0.2"
pep517 = "~=0.9.1"
Pillow = "~=9.3.0"
premailer = "~=3.7.0"
progress = "~=1.5"
psutil = "~=5.8.0"
psycopg2 = "~=2.8.6"
Pygments = "~=2.7.4"
pyparsing = "~=2.4.7"
PyPDF2 = "~=1.27.5"
PyPOM = "~=2.2.4"
python-dateutil = "~=2.8.1"
pytoml = "~=0.1.21"
pytz = "~=2020.5"
reportlab = "*"
requests = "~=2.31.0"
retrying = "~=1.3.3"
simplejson = "~=3.17.2"
six = "~=1.15.0"
soupsieve = "~=2.1"
sqlparse = "~=0.4.2"
static3 = "~=0.7.0"
svg2rlg = "~=0.3"
tini = "~=3.0.1"
tornado = "~=6.3"
urllib3 = "~=1.26.5"
whitenoise = "~=5.2.0"
yolk = "~=0.4.3"
zipp = "~=3.4.0"
"zope.component" = "~=4.6.2"
"zope.deferredimport" = "~=4.3.1"
"zope.deprecation" = "~=4.4.0"
"zope.event" = "~=4.5.0"
"zope.hookable" = "~=5.0.1"
"zope.interface" = "~=5.2.0"
"zope.proxy" = "~=4.3.5"
"zope.schema" = "~=6.0.1"
sentry-sdk = "*"
diff-match-patch = "*"
python-barcode = "*"
django-hCaptcha = "*"
importlib-metadata = "*"
django-hcaptcha = "*"
"z3c.rml" = "*"
pikepdf = "*"
django-queryable-properties = "*"
django-mass-edit = "*"
selenium = "~=4.9.1"
[dev-packages]
pycodestyle = "~=2.9.1"
coveralls = "*"
django-coverage-plugin = "*"
pytest-cov = "*"
pytest-django = "*"
pluggy = "*"
pytest-splinter = "*"
pytest = "*"
pytest-reverse = "*"
[requires]
python_version = "3.10"
[dev-packages.pytest-xdist]
extras = [ "psutil",]
version = "*"
[dev-packages.PyPOM]
extras = [ "splinter",]
version = "*"

2078
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -79,7 +79,9 @@ def api_key_required(function):
"""
Decorator for views that checks api_pk and api_key.
Failed users will be given a 403 error.
Should only be used for urls which include <api_pk> and <api_key> kwargs
Should only be used for urls which include <api_pk> and <api_key> kwargs.
Will update the kwargs to include the user object if successful (under the key 'user').
"""
def wrap(request, *args, **kwargs):
@@ -97,6 +99,7 @@ def api_key_required(function):
try:
user_object = models.Profile.objects.get(pk=userid)
kwargs = {**kwargs, 'user': user_object}
except models.Profile.DoesNotExist:
return error_resp

View File

@@ -35,6 +35,8 @@ if DEBUG:
ALLOWED_HOSTS.append('localhost')
ALLOWED_HOSTS.append('example.com')
ALLOWED_HOSTS.append('127.0.0.1')
ALLOWED_HOSTS.append('.app.github.dev')
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
if not DEBUG:
@@ -102,7 +104,10 @@ DATABASES = {
if not DEBUG:
import dj_database_url
DATABASES['default'] = dj_database_url.config()
if env("FRANKENRIGS_DATABASE_URL") is not None:
DATABASES['default'] = dj_database_url.config(env="FRANKENRIGS_DATABASE_URL")
else:
DATABASES['default'] = dj_database_url.config()
# Logging
LOGGING = {
@@ -222,6 +227,8 @@ USE_L10N = True
USE_TZ = True
USE_THOUSAND_SEPARATOR = False
# Need to allow seconds as datetime-local input type spits out a time that has seconds
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')

View File

@@ -48,7 +48,7 @@ class Index(generic.TemplateView): # Displays the current rig count along with
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['rig_count'] = models.Event.objects.rig_count()
context['now'] = models.Event.objects.events_in_bounds(timezone.now(), timezone.now()).exclude(dry_hire=True).exclude(status=models.Event.CANCELLED)
context['now'] = models.Event.objects.events_in_bounds(timezone.now(), timezone.now()).exclude(status=models.Event.CANCELLED).filter(is_rig=True, dry_hire=False)
return context

View File

@@ -154,9 +154,9 @@ class AssociateAdmin(VersionAdmin):
@admin.register(models.Profile)
class ProfileAdmin(UserAdmin, AssociateAdmin):
list_display = ('username', 'name', 'is_approved', 'is_superuser', 'is_supervisor', 'number_of_events', 'last_login')
list_display = ('username', 'name', 'is_approved', 'is_superuser', 'is_supervisor', 'number_of_events', 'last_login', 'date_joined')
list_display_links = ['username']
list_filter = UserAdmin.list_filter + ('is_approved',)
list_filter = UserAdmin.list_filter + ('is_approved', 'date_joined')
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {

View File

@@ -1,10 +1,11 @@
from datetime import datetime
from datetime import datetime, timedelta
import simplejson
from django import forms
from django.conf import settings
from django.core import serializers
from django.utils import timezone
from django.utils.html import format_html
from reversion import revisions as reversion
from RIGS import models
@@ -21,6 +22,7 @@ class EventForm(forms.ModelForm):
datetime_input_formats = list(settings.DATETIME_INPUT_FORMATS)
meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
access_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False)
parking_and_access = forms.BooleanField(label="Additional parking or access requirements (i.e. campus parking permits, wristbands)?", required=False)
items_json = forms.CharField()
@@ -97,6 +99,9 @@ class EventForm(forms.ModelForm):
raise forms.ValidationError(
'You haven\'t provided any client contact details. Please add a person or organisation.',
code='contact')
access = self.cleaned_data.get("access_at")
if 'warn-access' not in self.data and access is not None and access.date() < (self.cleaned_data.get("start_date") - timedelta(days=7)):
raise forms.ValidationError(format_html("Are you sure about that? Your access time seems a bit optimistic. If you're sure, save again. <input type='hidden' id='warn-access' name='warn-access' value='0'/>"), code='access_sanity')
return super().clean()
def save(self, commit=True):
@@ -121,7 +126,7 @@ class EventForm(forms.ModelForm):
fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date',
'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic',
'person', 'organisation', 'dry_hire', 'checked_in_by', 'status',
'purchase_order', 'collector', 'forum_url']
'purchase_order', 'collector', 'forum_url', 'parking_and_access']
class BaseClientEventAuthorisationForm(forms.ModelForm):

View File

@@ -254,7 +254,7 @@ class Command(BaseCommand):
new_invoice.void = True
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
models.Payment.objects.create(invoice=new_invoice, amount=new_invoice.balance,
date=datetime.date.today())
date=datetime.date.today(), method=random.choice(models.Payment.METHODS)[0])
if i == 1 or random.randint(0, 5) > 0: # Event 1 and 1 in 5 have a RA
models.RiskAssessment.objects.create(event=new_event, supervisor_consulted=bool(random.getrandbits(1)),
nonstandard_equipment=bool(random.getrandbits(1)),
@@ -276,6 +276,7 @@ class Command(BaseCommand):
nonstandard_emergency_procedure=bool(random.getrandbits(1)),
special_structures=bool(random.getrandbits(1)),
suspended_structures=bool(random.getrandbits(1)),
parking_and_access=bool(random.getrandbits(1)),
outside=bool(random.getrandbits(1)))
if i == 0 or random.randint(0, 1) > 0: # Event 1 and 1 in 10 have a Checklist
models.EventChecklist.objects.create(event=new_event,

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.19 on 2023-07-09 21:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0050_event_forum_url'),
]
operations = [
migrations.AlterField(
model_name='payment',
name='method',
field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('T', 'TEC Adjustment')], default='', max_length=2),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.25 on 2024-11-20 20:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0051_alter_payment_method'),
]
operations = [
migrations.AddField(
model_name='event',
name='parking_and_access',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.2.25 on 2024-11-20 21:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0052_event_parking_and_access'),
]
operations = [
migrations.AddField(
model_name='riskassessment',
name='parking_and_access',
field=models.BooleanField(default=False, help_text='Are there additional requirements for parking and access to the venue? (i.e. campus parking permits, event access wristbands)'),
preserve_default=False,
),
]

View File

@@ -77,7 +77,7 @@ class Profile(AbstractUser):
@classmethod
def users_awaiting_approval_count(cls):
# last_login = None ensures we only pick up genuinely new users, not those that have been deactivated for inactivity
return Profile.objects.filter(is_approved=False, last_login=None).count()
return Profile.objects.filter(is_approved=False, last_login=None, date_joined_date=timezone.now().date()).count()
def __str__(self):
return self.name
@@ -351,6 +351,9 @@ class Event(models.Model, RevisionMixin):
access_at = models.DateTimeField(blank=True, null=True)
meet_at = models.DateTimeField(blank=True, null=True)
# Venue requirements
parking_and_access = models.BooleanField(default=False)
# Crew management
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True,
on_delete=models.CASCADE)
@@ -376,6 +379,10 @@ class Event(models.Model, RevisionMixin):
return self.pk
return "????"
@property
def needs_mic(self):
return self.is_rig and not self.dry_hire
# Calculated values
"""
EX Vat
@@ -505,7 +512,7 @@ class Event(models.Model, RevisionMixin):
def can_check_in(self):
earliest = self.earliest_time
if isinstance(self.earliest_time, datetime.date):
earliest = datetime.datetime.combine(self.start_date, datetime.time(00, 00))
earliest = datetime.datetime.combine(self.earliest_time, datetime.time(00, 00))
tz = pytz.timezone(settings.TIME_ZONE)
earliest = tz.localize(earliest)
return not self.dry_hire and not self.status == Event.CANCELLED and earliest <= timezone.now()
@@ -688,13 +695,11 @@ class Payment(models.Model, RevisionMixin):
CASH = 'C'
INTERNAL = 'I'
EXTERNAL = 'E'
SUCORE = 'SU'
ADJUSTMENT = 'T'
METHODS = (
(CASH, 'Cash'),
(INTERNAL, 'Internal'),
(EXTERNAL, 'External'),
(SUCORE, 'SU Core'),
(ADJUSTMENT, 'TEC Adjustment'),
)
@@ -781,6 +786,9 @@ class RiskAssessment(ReviewableModel, RevisionMixin):
persons_responsible_structures = models.TextField(blank=True, default='', help_text="Who are the persons on site responsible for their use?")
rigging_plan = models.URLField(blank=True, default='', help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
# Venue Access
parking_and_access = models.BooleanField(help_text="Are there additional requirements for parking and access to the venue? (i.e. campus parking permits, event access wristbands)")
# Blimey that was a lot of options
supervisor_consulted = models.BooleanField(null=True)
@@ -805,6 +813,7 @@ class RiskAssessment(ReviewableModel, RevisionMixin):
'nonstandard_emergency_procedure': False,
'special_structures': False,
'suspended_structures': False,
'parking_and_access': False
}
inverted_fields = {key: value for (key, value) in expected_values.items() if not value}.keys()
@@ -945,6 +954,10 @@ class PowerTestRecord(ReviewableModel, RevisionMixin):
def activity_feed_string(self):
return str(self.event)
@property
def name(self):
return f"Power Test Record - {self.event}"
class EventCheckIn(models.Model):
event = models.ForeignKey('Event', related_name='crew', on_delete=models.CASCADE)

View File

@@ -3,6 +3,7 @@ import urllib.error
import urllib.parse
import urllib.request
from io import BytesIO
import datetime
from PyPDF2 import PdfFileReader, PdfFileMerger
from django.conf import settings
@@ -110,7 +111,7 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
if admin.last_emailed is None or admin.last_emailed + settings.EMAIL_COOLDOWN <= timezone.now():
context = {
'request': request,
'link_suffix': reverse("admin:RIGS_profile_changelist") + '?is_approved__exact=0',
'link_suffix': reverse("admin:RIGS_profile_changelist") + f'?is_approved__exact=0&date_joined__date={timezone.now().date()}',
'number_of_users': models.Profile.users_awaiting_approval_count(),
'to_name': admin.first_name
}

View File

@@ -22,6 +22,9 @@
<paraStyle name="center" alignment="center"/>
<paraStyle name="page-head" alignment="center" fontName="OpenSans-Bold" fontSize="16" leading="18" spaceAfter="0"/>
{% block extrastyles %}
{% endblock %}
<paraStyle name="style.event_description" fontName="OpenSans" textColor="DarkGray" />
<paraStyle name="style.item_description" fontName="OpenSans" textColor="DarkGray" leftIndent="10" />
<paraStyle name="style.specific_description" fontName="OpenSans" textColor="DarkGray" fontSize="10" />
@@ -84,6 +87,8 @@
<listStyle name="ul"
start="bulletchar"
leftIndent="0"
bulletDedent="10"
bulletFontSize="10"/>
</stylesheet>
@@ -137,6 +142,7 @@
<nextFrame/>
{% block content %}
{% endblock %}
<namedString id="lastPage"><pageNumber/></namedString>
</story>
</document>

View File

@@ -6,7 +6,36 @@
{% load total_invoices_todo from filters %}
{% block titleheader %}
<a class="navbar-brand" style="margin-left: auto; margin-right: auto;" href="/">RIGS</a>
<style>
.franken {
font-family: Fontdiner Swanky;
color: #00ff00;
animation: glow 1.5s infinite alternate;
}
@keyframes glow {
0% {
text-shadow: 0 0 5px #00ff00,
0 0 10px #00ff00,
0 0 20px #00ff00,
0 0 40px #00ff00;
}
50% {
text-shadow: 0 0 10px #00ff00,
0 0 20px #00ff00,
0 0 30px #00ff00,
0 0 60px #00ff00;
}
100% {
text-shadow: 0 0 5px #00ff00,
0 0 10px #00ff00,
0 0 20px #00ff00,
0 0 40px #00ff00;
}
}
</style>
<a class="navbar-brand" style="margin-left: auto; margin-right: auto;" href="/"><span class="franken">Franken</span>RIGS</a>
{% endblock %}
{% block titleelements %}
@@ -45,6 +74,7 @@
Invoices <span class="badge {% if todo == 0 %}badge-success{% else %}badge-danger{% endif %} badge-pill">{{ todo }}</span>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdownInvoices">
<a class="dropdown-item" href="{% url 'invoice_dashboard' %}"><span class="fas fa-chart-line"></span> Dashboard</a>
{% if perms.RIGS.add_invoice %}
<a class="dropdown-item text-nowrap" href="{% url 'invoice_waiting' %}"><span class="fas fa-briefcase text-danger"></span> Waiting <span class="badge {% if waiting == 0 %}badge-success{% else %}badge-danger{% endif %} badge-pill">{{ waiting }}</span></a>
{% endif %}

View File

@@ -26,6 +26,7 @@
var calendarEl = document.getElementById('calendar');
calendar = new FullCalendar.Calendar(calendarEl, {
firstDay: 1,
themeSystem: 'bootstrap',
aspectRatio: 1.5,
eventTimeFormat: {

View File

@@ -11,7 +11,7 @@
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}" async></script>
<script src="{% static 'js/selects.js' %}"></script>
{% endblock %}
{% block content %}
@@ -46,7 +46,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="col-sm-12" style="container-type: inline-size;">
{% with object_list as events %}
{% include 'partials/event_table.html' %}
{% endwith %}

View File

@@ -1,9 +1,21 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load markdown_tags %}
{% load button from filters %}
{% load static %}
{% block js %}
{{ block.super }}
<script>
$(document).keydown(function(e) {
if ((e.ctrlKey || e.metaKey) && e.keyCode == 80) {
window.open("{% url 'event_print' object.pk %}", '_blank');
return false;
}
});
</script>
{% endblock %}
{% block content %}
<div class="row my-3 py-3">
{% if not request.is_ajax %}
@@ -15,8 +27,11 @@
{% endif %}
{% if object.is_rig and perms.RIGS.view_event %}
{# only need contact details for a rig #}
<div class="col-md-6">
<div class="col-md-6 mb-3">
{% include 'partials/contact_details.html' %}
{% if object.parking_and_access or object.riskassessment.parking_and_access %}
{% include 'partials/parking_and_access.html' %}
{% endif %}
</div>
{% endif %}
<div class="col-md-6">
@@ -54,43 +69,9 @@
</div>
</div>
</div>
{% if event.can_check_in %}
<div class="col-sm-12">
<div class="card mt-3">
<div class="card-header">Crew Record</div>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Vehicle</th>
<th scope="col">Start Time</th>
<th scope="col">Role</th>
<th scope="col">End Time</th>
<th scope="col">{% if request.user.pk is event.mic.pk %}<a href="{% url 'event_checkin_override' event.pk %}" class="btn btn-sm btn-success"><span class="fas fa-plus"></span> Add</a>{% endif %}</th>
</tr>
</thead>
<tbody id="crewmembers">
{% for crew in object.crew.all %}
<tr>
<td>{{crew.person}}</td>
<td>{{crew.vehicle|default:"None"}}</td>
<td>{{crew.time}}</td>
<td>{{crew.role}}</td>
<td>{% if crew.end_time %}{{crew.end_time}}{% else %}<span class="text-success fas fa-clock" data-toggle="tooltip" title="This person is currently checked into this event"></span>{% endif %}</td>
<td>{% if crew.end_time %}{% if crew.person.pk == request.user.pk or event.mic.pk == request.user.pk %}{% button 'edit' 'edit_checkin' crew.pk clazz='btn-sm modal-href' %}{% endif %}{%endif%}</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center bg-warning">Apparently this event happened by magic...</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
{% include 'partials/crew_list.html' %}
{% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right">
{% include 'partials/event_detail_buttons.html' %}

View File

@@ -281,8 +281,15 @@
{{ form.dry_hire.label }} {% render_field form.dry_hire %}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<label data-toggle="tooltip" title="Do we need to secure campus parking permits, wristbands for backstage access or other non-standard requirements?">
{{ form.parking_and_access.label }} {% render_field form.parking_and_access %}
</label>
</div>
</div>
</div>
</div>
{# Status is needed on all events types and it looks good here in the form #}
<div class="form-group" data-toggle="tooltip" title="The current status of the event. Only mark as booked once paperwork is received">

View File

@@ -69,8 +69,11 @@
</div>
</div>
</div>
{% include 'partials/crew_list.html' with event=object.event %}
</div>
<div class="col-12 text-right">
<div class="col-12 text-right mt-4">
{% button 'edit' url='ec_edit' pk=object.pk %}
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
<a href="{% url 'event_pt' object.event.pk %}" class="btn btn-info"><span class="fas fa-paperclip"></span> <span

View File

@@ -165,8 +165,9 @@
</div>
</div>
<div class="col-12 text-right">
{% button 'print' 'pt_print' object.pk %}
{% button 'edit' url='pt_edit' pk=object.pk %}
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %}
{% include 'partials/review_status.html' with perm=perms.RIGS.review_power review='pt_review' %}
</div>
<div class="col-12 text-right">

View File

@@ -0,0 +1,235 @@
{% extends 'base_print.xml' %}
{% load filters %}
{% block extrastyles %}
<paraStyle name="style.powerReviewed" borderPadding="3" alignment="center" backColor="green" textColor="white"/>
<paraStyle name="style.powerUnreviewed" borderPadding="3" alignment="center" backColor="red" textColor="white"/>
<paraStyle name="style.smallText" fontSize="8"/>
<paraStyle leftIndent="2in" rightIndent="2in" name="style.smallEvent" fontSize="10" alignment="center" backColor="green" textColor="white" borderPadding="4" borderColor="black"/>
<paraStyle leftIndent="2in" rightIndent="2in" name="style.mediumEvent" fontSize="10" alignment="center" backColor="orange" textColor="white" borderPadding="4" borderColor="black"/>
<paraStyle leftIndent="2in" rightIndent="2in" name="style.largeEvent" fontSize="10" alignment="center" backColor="red" textColor="white" borderPadding="4" borderColor="black"/>
<blockTableStyle id="powerTable">
<blockValign value="middle"/>
<lineStyle kind="LINEABOVE" colorName="black" thickness="1"/>
<lineStyle kind="LINEBELOW" colorName="black" thickness="1"/>
<lineStyle kind="LINEAFTER" colorName="black" thickness="1"/>
<lineStyle kind="LINEBEFORE" colorName="black" thickness="1"/>
</blockTableStyle>
<blockTableStyle id="voltageTable">
<blockValign value="middle"/>
</blockTableStyle>
{% endblock %}
{% block content %}
<spacer length="15"/>
<h1>Power Test Record for <strong>{{ object.event }}</strong></h1>
<spacer length="15"/>
<h2>Client: {{ object.event.person|default:object.event.organisation }} | Venue: {{ object.event.venue }} | MIC: {{ object.event.mic }}</h2>
<spacer length="15"/>
<hr/>
<spacer length="15"/>
{% if object.reviewed_by %}
<para style="style.powerReviewed"><strong>Reviewed by: {{ object.reviewed_by }} at {{ object.reviewed_at|date:"D d/m/Y" }}</strong></para>
{% else %}
<para style="style.powerUnreviewed"><strong>Power test results not yet reviewed</strong></para>
{% endif %}
<spacer length="15"/>
<hr/>
<spacer length="15"/>
<h2 fontSize="16">Power Plan Information</h2>
<spacer length="15"/>
{% if object.event.riskassessment.event_size == 0 %}
<para style="style.smallEvent"><strong>Small Event</strong></para>
{% elif object.event.riskassessment.event_size == 1 %}
<para style="style.mediumEvent"><strong>Medium Event</strong></para>
{% elif object.event.riskassessment.event_size == 2 %}
<para style="style.largeEvent"><strong>Large Event</strong></para>
{% endif %}
<spacer length="15"/>
<blockTable colWidths="250,250">
<tr>
<td><para><strong>Power MIC:</strong> {{ object.power_mic }}</para></td>
<td><para><strong>Venue:</strong> {{ object.event.venue }}</para></td>
</tr>
<tr>
<td><para><strong>Event Date:</strong> {{ object.event.start_date |date:"D d/m/Y" }}</para></td>
<td><para><strong>Generators:</strong> {{ object.event.riskassessment.generators|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>Power Test taken at:</strong> {{ object.date_created|date:"D d/m/Y H:i" }}</para></td>
<td><para><strong>Other Companies Power:</strong> {{ object.event.riskassessment.other_companies_power|yesno|capfirst }}</para></td>
</tr>
</blockTable>
<spacer length="15"/>
{% if object.notes %}
<hr/>
<spacer length="15"/>
<para><strong>Additional Notes:</strong></para>
<spacer length="15"/>
<para>{{ object.notes }}</para>
<spacer length="15"/>
{% endif %}
<hr/>
<spacer length="15"/>
{% comment %}
0 - Small event
1 - Medium event (extra power records)
2 - Large event (extra power records)
{% endcomment %}
{% if object.event.riskassessment.event_size >= 1 %}
<para alignment="center"><strong>Power Test results enclosed on next page</strong></para>
<condPageBreak height="10in"/>
<h2 fontSize="16">Event Power Checklist</h2>
<spacer length="15"/>
<blockTable colWidths="250,270" style="powerTable">
<tr>
<td><para><strong>All circuit RCDs tested?</strong></para><para style="style.smallText">(using test button)</para></td>
<td><para>{{ object.all_rcds_tested|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>Public/performer accessible circuits tested?</strong></para><para style="style.smallText">(using socket tester)</para></td>
<td><para>{{ object.public_sockets_tested|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>Source RCD protected?</strong></para><para style="style.smallText">(if cable is more than 3m long)</para></td>
<td><para>{{ object.source_rcd|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>Appropriate and clear labelling on distribution and cabling?</strong></para></td>
<td><para>{{ object.labelling|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>Equipment appropriately earthed?</strong></para><para style="style.smallText">(truss, stage, generators, etc.)</para></td>
<td><para>{{ object.earthing|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>All equipment in PAT period?</strong><br/><br/></para></td>
<td><para>{{ object.pat|yesno|capfirst }}</para></td>
</tr>
</blockTable>
<spacer length="15"/>
<h2 fontSize="14">Power tests (First Distro)</h2>
<spacer length="5"/>
<blockTable colWidths="100,410" style="voltageTable">
<tr>
<td><para><strong>Voltage</strong></para><para style="style.smallText">(cube meter) / V</para></td>
<td>
<blockTable colWidths="100,100,100" style="powerTable">
<tr>
<td><para><strong>L1 - N</strong></para></td>
<td><para><strong>L2 - N</strong></para></td>
<td><para><strong>L3 - N</strong></para></td>
</tr>
<tr>
<td>{{ object.fd_voltage_l1}}</td>
<td>{{ object.fd_voltage_l2}}</td>
<td>{{ object.fd_voltage_l3}}</td>
</tr>
</blockTable>
</td>
</tr>
</blockTable>
<spacer length="10"/>
<blockTable colWidths="100,100,190,120" style="voltageTable">
<tr>
<td><para><strong>Phase Rotation</strong></para><para style="style.smallText">(if required)</para></td>
<td><para>{{ object.fd_phase_rotation|yesno|capfirst }}</para></td>
<td><para><strong>Earth Fault Loop Impedance (Z<sub>s</sub>) / Ω</strong></para></td>
<td><para>{{ object.fd_earth_fault }}</para></td>
</tr>
</blockTable>
<spacer length="15"/>
<para><strong>Prospective Short Circuit Current (PSCC)</strong> {{ object.fd_pssc }} A</para>
<spacer length="15"/>
<h2 fontSize="14">Power Tests (Worst Case Points)</h2>
<spacer length="15"/>
<blockTable colWidths="100,100,190,120" style="powerTable">
<tr>
<td><para><strong>Description</strong></para></td>
<td><para><strong>Polarity checked?</strong></para></td>
<td><para><strong>Voltage / V</strong></para></td>
<td><para><strong>Earth Fault Loop Impedance (Z<sub>s</sub>) / Ω</strong></para></td>
</tr>
{% if object.w1_description %}
<tr>
<td><para><strong>{{ object.w1_description }}</strong></para></td>
<td><para>{{ object.w1_polarity|yesno|capfirst }}</para></td>
<td><para>{{ object.w1_voltage }} V</para></td>
<td><para>{{ object.w1_earth_fault }}</para></td>
</tr>
{% endif %}
{% if object.w2_description %}
<tr>
<td><para><strong>{{ object.w2_description }}</strong></para></td>
<td><para>{{ object.w2_polarity|yesno|capfirst }}</para></td>
<td><para>{{ object.w2_voltage }} V</para></td>
<td><para>{{ object.w2_earth_fault }}</para></td>
</tr>
{% endif %}
{% if object.w3_description %}
<tr>
<td><para><strong>{{ object.w3_description }}</strong></para></td>
<td><para>{{ object.w3_polarity|yesno|capfirst }}</para></td>
<td><para>{{ object.w3_voltage }} V</para></td>
<td><para>{{ object.w3_earth_fault }}</para></td>
</tr>
{% endif %}
</blockTable>
{% else %}
{% comment %}
Small power test
{% endcomment %}
<h2 fontSize="16">Power Checklist</h2>
<spacer length="15"/>
<blockTable colWidths="250,270" style="powerTable">
<tr>
<td><para><strong>RCDs installed where needed and tested?</strong><br/><br/></para></td>
<td><para>{{ object.rcds|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>Electrical supplies tested?</strong><br/><br/></para></td>
<td><para>{{ object.supply_test|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>Equipment appropriately earthed?</strong></para><para style="style.smallText">(truss, stage, generators, etc.)</para></td>
<td><para>{{ object.earthing|yesno|capfirst }}</para></td>
</tr>
<tr>
<td><para><strong>All equipment in PAT period?</strong><br/><br/></para></td>
<td><para>{{ object.pat|yesno|capfirst }}</para></td>
</tr>
</blockTable>
{% endif %}
{% endblock %}

View File

@@ -124,6 +124,13 @@
<td><para>{{ object|help_text:'persons_responsible_structures'|striptags }}</para></td>
<td><para>{{ object.persons_responsible_structures|default:'N/A' }}</para></td>
</tr>
<tr>
<td colspan="2"><h3><strong>Venue Access</strong></h3></td>
</tr>
<tr>
<td><para>{{ object|help_text:'parking_and_access'|striptags }}</para></td>
<td><para>{{ object.parking_and_access|yesno|capfirst }}</para></td>
</tr>
</blockTable>
<spacer length="15"/>\
<hr/>

View File

@@ -151,8 +151,19 @@
</dl>
</div>
</div>
<div class="card card-default mb-3">
<div class="card-header">Venue Access</div>
<div class="card-body">
<dl class="row">
<dt class="col-10">{{ object|help_text:'parking_and_access' }}</dt>
<dd class="col-2">
{{ object.parking_and_access|yesnoi:'invert' }}
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 text-right">
{% button 'print' 'ra_print' object.pk %}

View File

@@ -9,7 +9,7 @@
{% endblock %}
{% block preload_js %}
<script src="{% static 'js/selects.js' %}" async></script>
<script src="{% static 'js/selects.js' %}"></script>
{% endblock %}
{% block js %}
@@ -162,6 +162,17 @@
</div>
</div>
</div>
<div class="row my-3">
<div class="col-12">
<div class="card">
<div class="card-header">Venue Access</div>
<div class="card-body">
<p><strong>If yes to the below, ensure you have communicated with the client and secured all necessary access prior to the event commencing.</strong></p>
{% include 'partials/yes_no_radio.html' with formitem=form.parking_and_access %}
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-sm-12 text-right">
<div class="btn-group">

View File

@@ -0,0 +1,106 @@
{% extends 'base_rigs.html' %}
{% load humanize %}
{% block content %}
<form method="GET" action="{% url 'invoice_dashboard' %}">
<div class="form-row">
<div class="form-group col-md-4">
<label for="time_filter">Time Filter</label>
<select id="time_filter" name="time_filter" class="form-control">
<option value="week" {% if time_filter == 'week' %}selected{% endif %}>Last Week (7 days)</option>
<option value="month" {% if time_filter == 'month' %}selected{% endif %}>Last Month (30 days)</option>
<option value="year" {% if time_filter == 'year' %}selected{% endif %}>Last Year</option>
<option value="all" {% if time_filter == 'all' %}selected{% endif %}>All Time</option>
</select>
</div>
</div>
</form>
<script>
$('#time_filter').change(function () {
$(this).closest('form').submit();
});
</script>
<h3>Overview</h3>
<!-- big cards in 2x2 grid with total_outstanding, total_events, total_invoices and total_payments, different backgrounds -->
<div class="card-deck">
<div class="card">
<a href="{% url 'invoice_waiting' %}" class="text-decoration-none text-white">
<div class="card-body bg-primary">
<h5 class="card-title text-center">Total Waiting</h5>
<p class="card-text text-center h3"><strong>£{{ total_waiting|floatformat:"2g" }}</strong></p>
</div>
</a>
</div>
<div class="card">
<a href="{% url 'invoice_list' %}" class="text-decoration-none text-dark">
<div class="card-body bg-info">
<h5 class="card-title text-center">Total Outstanding</h5>
<p class="card-text text-center h3"><strong>£{{ total_outstanding|floatformat:"2g" }}</strong></p>
</div>
</a>
</div>
<div class="card">
<div class="card-body bg-danger">
<h5 class="card-title text-center">Total Events</h5>
<p class="card-text text-center h3"><strong>{{ total_events }}</strong></p>
</div>
</div>
<div class="card">
<div class="card-body bg-success">
<h5 class="card-title text-center">Total Invoices</h5>
<p class="card-text text-center h3"><strong>{{ total_invoices }}</strong></p>
</div>
</div>
</div>
<br />
<h3>Payments</h3>
<br/>
<h4>Sources</h4>
<br/>
{% for source in payment_methods %}
<div class="card">
<div class="card-body">
<h5 class="card-title"><strong>{{ source.method }}</strong></h5>
<p class="card-text h3">£{{ source.total|floatformat:"2g" }}</p>
</div>
</div>
{% endfor %}
<br/>
<h4>Total</h4>
<br/>
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">Total Income</h5>
<p class="card-text text-center h3"><strong>£{{ total_income|floatformat:"2g" }}</strong></p>
</div>
</div>
<br/>
<h4>Invoice Payment Time</h4>
<br/>
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">Average Time to Pay</h5>
<p class="card-text text-center h3"><strong>{{ mean_invoice_to_payment|floatformat:"2g" }} days</strong></p>
</div>
</div>
{% endblock %}

View File

@@ -31,7 +31,7 @@
{% for event in object_list %}
<tr class="{{event.status_color}}">
<th scope="row"><a href="{% url 'event_detail' event.pk %}">{{ event.display_id }}</a><br>
<span class="text-muted">{{ event.get_status_display }}</span></th>
<span class="{% if event.get_status_display == 'Cancelled' %}text-danger{% endif %}">{{ event.get_status_display }}</span></th>
<td>{{ event.start_date }}</td>
<td>
{{ event.name }}

View File

@@ -23,7 +23,7 @@
</div>
{% endif %}
{% if object.organisation %}
<div class="card card-default">
<div class="card card-default mb-3">
<div class="card-header">Organisation Details</div>
<div class="card-body">
<dl class="row">
@@ -44,4 +44,4 @@
</dl>
</div>
</div>
{% endif %}
{% endif %}

View File

@@ -0,0 +1,50 @@
{% load button from filters %}
{% if event.can_check_in %}
<div class="col-sm-12">
<div class="card mt-3">
<div class="card-header">Crew Record</div>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Vehicle</th>
<th scope="col">Start Time</th>
<th scope="col">Role</th>
<th scope="col">End Time</th>
<th scope="col">{% if request.user.pk is event.mic.pk %}<a
href="{% url 'event_checkin_override' event.pk %}" class="btn btn-sm btn-success"><span
class="fas fa-plus"></span> Add</a>{% endif %}</th>
</tr>
</thead>
<tbody id="crewmembers">
{% for crew in event.crew.all %}
<tr>
<td>{{crew.person}}</td>
<td>{{crew.vehicle|default:"None"}}</td>
<td>{{crew.time}}</td>
<td>{{crew.role}}</td>
<td>{% if crew.end_time %}
{{crew.end_time}}
{% else %}
<span class="text-success fas fa-clock" data-toggle="tooltip"
title="This person is currently checked into this event"></span>{% endif %}
</td>
<td>{% if crew.end_time %}
{% if crew.person.pk == request.user.pk or event.mic.pk == request.user.pk %}
{% button 'edit' 'edit_checkin' crew.pk clazz='btn-sm modal-href' %}
{% endif %}
{% endif %}</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center bg-warning">Apparently this event happened by magic...</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}

View File

@@ -1,6 +1,6 @@
<h5 class="py-3"><a class="btn btn-info" data-toggle="collapse" href="#values" aria-expanded="false" aria-controls="values">View Threshold Values</a></h5>
<div class="row collapse" id="values">
<div class="table-responsive">
<div class="col-md-6 col-sm-12">
<table class="table table-bordered">
<thead>
<tr>
@@ -33,17 +33,20 @@
<thead>
<tr>
<th scope="row">Distro</th>
<th scope="row">Max PSSC (kA)</th>
<th scope="row">Max PSCC with Single Phase Supply (kA)</th>
<th scope="row">Max PSCC with Three Phase Supply (kA)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Intel & Toblerone distros</td>
<td>6</td>
<td>3</td>
</tr>
<tr>
<td>All other distros</td>
<td>10</td>
<td>5</td>
</tr>
</tbody>
</table>

View File

@@ -11,6 +11,7 @@
{{ object.venue|namewithnotes:'venue_detail' }}
</a>
{% endif %}
{% if object.parking_and_access or object.riskassessment.parking_and_access %}<span class="badge badge-warning">Additional Access Requirements</span>{% endif %}
</dd>
{% if object.venue %}
<dt class="col-sm-6">Venue Notes</dt>

View File

@@ -0,0 +1,147 @@
{% load namewithnotes from filters %}
{% load markdown_tags %}
<div class="card h-100 border-3 {{ border_class }} event-row">
<div class="card-header {{ header_bg }} {{ header_text }} py-3">
<div class="d-flex justify-content-between align-items-center">
<span class="d-flex align-items-center">
<h5 class="mb-0 mr-3">
<a href="{% url 'event_detail' event.pk %}"
class="{{ header_text }} text-decoration-underline fw-bold">
<strong>{{ event.display_id }}</strong> - {{ event.name }}
</a>
</h5>
{% if event.dry_hire %}
<span class="badge px-3 py-2 rounded-pill fs-6 text-dark bg-light">Dry Hire</span>
{% endif %}
</span>
<span class="badge fs-6 px-3 py-2 bg-light text-dark rounded-pill">{{ event.get_status_display }}</span>
</div>
</div>
<div class="card-body">
<div class="row align-items-start">
<div class="col-md-2 border-end event-dates">
<div class="mb-2">
<small class="text-muted">Meet at:</small>
{% if event.meet_at %}
<p class="mb-1">{{ event.meet_at|date:"D j M Y, H:i" }}</p>
{% else %}
<p class="mb-1">Not specified</p>
{% endif %}
</div>
<div class="mb-2">
<small class="text-muted">Access from:</small>
{% if event.access_at %}
<p class="mb-1">{{ event.access_at|date:"D j M Y, H:i" }}</p>
{% else %}
<p class="mb-1">Not specified</p>
{% endif %}
</div>
<div class="mb-2">
<small class="text-muted">Start:</small>
<p class="mb-1">
{% if event.start_date and event.start_time %}
{{ event.start_date|date:"D j M Y" }}, {{ event.start_time|date:"H:i" }}
{% elif event.start_date %}
{{ event.start_date|date:"D j M Y" }}
{% elif event.start_time %}
{{ event.start_time|date:"H:i" }}
{% else %}
Not specified
{% endif %}
</p>
</div>
<div class="mb-2">
<small class="text-muted">End:</small>
<p class="mb-1">
{% if event.end_date and event.end_time %}
{{ event.end_date|date:"D j M Y" }}, {{ event.end_time|date:"H:i" }}
{% elif event.end_date %}
{{ event.end_date|date:"D j M Y" }}
{% elif event.end_time %}
{{ event.end_time|date:"H:i" }}
{% else %}
Not specified
{% endif %}
</p>
</div>
</div>
<div class="col-md-10">
<div class="row">
<div class="col-md-6">
{% if event.venue %}
<div class="mb-3">
<small class="text-muted">Venue:</small>
<p class="mb-1">{{ event.venue|namewithnotes:'venue_detail' }}</p>
</div>
{% endif %}
{% if event.is_rig %}
<div class="mb-3">
<small class="text-muted">Client:</small>
<p class="mb-1">
{% if event.person %}
<a href="{{ event.person.get_absolute_url }}">{{ event.person.name }}</a>
{% if event.organisation %}
for <a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation }}</a>
{% endif %}
{% elif event.organisation %}
<a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation }}</a>
{% else %}
No client specified
{% endif %}
</p>
</div>
{% endif %}
{% if event.mic or event.needs_mic %}
<div class="mb-3">
<small class="text-muted">Member in Charge (MIC):</small>
<div class="d-flex align-items-center mt-1">
{% if event.mic %}
<img src="{{ event.mic.profile_picture }}" alt="{{ event.mic.name }}"
class="rounded-circle mr-1" width="32" height="32">
<span>
{% if perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{% endif %}
{{ event.mic.name }}
{% if perms.RIGS.view_profile %}
</a>
{% endif %}
</span>
{% else %}
<span class="text-danger">No MIC assigned</span>
{% endif %}
</div>
</div>
{% endif %}
</div>
<div class="col-md-6">
<div class="mb-3">
<small class="text-muted">Description:</small>
<p class="mb-1">{{ event.description|markdown }}</p>
</div>
<div class="mb-3">
<small class="text-muted">Status:</small>
<div class="mt-1">
{% include "partials/event_status.html" with status=event.status %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,9 +1,9 @@
<div>
<div id="event_status">
<span class="badge badge-{% if event.confirmed %}success{% elif event.cancelled %}dark{% else %}warning{% endif %}">Status: {{ event.get_status_display }}</span>
{% if event.is_rig %}
{% if event.sum_total > 0 %}
{% if event.purchase_order %}
<span class="badge badge-success">PO: {{ event.purchase_order }}</span>
<span class="badge badge-success">PO: Received</span>
{% elif event.authorised %}
<span class="badge badge-success">Authorisation: Complete <span class="fas fa-check"></span></span>
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
@@ -44,5 +44,8 @@
<span class="badge badge-info">Invoice: Not Generated</span>
{% endif %}
{% endif %}
{% if event.parking_and_access %}
<span class="badge badge-warning">Addititional Access Requirements</span>
{% endif %}
{% endif %}
</div>

View File

@@ -1,105 +1,70 @@
{% load namewithnotes from filters %}
{% load markdown_tags %}
<div class="table-responsive">
<table class="table mb-0" id="event_table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Dates & Times</th>
<th scope="col">Event Details</th>
<th scope="col">MIC</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr class="{% if event.cancelled %}
table-secondary
{% elif not event.is_rig %}
table-info
{% elif not event.mic %}
table-danger
{% elif event.confirmed and event.authorised %}
{% if event.dry_hire or event.riskassessment %}
table-success
{% else %}
table-warning
{% endif %}
{% else %}
table-warning
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
<!---Number-->
<th scope="row" id="event_number">{{ event.display_id }}</th>
<!--Dates & Times-->
<td id="event_dates" style="text-align: justify;">
{% if not event.cancelled %}
{% if event.meet_at %}
<span class="text-nowrap">Meet: <strong>{{ event.meet_at|date:"D d/m/Y H:i" }}</strong></span>
{% endif %}
{% if event.access_at %}
<br><span class="text-nowrap">Access: <strong>{{ event.access_at|date:"D d/m/Y H:i" }}</strong></span>
{% endif %}
{% endif %}
<span class="text-nowrap">Start: <strong>{{ event.start_date|date:"D d/m/Y" }}
{% if event.has_start_time %}
{{ event.start_time|date:"H:i" }}
{% endif %}</strong>
</span>
{% if event.end_date %}
<br>
<span class="text-nowrap">End: {% if event.end_date != event.start_date %}<strong>{{ event.end_date|date:"D d/m/Y" }}{% endif %}
{% if event.has_end_time %}
{{ event.end_time|date:"H:i" }}
{% endif %}</strong>
</span>
{% endif %}
</td>
<!---Details-->
<td id="event_details" class="w-100">
<h4>
<a href="{% url 'event_detail' event.pk %}">
{{ event.name }}
</a>
{% if event.venue %}
<small>at {{ event.venue|namewithnotes:'venue_detail' }}</small>
{% endif %}
{% if event.dry_hire %}
<span class="badge badge-secondary">Dry Hire</span>
{% endif %}
</h4>
{% if event.is_rig and not event.cancelled %}
<h5>
<a href="{{ event.person.get_absolute_url }}">{{ event.person.name }}</a>
{% if event.organisation %}
for <a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation.name }}</a>
{% endif %}
</h5>
{% endif %}
{% if not event.cancelled and event.description %}
<p>{{ event.description|markdown }}</p>
{% endif %}
{% include 'partials/event_status.html' %}
</td>
<!---MIC-->
<td id="event_mic" class="text-nowrap">
{% if event.mic %}
{% if perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{% endif %}
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo"/>
{{ event.mic }}
{% if perms.RIGS.view_profile %}
</a>
{% endif %}
{% elif event.is_rig %}
<span class="fas fa-user-slash"></span>
{% endif %}
</td>
</tr>
{% empty %}
<tr class="bg-warning">
<td colspan="4">No events found</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<style>
.light-link {
color: #ebf5ff !important;
}
.dark-link {
color: #4495ff !important;
}
.link-on-green {
color: #ffffff !important;
}
</style>
<div class="row">
{% for event in events %}
<div class="col-12 mb-4">
{% comment %} Determine card style based on event status {% endcomment %}
{% if event.cancelled %}
{% with border_class="border-secondary" header_bg="bg-secondary" header_text="light-link" %}
{% include "partials/event_row.html" %}
{% endwith %}
{% elif not event.is_rig %}
{% with border_class="border-primary" header_bg="bg-primary" header_text="light-link" %}
{% include "partials/event_row.html" %}
{% endwith %}
{% elif not event.mic %}
{% with border_class="border-danger" header_bg="bg-danger" header_text="light-link" %}
{% include "partials/event_row.html" %}
{% endwith %}
{% elif event.confirmed and event.authorised %}
{% if event.dry_hire or event.riskassessment %}
{% with border_class="border-success" header_bg="bg-success" header_text="link-on-green" %}
{% include "partials/event_row.html" %}
{% endwith %}
{% else %}
{% with border_class="border-warning" header_bg="bg-warning" header_text="dark-link" %}
{% include "partials/event_row.html" %}
{% endwith %}
{% endif %}
{% else %}
{% with border_class="border-warning" header_bg="bg-warning" header_text="dark-link" %}
{% include "partials/event_row.html" %}
{% endwith %}
{% endif %}
</div>
{% empty %}
<div class="col-12">
<div class="alert alert-info">
No events currently scheduled.
</div>
</div>
{% endfor %}

View File

@@ -0,0 +1,195 @@
{% load namewithnotes from filters %}
{% load markdown_tags %}
<style>
#event_table {
display: grid;
grid-template-columns: max-content min-content minmax(max-content, 1fr) max-content;
column-gap: 1em;
}
.eventgrid {
display: inherit;
grid-column: 1/5;
grid-template-columns: subgrid;
padding: 1em;
dt, dd { display: block; float: left; }
dt { clear: both; }
dd { float: right; }
}
.grid-header {
border-bottom: 1px solid grey;
border-top: 1px solid grey;
}
#event_status {
grid-column-start: 3;
}
#event_mic {
grid-row-start: 1;
grid-column-start: 4;
}
.c-none {
display: none;
}
.c-inline {
display: inline;
}
@container (width <= 500px) {
#event_table {
grid-template-columns: 1fr !important;
}
.eventgrid {
grid-column: 1/1 !important;
padding: 0.5em;
}
.grid-header {
display: none;
}
#event_dates {
order: 2;
}
#event_status {
order: 3;
}
#event_mic {
grid-row-start: auto;
grid-column-start: 4;
}
}
@container (width <= 700px) {
#event_table {
grid-template-columns: max-content;
column-gap: 0.5em;
}
.eventgrid {
grid-column: 1/3;
border: 1px solid grey;
}
#event_dates {
grid-row: 2;
grid-column: 1;
}
#event_number {
grid-row: 1;
grid-column: 1;
}
#event_mic {
grid-column: 2;
}
#event_status {
grid-column: span 2;
}
.grid-header, .c-md-none {
display: none;
}
}
@container (width > 700px) {
.c-lg-block {
display: block;
}
.c-lg-inline {
display: inline;
}
.c-lg-none, .c-md-none {
display: none;
}
}
</style>
<div id="event_table">
<div class="eventgrid grid-header font-weight-bold">
<div id="event_number">#</div>
<div id="event_dates">Dates & Times</div>
<div>Event Details</div>
<div id="event_mic">MIC</div>
</div>
{% for event in events %}
<div class="eventgrid {% if event.cancelled %}
table-secondary
{% elif not event.is_rig %}
table-info
{% elif not event.mic %}
table-danger
{% elif event.confirmed and event.authorised %}
{% if event.dry_hire or event.riskassessment %}
table-success
{% else %}
table-warning
{% endif %}
{% else %}
table-warning
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
<!---Number-->
<div class="font-weight-bold c-none c-lg-block" id="event_number">{{ event.display_id }}</div>
<!--Dates & Times-->
<div id="event_dates" style="min-width: 180px;">
<dl>
{% if not event.cancelled %}
{% if event.meet_at %}
<dt class="font-weight-normal">Meet:</dt>
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.meet_at|date:"D d/m/Y H:i" }}</dd>
{% endif %}
{% if event.access_at %}
<dt class="font-weight-normal">Access:</dt>
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.access_at|date:"D d/m/Y H:i" }}</dd>
{% endif %}
{% endif %}
<dt class="font-weight-normal">Start:</dt>
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.start_date|date:"D d/m/Y" }}
{% if event.has_start_time %}
{{ event.start_time|date:"H:i" }}
{% endif %}
</dd>
{% if event.end_date %}
<dt class="font-weight-normal">End:</dt>
<dd class="text-nowrap font-weight-bold text-lg-right">{{ event.end_date|date:"D d/m/Y" }}
{% if event.has_end_time %}
{{ event.end_time|date:"H:i" }}
{% endif %}
</dd>
{% endif %}
</dl>
</div>
<!---Details-->
<div id="event_details" class="w-100">
<h4>
<a href="{% url 'event_detail' event.pk %}">
<span class="c-inline c-lg-none">{{ event }}</span><span class="c-none c-lg-inline">{{ event.name }}</span>
</a>
{% if event.dry_hire %}
<span class="badge badge-secondary">Dry Hire</span>
{% endif %}
<br class="c-none c-lg-inline">
{% if event.venue %}
<small>at {{ event.venue|namewithnotes:'venue_detail' }}</small>
{% endif %}
</h4>
{% if event.is_rig and not event.cancelled %}
<h5>
<a href="{{ event.person.get_absolute_url }}">{{ event.person.name }}</a>
{% if event.organisation %}
for <a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation.name }}</a>
{% endif %}
</h5>
{% endif %}
{% if not event.cancelled and event.description %}
<p>{{ event.description|markdown }}</p>
{% endif %}
</div>
{% include 'partials/event_status.html' %}
<!---MIC-->
<div id="event_mic" class="text-nowrap">
<span class="c-md-none align-middle">MIC:</span>
{% if event.mic %}
{% if perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{% endif %}
<img src="{{ event.mic.profile_picture }}" class="event-mic-photo"/>
{{ event.mic }}
{% if perms.RIGS.view_profile %}
</a>
{% endif %}
{% elif event.is_rig %}
<span class="fas fa-exclamation"></span>
{% endif %}
</div>
</div>
{% endfor %}
</div>

View File

@@ -0,0 +1,22 @@
<div class="card card-default">
<div class="card-header">Parking and Access</div>
<div class="card-body">
<p>This venue has additional parking and/or access requirements.</p>
<p>Ensure the MIC has:</p>
<ul>
<li>Details of where to park</li>
<li>Details of how to access the venue</li>
<li>Details of any access restrictions</li>
<li>If on campus, sorted parking permits</li>
</ul>
{% if object.parking_and_access and object.riskassessment.parking_and_access %}
<small>Additional parking marked on both rig details and risk assessment.</small>
{% elif object.parking_and_access %}
<small>Additional parking marked on rig details.</small>
{% elif object.riskassessment.parking_and_access %}
<small>Additional parking marked on risk assessment.</small>
{% endif %}
</div>
</div>

View File

@@ -29,7 +29,15 @@
</div>
<div class="row pt-3">
<label class="col-sm-4 col-form-label"
for="{{ form.method.id_for_label }}">{{ form.method.label }}</label>
for="{{ form.method.id_for_label }}">{{ form.method.label }}
<span class="fas fa-info-circle text-info" data-toggle="collapse" data-target="#collapse" aria-expanded="false" aria-controls="collapse"></span>
<ul class="collapse" id="collapse">
<li>Cash - Self Explanatory</li>
<li>Internal - Transfers within the Students' Union only</li>
<li>External - All other transfers (<em>including</em> the University)</li>
<li>TEC Adjustment - Manual corrections</li>
</ul>
</label>
<div class="col-sm-8">
{% render_field form.method class+="form-control" %}
</div>

View File

@@ -3,16 +3,45 @@
{% block content %}
<div class="row align-items-center justify-content-between py-2 align-middle">
<div class="col-sm-12 col-md align-middle">
Key: <span class="table-success mr-1 px-2 rounded">Ready</span><span class="table-warning mr-1 px-2 rounded">Action Required</span><span class="table-danger mr-1 px-2 rounded">Needs MIC</span><span class="table-secondary mr-1 px-2 rounded">Cancelled</span><span class="table-info px-2 rounded">Non-Rig</span>
<div class="col-sm-12 col-md align-middle d-flex flex-wrap">
Key: <span class="table-success mr-1 px-2 rounded">Ready</span><span
class="table-warning mr-1 px-2 rounded text-nowrap">Action Required</span><span
class="table-danger mr-1 px-2 rounded text-nowrap">Needs MIC</span><span
class="table-secondary mr-1 px-2 rounded">Cancelled</span><span
class="table-info px-2 rounded text-nowrap">Non-Rig</span>
</div>
{% if perms.RIGS.add_event %}
<div class="col text-right">
{% button 'new' 'event_create' %}
</div>
{% endif %}
{% if not request.GET.legacy %}
{% if not request.GET.hide_cancelled %}
<a href="?hide_cancelled=true" class="btn btn-primary mr-3">Hide cancelled</a>
{% else %}
<a href="." class="btn btn-primary mr-3">Show cancelled</a>
{% endif %}
<a href="?legacy=true" class="btn btn-secondary">Legacy rigboard</a>
{% else %}
<a href="." class="btn btn-secondary">New rigboard</a>
{% endif %}
</div>
{% include 'partials/event_table.html' %}
{% if request.GET.legacy %}
<div class="alert alert-warning">
<strong>Warning:</strong> The legacy rigboard is being deprecated and will be removed in the future. Please use the
new rigboard.
</div>
{% endif %}
<div style="container-type: inline-size;">
{% if request.GET.legacy %}
{% include 'partials/legacy_event_table.html' %}
{% else %}
{% include 'partials/event_table.html' %}
{% endif %}
</div>
{% endblock %}

View File

@@ -20,7 +20,7 @@ def ra(basic_event, admin_user):
known_venue=True, safe_loading=True, safe_storage=True,
area_outside_of_control=True, barrier_required=True,
nonstandard_emergency_procedure=True, special_structures=False,
suspended_structures=False, outside=False)
suspended_structures=False, outside=False, parking_and_access=False)
yield ra
ra.delete()

View File

@@ -16,14 +16,14 @@ class Rigboard(BasePage):
URL_TEMPLATE = reverse('rigboard')
_add_item_selector = (By.XPATH, "//a[contains(@class,'btn-primary') and contains(., 'New')]")
_event_row_locator = (By.ID, 'event_row')
_event_row_locator = (By.CLASS_NAME, "event-row")
def add(self):
self.find_element(*self._add_item_selector).click()
class EventListRow(Region):
_event_number_locator = (By.ID, "event_number")
_event_dates_locator = (By.ID, "event_dates")
_event_dates_locator = (By.CLASS_NAME, "event-dates")
_event_details_locator = (By.ID, "event_details")
_event_mic_locator = (By.ID, "event_mic")
@@ -207,6 +207,7 @@ class CreateRiskAssessment(FormPage):
'suspended_structures': (regions.RadioSelect, (By.ID, 'id_suspended_structures')),
'supervisor_consulted': (regions.CheckBox, (By.ID, 'id_supervisor_consulted')),
'rigging_plan': (regions.TextBox, (By.ID, 'id_rigging_plan')),
'parking_and_access': (regions.RadioSelect, (By.ID, 'id_parking_and_access')),
}
@property

View File

@@ -91,8 +91,8 @@ class TestRigboard(BaseRigboardTest):
# self.live_server_url + '/event/create/', self.driver.current_url)
def test_event_order(self):
self.assertIn(self.testEvent.start_date.strftime('%a %d/%m/%Y'), self.page.events[0].dates)
self.assertIn(self.testEvent2.start_date.strftime('%a %d/%m/%Y'), self.page.events[1].dates)
self.assertIn(self.testEvent.start_date.strftime('%-d %b %Y'), self.page.events[0].dates)
self.assertIn(self.testEvent2.start_date.strftime('%-d %b %Y'), self.page.events[1].dates)
def test_add_button(self):
self.page.add()
@@ -127,7 +127,7 @@ class TestEventCreate(BaseRigboardTest):
# Fix it
self.page.end_date = datetime.date(2020, 1, 11)
self.page.access_at = datetime.datetime(2020, 1, 1, 9)
self.page.access_at = datetime.datetime(2020, 1, 8, 9)
self.page.dry_hire = True
self.page.status = "Booked"
self.page.collected_by = "Fred"
@@ -530,10 +530,11 @@ class TestCalendar(BaseRigboardTest):
self.page.toggle_filter('cancelled')
self.page.toggle_filter('provisional')
self.page.toggle_filter('confirmed')
self.page.toggle_filter('only_mic')
# and then check the url is correct
self.assertIn(
"rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false",
"rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false&only_mic=true",
self.page.cal_url)
# Awesome - all seems to work
@@ -759,6 +760,7 @@ def test_ra_creation(logged_in_browser, live_server, admin_user, basic_event):
page.barrier_required = False
page.nonstandard_emergency_procedure = False
page.special_structures = False
page.parking_and_access = False
# self.page.persons_responsible_structures = "Nobody and her cat, She"
page.suspended_structures = True

View File

@@ -100,6 +100,7 @@ urlpatterns = [
name='pt_edit'),
path('event/power/<int:pk>/review/', permission_required_with_403('RIGS.review_power')(views.MarkReviewed.as_view()),
name='pt_review', kwargs={'model': 'PowerTestRecord'}),
path('event/power/<int:pk>/print/', permission_required_with_403('RIGS.view_powertestrecord')(views.PowerPrint.as_view()), name='pt_print'),
path('event/<int:pk>/checkin/', login_required(views.EventCheckIn.as_view()),
name='event_checkin'),
@@ -114,7 +115,8 @@ urlpatterns = [
path('event/webhook/', views.RecieveForumWebhook.as_view(), name='webhook_recieve'),
# Finance
path('invoice/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceIndex.as_view()),
path('invoice/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceDashboard.as_view()), name='invoice_dashboard'),
path('invoice/outstanding', permission_required_with_403('RIGS.view_invoice')(views.InvoiceOutstanding.as_view()),
name='invoice_list'),
path('invoice/archive/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceArchive.as_view()),
name='invoice_archive'),

View File

@@ -5,7 +5,7 @@ import reversion
from django import forms
from django.contrib import messages
from django.db import transaction
from django.db.models import Q
from django.db.models import Sum
from django.http import Http404, HttpResponseRedirect
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
@@ -18,8 +18,76 @@ from RIGS import models
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
TIME_FILTERS = ["all", "year", "month", "week"]
class InvoiceIndex(generic.ListView):
def days_between(d1, d2):
diff = d2 - d1
return diff.total_seconds() / datetime.timedelta(days=1).total_seconds()
class InvoiceDashboard(generic.TemplateView):
template_name = 'invoice_dashboard.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page_title'] = "Invoice Dashboard"
context['description'] = "Overview of financial status of TEC rigs."
time_filter = self.request.GET.get('time_filter', 'all')
if time_filter not in TIME_FILTERS:
time_filter = 'all'
if time_filter == 'all':
context['events'] = models.Event.objects.filter(is_rig=True)
context['invoices'] = models.Invoice.objects.all()
context['payments'] = models.Payment.objects.all()
elif time_filter == 'year':
context['events'] = models.Event.objects.filter(is_rig=True, start_date__gte=datetime.date.today() - datetime.timedelta(days=365))
context['invoices'] = models.Invoice.objects.filter(invoice_date__gte=datetime.date.today() - datetime.timedelta(days=365))
context['payments'] = models.Payment.objects.filter(date__gte=datetime.date.today() - datetime.timedelta(days=365))
elif time_filter == 'month':
context['events'] = models.Event.objects.filter(is_rig=True, start_date__gte=datetime.date.today() - datetime.timedelta(days=30))
context['invoices'] = models.Invoice.objects.filter(invoice_date__gte=datetime.date.today() - datetime.timedelta(days=30))
context['payments'] = models.Payment.objects.filter(date__gte=datetime.date.today() - datetime.timedelta(days=30))
elif time_filter == 'week':
context['events'] = models.Event.objects.filter(is_rig=True, start_date__gte=datetime.date.today() - datetime.timedelta(days=7))
context['invoices'] = models.Invoice.objects.filter(invoice_date__gte=datetime.date.today() - datetime.timedelta(days=7))
context['payments'] = models.Payment.objects.filter(date__gte=datetime.date.today() - datetime.timedelta(days=7))
context["time_filter"] = time_filter
context['total_outstanding'] = sum([i.balance for i in models.Invoice.objects.outstanding_invoices()])
context['total_waiting'] = sum([i.sum_total for i in models.Event.objects.waiting_invoices()])
context['total_events'] = len(context['events'])
context['total_invoices'] = len(context['invoices'])
context['total_payments'] = len(context['payments'])
payment_methods = dict(models.Payment.METHODS)
context['payment_methods'] = context["payments"].values('method').annotate(total=Sum('amount')).order_by('method')
for method in context['payment_methods']:
method['method'] = payment_methods.get(method['method'], f"Unknown method ({method['method']})")
context["total_income"] = sum([i['total'] for i in context['payment_methods']])
payments = context['payments']
mean_duration = 0
for payment in payments:
mean_duration += days_between(payment.invoice.invoice_date, payment.date)
if len(payments) > 0:
mean_duration /= len(payments)
context['mean_invoice_to_payment'] = mean_duration
return context
class InvoiceOutstanding(generic.ListView):
model = models.Invoice
template_name = 'invoice_list.html'

View File

@@ -232,6 +232,16 @@ class RAPrint(PrintView):
return context
class PowerPrint(PrintView):
model = models.PowerTestRecord
template_name = 'hs/power_print.xml'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filename'] = f"PowerTestRecord_for_{context['object'].event.display_id}.pdf"
return context
class EventCheckIn(generic.CreateView, ModalURLMixin):
model = models.EventCheckIn
template_name = 'hs/eventcheckin_form.html'

View File

@@ -24,6 +24,7 @@ class CalendarICS(ICalFeed):
# Rig = 'rig' = True
# Provisional = 'provisional' = True
# Confirmed/Booked = 'confirmed' = True
# Only MIC = 'mic' = False
def get_object(self, request, *args, **kwargs):
params = {}
@@ -35,6 +36,9 @@ class CalendarICS(ICalFeed):
params['cancelled'] = request.GET.get('cancelled', 'false') == 'true'
params['provisional'] = request.GET.get('provisional', 'true') == 'true'
params['confirmed'] = request.GET.get('confirmed', 'true') == 'true'
params['only_mic'] = request.GET.get('only_mic', 'false') == 'true'
params['user'] = kwargs['user']
return params
@@ -73,6 +77,9 @@ class CalendarICS(ICalFeed):
filter = filter & typeFilters & statusFilters
if params['only_mic']:
filter = filter & Q(mic=params['user'])
return models.Event.objects.filter(filter).order_by('-start_date').select_related('person', 'organisation',
'venue', 'mic')

View File

@@ -41,8 +41,13 @@ class RigboardIndex(generic.TemplateView):
# get super context
context = super().get_context_data(**kwargs)
objects = models.Event.objects.current_events()
if self.request.GET.get('hide_cancelled', False):
objects = objects.exclude(status=models.Event.CANCELLED)
# call out method to get current events
context['events'] = models.Event.objects.current_events().select_related('riskassessment', 'invoice').prefetch_related('checklists')
context['events'] = objects.select_related('riskassessment', 'invoice').prefetch_related('checklists')
context['page_title'] = "Rigboard"
return context

View File

@@ -4,7 +4,7 @@
"scripts": {
"postdeploy": "python manage.py migrate && python manage.py generateSampleData"
},
"stack": "heroku-20",
"stack": "heroku-22",
"env": {
"DEBUG": {
"required": true
@@ -51,7 +51,7 @@
"url": "heroku/nodejs"
},
{
"url": "https://github.com/nottinghamtec/heroku-buildpack-python"
"url": "heroku/python"
}
]
}

View File

@@ -12,7 +12,7 @@
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}" async></script>
<script src="{% static 'js/selects.js' %}"></script>
{% endblock %}
{% block js %}

View File

@@ -11,7 +11,7 @@
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}" async></script>
<script src="{% static 'js/selects.js' %}"></script>
{% endblock %}
{% block js %}

View File

@@ -38,3 +38,17 @@ def test_asset(db, category, status):
asset, created = models.Asset.objects.get_or_create(asset_id="91991", description="Spaceflower", status=status, category=category, date_acquired=datetime.date(1991, 12, 26), replacement_cost=100)
yield asset
asset.delete()
@pytest.fixture
def test_status_2(db):
status = models.AssetStatus.objects.create(name="Lost", should_show=False)
yield status
status.delete()
@pytest.fixture
def test_asset_2(db, category, test_status_2):
asset, created = models.Asset.objects.get_or_create(asset_id="10", description="Working Mic", status=test_status_2, category=category, date_acquired=datetime.date(2001, 10, 20), replacement_cost=1000)
yield asset
asset.delete()

View File

@@ -1,5 +1,6 @@
import time
import datetime
import pytest
from django.utils import timezone
from selenium.webdriver.common.by import By
@@ -53,45 +54,45 @@ class TestAssetList(AutoLoginTest):
self.assertEqual("10", asset_ids[2])
self.assertEqual("C1", asset_ids[3])
def test_search(self):
self.page.set_query("10")
self.page.search()
self.assertTrue(len(self.page.assets) == 1)
self.assertEqual("Working Mic", self.page.assets[0].description)
self.assertEqual("10", self.page.assets[0].id)
self.page.set_query("light")
self.page.search()
self.assertTrue(len(self.page.assets) == 1)
self.assertEqual("A light", self.page.assets[0].description)
@pytest.mark.xfail(reason="Fails on CI for unknown reason", raises=AssertionError)
def test_search(logged_in_browser, admin_user, live_server, test_asset, test_asset_2, category, status, cable_type):
page = pages.AssetList(logged_in_browser.driver, live_server.url).open()
page.set_query(test_asset.asset_id)
page.search()
assert len(page.assets) == 1
assert page.assets[0].description == test_asset.description
assert page.assets[0].id == test_asset.asset_id
self.page.set_query("Random string")
self.page.search()
self.assertTrue(len(self.page.assets) == 0)
page.set_query(test_asset.description)
page.search()
assert len(page.assets) == 1
assert page.assets[0].description == test_asset.description
self.page.set_query("")
self.page.search()
# Only working stuff shown by default
self.assertTrue(len(self.page.assets) == 2)
page.set_query("Random string")
page.search()
assert len(page.assets) == 0
self.page.status_selector.toggle()
self.assertTrue(self.page.status_selector.is_open)
self.page.status_selector.select_all()
self.page.status_selector.toggle()
self.assertFalse(self.page.status_selector.is_open)
self.page.filter()
self.assertTrue(len(self.page.assets) == 4)
page.set_query("")
page.search()
# Only working stuff shown by default
assert len(page.assets) == 1
self.page.category_selector.toggle()
self.assertTrue(self.page.category_selector.is_open)
self.page.category_selector.set_option("Sound", True)
self.page.category_selector.close()
self.assertFalse(self.page.category_selector.is_open)
self.page.filter()
self.assertTrue(len(self.page.assets) == 2)
asset_ids = list(map(lambda x: x.id, self.page.assets))
self.assertEqual("1", asset_ids[0])
self.assertEqual("10", asset_ids[1])
page.status_selector.toggle()
assert page.status_selector.is_open
page.status_selector.select_all()
page.status_selector.toggle()
assert not page.status_selector.is_open
page.filter()
assert len(page.assets) == 2
page.category_selector.toggle()
assert page.category_selector.is_open
page.category_selector.set_option(category.name, True)
page.category_selector.close()
assert not page.category_selector.is_open
page.filter()
assert len(page.assets) == 2
def test_cable_create(logged_in_browser, admin_user, live_server, test_asset, category, status, cable_type):
@@ -243,6 +244,7 @@ class TestSupplierList(AutoLoginTest):
self.page.set_query("")
self.page.search()
time.sleep(1)
self.assertTrue(len(self.page.suppliers) == 7)
self.page.set_query("This is not a supplier")

View File

@@ -374,7 +374,7 @@ def generate_label(pk):
barcode = Code39(str(obj.asset_id), writer=ImageWriter())
logo_size = (200, 200)
image.paste(logo.resize(logo_size, Image.ANTIALIAS), box=(5, 5))
image.paste(logo.resize(logo_size, Image.LANCZOS), box=(5, 5))
barcode_image = barcode.render(writer_options={"quiet_zone": 0, "write_text": False})
width, height = barcode_image.size
image.paste(barcode_image.crop((0, 0, width, 100)), (int(((size[0] + logo_size[0]) - width) / 2), 40))

View File

@@ -16,7 +16,7 @@ const con = require('gulp-concat');
const gulpif = require('gulp-if');
function fonts(done) {
return gulp.src('node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.*')
return gulp.src('node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.*', { encoding: false })
.pipe(gulp.dest('pipeline/built_assets/fonts'))
.pipe(browsersync.stream());
}
@@ -70,16 +70,16 @@ function scripts() {
.pipe(gulpif(function(file) { return interaction.includes(file.relative);}, con('interaction.js')))
.pipe(gulpif(function(file) { return jpop.includes(file.relative);}, con('jpop.js')))
.pipe(flatten())
.pipe(terser())
// Only minify if filename does not already denote it as minified
.pipe(gulpif(function(file) { return file.path.indexOf("min") == -1;},terser()))
.pipe(gulp.dest(dest))
.pipe(browsersync.stream());
}
function browserSync(done) {
spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'});
// TODO Wait for Django server to come up before browsersync, it seems inconsistent
browsersync.init({
notify: false,
notify: true,
open: false,
port: 8001,
proxy: '127.0.0.1:8000'

10044
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,6 @@
"cssnano": "^5.0.13",
"easymde": "^2.16.1",
"fullcalendar": "^5.10.1",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-flatten": "^0.4.0",
"gulp-if": "^3.0.0",
@@ -28,13 +27,14 @@
"jquery": "^3.6.0",
"konami": "^1.6.3",
"moment": "^2.29.4",
"node-sass": "^7.0.3",
"node-sass": "^9.0.0",
"popper.js": "^1.16.1",
"postcss": "^8.4.5",
"postcss": "^8.4.31",
"uglify-js": "^3.14.5"
},
"devDependencies": {
"browser-sync": "^2.27.11"
"browser-sync": "^3.0.2",
"gulp": "^5.0.0"
},
"scripts": {
"gulp": "gulp",

View File

@@ -1,16 +1,11 @@
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
//console.log('Changing selected value');
obj.find('option').remove(); //Remove all the available options
obj.append( //Add the new option
$("<option></option>")
.attr("value",pk)
.text(text)
.data('update_url',update_url)
);
obj.selectpicker('render'); //Re-render the UI
obj.selectpicker('refresh'); //Re-render the UI
obj.selectpicker('val', pk); //Set the new value to be selected
obj[0].add(new Option(text, pk, true, true)); // Add new option
//obj.selectpicker('val', pk); //Set the new value to be selected
obj.selectpicker('refresh');
obj.change(); //Trigger the change function manually
//console.log(obj);
}
function refreshUpdateHref(obj) {

View File

@@ -17,14 +17,12 @@ jQuery(document).ready(function () {
});
}
});
var easter_egg = new Konami();
easter_egg.code = function () {
var easter_egg = new Konami(function () {
var s = document.createElement('script');
s.type = 'text/javascript';
document.body.appendChild(s);
s.src = '{% static "js/asteroids.min.js"%}';
ga('send', 'event', 'easter_egg', 'activated');
}
s.src = '/static/js/asteroids.min.js';
});
easter_egg.load();
});
//CTRL-Enter form submission

View File

@@ -9,3 +9,4 @@ $theme-colors: (
"primary": #3A52A2
) !default;
$enable-shadows: true;
$alert-color-level: 10;

View File

@@ -77,17 +77,8 @@
border-collapse: separate !important;
border-spacing: 0;
}
#event_table tr th {
border-right: 0 !important;
}
#event_table tr td {
border-left: 0 !important;
}
#event_table tr td:not(:last-child) {
border-right: 0 !important;
}
@each $color, $value in $theme-colors {
.table-#{$color} {
table.table-#{$color} {
> td,th {
border: 0.3em solid theme-color-level($color, -6) !important;
}
@@ -96,6 +87,11 @@
background-color: #222 !important;
}
}
#event_row.table-#{$color} {
border: 0.3em solid theme-color-level($color, -6) !important;
background-color: #222 !important;
color: white !important;
}
}
del {
color: black;
@@ -156,4 +152,7 @@
.modal {
overflow-y: auto !important; //Bootstrap Dark Theme overrides this to none for some insane reason so we need to change it back
}
.text-muted {
color: #c9c9c9 !important;
}
}

View File

@@ -281,3 +281,12 @@ html.embedded {
.bootstrap-select, button.btn.dropdown-toggle.bs-placeholder.btn-light {
padding-right: 1rem !important;
}
// New implementation of class dropped in Bootstrap 3
.dl-horizontal {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.7rem 0;
}
.dl-horizontal > dd, .dl-horizontal .markdown > p {
margin-bottom: 0 !important;
}

99
pyproject.toml Normal file
View File

@@ -0,0 +1,99 @@
[project]
name = "pyrigs"
version = "0.1.0"
description = "A Django-based event booking system designed for use by TEC PA and Lighting"
readme = "README.md"
requires-python = "~=3.10.0"
dependencies = [
"ansicolors~=1.1.8",
"asgiref~=3.3.1",
"backports.tempfile~=1.0",
"backports.weakref~=1.0.post1",
"beautifulsoup4~=4.9.3",
"Brotli~=1.0.9",
"cachetools~=4.2.1",
"chardet~=4.0.0",
"configparser~=5.0.1",
"contextlib2~=0.6.0.post1",
"cssselect~=1.1.0",
"cssutils~=1.0.2",
"dj-database-url~=0.5.0",
"dj-static~=0.0.6",
"Django~=3.2",
"django-debug-toolbar~=4.0.0",
"django-filter~=2.4.0",
"django-ical~=1.7.1",
"django-recurrence~=1.10.3",
"django-registration-redux~=2.9",
"django-reversion~=3.0.9",
"django-widget-tweaks~=1.4.8",
"django-htmlmin~=0.11.0",
"envparse",
"gunicorn~=22.0.0",
"icalendar~=4.0.7",
"idna~=3.7",
"Markdown~=3.3.3",
"msgpack~=1.0.2",
"pep517~=0.9.1",
"Pillow~=10.0.1",
"premailer~=3.7.0",
"progress~=1.5",
"psutil~=5.8.0",
"psycopg2-binary",
"Pygments~=2.15.0",
"pyparsing~=2.4.7",
"PyPDF2~=1.27.5",
"PyPOM~=2.2.4",
"python-dateutil~=2.8.1",
"pytoml~=0.1.21",
"pytz~=2020.5",
"reportlab",
"requests~=2.32.3",
"retrying~=1.3.3",
"simplejson~=3.17.2",
"six~=1.15.0",
"soupsieve~=2.1",
"sqlparse~=0.5.0",
"static3~=0.7.0",
"svg2rlg~=0.3",
"tini~=3.0.1",
"tornado~=6.3",
"urllib3~=2.6.3",
"whitenoise~=5.2.0",
"yolk~=0.4.3",
"zipp~=3.4.0",
"zope.component~=4.6.2",
"zope.deferredimport~=4.3.1",
"zope.deprecation~=4.4.0",
"zope.event~=4.5.0",
"zope.hookable~=5.0.1",
"zope.proxy~=4.3.5",
"zope.schema~=6.0.1",
"sentry-sdk",
"diff-match-patch",
"python-barcode",
"django-hCaptcha",
"importlib-metadata",
"django-hcaptcha",
"z3c.rml",
"pikepdf",
"django-queryable-properties",
"django-mass-edit",
"selenium~=4.9.1",
"zope.interface",
]
[dependency-groups]
dev = [
"pycodestyle~=2.9.1",
"coveralls",
"django-coverage-plugin",
"pytest-cov",
"pytest-django",
"pluggy",
"pytest-splinter",
"pytest",
"pytest-reverse",
"pytest-xdist[psutil]",
"PyPOM[splinter]",
]

View File

@@ -1,2 +1,3 @@
[pycodestyle]
max-line-length = 320
exclude = .venv

View File

@@ -13,6 +13,10 @@
<meta name="theme-color" content="#3853a4">
<meta name="color-scheme" content="light dark">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fontdiner+Swanky&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" href="{% static 'imgs/pyrigs-avatar.png' %}">
<link rel="apple-touch-icon" href="{% static 'imgs/pyrigs-avatar.png' %}">
@@ -35,8 +39,8 @@
{% endif %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" role="navigation">
<div class="container">
<a class="navbar-brand" style="position: absolute; left:0.5em; top: 2px;" href="{% if request.user.is_authenticated %}https://rigs.nottinghamtec.co.uk{%else%}https://nottinghamtec.co.uk{%endif%}">
<img src="{% static 'imgs/logo.webp' %}" width="40" height="40" alt="TEC's Logo: Serif 'TEC' vertically next to a blue box with the words 'PA and Lighting', surrounded by graduated rings" id="logo">
<a class="navbar-brand" href="{% if request.user.is_authenticated %}https://rigs.nottinghamtec.co.uk{%else%}https://nottinghamtec.co.uk{%endif%}">
<img src="{% static 'imgs/logo.webp' %}" class="mr-auto" style="max-height: 40px;" alt="TEC's Logo: Serif 'TEC' vertically next to a blue box with the words 'PA and Lighting', surrounded by graduated rings" id="logo">
</a>
{% block titleheader %}
{% endblock %}

View File

@@ -9,9 +9,11 @@
<h1 class="col-sm-12 pb-3">R<small class="text-muted">ig</small> I<small class="text-muted">nformation</small> G<small class="text-muted">athering</small> S<small class="text-muted">ystem</small></h1>
<h2 class="col-sm-12 pb-3">Welcome back {{ user.get_full_name }}, there {%if rig_count == 1 %}is one rig coming up{%else%}are {{ rig_count|apnumber }} rigs coming up.{%endif%}</h2>
{% if now %}
<div class="col-sm-12 alert alert-primary rounded-0 mx-auto">
<div class="col-sm-12">
{% for event in now %}
Event {{ event }} is happening now! <a href="{% url 'event_checkin' event.pk %}" class="btn btn-success btn-sm modal-href align-baseline {% if request.user.current_event %}disabled{%endif%}"><span class="fas fa-user-clock"></span> <span class="d-none d-sm-inline">Check In</span></a><br/>
<div class="alert alert-primary rounded-0">
Event <a href="{% url 'event_detail' event.pk %}" class="text-danger">{{ event }}</a> is happening today! <a href="{% url 'event_checkin' event.pk %}" class="btn btn-success btn-sm modal-href align-baseline {% if request.user.current_event %}disabled{%endif%}"><span class="fas fa-user-clock"></span> <span class="d-none d-sm-inline">Check In</span></a><br/>
</div>
{% endfor %}
</div>
{% endif %}
@@ -54,6 +56,9 @@
<a class="list-group-item list-group-item-action" href="{% url 'trainee_list' %}"><span class="fas fa-users"></span><span class="align-middle"> Trainee List</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'level_list' %}"><span class="fas fa-layer-group"></span> <span class="align-middle">Level List</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'item_list' %}"><span class="fas fa-sitemap"></span> <span class="align-middle">Item List</span></a>
{% if request.user.is_supervisor %}
<a class="list-group-item list-group-item-action" href="{% url 'session_log' %}"><span class="fas fa-users"></span> <span class="align-middle">Log Session</span></a>
{% endif %}
</div>
</div>
</div>

View File

@@ -78,10 +78,10 @@
</tr>
{% endfor %}
<tr><th colspan="3" class="text-center">{{object}}</th></tr>
<tr>
<td><ul class="list-unstyled">{% for req in object.started_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 0 %} {% if request.user.is_supervisor %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.complete_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 1 %} {% if request.user.is_supervisor %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline" href="{% url 'remove_requirement' pk=req.pk %}"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.passed_out_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 2 %} {% if request.user.is_supervisor %}<a type="button" class="btn btn-link tn-sm p-0 align-baseline"" href="{% url 'remove_requirement' pk=req.pk %}" title="Delete requirement"><span class="fas fa-trash-alt text-danger"></span></a>{%endif%}</li>{% endfor %}</ul></td>
<tr>
<td><ul class="list-unstyled">{% for req in object.started_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 0 %}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.complete_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 1 %}</li>{% endfor %}</ul></td>
<td><ul class="list-unstyled">{% for req in object.passed_out_requirements %}<li>{{ req.item }} {% user_has_qualification u req.item 2 %}</li>{% endfor %}</ul></td>
</tr>
</tbody>
</table>

View File

@@ -1,6 +1,9 @@
<label for="supervisor" class="col-sm-2 col-form-label">Supervisor</label>
<select name="supervisor" id="supervisor_id" class="selectpicker col-sm-10" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required data-noclear="true">
{% if supervisor %}
<select name="supervisor" id="supervisor_id" class="selectpicker col-sm-10" data-live-search="true"
data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" required
data-noclear="true">
{% if supervisor %}
<option value="{{form.supervisor.value}}" selected>{{ supervisor }}</option>
{% endif %}
{% endif %}
<option value="{{request.user.pk}}" selected>{{ request.user }}</option>
</select>

View File

@@ -17,8 +17,8 @@ def select_super(page, supervisor):
assert page.supervisor_selector.is_open
page.supervisor_selector.search(supervisor.name[:-6])
time.sleep(2) # Slow down for javascript
page.supervisor_selector.set_option(supervisor.name, True)
assert page.supervisor_selector.options[0].selected
page.supervisor_selector.toggle()
def test_add_qualification(logged_in_browser, live_server, trainee, supervisor, training_item):
@@ -40,6 +40,7 @@ def test_add_qualification(logged_in_browser, live_server, trainee, supervisor,
page.item_selector.toggle()
select_super(page, supervisor)
page.supervisor_selector.toggle()
page.submit()
assert page.success

View File

@@ -12,8 +12,8 @@ class Command(BaseCommand):
def handle(self, *args, **options):
for person in Profile.objects.all():
# Inactivate users that have not logged in for a year (or have never logged in)
if person.last_login is None or (timezone.now() - person.last_login).days > 365:
# Inactivate users that have not logged in for a year
if person.last_login is not None and (timezone.now() - person.last_login).days > 365:
person.is_active = False
person.is_approved = False
person.save()

View File

@@ -126,6 +126,9 @@
<label class="checkbox-inline ml-lg-2">
<input type="checkbox" value="confirmed" data-default="true" checked> Confirmed/Booked
</label>
<label class="checkbox-inline ml-lg-2">
<input type="checkbox" value="only_mic" data-default="false" > Only MIC
</label>
</div>
</form>
</dd>
@@ -167,9 +170,11 @@
<div class="col-lg-6">
<div class="card">
<div class="card-header">Events</div>
<div style="container-type: size; height: 30vh; overflow-y: scroll;">
{% with object.latest_events as events %}
{% include 'partials/event_table.html' %}
{% endwith %}
</div>
</div>
</div>
</div>

1651
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff