Compare commits

..

31 Commits

Author SHA1 Message Date
a9b034255e Fix pycodestyle, experiment with custom buildpack 2021-02-08 19:20:38 +00:00
6676183443 Remove dark mode switch from gulpfile 2021-02-08 18:21:56 +00:00
3f93cebf41 Much prefetch/select related optimisations 2021-02-08 18:18:16 +00:00
603e919ad0 Fix sql efficency for rigboard index 2021-02-08 16:49:02 +00:00
a0b70a3cac Minor template tweaks 2021-02-08 16:33:15 +00:00
a11e32252f Make some improvements as suggested by DjangoDoctor 2021-02-08 16:27:57 +00:00
e48e016cb9 Tests actually work again 2021-02-08 15:18:20 +00:00
ef1d9868da Revert to old method of sample data gen
bulk_create is super quick, but no autoincrement on sqlite is killer when trying to run tests.
2021-02-08 12:57:08 +00:00
788fb3efe6 Yet more test shenanigans
Can you tell I'm getting fed up?
2021-02-07 02:58:34 +00:00
4f912932ca Make dark theme a user level property, lazy load dark CSS
- Also now respects the colour-scheme media query
- Added meta tag to tell the browser we support dark theme, allowing dark UA stylesheet if the user sends said media query
- Means you only have to set it once per account rather than once per machine
- Dark themed embeds!
2021-02-06 16:48:10 +00:00
0598612c15 First pass at updating event embed
Seems I forgot about those in BS4 port, oops
2021-02-06 01:02:25 +00:00
656f9fdd25 Stop browsersync automatically opening
Annoying focus stealing be gone
2021-02-06 00:45:23 +00:00
ccda38918c Properly migrate to Sentry from Raven 2021-02-06 00:42:11 +00:00
a1edf80dd0 Minor test futzing 2021-02-05 03:16:19 +00:00
83fe526cbd Init signals.py for assets 2021-02-05 02:34:25 +00:00
1d63bd940d More test munging 2021-02-05 01:17:23 +00:00
c090163f40 SQL efficiency on asset list 2021-02-05 00:58:30 +00:00
baa3b2c9c6 More migration to fixtures 2021-02-05 00:04:15 +00:00
462a16ec42 Move user/group setup into new generateSampleUserData command 2021-02-04 16:08:18 +00:00
6cb3d1855a Fix model tests for vat rate fixture 2021-02-04 13:41:53 +00:00
9279131edf Migrate to pipenv
Closes #384
2021-02-04 13:17:05 +00:00
3853ad0871 Much test refactoring 2021-02-04 13:06:23 +00:00
7eea868575 Derp fixes 2021-02-01 15:47:19 +00:00
fc6e66c7f5 Cache static directory on CI, skip npm install and gulp build on hit
Should speed up CI runs a lot
2021-02-01 15:39:35 +00:00
01ed05ecd9 Fix fonts, better JS compression, remove unused print.scss 2021-02-01 15:31:10 +00:00
20e5d25130 Add smol tec logo to navbar 2021-02-01 15:16:46 +00:00
11db880ac3 Optimise generateSampleData by ~10x
Does remove reversion creation for now...
2021-02-01 14:09:14 +00:00
87caab6c8e Serve minified css 2021-02-01 02:46:28 +00:00
d79366d2e6 Enable HTMLMin, further Whitenoise config 2021-02-01 02:30:17 +00:00
10f2152d8b Fix some unnecessary CRSF exemptions 2021-01-31 21:43:30 +00:00
0154ecb6d8 Deduplicate OEmbed view 2021-01-31 21:32:32 +00:00
170 changed files with 6193 additions and 9026 deletions

View File

@@ -1,5 +1,3 @@
[run] [run]
omit = */migrations/* plugins = django_coverage_plugin
*/tests/* omit = */migrations/*, */tests/*
*/site-packages/*
*/distutils/*

View File

@@ -10,36 +10,38 @@ jobs:
build: build:
if: "!contains(github.event.head_commit.message, '[ci skip]')" if: "!contains(github.event.head_commit.message, '[ci skip]')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
# strategy:
# matrix:
# browser: ['chrome']
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# BROWSER: ${{ matrix.browser }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9.1
- uses: actions/cache@v2
id: pcache
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Install Dependencies
run: |
python -m pip install --upgrade pip pipenv
pipenv install -d
# if: steps.pcache.outputs.cache-hit != 'true'
- name: Cache Static Files - name: Cache Static Files
id: static-cache id: static-cache
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: 'pipeline/built_assets' path: 'static/'
key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }} key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
- uses: bahmutov/npm-install@v1 - uses: bahmutov/npm-install@v1
if: steps.static-cache.outputs.cache-hit != 'true' if: steps.static-cache.outputs.cache-hit != 'true'
- run: node node_modules/gulp/bin/gulp build - run: node node_modules/gulp/bin/gulp build
if: steps.static-cache.outputs.cache-hit != 'true' if: steps.static-cache.outputs.cache-hit != 'true'
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Cache python deps
uses: actions/cache@v2
with:
path: ${{ env.pythonLocation }}
key: ${{ env.pythonLocation }}-${{ hashFiles('Pipfile.lock') }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install pipenv
pipenv install -d
- name: Basic Checks - name: Basic Checks
run: | run: |
pipenv run pycodestyle . --exclude=migrations,node_modules pipenv run pycodestyle . --exclude=migrations,node_modules
@@ -47,7 +49,7 @@ jobs:
pipenv run python manage.py makemigrations --check --dry-run pipenv run python manage.py makemigrations --check --dry-run
pipenv run python manage.py collectstatic --noinput pipenv run python manage.py collectstatic --noinput
- name: Run Tests - name: Run Tests
run: pipenv run pytest -n auto -vv --cov run: pipenv run pytest --cov -n 8
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:

1
.gitignore vendored
View File

@@ -26,7 +26,6 @@ var/
.installed.cfg .installed.cfg
*.egg *.egg
node_modules/ node_modules/
data/
# Continer extras # Continer extras
.vagrant .vagrant

166
Pipfile
View File

@@ -4,101 +4,91 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
ansicolors = "~=1.1.8" ansicolors = "==1.1.8"
asgiref = "~=3.3.1" asgiref = "==3.3.1"
"backports.tempfile" = "~=1.0" "backports.tempfile" = "==1.0"
"backports.weakref" = "~=1.0.post1" "backports.weakref" = "==1.0.post1"
beautifulsoup4 = "~=4.9.3" beautifulsoup4 = "==4.9.3"
Brotli = "~=1.0.9" Brotli = "==1.0.9"
cachetools = "~=4.2.1" cachetools = "==4.2.1"
certifi = "~=2020.12.5" certifi = "==2020.12.5"
chardet = "~=4.0.0" chardet = "==4.0.0"
configparser = "~=5.0.1" configparser = "==5.0.1"
contextlib2 = "~=0.6.0.post1" contextlib2 = "==0.6.0.post1"
cssselect = "~=1.1.0" cssselect = "==1.1.0"
cssutils = "~=1.0.2" cssutils = "==1.0.2"
dj-database-url = "~=0.5.0" diff-match-patch = "==20200713"
dj-static = "~=0.0.6" dj-database-url = "==0.5.0"
Django = "~=3.2" dj-static = "==0.0.6"
django-debug-toolbar = "~=3.2" Django = "==3.1.5"
django-filter = "~=2.4.0" django-debug-toolbar = "==3.2"
django-ical = "~=1.7.1" django-filter = "==2.4.0"
django-recurrence = "~=1.10.3" django-gulp = "==4.1.0"
django-registration-redux = "~=2.9" django-ical = "==1.7.1"
django-reversion = "~=3.0.9" django-recaptcha = "==2.0.6"
django-toolbelt = "~=0.0.1" django-recurrence = "==1.10.3"
django-widget-tweaks = "~=1.4.8" django-registration-redux = "==2.9"
django-htmlmin = "~=0.11.0" django-reversion = "==3.0.9"
envparse = "~=0.2.0" django-toolbelt = "==0.0.1"
gunicorn = "~=20.0.4" django-widget-tweaks = "==1.4.8"
icalendar = "~=4.0.7" django-htmlmin = "==0.11.0"
idna = "~=2.10" envparse = "==0.2.0"
lxml = "~=4.7.1" gunicorn = "==20.0.4"
Markdown = "~=3.3.3" icalendar = "==4.0.7"
msgpack = "~=1.0.2" idna = "==2.10"
pep517 = "~=0.9.1" importlib-metadata = "==3.4.0"
Pillow = "~=9.0.0" lxml = "==4.6.2"
premailer = "~=3.7.0" Markdown = "==3.3.3"
progress = "~=1.5" msgpack = "==1.0.2"
psutil = "~=5.8.0" pep517 = "==0.9.1"
psycopg2 = "~=2.8.6" Pillow = "==8.1.0"
Pygments = "~=2.7.4" pluggy = "==0.13.1"
pyparsing = "~=2.4.7" premailer = "==3.7.0"
PyPDF2 = "~=1.26.0" progress = "==1.5"
PyPOM = "~=2.2.0" psutil = "==5.8.0"
python-dateutil = "~=2.8.1" psycopg2 = "==2.8.6"
pytoml = "~=0.1.21" Pygments = "==2.7.4"
pytz = "~=2020.5" pyparsing = "==2.4.7"
reportlab = "~=3.5.59" PyPDF2 = "==1.26.0"
requests = "~=2.25.1" PyPOM = "==2.2.0"
retrying = "~=1.3.3" python-dateutil = "==2.8.1"
simplejson = "~=3.17.2" pytoml = "==0.1.21"
six = "~=1.15.0" pytz = "==2020.5"
soupsieve = "~=2.1" pytest-django = "==4.1.0"
sqlparse = "~=0.4.2" pytest-xdist = "==2.2.0"
static3 = "~=0.7.0" pytest-cov = "==2.11.1"
svg2rlg = "~=0.3" reportlab = "==3.5.59"
tini = "~=3.0.1" requests = "==2.25.1"
tornado = "~=6.1" retrying = "==1.3.3"
urllib3 = "~=1.26.5" selenium = "==3.141.0"
whitenoise = "~=5.2.0" simplejson = "==3.17.2"
yolk = "~=0.4.3" six = "==1.15.0"
"z3c.rml" = "~=4.1.2" soupsieve = "==2.1"
zipp = "~=3.4.0" sqlparse = "==0.4.1"
"zope.component" = "~=4.6.2" static3 = "==0.7.0"
"zope.deferredimport" = "~=4.3.1" svg2rlg = "==0.3"
"zope.deprecation" = "~=4.4.0" tini = "==3.0.1"
"zope.event" = "~=4.5.0" tornado = "==6.1"
"zope.hookable" = "~=5.0.1" urllib3 = "==1.26.2"
"zope.interface" = "~=5.2.0" whitenoise = "==5.2.0"
"zope.proxy" = "~=4.3.5" yolk = "==0.4.3"
"zope.schema" = "~=6.0.1" "z3c.rml" = "==4.1.2"
zipp = "==3.4.0"
"zope.component" = "==4.6.2"
"zope.deferredimport" = "==4.3.1"
"zope.deprecation" = "==4.4.0"
"zope.event" = "==4.5.0"
"zope.hookable" = "==5.0.1"
"zope.interface" = "==5.2.0"
"zope.proxy" = "==4.3.5"
"zope.schema" = "==6.0.1"
sentry-sdk = "*" sentry-sdk = "*"
diff-match-patch = "*"
python-barcode = "*"
django-hCaptcha = "*"
importlib-metadata = "*"
django-hcaptcha = "*"
[dev-packages] [dev-packages]
selenium = "~=3.141.0"
pycodestyle = "*" pycodestyle = "*"
coveralls = "*" coveralls = "*"
django-coverage-plugin = "*" django-coverage-plugin = "*"
pytest-cov = "*" pytest-cov = "*"
pytest-django = "*"
pluggy = "*"
pytest-splinter = "*"
pytest = "*"
pytest-reverse = "*"
[requires] [requires]
python_version = "3.9" python_version = "3.9"
[dev-packages.pytest-xdist]
extras = [ "psutil",]
version = "*"
[dev-packages.PyPOM]
extras = [ "splinter",]
version = "*"

1283
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,18 +9,23 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
""" """
import datetime import datetime
from pathlib import Path # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import secrets import secrets
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.django import DjangoIntegration
from envparse import env from envparse import env
# Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = os.path.dirname(os.path.dirname(__file__))
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY', default='gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e') SECRET_KEY = env('SECRET_KEY', default='gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG', cast=bool, default=True) DEBUG = env('DEBUG', cast=bool, default=True)
STAGING = env('STAGING', cast=bool, default=False) STAGING = env('STAGING', cast=bool, default=False)
@@ -61,13 +66,12 @@ INSTALLED_APPS = (
'users', 'users',
'RIGS', 'RIGS',
'assets', 'assets',
'training',
'debug_toolbar', 'debug_toolbar',
'registration', 'registration',
'reversion', 'reversion',
'captcha',
'widget_tweaks', 'widget_tweaks',
'hcaptcha',
) )
MIDDLEWARE = ( MIDDLEWARE = (
@@ -93,7 +97,7 @@ WSGI_APPLICATION = 'PyRIGS.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': str(BASE_DIR / 'db.sqlite3'), 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
} }
} }
@@ -187,9 +191,12 @@ LOGOUT_URL = '/user/logout/'
ACCOUNT_ACTIVATION_DAYS = 7 ACCOUNT_ACTIVATION_DAYS = 7
# CAPTCHA settings # reCAPTCHA settings
HCAPTCHA_SITEKEY = env('HCAPTCHA_SITEKEY', '10000000-ffff-ffff-ffff-000000000001') RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY', default="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
HCAPTCHA_SECRET = env('HCAPTCHA_SECRET', '0x0000000000000000000000000000000000000000') RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PUBLIC_KEY', default="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
NOCAPTCHA = True
SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']
# Email # Email
EMAILER_TEST = False EMAILER_TEST = False
@@ -228,16 +235,19 @@ DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = str(BASE_DIR / 'static/') STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
STATIC_DIRS = [
os.path.join(BASE_DIR, 'static/'),
]
STATICFILES_DIRS = [ STATICFILES_DIRS = [
str(BASE_DIR / 'pipeline/built_assets'), os.path.join(BASE_DIR, 'pipeline/built_assets'),
] ]
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [ 'DIRS': [
BASE_DIR / 'templates' os.path.join(BASE_DIR, 'templates')
], ],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
@@ -260,5 +270,3 @@ USE_GRAVATAR = True
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf" TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk' AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

View File

@@ -11,6 +11,7 @@ from selenium.webdriver.support.wait import WebDriverWait
from RIGS import models as rigsmodels from RIGS import models as rigsmodels
from . import pages from . import pages
from envparse import env
from pytest_django.asserts import assertContains from pytest_django.asserts import assertContains

View File

@@ -71,7 +71,6 @@ class BootstrapSelectElement(Region):
self.find_element(*self._deselect_all_locator).click() self.find_element(*self._deselect_all_locator).click()
def search(self, query): def search(self, query):
# self.wait.until(expected_conditions.visibility_of_element_located(self._status_locator))
search_box = self.find_element(*self._search_locator) search_box = self.find_element(*self._search_locator)
self.open() self.open()
search_box.clear() search_box.clear()
@@ -84,7 +83,7 @@ class BootstrapSelectElement(Region):
return [self.BootstrapSelectOption(self, i) for i in options] return [self.BootstrapSelectOption(self, i) for i in options]
def set_option(self, name, selected): def set_option(self, name, selected):
options = [x for x in self.options if x.name == name] options = list((x for x in self.options if x.name == name))
assert len(options) == 1 assert len(options) == 1
options[0].set_selected(selected) options[0].set_selected(selected)
@@ -117,15 +116,6 @@ class TextBox(Region):
self.root.send_keys(value) self.root.send_keys(value)
class SimpleMDETextArea(Region):
@property
def value(self):
return self.driver.execute_script("return document.querySelector('#' + arguments[0]).nextSibling.nextSibling.CodeMirror.getDoc().getValue();", self.root.get_attribute("id"))
def set_value(self, value):
self.driver.execute_script("document.querySelector('#' + arguments[0]).nextSibling.nextSibling.CodeMirror.getDoc().setValue(arguments[1]);", self.root.get_attribute("id"), value)
class CheckBox(Region): class CheckBox(Region):
def toggle(self): def toggle(self):
self.root.click() self.root.click()

View File

@@ -8,13 +8,18 @@ from pytest_django.asserts import assertRedirects, assertContains, assertNotCont
from pytest_django.asserts import assertTemplateUsed, assertInHTML from pytest_django.asserts import assertTemplateUsed, assertInHTML
from PyRIGS import urls from PyRIGS import urls
from RIGS.models import Event, Profile from RIGS.models import Event
from assets.models import Asset from assets.models import Asset
from django.db import connection from django.db import connection
import pytest
from django.core.management import call_command
from django.template.defaultfilters import striptags from django.template.defaultfilters import striptags
from django.urls.exceptions import NoReverseMatch from django.urls.exceptions import NoReverseMatch
from django.test import TestCase, TransactionTestCase from RIGS.models import Event
from assets.models import Asset
from django.db import connection
from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
@@ -44,7 +49,7 @@ def get_request_url(url):
@pytest.mark.parametrize("command", ['generateSampleAssetsData', 'generateSampleRIGSData', 'generateSampleUserData', @pytest.mark.parametrize("command", ['generateSampleAssetsData', 'generateSampleRIGSData', 'generateSampleUserData',
'deleteSampleData', 'generateSampleTrainingData', 'generate_sample_training_users']) 'deleteSampleData'])
def test_production_exception(command): def test_production_exception(command):
from django.core.management.base import CommandError from django.core.management.base import CommandError
with pytest.raises(CommandError, match=".*production"): with pytest.raises(CommandError, match=".*production"):
@@ -62,76 +67,79 @@ class TestSampleDataGenerator(TestCase):
assert Event.objects.all().count() == 0 assert Event.objects.all().count() == 0
@override_settings(DEBUG=True) class TestSampleDataGenerator(TestCase):
@pytest.mark.skip(reason="broken") @override_settings(DEBUG=True)
def test_unauthenticated(client): # Nothing should be available to the unauthenticated def setUp(self):
call_command('generateSampleData') call_command('generateSampleData')
for url in find_urls_recursive(urls.urlpatterns):
request_url = get_request_url(url) def test_unauthenticated(self): # Nothing should be available to the unauthenticated
if request_url and 'user' not in request_url: # User module is full of edge cases for url in find_urls_recursive(urls.urlpatterns):
response = client.get(request_url, follow=True, HTTP_HOST='example.com') request_url = get_request_url(url)
assertContains(response, 'Login') if request_url and 'user' not in request_url: # User module is full of edge cases
if 'application/json+oembed' in response.content.decode(): response = self.client.get(request_url, follow=True, HTTP_HOST='example.com')
assertTemplateUsed(response, 'login_redirect.html') assertContains(response, 'Login')
else: if 'application/json+oembed' in response.content.decode():
if "embed" in str(url): assertTemplateUsed(response, 'login_redirect.html')
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
else: else:
expected_url = "{0}?next={1}".format(reverse('login'), request_url) if "embed" in str(url):
assertRedirects(response, expected_url) expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
call_command('deleteSampleData') else:
expected_url = "{0}?next={1}".format(reverse('login'), request_url)
assertRedirects(response, expected_url)
def test_page_titles(self):
assert self.client.login(username='superuser', password='superuser')
for url in filter((lambda u: "embed" not in u.name), find_urls_recursive(urls.urlpatterns)):
request_url = get_request_url(url)
response = self.client.get(request_url)
if hasattr(response, "context_data") and "page_title" in response.context_data:
expected_title = striptags(response.context_data["page_title"])
assertInHTML('<title>{} | Rig Information Gathering System'.format(expected_title),
response.content.decode())
print("{} | {}".format(request_url, expected_title)) # If test fails, tell me where!
self.client.logout()
@override_settings(DEBUG=True) def test_basic_access(self):
@pytest.mark.skip(reason="broken") assert self.client.login(username="basic", password="basic")
def test_basic_access(client):
call_command('generateSampleData')
assert client.login(username="basic", password="basic")
url = reverse('asset_list') url = reverse('asset_list')
response = client.get(url) response = self.client.get(url)
# Check edit and duplicate buttons NOT shown in list # Check edit and duplicate buttons NOT shown in list
assertNotContains(response, 'Edit') assertNotContains(response, 'Edit')
assertNotContains(response, assertNotContains(response,
'Duplicate') # If this line is randomly failing, check the debug toolbar HTML hasn't crept in 'Duplicate') # If this line is randomly failing, check the debug toolbar HTML hasn't crept in
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id}) url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
response = client.get(url) response = self.client.get(url)
assertNotContains(response, 'Purchase Details') assertNotContains(response, 'Purchase Details')
assertNotContains(response, 'View Revision History') assertNotContains(response, 'View Revision History')
urlz = {'asset_history', 'asset_update', 'asset_duplicate'} urlz = {'asset_history', 'asset_update', 'asset_duplicate'}
for url_name in urlz: for url_name in urlz:
request_url = reverse(url_name, kwargs={'pk': Asset.objects.first().asset_id}) request_url = reverse(url_name, kwargs={'pk': Asset.objects.first().asset_id})
response = client.get(request_url, follow=True) response = self.client.get(request_url, follow=True)
assert response.status_code == 403
request_url = reverse('supplier_create')
response = self.client.get(request_url, follow=True)
assert response.status_code == 403 assert response.status_code == 403
request_url = reverse('supplier_create') request_url = reverse('supplier_update', kwargs={'pk': 1})
response = client.get(request_url, follow=True) response = self.client.get(request_url, follow=True)
assert response.status_code == 403 assert response.status_code == 403
self.client.logout()
request_url = reverse('supplier_update', kwargs={'pk': 1}) def test_keyholder_access(self):
response = client.get(request_url, follow=True) assert self.client.login(username="keyholder", password="keyholder")
assert response.status_code == 403
client.logout()
call_command('deleteSampleData')
url = reverse('asset_list')
response = self.client.get(url)
# Check edit and duplicate buttons shown in list
assertContains(response, 'Edit')
assertContains(response, 'Duplicate')
@override_settings(DEBUG=True) url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
@pytest.mark.skip(reason="broken") response = self.client.get(url)
def test_keyholder_access(client): assertContains(response, 'Purchase Details')
call_command('generateSampleData') assertContains(response, 'View Revision History')
assert client.login(username="keyholder", password="keyholder") self.client.logout()
url = reverse('asset_list')
response = client.get(url)
# Check edit and duplicate buttons shown in list
assertContains(response, 'Edit')
assertContains(response, 'Duplicate')
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
response = client.get(url)
assertContains(response, 'Purchase Details')
assertContains(response, 'View Revision History')
client.logout()
call_command('deleteSampleData')

View File

@@ -12,7 +12,6 @@ urlpatterns = [
path('', include('versioning.urls')), path('', include('versioning.urls')),
path('', include('RIGS.urls')), path('', include('RIGS.urls')),
path('assets/', include('assets.urls')), path('assets/', include('assets.urls')),
path('training/', include('training.urls')),
path('', login_required(views.Index.as_view()), name='index'), path('', login_required(views.Index.as_view()), name='index'),
@@ -28,14 +27,13 @@ urlpatterns = [
path('', include('users.urls')), path('', include('users.urls')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path("robots.txt", TemplateView.as_view(template_name="robots.txt", content_type="text/plain")),
] ]
if settings.DEBUG: if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns() urlpatterns += staticfiles_urlpatterns()
import debug_toolbar import debug_toolbar
urlpatterns += [ urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)), path('__debug__/', include(debug_toolbar.urls)),
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")), path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
] ] + urlpatterns

View File

@@ -16,7 +16,6 @@ from django.views.decorators.clickjacking import xframe_options_exempt
from RIGS import models from RIGS import models
from assets import models as asset_models from assets import models as asset_models
from training import models as training_models
def is_ajax(request): def is_ajax(request):
@@ -39,8 +38,7 @@ class SecureAPIRequest(generic.View):
'organisation': models.Organisation, 'organisation': models.Organisation,
'profile': models.Profile, 'profile': models.Profile,
'event': models.Event, 'event': models.Event,
'supplier': asset_models.Supplier, 'supplier': asset_models.Supplier
'training_item': training_models.TrainingItem,
} }
perms = { perms = {
@@ -49,8 +47,7 @@ class SecureAPIRequest(generic.View):
'organisation': 'RIGS.view_organisation', 'organisation': 'RIGS.view_organisation',
'profile': 'RIGS.view_profile', 'profile': 'RIGS.view_profile',
'event': None, 'event': None,
'supplier': None, 'supplier': None
'training_item': None, # TODO
} }
''' '''
@@ -78,9 +75,6 @@ class SecureAPIRequest(generic.View):
fields = request.GET.get('fields', None) fields = request.GET.get('fields', None)
if fields: if fields:
fields = fields.split(",") fields = fields.split(",")
filters = request.GET.get('filters', [])
if filters:
filters = filters.split(",")
# Supply data for one record # Supply data for one record
if pk: if pk:
@@ -101,13 +95,8 @@ class SecureAPIRequest(generic.View):
for field in fields: for field in fields:
q = Q(**{field + "__icontains": part}) q = Q(**{field + "__icontains": part})
qs.append(q) qs.append(q)
queries.append(reduce(operator.or_, qs)) queries.append(reduce(operator.or_, qs))
for f in filters:
q = Q(**{f: True})
queries.append(q)
# Build the data response list # Build the data response list
results = [] results = []
query = reduce(operator.and_, queries) query = reduce(operator.and_, queries)

View File

@@ -0,0 +1 @@
default_app_config = 'RIGS.apps.RIGSAppConfig'

View File

@@ -14,7 +14,7 @@ from reversion.admin import VersionAdmin
from RIGS import models from RIGS import models
from users import forms as user_forms from users import forms as user_forms
# Register your models here.
admin.site.register(models.VatRate, VersionAdmin) admin.site.register(models.VatRate, VersionAdmin)
admin.site.register(models.Event, VersionAdmin) admin.site.register(models.Event, VersionAdmin)
admin.site.register(models.EventItem, VersionAdmin) admin.site.register(models.EventItem, VersionAdmin)

View File

@@ -24,7 +24,7 @@ class InvoiceIndex(generic.ListView):
template_name = 'invoice_list.html' template_name = 'invoice_list.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(InvoiceIndex, self).get_context_data(**kwargs)
total = 0 total = 0
for i in context['object_list']: for i in context['object_list']:
total += i.balance total += i.balance
@@ -33,7 +33,20 @@ class InvoiceIndex(generic.ListView):
return context return context
def get_queryset(self): def get_queryset(self):
return self.model.objects.outstanding_invoices() # Manual query is the only way I have found to do this efficiently. Not ideal but needs must
sql = "SELECT * FROM " \
"(SELECT " \
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
"AS sub " \
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
"ORDER BY invoice_date"
query = self.model.objects.raw(sql)
return query
class InvoiceDetail(generic.DetailView): class InvoiceDetail(generic.DetailView):
@@ -41,15 +54,8 @@ class InvoiceDetail(generic.DetailView):
template_name = 'invoice_detail.html' template_name = 'invoice_detail.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(InvoiceDetail, self).get_context_data(**kwargs)
invoice_date = self.object.invoice_date.strftime("%d/%m/%Y") context['page_title'] = "Invoice {} ({})".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
context['page_title'] = f"Invoice {self.object.display_id} ({invoice_date}) "
if self.object.void:
context['page_title'] += "<span class='badge badge-warning float-right'>VOID</span>"
elif self.object.is_closed:
context['page_title'] += "<span class='badge badge-success float-right'>PAID</span>"
else:
context['page_title'] += "<span class='badge badge-info float-right'>OUTSTANDING</span>"
return context return context
@@ -118,7 +124,7 @@ class InvoiceArchive(generic.ListView):
paginate_by = 25 paginate_by = 25
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(InvoiceArchive, self).get_context_data(**kwargs)
context['page_title'] = "Invoice Archive" context['page_title'] = "Invoice Archive"
context['description'] = "This page displays all invoices: outstanding, paid, and void" context['description'] = "This page displays all invoices: outstanding, paid, and void"
return context return context
@@ -167,7 +173,24 @@ class InvoiceWaiting(generic.ListView):
return context return context
def get_queryset(self): def get_queryset(self):
return self.model.objects.waiting_invoices() return self.get_objects()
def get_objects(self):
# TODO find a way to select items
events = self.model.objects.filter(
(
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
Q(end_date__lte=datetime.date.today()) # Has end date, finishes before
) & Q(invoice__isnull=True) & # Has not already been invoiced
Q(is_rig=True) # Is a rig (not non-rig)
).order_by('start_date') \
.select_related('person',
'organisation',
'venue', 'mic') \
.prefetch_related('items')
return events
class InvoiceEvent(generic.View): class InvoiceEvent(generic.View):
@@ -197,7 +220,7 @@ class PaymentCreate(generic.CreateView):
template_name = 'payment_form.html' template_name = 'payment_form.html'
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super(generic.CreateView, self).get_initial()
invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None)) invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None))
if invoicepk is None: if invoicepk is None:
raise Http404() raise Http404()

View File

@@ -8,7 +8,6 @@ from django.utils import timezone
from reversion import revisions as reversion from reversion import revisions as reversion
from RIGS import models from RIGS import models
from training.models import TrainingLevel
# Override the django form defaults to use the HTML date/time/datetime UI elements # Override the django form defaults to use the HTML date/time/datetime UI elements
forms.DateField.widget = forms.DateInput(attrs={'type': 'date'}) forms.DateField.widget = forms.DateInput(attrs={'type': 'date'})
@@ -97,10 +96,10 @@ class EventForm(forms.ModelForm):
raise forms.ValidationError( raise forms.ValidationError(
'You haven\'t provided any client contact details. Please add a person or organisation.', 'You haven\'t provided any client contact details. Please add a person or organisation.',
code='contact') code='contact')
return super().clean() return super(EventForm, self).clean()
def save(self, commit=True): def save(self, commit=True):
m = super().save(commit=False) m = super(EventForm, self).save(commit=False)
if (commit): if (commit):
m.save() m.save()
@@ -139,7 +138,7 @@ class BaseClientEventAuthorisationForm(forms.ModelForm):
class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm): class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super(InternalClientEventAuthorisationForm, self).__init__(**kwargs)
self.fields['uni_id'].required = True self.fields['uni_id'].required = True
self.fields['account_code'].required = True self.fields['account_code'].required = True
@@ -154,7 +153,7 @@ class EventAuthorisationRequestForm(forms.Form):
class EventRiskAssessmentForm(forms.ModelForm): class EventRiskAssessmentForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super(EventRiskAssessmentForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items(): for name, field in self.fields.items():
if str(name) == 'supervisor_consulted': if str(name) == 'supervisor_consulted':
field.widget = forms.CheckboxInput() field.widget = forms.CheckboxInput()
@@ -165,9 +164,6 @@ class EventRiskAssessmentForm(forms.ModelForm):
], attrs={'class': 'custom-control-input', 'required': 'true'}) ], attrs={'class': 'custom-control-input', 'required': 'true'})
def clean(self): def clean(self):
if self.cleaned_data.get('big_power'):
if not self.cleaned_data.get('power_mic').level_qualifications.filter(level__department=TrainingLevel.POWER).exists():
self.add_error('power_mic', forms.ValidationError("Your Power MIC must be a Power Technician.", code="power_tech_required"))
# Check expected values # Check expected values
unexpected_values = [] unexpected_values = []
for field, value in models.RiskAssessment.expected_values.items(): for field, value in models.RiskAssessment.expected_values.items():
@@ -185,7 +181,7 @@ class EventRiskAssessmentForm(forms.ModelForm):
class EventChecklistForm(forms.ModelForm): class EventChecklistForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super(EventChecklistForm, self).__init__(*args, **kwargs)
self.fields['date'].widget.format = '%Y-%m-%d' self.fields['date'].widget.format = '%Y-%m-%d'
for name, field in self.fields.items(): for name, field in self.fields.items():
if field.__class__ == forms.NullBooleanField: if field.__class__ == forms.NullBooleanField:

View File

@@ -70,11 +70,6 @@ class EventRiskAssessmentDetail(generic.DetailView):
model = models.RiskAssessment model = models.RiskAssessment
template_name = 'risk_assessment_detail.html' template_name = 'risk_assessment_detail.html'
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentDetail, self).get_context_data(**kwargs)
context['page_title'] = "Risk Assessment for Event <a href='{}'>{} {}</a>".format(self.object.event.get_absolute_url(), self.object.event.display_id, self.object.event.name)
return context
class EventRiskAssessmentList(generic.ListView): class EventRiskAssessmentList(generic.ListView):
paginate_by = 20 paginate_by = 20
@@ -82,7 +77,7 @@ class EventRiskAssessmentList(generic.ListView):
template_name = 'hs_object_list.html' template_name = 'hs_object_list.html'
def get_queryset(self): def get_queryset(self):
return self.model.objects.exclude(event__status=models.Event.CANCELLED).order_by('reviewed_at').select_related('event') return self.model.objects.order_by('reviewed_at').select_related('event')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs) context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
@@ -112,7 +107,7 @@ class EventChecklistDetail(generic.DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(EventChecklistDetail, self).get_context_data(**kwargs) context = super(EventChecklistDetail, self).get_context_data(**kwargs)
context['page_title'] = "Event Checklist for Event <a href='{}'>{} {}</a>".format(self.object.event.get_absolute_url(), self.object.event.display_id, self.object.event.name) context['page_title'] = "Event Checklist for Event {} {}".format(self.object.event.display_id, self.object.event.name)
return context return context
@@ -187,9 +182,6 @@ class EventChecklistList(generic.ListView):
model = models.EventChecklist model = models.EventChecklist
template_name = 'hs_object_list.html' template_name = 'hs_object_list.html'
def get_queryset(self):
return self.model.objects.exclude(event__status=models.Event.CANCELLED).order_by('reviewed_at').select_related('event')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(EventChecklistList, self).get_context_data(**kwargs) context = super(EventChecklistList, self).get_context_data(**kwargs)
context['title'] = 'Event Checklist' context['title'] = 'Event Checklist'
@@ -218,7 +210,7 @@ class HSList(generic.ListView):
template_name = 'hs_list.html' template_name = 'hs_list.html'
def get_queryset(self): def get_queryset(self):
return models.Event.objects.all().exclude(status=models.Event.CANCELLED).order_by('-start_date').select_related('riskassessment').prefetch_related('checklists') return models.Event.objects.all().order_by('-start_date').select_related('riskassessment').prefetch_related('checklists')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(HSList, self).get_context_data(**kwargs) context = super(HSList, self).get_context_data(**kwargs)

View File

@@ -3,7 +3,6 @@ from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from assets import models from assets import models
from RIGS import models as rigsmodels from RIGS import models as rigsmodels
from training import models as tmodels
class Command(BaseCommand): class Command(BaseCommand):
@@ -32,11 +31,6 @@ class Command(BaseCommand):
self.delete_objects(rigsmodels.Payment) self.delete_objects(rigsmodels.Payment)
self.delete_objects(rigsmodels.RiskAssessment) self.delete_objects(rigsmodels.RiskAssessment)
self.delete_objects(rigsmodels.EventChecklist) self.delete_objects(rigsmodels.EventChecklist)
self.delete_objects(tmodels.TrainingCategory)
self.delete_objects(tmodels.TrainingItem)
self.delete_objects(tmodels.TrainingLevel)
self.delete_objects(tmodels.TrainingItemQualification)
self.delete_objects(tmodels.TrainingLevelRequirement)
def delete_objects(self, model): def delete_objects(self, model):
for obj in model.objects.all(): for obj in model.objects.all():

View File

@@ -12,4 +12,3 @@ class Command(BaseCommand):
call_command('generateSampleUserData') call_command('generateSampleUserData')
call_command('generateSampleRIGSData') call_command('generateSampleRIGSData')
call_command('generateSampleAssetsData') call_command('generateSampleAssetsData')
call_command('generateSampleTrainingData')

View File

@@ -21,7 +21,6 @@ class Command(BaseCommand):
profiles = models.Profile.objects.all() profiles = models.Profile.objects.all()
def handle(self, *args, **options): def handle(self, *args, **options):
print("Generating rigboard data")
from django.conf import settings from django.conf import settings
if not (settings.DEBUG or settings.STAGING): if not (settings.DEBUG or settings.STAGING):
@@ -36,7 +35,6 @@ class Command(BaseCommand):
self.setup_organisations() self.setup_organisations()
self.setup_venues() self.setup_venues()
self.setup_events() self.setup_events()
print("Done generating rigboard data")
def setup_people(self): def setup_people(self):
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe", names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",

View File

@@ -3,7 +3,6 @@
from django.db import models, migrations from django.db import models, migrations
import RIGS.models import RIGS.models
import versioning
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -26,6 +25,6 @@ class Migration(migrations.Migration):
], ],
options={ options={
}, },
bases=(models.Model, versioning.versioning.RevisionMixin), bases=(models.Model, RIGS.models.RevisionMixin),
), ),
] ]

View File

@@ -3,7 +3,6 @@
from django.db import models, migrations from django.db import models, migrations
import RIGS.models import RIGS.models
import versioning
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -22,6 +21,6 @@ class Migration(migrations.Migration):
], ],
options={ options={
}, },
bases=(models.Model, versioning.versioning.RevisionMixin), bases=(models.Model, RIGS.models.RevisionMixin),
), ),
] ]

View File

@@ -4,7 +4,6 @@
from django.db import models, migrations from django.db import models, migrations
from django.conf import settings from django.conf import settings
import RIGS.models import RIGS.models
import versioning
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -42,7 +41,7 @@ class Migration(migrations.Migration):
], ],
options={ options={
}, },
bases=(models.Model, versioning.versioning.RevisionMixin), bases=(models.Model, RIGS.models.RevisionMixin),
), ),
migrations.CreateModel( migrations.CreateModel(
name='EventItem', name='EventItem',
@@ -71,7 +70,7 @@ class Migration(migrations.Migration):
], ],
options={ options={
}, },
bases=(models.Model, versioning.versioning.RevisionMixin), bases=(models.Model, RIGS.models.RevisionMixin),
), ),
migrations.AddField( migrations.AddField(
model_name='event', model_name='event',

View File

@@ -4,7 +4,6 @@ import RIGS.models
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import versioning
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -59,7 +58,7 @@ class Migration(migrations.Migration):
'ordering': ['event'], 'ordering': ['event'],
'permissions': [('review_eventchecklist', 'Can review Event Checklists')], 'permissions': [('review_eventchecklist', 'Can review Event Checklists')],
}, },
bases=(models.Model, versioning.versioning.RevisionMixin), bases=(models.Model, RIGS.models.RevisionMixin),
), ),
migrations.CreateModel( migrations.CreateModel(
name='EventChecklistCrew', name='EventChecklistCrew',
@@ -70,7 +69,7 @@ class Migration(migrations.Migration):
('end', models.DateTimeField()), ('end', models.DateTimeField()),
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')), ('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='crew', to='RIGS.eventchecklist')),
], ],
bases=(models.Model, versioning.versioning.RevisionMixin), bases=(models.Model, RIGS.models.RevisionMixin),
), ),
migrations.CreateModel( migrations.CreateModel(
name='EventChecklistVehicle', name='EventChecklistVehicle',
@@ -79,7 +78,7 @@ class Migration(migrations.Migration):
('vehicle', models.CharField(max_length=255)), ('vehicle', models.CharField(max_length=255)),
('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')), ('checklist', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicles', to='RIGS.eventchecklist')),
], ],
bases=(models.Model, versioning.versioning.RevisionMixin), bases=(models.Model, RIGS.models.RevisionMixin),
), ),
migrations.CreateModel( migrations.CreateModel(
name='RiskAssessment', name='RiskAssessment',
@@ -118,7 +117,7 @@ class Migration(migrations.Migration):
'ordering': ['event'], 'ordering': ['event'],
'permissions': [('review_riskassessment', 'Can review Risk Assessments')], 'permissions': [('review_riskassessment', 'Can review Risk Assessments')],
}, },
bases=(models.Model, versioning.versioning.RevisionMixin), bases=(models.Model, RIGS.models.RevisionMixin),
), ),
migrations.RemoveField( migrations.RemoveField(
model_name='eventcrew', model_name='eventcrew',

View File

@@ -1,67 +0,0 @@
# Generated by Django 3.1.7 on 2021-03-02 11:48
from django.db import migrations
def postgres_migration_prep(apps, schema_editor):
model = apps.get_model("RIGS", "Event")
for field in ["auth_request_to", "collector", "description", "notes", "purchase_order"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "EventAuthorisation")
for field in ["account_code", "uni_id"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "EventChecklist")
for field in ["extinguishers_location", "hs_location", "w1_description", "w2_description", "w3_description"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "EventItem")
for field in ["description"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "Organisation")
for field in ["address", "email", "notes", "phone"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "Payment")
for field in ["method"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "Person")
for field in ["address", "email", "notes", "phone"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "Profile")
for field in ["phone"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "RiskAssessment")
for field in ["general_notes", "persons_responsible_structures", "power_notes", "rigging_plan", "sound_notes"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
model = apps.get_model("RIGS", "Venue")
for field in ["address", "email", "notes", "phone"]:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0039_auto_20210123_1910'),
]
operations = [
migrations.RunPython(postgres_migration_prep, migrations.RunPython.noop)
]

View File

@@ -1,4 +1,4 @@
# Generated by Django 3.2.11 on 2022-01-09 14:56 # Generated by Django 3.1.5 on 2021-02-06 10:43
from django.db import migrations, models from django.db import migrations, models
@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('RIGS', '0043_auto_20211027_1519'), ('RIGS', '0039_auto_20210123_1910'),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='profile', model_name='profile',
name='is_supervisor', name='dark_theme',
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
] ]

View File

@@ -1,4 +1,4 @@
# Generated by Django 3.1.7 on 2021-03-02 12:04 # Generated by Django 3.1.5 on 2021-02-08 16:03
import RIGS.models import RIGS.models
from django.db import migrations, models from django.db import migrations, models
@@ -7,27 +7,10 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('RIGS', '0040_auto_20210302_1148'), ('RIGS', '0040_profile_dark_theme'),
] ]
operations = [ operations = [
migrations.RemoveField(
model_name='event',
name='meet_info',
),
migrations.RemoveField(
model_name='event',
name='payment_method',
),
migrations.RemoveField(
model_name='event',
name='payment_received',
),
migrations.AddField(
model_name='profile',
name='dark_theme',
field=models.BooleanField(default=False),
),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='auth_request_to', name='auth_request_to',
@@ -43,11 +26,26 @@ class Migration(migrations.Migration):
name='description', name='description',
field=models.TextField(blank=True, default=''), field=models.TextField(blank=True, default=''),
), ),
migrations.AlterField(
model_name='event',
name='meet_info',
field=models.CharField(blank=True, default='', max_length=255),
),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='notes', name='notes',
field=models.TextField(blank=True, default=''), field=models.TextField(blank=True, default=''),
), ),
migrations.AlterField(
model_name='event',
name='payment_method',
field=models.CharField(blank=True, default='', max_length=255),
),
migrations.AlterField(
model_name='event',
name='payment_received',
field=models.CharField(blank=True, default='', max_length=255),
),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='purchase_order', name='purchase_order',
@@ -146,7 +144,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='profile', model_name='profile',
name='phone', name='phone',
field=models.CharField(blank=True, default='', max_length=13), field=models.CharField(default='', max_length=13, null=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='riskassessment', model_name='riskassessment',

View File

@@ -1,34 +0,0 @@
# Generated by Django 3.1.13 on 2021-10-07 22:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0041_auto_20210302_1204'),
]
operations = [
migrations.AlterField(
model_name='eventchecklist',
name='fd_earth_fault',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', max_digits=5, null=True, verbose_name='Earth Fault Loop Impedance'),
),
migrations.AlterField(
model_name='eventchecklist',
name='w1_earth_fault',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', max_digits=5, null=True, verbose_name='Earth Fault Loop Impedance'),
),
migrations.AlterField(
model_name='eventchecklist',
name='w2_earth_fault',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', max_digits=5, null=True, verbose_name='Earth Fault Loop Impedance'),
),
migrations.AlterField(
model_name='eventchecklist',
name='w3_earth_fault',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (Z<small>S</small>)', max_digits=5, null=True, verbose_name='Earth Fault Loop Impedance'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 3.1.13 on 2021-10-27 14:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0042_auto_20211007_2338'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='initials',
field=models.CharField(max_length=5, null=True),
),
]

View File

@@ -20,16 +20,13 @@ from reversion.models import Version
class Profile(AbstractUser): class Profile(AbstractUser):
initials = models.CharField(max_length=5, null=True, blank=False) initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
phone = models.CharField(max_length=13, blank=True, default='') phone = models.CharField(max_length=13, null=True, default='')
api_key = models.CharField(max_length=40, blank=True, editable=False, default='') api_key = models.CharField(max_length=40, blank=True, editable=False, default='')
is_approved = models.BooleanField(default=False) is_approved = models.BooleanField(default=False)
# Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that... # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
last_emailed = models.DateTimeField(blank=True, null=True) last_emailed = models.DateTimeField(blank=True, null=True)
dark_theme = models.BooleanField(default=False) dark_theme = models.BooleanField(default=False)
is_supervisor = models.BooleanField(default=False)
reversion_hide = True
@classmethod @classmethod
def make_api_key(cls): def make_api_key(cls):
@@ -68,8 +65,10 @@ class Profile(AbstractUser):
def __str__(self): def __str__(self):
return self.name return self.name
# TODO move to versioning - currently get import errors with that
class RevisionMixin:
class RevisionMixin(object):
@property @property
def is_first_version(self): def is_first_version(self):
versions = Version.objects.get_for_object(self) versions = Version.objects.get_for_object(self)
@@ -99,7 +98,7 @@ class RevisionMixin:
version = self.current_version version = self.current_version
if version is None: if version is None:
return None return None
return f"V{version.pk} | R{version.revision.pk}" return "V{0} | R{1}".format(version.pk, version.revision.pk)
class Person(models.Model, RevisionMixin): class Person(models.Model, RevisionMixin):
@@ -207,7 +206,7 @@ class VatRate(models.Model, RevisionMixin):
get_latest_by = 'start_at' get_latest_by = 'start_at'
def __str__(self): def __str__(self):
return f"{self.comment} {self.start_at} @ {self.as_percent}%" return self.comment + " " + str(self.start_at) + " @ " + str(self.as_percent) + "%"
class Venue(models.Model, RevisionMixin): class Venue(models.Model, RevisionMixin):
@@ -279,19 +278,6 @@ class EventManager(models.Manager):
).count() ).count()
return event_count return event_count
def waiting_invoices(self):
events = self.filter(
(
models.Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
models.Q(end_date__lte=datetime.date.today()) # Or has end date, finishes before
) & models.Q(invoice__isnull=True) & # Has not already been invoiced
models.Q(is_rig=True) # Is a rig (not non-rig)
).order_by('start_date') \
.select_related('person', 'organisation', 'venue', 'mic') \
.prefetch_related('items')
return events
@reversion.register(follow=['items']) @reversion.register(follow=['items'])
class Event(models.Model, RevisionMixin): class Event(models.Model, RevisionMixin):
@@ -326,6 +312,7 @@ class Event(models.Model, RevisionMixin):
end_time = models.TimeField(blank=True, null=True) end_time = models.TimeField(blank=True, null=True)
access_at = models.DateTimeField(blank=True, null=True) access_at = models.DateTimeField(blank=True, null=True)
meet_at = models.DateTimeField(blank=True, null=True) meet_at = models.DateTimeField(blank=True, null=True)
meet_info = models.CharField(max_length=255, blank=True, default='')
# Crew management # Crew management
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True, checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True,
@@ -334,6 +321,8 @@ class Event(models.Model, RevisionMixin):
verbose_name="MIC", on_delete=models.CASCADE) verbose_name="MIC", on_delete=models.CASCADE)
# Monies # Monies
payment_method = models.CharField(max_length=255, blank=True, default='')
payment_received = models.CharField(max_length=255, blank=True, default='')
purchase_order = models.CharField(max_length=255, blank=True, default='', verbose_name='PO') purchase_order = models.CharField(max_length=255, blank=True, default='', verbose_name='PO')
collector = models.CharField(max_length=255, blank=True, default='', verbose_name='collected by') collector = models.CharField(max_length=255, blank=True, default='', verbose_name='collected by')
@@ -344,14 +333,11 @@ class Event(models.Model, RevisionMixin):
@property @property
def display_id(self): def display_id(self):
if self.pk: if self.is_rig:
if self.is_rig: return str("N%05d" % self.pk)
return str("N%05d" % self.pk) else:
return self.pk return self.pk
return "????"
# Calculated values # Calculated values
""" """
EX Vat EX Vat
@@ -373,9 +359,6 @@ class Event(models.Model, RevisionMixin):
@property @property
def vat(self): def vat(self):
# No VAT is owed on internal transfers
if self.internal:
return 0
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01')) return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01'))
""" """
@@ -475,7 +458,7 @@ class Event(models.Model, RevisionMixin):
return reverse('event_detail', kwargs={'pk': self.pk}) return reverse('event_detail', kwargs={'pk': self.pk})
def __str__(self): def __str__(self):
return f"{self.display_id}: {self.name}" return "{}: {}".format(self.display_id, self.name)
def clean(self): def clean(self):
errdict = {} errdict = {}
@@ -521,11 +504,11 @@ class EventItem(models.Model, RevisionMixin):
ordering = ['order'] ordering = ['order']
def __str__(self): def __str__(self):
return f"{self.event_id}.{self.order}: {self.event.name} | {self.name}" return "{}.{}: {} | {}".format(self.event_id, self.order, self.event.name, self.name)
@property @property
def activity_feed_string(self): def activity_feed_string(self):
return f"item {self.name}" return str("item {}".format(self.name))
@reversion.register @reversion.register
@@ -543,24 +526,7 @@ class EventAuthorisation(models.Model, RevisionMixin):
@property @property
def activity_feed_string(self): def activity_feed_string(self):
return f"{self.event.display_id} (requested by {self.sent_by.initials})" return "{} (requested by {})".format(self.event.display_id, self.sent_by.initials)
class InvoiceManager(models.Manager):
def outstanding_invoices(self):
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
sql = "SELECT * FROM " \
"(SELECT " \
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
"AS sub " \
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
"ORDER BY invoice_date"
query = self.raw(sql)
return query
@reversion.register(follow=['payment_set']) @reversion.register(follow=['payment_set'])
@@ -571,8 +537,6 @@ class Invoice(models.Model, RevisionMixin):
reversion_perm = 'RIGS.view_invoice' reversion_perm = 'RIGS.view_invoice'
objects = InvoiceManager()
@property @property
def sum_total(self): def sum_total(self):
return self.event.sum_total return self.event.sum_total
@@ -671,6 +635,7 @@ class RiskAssessment(models.Model, RevisionMixin):
# Power # Power
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?") big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
# If yes to the above two, you must answer...
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True, power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)") verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC? (if yes to the above question, this person <em>must</em> be a Power Technician or Power Supervisor)")
outside = models.BooleanField(help_text="Is the event outdoors?") outside = models.BooleanField(help_text="Is the event outdoors?")
@@ -742,7 +707,7 @@ class RiskAssessment(models.Model, RevisionMixin):
] ]
@cached_property @cached_property
def fieldz(self): def fields(self):
return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created] return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
@property @property
@@ -802,21 +767,21 @@ class EventChecklist(models.Model, RevisionMixin):
fd_voltage_l2 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L2-N", help_text="L2 - N") fd_voltage_l2 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L2-N", help_text="L2 - N")
fd_voltage_l3 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L3-N", help_text="L3 - N") fd_voltage_l3 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L3-N", help_text="L3 - N")
fd_phase_rotation = models.BooleanField(blank=True, null=True, verbose_name="Phase Rotation", help_text="Phase Rotation<br><small>(if required)</small>") fd_phase_rotation = models.BooleanField(blank=True, null=True, verbose_name="Phase Rotation", help_text="Phase Rotation<br><small>(if required)</small>")
fd_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)") fd_earth_fault = models.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current") fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
# Worst case points # Worst case points
w1_description = models.CharField(blank=True, default='', max_length=255, help_text="Description") w1_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
w1_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?") w1_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage") w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w1_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)") w1_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w2_description = models.CharField(blank=True, default='', max_length=255, help_text="Description") w2_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?") w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage") w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w2_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)") w2_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w3_description = models.CharField(blank=True, default='', max_length=255, help_text="Description") w3_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?") w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage") w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w3_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)") w3_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>") all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>")
public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>") public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>")
@@ -827,16 +792,16 @@ class EventChecklist(models.Model, RevisionMixin):
inverted_fields = [] inverted_fields = []
@cached_property
def fields(self):
return [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
class Meta: class Meta:
ordering = ['event'] ordering = ['event']
permissions = [ permissions = [
('review_eventchecklist', 'Can review Event Checklists') ('review_eventchecklist', 'Can review Event Checklists')
] ]
@cached_property
def fieldz(self):
return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
@property @property
def activity_feed_string(self): def activity_feed_string(self):
return str(self.event) return str(self.event)

View File

@@ -11,7 +11,7 @@ import simplejson
from PyPDF2 import PdfFileMerger, PdfFileReader from PyPDF2 import PdfFileMerger, PdfFileReader
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.staticfiles import finders from django.contrib.staticfiles.storage import staticfiles_storage
from django.core import signing from django.core import signing
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
@@ -38,7 +38,7 @@ class RigboardIndex(generic.TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
# get super context # get super context
context = super().get_context_data(**kwargs) context = super(RigboardIndex, self).get_context_data(**kwargs)
# call out method to get current events # call out method to get current events
context['events'] = models.Event.objects.current_events().select_related('riskassessment', 'invoice').prefetch_related('checklists') context['events'] = models.Event.objects.current_events().select_related('riskassessment', 'invoice').prefetch_related('checklists')
@@ -50,7 +50,7 @@ class WebCalendar(generic.TemplateView):
template_name = 'calendar.html' template_name = 'calendar.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(WebCalendar, self).get_context_data(**kwargs)
context['view'] = kwargs.get('view', '') context['view'] = kwargs.get('view', '')
context['date'] = kwargs.get('date', '') context['date'] = kwargs.get('date', '')
return context return context
@@ -61,8 +61,8 @@ class EventDetail(generic.DetailView):
model = models.Event model = models.Event
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(EventDetail, self).get_context_data(**kwargs)
title = f"{self.object.display_id} | {self.object.name}" title = "{} | {}".format(self.object.display_id, self.object.name)
if self.object.dry_hire: if self.object.dry_hire:
title += " <span class='badge badge-secondary'>Dry Hire</span>" title += " <span class='badge badge-secondary'>Dry Hire</span>"
context['page_title'] = title context['page_title'] = title
@@ -84,7 +84,7 @@ class EventCreate(generic.CreateView):
template_name = 'event_form.html' template_name = 'event_form.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(EventCreate, self).get_context_data(**kwargs)
context['page_title'] = "New Event" context['page_title'] = "New Event"
context['edit'] = True context['edit'] = True
context['currentVAT'] = models.VatRate.objects.current_rate() context['currentVAT'] = models.VatRate.objects.current_rate()
@@ -110,8 +110,8 @@ class EventUpdate(generic.UpdateView):
template_name = 'event_form.html' template_name = 'event_form.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(EventUpdate, self).get_context_data(**kwargs)
context['page_title'] = f"Event {self.object.display_id}" context['page_title'] = "Event {}".format(self.object.display_id)
context['edit'] = True context['edit'] = True
form = context['form'] form = context['form']
@@ -134,7 +134,7 @@ class EventUpdate(generic.UpdateView):
if hasattr(self.object, 'authorised'): if hasattr(self.object, 'authorised'):
messages.warning(self.request, messages.warning(self.request,
'This event has already been authorised by the client, any changes to the price will require reauthorisation.') 'This event has already been authorised by the client, any changes to the price will require reauthorisation.')
return super().render_to_response(context, **response_kwargs) return super(EventUpdate, self).render_to_response(context, **response_kwargs)
def get_success_url(self): def get_success_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.object.pk}) return reverse_lazy('event_detail', kwargs={'pk': self.object.pk})
@@ -142,7 +142,7 @@ class EventUpdate(generic.UpdateView):
class EventDuplicate(EventUpdate): class EventDuplicate(EventUpdate):
def get_object(self, queryset=None): def get_object(self, queryset=None):
old = super().get_object(queryset) # Get the object (the event you're duplicating) old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating)
new = copy.copy(old) # Make a copy of the object in memory new = copy.copy(old) # Make a copy of the object in memory
new.based_on = old # Make the new event based on the old event new.based_on = old # Make the new event based on the old event
new.purchase_order = None # Remove old PO new.purchase_order = None # Remove old PO
@@ -151,7 +151,6 @@ class EventDuplicate(EventUpdate):
# Clear checked in by if it's a dry hire # Clear checked in by if it's a dry hire
if new.dry_hire is True: if new.dry_hire is True:
new.checked_in_by = None new.checked_in_by = None
new.collector = None
# Remove all the authorisation information from the new event # Remove all the authorisation information from the new event
new.auth_request_to = '' new.auth_request_to = ''
@@ -167,8 +166,8 @@ class EventDuplicate(EventUpdate):
return new return new
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(EventDuplicate, self).get_context_data(**kwargs)
context['page_title'] = f"Duplicate of Event {self.object.display_id}" context['page_title'] = "Duplicate of Event {}".format(self.object.display_id)
context["duplicate"] = True context["duplicate"] = True
return context return context
@@ -210,7 +209,8 @@ class EventArchive(generic.ListView):
paginate_by = 25 paginate_by = 25
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) # get super context
context = super(EventArchive, self).get_context_data(**kwargs)
context['start'] = self.request.GET.get('start', None) context['start'] = self.request.GET.get('start', None)
context['end'] = self.request.GET.get('end', datetime.date.today().strftime('%Y-%m-%d')) context['end'] = self.request.GET.get('end', datetime.date.today().strftime('%Y-%m-%d'))
@@ -265,7 +265,7 @@ class EventArchive(generic.ListView):
# Preselect related for efficiency # Preselect related for efficiency
qs.select_related('person', 'organisation', 'venue', 'mic') qs.select_related('person', 'organisation', 'venue', 'mic')
if not qs.exists(): if len(qs) == 0:
messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.") messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.")
return qs return qs
@@ -274,7 +274,6 @@ class EventArchive(generic.ListView):
class EventAuthorise(generic.UpdateView): class EventAuthorise(generic.UpdateView):
template_name = 'eventauthorisation_form.html' template_name = 'eventauthorisation_form.html'
success_template = 'eventauthorisation_success.html' success_template = 'eventauthorisation_success.html'
preview = False
def form_valid(self, form): def form_valid(self, form):
self.object = form.save() self.object = form.save()
@@ -282,7 +281,7 @@ class EventAuthorise(generic.UpdateView):
self.template_name = self.success_template self.template_name = self.success_template
messages.add_message(self.request, messages.SUCCESS, messages.add_message(self.request, messages.SUCCESS,
'Success! Your event has been authorised. ' + 'Success! Your event has been authorised. ' +
f'You will also receive email confirmation to {self.object.email}.') 'You will also receive email confirmation to %s.' % self.object.email)
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
@property @property
@@ -296,13 +295,12 @@ class EventAuthorise(generic.UpdateView):
return forms.InternalClientEventAuthorisationForm return forms.InternalClientEventAuthorisationForm
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(EventAuthorise, self).get_context_data(**kwargs)
context['event'] = self.event context['event'] = self.event
context['tos_url'] = settings.TERMS_OF_HIRE_URL context['tos_url'] = settings.TERMS_OF_HIRE_URL
context['page_title'] = f"{self.event.display_id}: {self.event.name}" context['page_title'] = "{}: {}".format(self.event.display_id, self.event.name)
if self.event.dry_hire: if self.event.dry_hire:
context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>' context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>'
context['preview'] = self.preview
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@@ -318,7 +316,7 @@ class EventAuthorise(generic.UpdateView):
return super(EventAuthorise, self).get(request, *args, **kwargs) return super(EventAuthorise, self).get(request, *args, **kwargs)
def get_form(self, **kwargs): def get_form(self, **kwargs):
form = super().get_form(**kwargs) form = super(EventAuthorise, self).get_form(**kwargs)
form.instance.event = self.event form.instance.event = self.event
form.instance.email = self.request.email form.instance.email = self.request.email
form.instance.sent_by = self.request.sent_by form.instance.sent_by = self.request.sent_by
@@ -334,7 +332,7 @@ class EventAuthorise(generic.UpdateView):
except (signing.BadSignature, AssertionError, KeyError, models.Profile.DoesNotExist): except (signing.BadSignature, AssertionError, KeyError, models.Profile.DoesNotExist):
raise SuspiciousOperation( raise SuspiciousOperation(
"This URL is invalid. Please ask your TEC contact for a new URL") "This URL is invalid. Please ask your TEC contact for a new URL")
return super().dispatch(request, *args, **kwargs) return super(EventAuthorise, self).dispatch(request, *args, **kwargs)
class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin): class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin):
@@ -344,7 +342,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
@method_decorator(decorators.nottinghamtec_address_required) @method_decorator(decorators.nottinghamtec_address_required)
def dispatch(self, *args, **kwargs): def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs) return super(EventAuthorisationRequest, self).dispatch(*args, **kwargs)
@property @property
def object(self): def object(self):
@@ -389,7 +387,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
to=[email], to=[email],
reply_to=[self.request.user.email], reply_to=[self.request.user.email],
) )
css = finders.find('css/email.css') css = staticfiles_storage.path('css/email.css')
html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context), html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context),
external_styles=css).transform() external_styles=css).transform()
msg.attach_alternative(html, 'text/html') msg.attach_alternative(html, 'text/html')
@@ -404,19 +402,19 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
model = models.Event model = models.Event
def render_to_response(self, context, **response_kwargs): def render_to_response(self, context, **response_kwargs):
css = finders.find('css/email.css') from django.contrib.staticfiles.storage import staticfiles_storage
response = super().render_to_response(context, **response_kwargs) css = staticfiles_storage.path('css/email.css')
response = super(EventAuthoriseRequestEmailPreview, self).render_to_response(context, **response_kwargs)
assert isinstance(response, HttpResponse) assert isinstance(response, HttpResponse)
response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform() response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
return response return response
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(EventAuthoriseRequestEmailPreview, self).get_context_data(**kwargs)
context['hmac'] = signing.dumps({ context['hmac'] = signing.dumps({
'pk': self.object.pk, 'pk': self.object.pk,
'email': self.request.GET.get('email', 'hello@world.test'), 'email': self.request.GET.get('email', 'hello@world.test'),
'sent_by': self.request.user.pk, 'sent_by': self.request.user.pk,
}) })
context['to_name'] = self.request.GET.get('to_name', None) context['to_name'] = self.request.GET.get('to_name', None)
context['target'] = 'event_authorise_form_preview'
return context return context

View File

@@ -6,7 +6,7 @@ from io import BytesIO
from PyPDF2 import PdfFileReader, PdfFileMerger from PyPDF2 import PdfFileReader, PdfFileMerger
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles import finders from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.cache import cache from django.core.cache import cache
from django.core.mail import EmailMessage, EmailMultiAlternatives from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.db.models.signals import post_save from django.db.models.signals import post_save
@@ -63,7 +63,7 @@ def send_eventauthorisation_success_email(instance):
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS], reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
) )
css = finders.find('css/email.css') css = staticfiles_storage.path('css/email.css')
html = Premailer(get_template("eventauthorisation_client_success.html").render(context), html = Premailer(get_template("eventauthorisation_client_success.html").render(context),
external_styles=css).transform() external_styles=css).transform()
client_email.attach_alternative(html, 'text/html') client_email.attach_alternative(html, 'text/html')
@@ -121,7 +121,7 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
to=[admin.email], to=[admin.email],
reply_to=[user.email], reply_to=[user.email],
) )
css = finders.find('css/email.css') css = staticfiles_storage.path('css/email.css')
html = Premailer(get_template("admin_awaiting_approval.html").render(context), html = Premailer(get_template("admin_awaiting_approval.html").render(context),
external_styles=css).transform() external_styles=css).transform()
email.attach_alternative(html, 'text/html') email.attach_alternative(html, 'text/html')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,9 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %} {% load static %}
{% load invoices_waiting from filters %}
{% load invoices_outstanding from filters %}
{% load total_invoices_todo from filters %}
{% block titleheader %} {% block titleheader %}
<a class="navbar-brand" style="margin-left: auto; margin-right: auto;" href="/">RIGS</a> <a class="navbar-brand" href="/">RIGS</a>
{% endblock %} {% endblock %}
{% block titleelements %} {% block titleelements %}
@@ -47,17 +44,14 @@
{% endif %} {% endif %}
{% if perms.RIGS.view_invoice %} {% if perms.RIGS.view_invoice %}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
{% total_invoices_todo as todo %}
{% invoices_waiting as waiting %}
{% invoices_outstanding as outstanding %}
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownInvoices" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownInvoices" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Invoices <span class="badge {% if todo == 0 %}badge-success{% else %}badge-danger{% endif %} badge-pill">{{ todo }}</span> Invoices
</a> </a>
<div class="dropdown-menu" aria-labelledby="navbarDropdownInvoices"> <div class="dropdown-menu" aria-labelledby="navbarDropdownInvoices">
{% if perms.RIGS.add_invoice %} {% 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> <a class="dropdown-item" href="{% url 'invoice_waiting' %}"><span class="fas fa-briefcase text-danger"></span> Waiting</a>
{% endif %} {% endif %}
<a class="dropdown-item" href="{% url 'invoice_list' %}"><span class="fas fa-pound-sign text-warning"></span> Outstanding <span class="badge {% if outstanding == 0 %}badge-success{% else %}badge-danger{% endif %} badge-pill">{{ outstanding }}</span></a> <a class="dropdown-item" href="{% url 'invoice_list' %}"><span class="fas fa-pound-sign text-warning"></span> Outstanding</a>
<a class="dropdown-item" href="{% url 'invoice_archive' %}"><span class="fas fa-book"></span> Archive</a> <a class="dropdown-item" href="{% url 'invoice_archive' %}"><span class="fas fa-book"></span> Archive</a>
</div> </div>
</li> </li>
@@ -80,7 +74,6 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ block.super }}
<script src="{% static 'js/tooltip.js' %}"></script> <script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/popover.js' %}"></script> <script src="{% static 'js/popover.js' %}"></script>
<script> <script>

View File

@@ -5,13 +5,11 @@
{% load static %} {% load static %}
{% block css %} {% block css %}
{{ block.super }} <link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/>
{% endblock %} {% endblock %}
{% block preload_js %} {% block preload_js %}
{{ block.super }} <script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/selects.js' %}" async></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -8,7 +8,7 @@
<div class="row"> <div class="row">
<div class="col-12 text-right my-3"> <div class="col-12 text-right my-3">
{% button 'edit' url='ec_edit' pk=object.pk %} {% button 'edit' url='ec_edit' pk=object.pk %}
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %} {% button 'view' url='event_detail' pk=object.pk text="Event" %}
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %} {% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
</div> </div>
</div> </div>
@@ -32,11 +32,7 @@
</dd> </dd>
<dt class="col-6">{{ object|help_text:'power_mic' }}</dt> <dt class="col-6">{{ object|help_text:'power_mic' }}</dt>
<dd class="col-6"> <dd class="col-6">
{% if object.power_mic %}
<a href="{% url 'profile_detail' object.power_mic.pk %}">{{ object.power_mic.name }}</a> <a href="{% url 'profile_detail' object.power_mic.pk %}">{{ object.power_mic.name }}</a>
{% else %}
None
{% endif %}
</dd> </dd>
</dl> </dl>
<p>List vehicles and their drivers</p> <p>List vehicles and their drivers</p>
@@ -102,10 +98,6 @@
<td>{{crew.role}}</td> <td>{{crew.role}}</td>
<td>{{crew.end}}</td> <td>{{crew.end}}</td>
</tr> </tr>
{% empty %}
<tr>
<td colspan="4" class="text-center bg-warning">Apparently this event happened by magic...</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@@ -113,27 +105,9 @@
</div> </div>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header">Power {% include 'partials/event_size.html' with object=object.event.riskassessment %}</div> <div class="card-header">Power {% include 'partials/event_size.html' with object=object.event.riskassessment %}</div>
{% if object.event.riskassessment.event_size != 2 %}
<div class="card-body"> <div class="card-body">
{% if object.event.riskassessment.event_size == 0 %} {% if object.event.riskassessment.event_size == 1 %}
<dl class="row">
<dt class="col-10">{{ object|help_text:'rcds'|safe }}</dt>
<dd class="col-2">
{{ object.rcds|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'supply_test'|safe }}</dt>
<dd class="col-2">
{{ object.supply_test|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'earthing'|safe }}</dt>
<dd class="col-2">
{{ object.earthing|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'pat'|safe }}</dt>
<dd class="col-2">
{{ object.pat|yesnoi }}
</dd>
</dl>
{% else %}
<dl class="row"> <dl class="row">
<dt class="col-10">{{ object|help_text:'source_rcd'|safe }}</dt> <dt class="col-10">{{ object|help_text:'source_rcd'|safe }}</dt>
<dd class="col-2"> <dd class="col-2">
@@ -238,8 +212,28 @@
</dl> </dl>
<hr> <hr>
{% include 'partials/ec_power_info.html' %} {% include 'partials/ec_power_info.html' %}
{% else %}
<dl class="row">
<dt class="col-10">{{ object|help_text:'rcds'|safe }}</dt>
<dd class="col-2">
{{ object.rcds|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'supply_test'|safe }}</dt>
<dd class="col-2">
{{ object.supply_test|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'earthing'|safe }}</dt>
<dd class="col-2">
{{ object.earthing|yesnoi }}
</dd>
<dt class="col-10">{{ object|help_text:'pat'|safe }}</dt>
<dd class="col-2">
{{ object.pat|yesnoi }}
</dd>
</dl>
{% endif %} {% endif %}
</div> </div>
{% endif %}
</div> </div>
<div class="col-12 text-right"> <div class="col-12 text-right">
{% button 'edit' url='ec_edit' pk=object.pk %} {% button 'edit' url='ec_edit' pk=object.pk %}

View File

@@ -7,19 +7,25 @@
{% block css %} {% block css %}
{{ block.super }} {{ block.super }}
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/> <link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
{% endblock %} {% endblock %}
{% block preload_js %} {% block preload_js %}
{{ block.super }} {{ block.super }}
<script src="{% static 'js/selects.js' %}"></script> <script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ block.super }} {{ block.super }}
<script src="{% static 'js/autocompleter.js' %}"></script> <script src="{% static 'js/jquery-ui.js' %}"></script><!--TODO optimise-->
<script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/modal.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script> <script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/autocompleter.js' %}"></script>
{% include 'partials/datetime-fix.html' %} {% include 'partials/datetime-fix.html' %}
<script> <script>
@@ -128,14 +134,14 @@
<tbody id="vehiclest" data-pk="-1"> <tbody id="vehiclest" data-pk="-1">
<tr id="vehicles_new" style="display: none;"> <tr id="vehicles_new" style="display: none;">
<td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td> <td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td>
<td><select data-container="body" class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" name="driver_new" disabled="true"></select></td> <td><select class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" name="driver_new" disabled="true"></select></td>
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button></td> <td><button type="button" class="btn btn-danger btn-sm mt-1" data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button></td>
</tr> </tr>
{% for i in object.vehicles.all %} {% for i in object.vehicles.all %}
<tr id="vehicles_{{i.pk}}"> <tr id="vehicles_{{i.pk}}">
<td><input name="vehicle_{{i.pk}}" type="text" class="form-control" value="{{ i.vehicle }}"/></td> <td><input name="vehicle_{{i.pk}}" type="text" class="form-control" value="{{ i.vehicle }}"/></td>
<td> <td>
<select data-container="body" name="driver_{{i.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials"> <select name="driver_{{i.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if i.driver != '' %} {% if i.driver != '' %}
<option value="{{i.driver.pk}}" selected="selected">{{ i.driver.name }}</option> <option value="{{i.driver.pk}}" selected="selected">{{ i.driver.name }}</option>
{% endif %} {% endif %}
@@ -196,7 +202,7 @@
<tbody id="crewmemberst" data-pk="-1"> <tbody id="crewmemberst" data-pk="-1">
<tr id="crew_new" style="display: none;"> <tr id="crew_new" style="display: none;">
<td> <td>
<select name="crewmember_new" class="form-control" data-container="body" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" disabled="true"></select> <select name="crewmember_new" class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" disabled="true"></select>
</td> </td>
<td style="min-width: 15ch"><input name="start_new" type="datetime-local" class="form-control" value="{{ i.start }}" disabled=""/></td> <td style="min-width: 15ch"><input name="start_new" type="datetime-local" class="form-control" value="{{ i.start }}" disabled=""/></td>
<td style="min-width: 15ch"><input name="role_new" type="text" class="form-control" value="{{ i.role }}" disabled="true"/></td> <td style="min-width: 15ch"><input name="role_new" type="text" class="form-control" value="{{ i.role }}" disabled="true"/></td>
@@ -206,7 +212,7 @@
{% for crew in object.crew.all %} {% for crew in object.crew.all %}
<tr id="crew_{{crew.pk}}"> <tr id="crew_{{crew.pk}}">
<td> <td>
<select data-container="body" name="crewmember_{{crew.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials"> <select name="crewmember_{{crew.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if crew.crewmember != '' %} {% if crew.crewmember != '' %}
<option value="{{crew.crewmember.pk}}" selected="selected">{{ crew.crewmember.name }}</option> <option value="{{crew.crewmember.pk}}" selected="selected">{{ crew.crewmember.name }}</option>
{% endif %} {% endif %}
@@ -244,19 +250,12 @@
</div> </div>
</div> </div>
</div> </div>
{% else %} {% elif event.riskassessment.event_size == 1 %}
<div class="row my-3" id="size-1"> <div class="row my-3" id="size-1">
<div class="col-12"> <div class="col-12">
{% if event.riskassessment.event_size == 1 %}
<div class="card border-warning"> <div class="card border-warning">
<div class="card-header">Electrical Checks <small>for Medium TEC Events </small></div> <div class="card-header">Electrical Checks <small>for Medium TEC Events </small></div>
<div class="card-body"> <div class="card-body">
{% else %}
<div class="card border-danger">
<div class="card-header">Electrical Checks <small>for Large TEC Events</small></div>
<div class="card-body">
<div class="alert alert-danger"><strong>Here be dragons. Ensure you have appeased the Power Gods before continuing... (If you didn't check with a Supervisor, <em>you cannot continue your event!</em>)</strong></div>
{% endif %}
{% include 'partials/checklist_checkbox.html' with formitem=form.source_rcd %} {% include 'partials/checklist_checkbox.html' with formitem=form.source_rcd %}
{% include 'partials/checklist_checkbox.html' with formitem=form.labelling %} {% include 'partials/checklist_checkbox.html' with formitem=form.labelling %}
{% include 'partials/checklist_checkbox.html' with formitem=form.earthing %} {% include 'partials/checklist_checkbox.html' with formitem=form.earthing %}
@@ -346,6 +345,17 @@
</div> </div>
</div> </div>
</div> </div>
{% else %}
<div class="row my-3" id="size-2">
<div class="col-12">
<div class="card border-danger">
<div class="card-header">Electrical Checks <small>for Large TEC Events</small></div>
<div class="card-body">
<p>Outside the scope of this assessment. <strong>I really hope you checked with a supervisor...</strong></p>
</div>
</div>
</div>
</div>
{% endif %} {% endif %}
<div class="row mt-3"> <div class="row mt-3">
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">

View File

@@ -1,20 +1,63 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load linkornone from filters %}
{% load markdown_tags %} {% load namewithnotes from filters %}
{% block content %} {% block content %}
<div class="row my-3 py-3"> <div class="row my-3 py-3">
{% if not request.is_ajax %} {% if not request.is_ajax %}
{% if perms.RIGS.view_event %} {% if perms.RIGS.view_event %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'partials/event_detail_buttons.html' %} {% include 'event_detail_buttons.html' %}
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if object.is_rig and perms.RIGS.view_event %} {% if object.is_rig and perms.RIGS.view_event %}
{# only need contact details for a rig #} {# only need contact details for a rig #}
<div class="col-md-6"> <div class="col-md-6">
{% include 'partials/contact_details.html' %} {% if event.person %}
<div class="card card-default mb-3">
<div class="card-header">Contact Details</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-6">Person</dt>
<dd class="col-sm-6">
{% if object.person %}
<a href="{% url 'person_detail' object.person.pk %}" class="modal-href">
{{ object.person|namewithnotes:'person_detail' }}
</a>
{% endif %}
</dd>
<dt class="col-sm-6">Email</dt>
<dd class="col-sm-6">{{ object.person.email|linkornone:'mailto' }}</dd>
<dt class="col-sm-6">Phone Number</dt>
<dd class="col-sm-6">{{ object.person.phone|linkornone:'tel' }}</dd>
</dl>
</div>
</div>
{% endif %}
{% if event.organisation %}
<div class="card card-default">
<div class="card-header">Organisation</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-6">Organisation</dt>
<dd class="col-sm-6">
{% if object.organisation %}
<a href="{% url 'organisation_detail' object.organisation.pk %}" class="modal-href">
{{ object.organisation|namewithnotes:'organisation_detail' }}
</a>
{% endif %}
</dd>
<dt class="col-sm-6">Email</dt>
<dd class="col-sm-6">{{ object.organisation.email|linkornone:'mailto' }}</dd>
<dt class="col-sm-6">Phone Number</dt>
<dd class="col-sm-6">{{ object.organisation.phone|linkornone:'tel' }}</dd>
<dt class="col-sm-6">Has SU Account</dt>
<dd class="col-sm-6">{{ event.organisation.union_account|yesno|capfirst }}</dd>
</dl>
</div>
</div>
{% endif %}
</div> </div>
{% endif %} {% endif %}
<div class="col-md-6"> <div class="col-md-6">
@@ -34,7 +77,7 @@
{% endif %} {% endif %}
{% if not request.is_ajax and perms.RIGS.view_event %} {% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'partials/event_detail_buttons.html' %} {% include 'event_detail_buttons.html' %}
</div> </div>
{% endif %} {% endif %}
{% if event.is_rig %} {% if event.is_rig %}
@@ -45,16 +88,16 @@
{% if perms.RIGS.view_event %} {% if perms.RIGS.view_event %}
<h4>Notes</h4> <h4>Notes</h4>
<hr> <hr>
<p class="dont-break-out">{{ event.notes|markdown }}</p> <p class="dont-break-out">{{ event.notes|linebreaksbr }}</p>
{% endif %} {% endif %}
<br> <br>
{% include 'partials/item_table.html' %} {% include 'item_table.html' %}
</div> </div>
</div> </div>
</div> </div>
{% if not request.is_ajax and perms.RIGS.view_event %} {% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'partials/event_detail_buttons.html' %} {% include 'event_detail_buttons.html' %}
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@@ -9,7 +9,7 @@
{% if event.internal %} {% if event.internal %}
<a class="btn item-add modal-href event-authorise-request <a class="btn item-add modal-href event-authorise-request
{% if event.authorised %} {% if event.authorised %}
btn-success active btn-success
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %} {% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
btn-warning btn-warning
{% elif event.auth_request_to %} {% elif event.auth_request_to %}
@@ -19,7 +19,7 @@
{% endif %} {% endif %}
" "
href="{% url 'event_authorise_request' object.pk %}"> href="{% url 'event_authorise_request' object.pk %}">
<span class="fas fa-paper-plane"></span> <i class="fas fa-paper-plane"></i>
<span class="d-none d-sm-inline"> <span class="d-none d-sm-inline">
{% if event.authorised %} {% if event.authorised %}
Authorised Authorised
@@ -47,7 +47,5 @@
class="fas fa-pound-sign"></span> class="fas fa-pound-sign"></span>
<span class="d-none d-sm-inline">Invoice</span></a> <span class="d-none d-sm-inline">Invoice</span></a>
{% endif %} {% endif %}
<a href="https://docs.google.com/forms/d/e/1FAIpQLSf-TBOuJZCTYc2L8DWdAaC3_Werq0ulsUs8-6G85I6pA9WVsg/viewform" class="btn btn-danger"><span class="fas fa-file-invoice-dollar"></span> <span class="d-none d-sm-inline">Subhire Insurance Form</span></a>
{% endif %} {% endif %}
</div> </div>

View File

@@ -1,9 +1,8 @@
{% extends 'base_embed.html' %} {% extends 'base_embed.html' %}
{% load static %} {% load static %}
{% block extra-head %} {% block js %}
<link href="{% static 'fontawesome_free/css/fontawesome.css' %}" rel="stylesheet" type="text/css"> <script src="{% static 'js/all.js' %}"></script>
<link href="{% static 'fontawesome_free/css/solid.css' %}" rel="stylesheet" type="text/css">
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -7,48 +7,44 @@
{% block css %} {% block css %}
{{ block.super }} {{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/> <link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'css/simplemde.min.css' %}"> <link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/flatpickr.css' %}"/>
{% endblock %} {% endblock %}
{% block preload_js %} {% block preload_js %}
{{ block.super }} {{ block.super }}
<script src="{% static 'js/selects.js' %}"></script> <script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/simplemde.min.js' %}"></script> <script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ block.super }} {{ block.super }}
<script src="{% static 'js/autocompleter.js' %}"></script> <script src="{% static 'js/jquery-ui.js' %}"></script><!--TODO optimise--->
<script src="{% static 'js/interaction.js' %}"></script> <script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/modal.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script> <script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/autocompleter.js' %}"></script>
{% include 'partials/datetime-fix.html' %} {% include 'partials/datetime-fix.html' %}
<script> <script>
const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches; const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches;
$(document).ready(function () { $(document).ready(function () {
dur = matches ? 0 : 500; dur = matches ? 0 : 500;
{% if object.pk %} {% if not object.pk and not form.errors %}
// Editing $('.form-hws').slideUp(dur, function () {
{% if not object.is_rig %} $('.form-is_rig').slideUp(dur);
});
{% elif not object.pk and form.errors %}
if ($('#{{form.is_rig.auto_id}}').attr('checked') !== 'checked') {
$('.form-is_rig').hide(); $('.form-is_rig').hide();
{% endif %} }
//Creation {% endif %}
{% else %} {% if not object.pk %}
// If there were errors, apply the previous Rig/not-Rig selection
{% if form.errors %}
$('.form-hws').show();
if ($('#{{form.is_rig.auto_id}}').attr('checked') !== 'checked') {
$('.form-is_rig').hide();
}
{% else %}
//Initial hide
$('.form-hws').slideUp(dur);
{% endif %}
//Button handling
$('#is_rig-selector button').on('click', function () { $('#is_rig-selector button').on('click', function () {
$('.form-non_rig').slideDown(dur); //Non rig stuff also needed for rig, so always slide down $('.form-non_rig').slideDown(dur);
if ($(this).data('is_rig') === 1) { if ($(this).data('is_rig') === 1) {
$('#{{form.is_rig.auto_id}}').prop('checked', true); $('#{{form.is_rig.auto_id}}').prop('checked', true);
if ($('.form-non_rig').is(':hidden')) { if ($('.form-non_rig').is(':hidden')) {
@@ -58,6 +54,7 @@
} }
$('.form-hws, .form-hws .form-is_rig').css('overflow', 'visible'); $('.form-hws, .form-hws .form-is_rig').css('overflow', 'visible');
} else { } else {
$('#{{form.is_rig.auto_id}}').prop('checked', false); $('#{{form.is_rig.auto_id}}').prop('checked', false);
$('.form-is_rig').slideUp(dur); $('.form-is_rig').slideUp(dur);
} }
@@ -65,26 +62,23 @@
{% endif %} {% endif %}
}); });
$(document).ready(function () { $(document).ready(function () {
setupMDE('#id_description');
setupMDE('#id_notes');
setupMDE('#item_description');
$('#itemModal').on('shown.bs.modal', function (e) {
$('#item_description').data('mde_editor').value(
$('#item_description').val()
);
});
setupItemTable($("#{{ form.items_json.id_for_label }}").val()); setupItemTable($("#{{ form.items_json.id_for_label }}").val());
}); });
$(function () { $(function () {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
}); })
</script> </script>
<noscript>
<style>
.form-hws {
display: inherit !important;
}
</style>
</noscript>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% include 'partials/item_modal.html' %} {% include 'item_modal.html' %}
<form class="itemised_form" role="form" method="POST"> <form class="itemised_form" role="form" method="POST">
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
@@ -180,7 +174,7 @@
<label for="{{ form.description.id_for_label }}" <label for="{{ form.description.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.description.label }}</label> class="col-sm-4 col-form-label">{{ form.description.label }}</label>
<div class="col-sm-12"> <div class="col-sm-8">
{% render_field form.description class+="form-control" %} {% render_field form.description class+="form-control" %}
</div> </div>
</div> </div>
@@ -338,7 +332,7 @@
<div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)"> <div class="form-group" data-toggle="tooltip" title="The purchase order number (for external clients)">
<label for="{{ form.purchase_order.id_for_label }}" <label for="{{ form.purchase_order.id_for_label }}"
class="col-sm-4 col-fitem_tableorm-label">{{ form.purchase_order.label }}</label> class="col-sm-4 col-form-label">{{ form.purchase_order.label }}</label>
<div class="col-sm-8"> <div class="col-sm-8">
{% render_field form.purchase_order class+="form-control" %} {% render_field form.purchase_order class+="form-control" %}
@@ -357,10 +351,10 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="form-group" data-toggle="tooltip" title="Notes on the event. This is only visible to keyholders, and is not displayed on the paperwork"> <div class="form-group" data-toggle="tooltip" title="Notes on the event. This is only visible to keyholders, and is not displayed on the paperwork">
<label for="{{ form.notes.id_for_label }}">{{ form.notes.label }}</label> <label for="{{ form.notes.id_for_label }}">{{ form.notes.label }}</label>
{% render_field form.notes class+="form-control md-enabled" %} {% render_field form.notes class+="form-control" %}
</div> </div>
</div> </div>
{% include 'partials/item_table.html' %} {% include 'item_table.html' %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE document SYSTEM "rml.dtd"> <!DOCTYPE document SYSTEM "rml.dtd">
<document filename="{{filename}}"> <document filename="{{filename}}">
<docinit> <docinit>
<registerTTFont faceName="OpenSans" fileName="static/fonts/OpenSans-Regular.tff"/> <registerTTFont faceName="OpenSans" fileName="static/fonts/OpenSans-Regular.tff"/>
@@ -74,14 +75,6 @@
<lineStyle kind="linebelow" start="3,0" stop="3,0" colorName="black"/> <lineStyle kind="linebelow" start="3,0" stop="3,0" colorName="black"/>
<lineStyle kind="linebelow" start="5,0" stop="5,0" colorName="black"/> <lineStyle kind="linebelow" start="5,0" stop="5,0" colorName="black"/>
</blockTableStyle> </blockTableStyle>
<listStyle name="ol"
bulletFormat="%s."
bulletFontSize="10" />
<listStyle name="ul"
start="bulletchar"
bulletFontSize="10"/>
</stylesheet> </stylesheet>
<template > {# Note: page is 595x842 points (1 point=1/72in) #} <template > {# Note: page is 595x842 points (1 point=1/72in) #}

View File

@@ -1,6 +1,4 @@
{% load markdown_tags %}
{% load filters %} {% load filters %}
<setNextFrame name="main"/> <setNextFrame name="main"/>
<nextFrame/> <nextFrame/>
<blockTable style="headLayout" colWidths="330,165"> <blockTable style="headLayout" colWidths="330,165">
@@ -12,8 +10,10 @@
<b>{{object.start_date|date:"D jS N Y"}}</b> <b>{{object.start_date|date:"D jS N Y"}}</b>
</para> </para>
<keepInFrame maxHeight="500" onOverflow="shrink"> <keepInFrame>
{{ object.description|default_if_none:""|markdown:"rml" }} <para style="style.event_description">
{{ object.description|default_if_none:""|linebreaksxml }}
</para>
</keepInFrame> </keepInFrame>
</td> </td>
<td> <td>
@@ -184,27 +184,25 @@
{% if item.description %} {% if item.description %}
</para> </para>
<para style="item_description"> <para style="item_description">
{{ item.description|markdown:"rml" }} <em>{{ item.description|linebreaksxml }}</em>
</para> </para>
<para> <para>
{% endif %} {% endif %}
</para> </para>
</td> </td>
<td>£{{ item.cost|floatformat:2 }}</td> <td>£ {{ item.cost|floatformat:2 }}</td>
<td>{{ item.quantity }}</td> <td>{{ item.quantity }}</td>
<td>£{{ item.total_cost|floatformat:2 }}</td> <td>£ {{ item.total_cost|floatformat:2 }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</blockTable> </blockTable>
<keepTogether> <keepTogether>
<blockTable style="totalTable" colWidths="300,115,80"> <blockTable style="totalTable" colWidths="300,115,80">
{% if object.vat > 0 %}
<tr> <tr>
<td>{% if quote %}VAT Registration Number: 170734807</td> <td>{% if quote %}VAT Registration Number: 170734807{% endif %}</td>
<td>Total (ex. VAT){% endif %}</td> <td>Total (ex. VAT)</td>
<td>£ {{ object.sum_total|floatformat:2 }}</td> <td>£ {{ object.sum_total|floatformat:2 }}</td>
</tr> </tr>
{% endif %}
<tr> <tr>
<td> <td>
{% if quote %} {% if quote %}
@@ -213,10 +211,8 @@
</para> </para>
{% endif %} {% endif %}
</td> </td>
{% if object.vat > 0 %}
<td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td> <td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td>
<td>£{{ object.vat|floatformat:2 }}</td> <td>£ {{ object.vat|floatformat:2 }}</td>
{% endif %}
</tr> </tr>
<tr> <tr>
<td> <td>
@@ -228,7 +224,7 @@
</td> </td>
{% if invoice %} {% if invoice %}
<td>Total</td> <td>Total</td>
<td>£{{ object.total|floatformat:2 }}</td> <td>£ {{ object.total|floatformat:2 }}</td>
{% else %} {% else %}
<td> <td>
<para> <para>
@@ -237,7 +233,7 @@
</td> </td>
<td> <td>
<para> <para>
<b>£{{ object.total|floatformat:2 }}</b> <b>£ {{ object.total|floatformat:2 }}</b>
</para> </para>
</td> </td>
{% endif %} {% endif %}
@@ -271,7 +267,7 @@
<tr> <tr>
<td>{{ payment.get_method_display }}</td> <td>{{ payment.get_method_display }}</td>
<td>{{ payment.date }}</td> <td>{{ payment.date }}</td>
<td>£{{ payment.amount|floatformat:2 }}</td> <td>£ {{ payment.amount|floatformat:2 }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</blockTable> </blockTable>
@@ -279,18 +275,18 @@
<tr> <tr>
<td></td> <td></td>
<td>Payment Total</td> <td>Payment Total</td>
<td>£{{ object.invoice.payment_total|floatformat:2 }}</td> <td>£ {{ object.invoice.payment_total|floatformat:2 }}</td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<td> <td>
<para> <para>
<b>Balance</b> {% if object.vat > 0 %}(ex. VAT){% endif %} <b>Balance</b> (ex. VAT)
</para> </para>
</td> </td>
<td> <td>
<para> <para>
<b>£{{ object.invoice.balance|floatformat:2 }}</b> <b>£ {{ object.invoice.balance|floatformat:2 }}</b>
</para> </para>
</td> </td>
</tr> </tr>
@@ -320,7 +316,7 @@
<tr> <tr>
<td>General Enquires and 24 Hour Emergency Contact: 0115 84 68720</td> <td>General Enquires and 24 Hour Emergency Contact: 0115 84 68720</td>
</tr> </tr>
{% elif object.vat > 0 %} {% else %}
<tr> <tr>
<td> <td>
<para>VAT Registration Number: 170734807</para> <para>VAT Registration Number: 170734807</para>

View File

@@ -0,0 +1,10 @@
<blockTable style="signatureTable" colWidths="50,120,60,120,35,110">
<tr>
<td>Signature</td>
<td></td>
<td>Print Name</td>
<td></td>
<td>Date</td>
<td></td>
</tr>
</blockTable>

View File

@@ -26,7 +26,7 @@
<div class="col-sm-12"> <div class="col-sm-12">
<div class="card"> <div class="card">
{% with object=event auth=True %} {% with object=event auth=True %}
{% include 'partials/item_table.html' %} {% include 'item_table.html' %}
{% endwith %} {% endwith %}
</div> </div>
</div> </div>

View File

@@ -1,17 +1,19 @@
{% extends 'base_client_email.html' %} {% extends 'base_client_email.html' %}
{% block content %} {% block content %}
<p>Hi {{ to_name|default:"there" }},</p> <p>Hi {{ to_name|default:"there" }},</p>
<p><b>{{ request.user.get_full_name }}</b> has requested that you authorise <b>{{ object.display_id }} <p><b>{{ request.user.get_full_name }}</b> has requested that you authorise <b>N{{ object.pk|stringformat:"05d" }}
| {{ object.name }}</b>{% if not to_name %} on behalf of <b>{% if object.person %}{{ object.person.name }}{% else %}{{ object.organisation.name }}{% endif %}</b>{% endif %}.</p> | {{ object.name }}</b>{% if not to_name %} on behalf of <b>{{ object.person.name }}</b>{% endif %}.</p>
<p> <p>
Please find the link below to complete the event booking process. Please find the link below to complete the event booking process.
Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward {% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
this Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward
email on. this
email on.
{% endif %}
</p> </p>
@@ -21,7 +23,7 @@
<table border="0" cellspacing="0" cellpadding="0"> <table border="0" cellspacing="0" cellpadding="0">
<tr> <tr>
<td class="button" align="center"> <td class="button" align="center">
<a href="{{ request.scheme }}://{{ request.get_host }}{% url target|default:'event_authorise' object.pk hmac %}"> <a href="{{ request.scheme }}://{{ request.get_host }}{% url 'event_authorise' object.pk hmac %}">
Complete Authorisation Form Complete Authorisation Form
</a> </a>
</td> </td>

View File

@@ -1,6 +1,6 @@
Hi {{ to_name|default:"there" }}, Hi {{ to_name|default:"there" }},
{{ request.user.get_full_name }} has requested that you authorise N{{ object.pk|stringformat:"05d" }}| {{ object.name }}{% if not to_name %} on behalf of {% if object.person %}{{ object.person.name }}{% else %}{{ object.organisation.name }}{% endif %}{% endif %}. {{ request.user.get_full_name }} has requested that you authorise N{{ object.pk|stringformat:"05d" }}| {{ object.name }}{% if not to_name %} on behalf of {{ object.person.name }}{% endif %}.
Please find the link below to complete the event booking process. Please find the link below to complete the event booking process.
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #} {% if object.event.organisation and object.event.organisation.union_account %}{# internal #}

View File

@@ -1,6 +1,9 @@
{% extends 'eventauthorisation.html' %} {% extends 'eventauthorisation.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% block js %}
{% endblock %}
{% block authorisation %} {% block authorisation %}
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
@@ -83,7 +86,7 @@
<div class="text-right"> <div class="text-right">
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-primary btn-lg" type="submit" {% if preview %}disabled="" data-toggle="tooltip" title="This is only a preview!"{%endif%}>Authorise</button> <button class="btn btn-primary btn-lg" type="submit">Authorise</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,48 +1,24 @@
{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %}
{% load button from filters %}
{% block title %}Request Authorisation{% endblock %} {% block title %}Request Authorisation{% endblock %}
{% block js %}
<script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/popover.js' %}"></script>
<script src="{% static 'js/clipboard.min.js' %}"></script>
<script>
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
$(e.trigger).popover('show');
window.setTimeout(function () {$(e.trigger).popover('hide')}, 3000);
e.clearSelection();
});
</script>
{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<div class="alert alert-warning pb-0"> <div class="alert alert-warning">
<h1>Send authorisation request email.</h1> <h1>Send authorisation request email.</h1>
<p>Pressing send will email the address provided. <strong>Please triple check everything before continuing.</strong></p> <p>Pressing send will email the address provided. Please triple check everything before continuing.</p>
</div> </div>
<div class="alert alert-info pb-0"> <div class="alert alert-info">
{% if object.person.email or object.organisation.email %}
<dl class="dl-horizontal"> <dl class="dl-horizontal">
{% if object.person.email %}
<dt>Person Email</dt> <dt>Person Email</dt>
<dd><span id="person-email">{{ object.person.email }}</span>{% button 'copy' id='#person-email' %}</dd> <dd>{{ object.person.email }}</dd>
{% endif %}
{% if object.organisation.email %}
<dt>Organisation Email</dt> <dt>Organisation Email</dt>
<dd><span id="org-email">{{ object.organisation.email }}</span>{% button 'copy' id='#org-email' %}</dd> <dd>{{ object.organisation.email }}</dd>
{% endif %}
</dl> </dl>
{% else %}
<p>No email addresses saved to the client &#3232;_&#3232;</p>
{% endif %}
</div> </div>
<form action="{{ form.action|default:request.path }}" method="POST" id="auth-request-form"> <form action="{{ form.action|default:request.path }}" method="POST" id="auth-request-form">
{% csrf_token %} {% csrf_token %}
@@ -54,6 +30,14 @@
{% render_field form.email type="email" class+="form-control" %} {% render_field form.email type="email" class+="form-control" %}
</div> </div>
</div> </div>
<div class="text-right col-sm-3 offset-sm-9">
<div class="form-group">
<button type="submit" class="form-control btn btn-primary">
<i class="fas fa-paper-plane"></i>
Send
</button>
</div>
</div>
</form> </form>
</div> </div>
</div> </div>
@@ -64,14 +48,3 @@
}); });
</script> </script>
{% endblock %} {% endblock %}
{% block footer %}
<div class="form-row">
<div class="btn-group" role="group">
<a type="button" target="_blank" href="{% url 'event_authorise_preview' object.pk %}" class="btn btn-info text-nowrap"><span class="fas fa-drafting-compass"></span> Preview</a>
<button type="submit" class="form-control btn btn-primary" form="auth-request-form">
<span class="fas fa-paper-plane"></span> Send
</button>
</div>
</div>
{% endblock %}

View File

@@ -4,11 +4,10 @@
{% block content %} {% block content %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table mb-0 table-sm"> <table class="table mb-0">
<thead> <thead>
<tr> <tr>
<th scope="col">Event</th> <th scope="col">Event</th>
<th scope="col">MIC</th>
<th scope="col">Dates</th> <th scope="col">Dates</th>
<th scope="col">RA</th> <th scope="col">RA</th>
<th scope="col">Checklists</th> <th scope="col">Checklists</th>
@@ -17,8 +16,7 @@
<tbody> <tbody>
{% for event in object_list %} {% for event in object_list %}
<tr id="event_row"> <tr id="event_row">
<th scope="row" id="event_number"><a href="{% url 'event_detail' event.pk %}">{{ event }}</a><br><small>{{ event.get_status_display }}</small></th> <th scope="row" id="event_number"><a href="{% url 'event_detail' event.pk %}">{{ event }}</a></th>
<td>{% if event.mic is not None %}<a href="{% url 'profile_detail' event.mic.pk %}">{% else %}<span class="text-danger">{% endif %}{{ event.mic }}{% if event.mic is not None %}</a>{% else %}</span>{%endif%}</td>
<!--Dates--> <!--Dates-->
<td id="event_dates"> <td id="event_dates">
<span><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></span> <span><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></span>

View File

@@ -15,13 +15,13 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="table-responsive"> <div class="table-responsive">
<table class="table mb-0 table-sm"> <table class="table mb-0">
<thead> <thead>
<tr> <tr>
<th scope="col">Event</th> <th scope="col">Event</th>
{# mmm hax #} {# mmm hax #}
{% if object_list.0 != None %} {% if object_list.0 != None %}
{% for field in object_list.0.fieldz %} {% for field in object_list.0.fields %}
<th scope="col">{{ object_list.0|verbose_name:field|title }}</th> <th scope="col">{{ object_list.0|verbose_name:field|title }}</th>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@@ -32,8 +32,8 @@
{% for object in object_list %} {% for object in object_list %}
<tr class="{% if object.reviewed_by %}table-success{%endif%}"> <tr class="{% if object.reviewed_by %}table-success{%endif%}">
{# General #} {# General #}
<th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a><br><small>{{ object.event.get_status_display }}</small></th> <th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a></th>
{% for field in object_list.0.fieldz %} {% for field in object_list.0.fields %}
<td>{{ object|get_field:field }}</td> <td>{{ object|get_field:field }}</td>
{% endfor %} {% endfor %}
{# Buttons #} {# Buttons #}

View File

@@ -2,88 +2,102 @@
{% load button from filters %} {% load button from filters %}
{% block content %} {% block content %}
<div class="row py-4"> <div class="col-sm-12">
<div class="col-sm-12 text-right px-0"> <div class="row justify-content-end py-3">
<div class="btn-group"> <div class="col-sm-4 text-right">
<a href="{% url 'event_detail' object.event.pk %}" class="btn btn-primary">Open Event Page <span class="fas fa-eye"></span></a> <div class="btn-group btn-page">
<a href="{% url 'invoice_delete' object.pk %}" class="btn btn-danger" title="Delete Invoice"> <a href="{% url 'invoice_delete' object.pk %}" class="btn btn-danger" title="Delete Invoice">
<span class="fas fa-times"></span> <span <span class="fas fa-times"></span> <span
class="d-none d-sm-inline">Delete</span> class="d-none d-sm-inline">Delete</span>
</a>
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-warning" title="Void Invoice">
<span class="fas fa-ban"></span> <span
class="d-none d-sm-inline">Void</span>
</a>
{% button 'print' url='invoice_print' pk=object.pk %}
</div>
</div>
<div>
<div class="row py-4">
{% with object.event as object %}
<div class="col-sm-6">
{% include 'partials/contact_details.html' %}
</div>
<div class="col-sm-6">
{% include 'partials/event_details.html' %}
</div>
{% if object.event.internal %}
<div class="col-sm-6">
{% include 'partials/auth_details.html' %}
</div>
{% endif %}
{% endwith %}
</div>
<div class="row py-4">
<div class="col-sm-6">
<div class="card card-default">
<div class="card-body">
<div class="text-right py-3">
<a href="{% url 'payment_create' %}?invoice={{ object.pk }}"
class="btn btn-success modal-href"
data-target="#{{ form.person.id_for_label }}">
<span class="fas fa-plus"></span> Add
</a> </a>
<a href="{% url 'invoice_void' object.pk %}" class="btn btn-warning" title="Void Invoice">
<span class="fas fa-ban"></span> <span
class="d-none d-sm-inline">Void</span>
</a>
{% button 'print' url='invoice_print' pk=object.pk %}
</div> </div>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Amount</th>
<th scope="col">Method</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for payment in object.payment_set.all %}
<tr>
<th scope="row">{{ payment.date }}</th>
<td>{{ payment.amount|floatformat:2 }}</td>
<td>{{ payment.get_method_display }}</td>
<td>
<a href="{% url 'payment_delete' payment.pk %}" class="btn btn-small btn-danger"><span class="fas fa-times"></span></a>
</td>
</tr>
{% endfor %}
<tr>
<td class="text-right"><strong>Balance:</strong></td>
<td>{{ object.balance|floatformat:2 }}</td>
<td></td>
<td></td>
</tbody>
</table>
</div> </div>
</div> </div>
</div>
<div class="col-sm-6"> <div class="row">
<div class="card"> <div class="col-sm-6">
{% with object.event as object %} <div class="card card-default">
{% include 'partials/item_table.html' %} <div class="card-header">Invoice Details<span class="float-right">
{% endwith %} {% if object.void %}(VOID){% elif object.is_closed %}(PAID){% else %}(OUTSTANDING){% endif %}
</span></div>
<div class="card-body">
{% if object.event.organisation %}
{{ object.event.organisation.name }}<br/>
{{ object.event.organisation.address|linebreaksbr }}
{% else %}
{{ object.event.person.name }}<br/>
{{ object.event.person.address|linebreaksbr }}
{% endif %}
</div>
</div>
</div>
<div class="col-sm-6">
{% include 'partials/event_details.html' %}
</div>
{% if object.event.internal %}
<div class="col-sm-6">
{% include 'partials/auth_details.html' %}
</div>
{% endif %}
</div>
<div class="row py-4">
<div class="col-sm-6">
<div class="card card-default">
<div class="card-body">
<div class="text-right py-3">
<a href="{% url 'payment_create' %}?invoice={{ object.pk }}"
class="btn btn-success modal-href"
data-target="#{{ form.person.id_for_label }}">
<span class="fas fa-plus"></span> Add
</a>
</div>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Amount</th>
<th scope="col">Method</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for payment in object.payment_set.all %}
<tr>
<th scope="row">{{ payment.date }}</th>
<td>{{ payment.amount|floatformat:2 }}</td>
<td>{{ payment.get_method_display }}</td>
<td>
<a href="{% url 'payment_delete' payment.pk %}" class="btn btn-small btn-danger"><span class="fas fa-times"></span></a>
</td>
</tr>
{% endfor %}
<tr>
<td class="text-right"><strong>Balance:</strong></td>
<td>{{ object.balance|floatformat:2 }}</td>
<td></td>
<td></td>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card">
{% with object.event as object %}
{% include 'item_table.html' %}
{% endwith %}
</div>
</div>
</div>
<div class="col-12 text-right">
{% include 'partials/last_edited.html' with target="invoice_history" %}
</div> </div>
</div> </div>
</div>
<div class="col-12 text-right">
{% include 'partials/last_edited.html' with target="invoice_history" %}
</div>
{% endblock %} {% endblock %}

View File

@@ -16,10 +16,10 @@
id="item_name"/> id="item_name"/>
</div> </div>
</div> </div>
<div class="form-group form-row" data-toggle="tooltip" title="A detailed description of the kit. MD enabled."> <div class="form-group form-row">
<label for="item_description" class="col-sm-2 col-form-label">Description</label> <label for="item_description" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea type="text" placeholder="Description" class="form-control md-enabled" <textarea type="text" placeholder="Description" class="form-control"
id="item_description" rows="8"></textarea> id="item_description" rows="8"></textarea>
</div> </div>
</div> </div>

View File

@@ -1,17 +1,16 @@
{% load markdown_tags %}
<tr id="item-{{item.pk}}" data-pk="{{item.pk}}" class="item_row"> <tr id="item-{{item.pk}}" data-pk="{{item.pk}}" class="item_row">
<th scope="row"> <th scope="row">
<span class="name">{{ item.name }}</span> <span class="name">{{ item.name }}</span>
<div class="item-description"> <div class="item-description">
<em class="description">{{item.description|markdown}}</em> <em class="description">{{item.description|linebreaksbr}}</em>
</div> </div>
</th> </th>
{% if perms.RIGS.view_event %} {% if perms.RIGS.view_event %}
<td>£<span class="cost">{{item.cost|floatformat:2}}</span></td> <td>£&nbsp;<span class="cost">{{item.cost|floatformat:2}}</span></td>
{% endif %} {% endif %}
<td class="quantity">{{item.quantity}}</td> <td class="quantity">{{item.quantity}}</td>
{% if perms.RIGS.view_event %} {% if perms.RIGS.view_event %}
<td>£<span class="sub-total" data-subtotal="{{item.total_cost}}">{{item.total_cost|floatformat:2}}</span></td> <td>£&nbsp;<span class="sub-total" data-subtotal="{{item.total_cost}}">{{item.total_cost|floatformat:2}}</span></td>
{% endif %} {% endif %}
{% if edit %} {% if edit %}
<td class="vert-align text-right"> <td class="vert-align text-right">

View File

@@ -23,17 +23,16 @@
</thead> </thead>
<tbody id="item-table-body"> <tbody id="item-table-body">
{% for item in object.items.all %} {% for item in object.items.all %}
{% include 'partials/item_row.html' %} {% include 'item_row.html' %}
{% endfor %} {% endfor %}
</tbody> </tbody>
{% if auth or perms.RIGS.view_event %} {% if auth or perms.RIGS.view_event %}
<tfoot style="font-weight: bold"> <tfoot>
<tr> <tr>
<td rowspan="3" colspan="2"></td> <td rowspan="3" colspan="2"></td>
<td>Total {% if object.vat > 0 or not object.pk %}(ex. VAT){% endif %}</td> <td>Total (ex. VAT)</td>
<td colspan="2">£<span id="sumtotal">{{object.sum_total|default:0|floatformat:2}}</span></td> <td colspan="2">£ <span id="sumtotal">{{object.sum_total|default:0|floatformat:2}}</span></td>
</tr> </tr>
{% if object.vat > 0 or not object.pk %}
<tr> <tr>
{% if not object.pk %} {% if not object.pk %}
<td id="vat-rate" data-rate="{{currentVAT.rate}}">VAT @ <td id="vat-rate" data-rate="{{currentVAT.rate}}">VAT @
@@ -42,13 +41,12 @@
<td id="vat-rate" data-rate="{{object.vat_rate.rate}}">VAT @ <td id="vat-rate" data-rate="{{object.vat_rate.rate}}">VAT @
{{object.vat_rate.as_percent|floatformat|default:"TBD"}}%</td> {{object.vat_rate.as_percent|floatformat|default:"TBD"}}%</td>
{% endif %} {% endif %}
<td colspan="2">£<span id="vat">{{object.vat|default:0|floatformat:2}}</span></td> <td colspan="2">£ <span id="vat">{{object.vat|default:0|floatformat:2}}</span></td>
</tr> </tr>
<tr> <tr>
<td>Total</td> <td>Total</td>
<td colspan="2">£<span id="total">{{object.total|default:0|floatformat:2}}</span></td> <td colspan="2">£ <span id="total">{{object.total|default:0|floatformat:2}}</span></td>
</tr> </tr>
{% endif %}
</tfoot> </tfoot>
{% endif %} {% endif %}
</table> </table>
@@ -61,9 +59,9 @@
<em class="description"></em> <em class="description"></em>
</div> </div>
</td> </td>
<td>£<span class="cost"></span></td> <td>£&nbsp;<span class="cost"></span></td>
<td class="quantity"></td> <td class="quantity"></td>
<td>£<span class="sub-total"></span></td> <td>£&nbsp;<span class="sub-total"></span></td>
{% if edit %} {% if edit %}
<td class="vert-align text-right"> <td class="vert-align text-right">
<div class="btn-group" role="group" aria-label="Action buttons"> <div class="btn-group" role="group" aria-label="Action buttons">

View File

@@ -1,15 +1,15 @@
<div class="card card-default <div class="card card-default
{% if event.authorised %} {% if object.authorised %}
border-success card-success
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %} {% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
border-warning card-warning
{% elif event.auth_request_to %} {% elif event.auth_request_to %}
border-info card-info
{% endif %} {% endif %}
"> ">
<div class="card-header">Client Authorisation</div> <div class="card-header">Client Authorisation</div>
<div class="card-body row"> <div class="card-body row">
<dl class="col-sm-6"> <dl class="col-md-6">
<dt>Authorisation Request</dt> <dt>Authorisation Request</dt>
<dd>{{ object.auth_request_to|yesno:"Yes,No" }}</dd> <dd>{{ object.auth_request_to|yesno:"Yes,No" }}</dd>
@@ -22,8 +22,8 @@
<dt>To</dt> <dt>To</dt>
<dd>{{ object.auth_request_to }}</dd> <dd>{{ object.auth_request_to }}</dd>
</dl> </dl>
<dl class="col-sm-6"> <dd class="d-block d-sm-none">&nbsp;</dd>
<hr class="d-block d-sm-none"> <dl class="col-md-6">
<dt>Authorised</dt> <dt>Authorised</dt>
<dd>{{ object.authorised|yesno:"Yes,No" }}</dd> <dd>{{ object.authorised|yesno:"Yes,No" }}</dd>

View File

@@ -1,29 +1,27 @@
<div class="col-sm-6"> <div class="col-sm-6">
{% if event.person %}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header">Contact Details</div> <div class="card-header">Contact Details</div>
<div class="card-body"> <div class="card-body">
<dl class="row"> <dl class="row">
<dt class="col-sm-5">Person</dt> <dt class="col-sm-5">Person</dt>
<dd class="col-sm-7"> <dd class="col-sm-7">
{{ event.person.name }} {% if event.person %}
{{ event.person.name }}
{% endif %}
</dd> </dd>
{% if event.person.email %}
<dt class="col-sm-5">Email</dt> <dt class="col-sm-5">Email</dt>
<dd class="col-sm-7"> <dd class="col-sm-7">
<span class="overflow-ellipsis">{{ event.person.email }}</span> <span class="overflow-ellipsis">{{ event.person.email }}</span>
</dd> </dd>
{% endif %}
{% if event.person.phone %}
<dt class="col-sm-5">Phone Number</dt> <dt class="col-sm-5">Phone Number</dt>
<dd class="col-sm-7">{{ event.person.phone }}</dd> <dd class="col-sm-7">{{ event.person.phone }}</dd>
{% endif %}
</dl> </dl>
</div> </div>
</div> </div>
{% endif %}
{% if event.organisation %} {% if event.organisation %}
<div class="card"> <div class="card mt-3">
<div class="card-header">Organisation Details</div> <div class="card-header">Organisation Details</div>
<div class="card-body"> <div class="card-body">
<dl class="row"> <dl class="row">
@@ -31,10 +29,9 @@
<dd class="col-sm-7"> <dd class="col-sm-7">
{{ event.organisation.name }} {{ event.organisation.name }}
</dd> </dd>
{% if event.organisation.phone %}
<dt class="col-sm-5">Phone Number</dt> <dt class="col-sm-5">Phone Number</dt>
<dd class="col-sm-7">{{ event.organisation.phone }}</dd> <dd class="col-sm-7">{{ object.organisation.phone }}</dd>
{% endif %}
</dl> </dl>
</div> </div>
</div> </div>
@@ -46,12 +43,15 @@
<div class="card-header">Event Info</div> <div class="card-header">Event Info</div>
<div class="card-body"> <div class="card-body">
<dl class="row"> <dl class="row">
{% if event.venue %}
<dt class="col-sm-5">Event Venue</dt> <dt class="col-sm-5">Event Venue</dt>
<dd class="col-sm-7"> <dd class="col-sm-7">
{{ event.venue }} {% if object.venue %}
<a href="{% url 'venue_detail' object.venue.pk %}" class="modal-href">
{{ object.venue }}
</a>
{% endif %}
</dd> </dd>
{% endif %}
<dt class="col-sm-5">Status</dt> <dt class="col-sm-5">Status</dt>
<dd class="col-sm-7">{{ event.get_status_display }}</dd> <dd class="col-sm-7">{{ event.get_status_display }}</dd>

View File

@@ -1,47 +0,0 @@
{% load linkornone from filters %}
{% load namewithnotes from filters %}
{% if object.person %}
<div class="card card-default mb-3">
<div class="card-header">Person Details</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-6">Person</dt>
<dd class="col-sm-6">
{% if object.person %}
<a href="{% url 'person_detail' object.person.pk %}" class="modal-href">
{{ object.person|namewithnotes:'person_detail' }}
</a>
{% endif %}
</dd>
<dt class="col-sm-6">Email</dt>
<dd class="col-sm-6">{{ object.person.email|linkornone:'mailto' }}</dd>
<dt class="col-sm-6">Phone Number</dt>
<dd class="col-sm-6">{{ object.person.phone|linkornone:'tel' }}</dd>
</dl>
</div>
</div>
{% endif %}
{% if object.organisation %}
<div class="card card-default">
<div class="card-header">Organisation Details</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-6">Organisation</dt>
<dd class="col-sm-6">
{% if object.organisation %}
<a href="{% url 'organisation_detail' object.organisation.pk %}" class="modal-href">
{{ object.organisation|namewithnotes:'organisation_detail' }}
</a>
{% endif %}
</dd>
<dt class="col-sm-6">Email</dt>
<dd class="col-sm-6">{{ object.organisation.email|linkornone:'mailto' }}</dd>
<dt class="col-sm-6">Phone Number</dt>
<dd class="col-sm-6">{{ object.organisation.phone|linkornone:'tel' }}</dd>
<dt class="col-sm-6">Has SU Account</dt>
<dd class="col-sm-6">{{ event.organisation.union_account|yesno|capfirst }}</dd>
</dl>
</div>
</div>
{% endif %}

View File

@@ -1,5 +1,4 @@
{% load namewithnotes from filters %} {% load namewithnotes from filters %}
{% load markdown_tags %}
<div class="card card-info"> <div class="card card-info">
<div class="card-header">Event Info</div> <div class="card-header">Event Info</div>
<div class="card-body"> <div class="card-body">
@@ -21,7 +20,15 @@
{% if event.is_rig %} {% if event.is_rig %}
<dt class="col-sm-6">Event MIC</dt> <dt class="col-sm-6">Event MIC</dt>
<dd class="col-sm-6">{% include 'partials/linked_name.html' with profile=event.mic %}</dd> <dd class="col-sm-6">
{% if event.mic and perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{{ event.mic.name }}
</a>
{% else %}
{{ event.mic.name }}
{% endif %}
</dd>
{% endif %} {% endif %}
<dt class="col-sm-6">Status</dt> <dt class="col-sm-6">Status</dt>
@@ -47,7 +54,7 @@
<dd class="col-sm-12">&nbsp;</dd> <dd class="col-sm-12">&nbsp;</dd>
<dt class="col-sm-6">Event Description</dt> <dt class="col-sm-6">Event Description</dt>
<dd class="dont-break-out col-sm-12">{{ event.description|markdown }}</dd> <dd class="dont-break-out col-sm-12">{{ event.description|linebreaksbr }}</dd>
<dd class="col-sm-12">&nbsp;</dd> <dd class="col-sm-12">&nbsp;</dd>
@@ -64,7 +71,7 @@
{% if event.dry_hire %} {% if event.dry_hire %}
<dt class="col-sm-6">Checked In By</dt> <dt class="col-sm-6">Checked In By</dt>
<dd class="col-sm-6">{% include 'partials/linked_name.html' with profile=event.checked_in_by %}</dd> <dd class="col-sm-6">{{ object.checked_in_by.name }}</dd>
{% endif %} {% endif %}
{% if event.is_rig %} {% if event.is_rig %}

View File

@@ -1,18 +1,12 @@
<div> <h5>
<span class="badge badge-{% if event.confirmed %}success{% elif event.cancelled %}dark{% else %}warning{% endif %}">Status: {{ event.get_status_display }}</span> <span class="badge badge-{% if event.confirmed %}success{% elif event.cancelled %}dark{% else %}warning{% endif %}">Status: {{ event.get_status_display }}</span>
{% if event.is_rig %} {% if event.is_rig %}
{% if event.sum_total > 0 %} {% if event.purchase_order %}
{% if event.purchase_order %} <span class="badge badge-success">PO: {{ event.purchase_order }}</span>
<span class="badge badge-success">PO: {{ event.purchase_order }}</span> {% elif event.authorised %}
{% elif event.authorised %} <span class="badge badge-success">Authorisation: Complete <span class="fas fa-check"></span></span>
<span class="badge badge-success">Authorisation: Complete <span class="fas fa-check"></span></span> {% else %}
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %} <span class="badge badge-danger">Authorisation: <span class="fas fa-times"></span></span>
<span class="badge badge-warning"> Authorisation: Issue <span class="fas fa-exclamation-circle"></span></span>
{% elif event.auth_request_to %}
<span class="badge badge-info"> Authorisation: Sent <span class="fas fa-paper-plane"></span></span>
{% else %}
<span class="badge badge-danger">Authorisation: <span class="fas fa-times"></span></span>
{% endif %}
{% endif %} {% endif %}
{% if not event.dry_hire %} {% if not event.dry_hire %}
{% if event.riskassessment %} {% if event.riskassessment %}
@@ -20,6 +14,8 @@
{% else %} {% else %}
<span class="badge badge-danger">RA: <span class="fas fa-times"></span></span> <span class="badge badge-danger">RA: <span class="fas fa-times"></span></span>
{% endif %} {% endif %}
{% else %}
<span class="badge badge-secondary">RA: N/A</span>
{% endif %} {% endif %}
{% if not event.dry_hire %} {% if not event.dry_hire %}
{% if event.hs_done %} {% if event.hs_done %}
@@ -28,6 +24,8 @@
{% else %} {% else %}
<span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span> <span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span>
{% endif %} {% endif %}
{% else %}
<span class="badge badge-secondary">Checklist: N/A</span>
{% endif %} {% endif %}
{% if perms.RIGS.view_invoice %} {% if perms.RIGS.view_invoice %}
{% if event.invoice %} {% if event.invoice %}
@@ -43,4 +41,4 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </h5>

View File

@@ -25,30 +25,30 @@
{% endif %} {% endif %}
{% else %} {% else %}
table-warning table-warning
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row"> {% endif %}" id="event_row">
<!---Number--> <!---Number-->
<th scope="row" id="event_number">{{ event.display_id }}</th> <th scope="row" id="event_number">{{ event.display_id }}</th>
<!--Dates & Times--> <!--Dates & Times-->
<td id="event_dates"> <td id="event_dates">
<span class="text-nowrap">Start: <strong>{{ event.start_date|date:"D d/m/Y" }} <span class="text-nowrap">Start: <strong>{{ event.start_date|date:"D d/m/Y" }}</strong>
{% if event.has_start_time %} {% if event.has_start_time %}
{{ event.start_time|date:"H:i" }} {{ event.start_time|date:"H:i" }}
{% endif %}</strong> {% endif %}
</span> </span>
{% if event.end_date %} {% if event.end_date %}
<br> <br>
<span class="text-nowrap">End: {% if event.end_date != event.start_date %}<strong>{{ event.end_date|date:"D d/m/Y" }}{% endif %} <span class="text-nowrap">End: {% if event.end_date != event.start_date %}<strong>{{ event.end_date|date:"D d/m/Y" }}</strong>{% endif %}
{% if event.has_end_time %} {% if event.has_end_time %}
{{ event.end_time|date:"H:i" }} {{ event.end_time|date:"H:i" }}
{% endif %}</strong> {% endif %}
</span> </span>
{% endif %} {% endif %}
{% if not event.cancelled %} {% if not event.cancelled %}
{% if event.meet_at %} {% if event.meet_at %}
<br><span class="text-nowrap">Meet: <strong>{{ event.meet_at|date:"D d/m/Y H:i" }}</strong></span> <br><span>Crew meet: <strong>{{ event.meet_at|date:"H:i" }}</strong> {{ event.meet_at|date:"(d/m/Y)" }}</span>
{% endif %} {% endif %}
{% if event.access_at %} {% if event.access_at %}
<br><span class="text-nowrap">Access: <strong>{{ event.access_at|date:" D d/m/Y H:i" }}</strong></span> <br><span>Access at: <strong>{{ event.access_at|date:"H:i" }}</strong> {{ event.access_at|date:"(d/m/Y)" }}</span>
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
@@ -67,9 +67,9 @@
</h4> </h4>
{% if event.is_rig and not event.cancelled %} {% if event.is_rig and not event.cancelled %}
<h5> <h5>
<a href="{{ event.person.get_absolute_url }}">{{ event.person.name }}</a> {{ event.person.name }}
{% if event.organisation %} {% if event.organisation %}
for <a href="{{ event.organisation.get_absolute_url }}">{{ event.organisation.name }}</a> for {{ event.organisation.name }}
{% endif %} {% endif %}
</h5> </h5>
{% endif %} {% endif %}
@@ -90,7 +90,7 @@
</a> </a>
{% endif %} {% endif %}
{% elif event.is_rig %} {% elif event.is_rig %}
<span class="fas fa-user-slash"></span> <span class="fas fa-exclamation"></span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@@ -1,7 +0,0 @@
{% if profile and perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' profile.pk %}" class="modal-href">
{{ profile.name }}
</a>
{% else %}
{{ profile.name }}
{% endif %}

View File

@@ -2,9 +2,9 @@
{% load button from filters %} {% load button from filters %}
{% block content %} {% block content %}
<div class="row align-items-center justify-content-between py-2 align-middle"> <div class="row align-items-center justify-content-between py-2">
<div class="col-sm-12 col-md align-middle"> <div class="col-sm-12 col-md">
Key: <span class="table-success mr-1 px-2 rounded">Ready</span><span class="table-warning mr-1 px-2 rounded">Action Required</span><span class="table-danger mr-1 px-2 rounded">Needs MIC</span><span class="table-secondary mr-1 px-2 rounded">Cancelled</span><span class="table-info px-2 rounded">Non-Rig</span> Key: <span class="table-success mr-1 px-2">Ready</span><span class="table-warning mr-1 px-2">Action Required</span><span class="table-danger mr-1 px-2">Needs MIC</span><span class="table-secondary mr-1 px-2">Cancelled</span><span class="table-info px-2">Non-Rig</span>
</div> </div>
{% if perms.RIGS.add_event %} {% if perms.RIGS.add_event %}
<div class="col text-right"> <div class="col text-right">

View File

@@ -1,4 +1,5 @@
{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% block title %}Risk Assessment for Event N{{ object.event.pk|stringformat:"05d" }} {{ object.event.name }}{% endblock %}
{% load help_text from filters %} {% load help_text from filters %}
{% load yesnoi from filters %} {% load yesnoi from filters %}
{% load linkornone from filters %} {% load linkornone from filters %}
@@ -6,6 +7,7 @@
{% block content %} {% block content %}
<div class="row py-3"> <div class="row py-3">
<div class="col-12"> <div class="col-12">
<h3>Risk Assessment for Event N{{ object.event.pk|stringformat:"05d" }} {{ object.event.name }}</h3>
<div class="card card-default mb-3"> <div class="card card-default mb-3">
<div class="card-header">General</div> <div class="card-header">General</div>
<div class="card-body"> <div class="card-body">
@@ -45,15 +47,15 @@
<dd class="col-sm-6"> <dd class="col-sm-6">
{{ object.big_power|yesnoi:'invert' }} {{ object.big_power|yesnoi:'invert' }}
</dd> </dd>
<dt class="col-sm-6">{{ object|help_text:'power_mic'|safe }}</dt> <dt class="col-sm-6">{{ object|help_text:'power_mic' }}</dt>
<dd class="col-sm-6"> <dd class="col-sm-6">
{{ object.power_mic.name|default:'None' }} {{ object.power_mic.name|default:'None' }}
</dd> </dd>
<dt class="col-sm-6">{{ object|help_text:'outside' }}</dt> <dt class="col-sm-6">{{ object|help_text:'generators' }}</dt>
<dd class="col-sm-6"> <dd class="col-sm-6">
{{ object.outside|yesnoi:'invert' }} {{ object.outside|yesnoi:'invert' }}
</dd> </dd>
<dt class="col-sm-6">{{ object|help_text:'generators' }}</dt> <dt class="col-sm-6">{{ object|help_text:'outside' }}</dt>
<dd class="col-sm-6"> <dd class="col-sm-6">
{{ object.generators|yesnoi:'invert' }} {{ object.generators|yesnoi:'invert' }}
</dd> </dd>
@@ -95,64 +97,58 @@
</dl> </dl>
</div> </div>
</div> </div>
<div class="row"> <div class="card card-default mb-3">
<div class="col-lg-6 col-12"> <div class="card-header">Site Details</div>
<div class="card card-default mb-3"> <div class="card-body">
<div class="card-header">Site Details</div> <dl class="row">
<div class="card-body"> <dt class="col-sm-6">{{ object|help_text:'known_venue' }}</dt>
<dl class="row"> <dd class="col-sm-6">
<dt class="col-10">{{ object|help_text:'known_venue' }}</dt> {{ object.known_venue|yesnoi:'invert' }}
<dd class="col-2"> </dd>
{{ object.known_venue|yesnoi:'invert' }} <dt class="col-sm-6">{{ object|help_text:'safe_loading'|safe }}</dt>
</dd> <dd class="col-sm-6">
<dt class="col-10">{{ object|help_text:'safe_loading'|safe }}</dt> {{ object.safe_loading|yesnoi:'invert' }}
<dd class="col-2"> </dd>
{{ object.safe_loading|yesnoi:'invert' }} <dt class="col-sm-6">{{ object|help_text:'safe_storage' }}</dt>
</dd> <dd class="col-sm-6">
<dt class="col-10">{{ object|help_text:'safe_storage' }}</dt> {{ object.safe_storage|yesnoi:'invert' }}
<dd class="col-2"> </dd>
{{ object.safe_storage|yesnoi:'invert' }} <dt class="col-sm-6">{{ object|help_text:'area_outside_of_control' }}</dt>
</dd> <dd class="col-sm-6">
<dt class="col-10">{{ object|help_text:'area_outside_of_control' }}</dt> {{ object.area_outside_of_control|yesnoi:'invert' }}
<dd class="col-2"> </dd>
{{ object.area_outside_of_control|yesnoi:'invert' }} <dt class="col-sm-6">{{ object|help_text:'barrier_required' }}</dt>
</dd> <dd class="col-sm-6">
<dt class="col-10">{{ object|help_text:'barrier_required' }}</dt> {{ object.barrier_required|yesnoi:'invert' }}
<dd class="col-2"> </dd>
{{ object.barrier_required|yesnoi:'invert' }} <dt class="col-sm-6">{{ object|help_text:'nonstandard_emergency_procedure' }}</dt>
</dd> <dd class="col-sm-6">
<dt class="col-10">{{ object|help_text:'nonstandard_emergency_procedure' }}</dt> {{ object.nonstandard_emergency_procedure|yesnoi:'invert' }}
<dd class="col-2"> </dd>
{{ object.nonstandard_emergency_procedure|yesnoi:'invert' }} </dl>
</dd>
</dl>
</div>
</div>
</div> </div>
<div class="col-lg-6 col-12"> </div>
<div class="card card-default mb-3"> <div class="card card-default mb-3">
<div class="card-header">Structures</div> <div class="card-header">Structures</div>
<div class="card-body"> <div class="card-body">
<dl class="row"> <dl class="row">
<dt class="col-10">{{ object|help_text:'special_structures' }}</dt> <dt class="col-sm-6">{{ object|help_text:'special_structures' }}</dt>
<dd class="col-2"> <dd class="col-sm-6">
{{ object.special_structures|yesnoi:'invert' }} {{ object.special_structures|yesnoi:'invert' }}
</dd> </dd>
<dt class="col-10">{{ object|help_text:'suspended_structures' }}</dt> <dt class="col-sm-6">{{ object|help_text:'suspended_structures' }}</dt>
<dd class="col-2"> <dd class="col-sm-6">
{{ object.suspended_structures|yesnoi:'invert' }} {{ object.suspended_structures|yesnoi:'invert' }}
</dd> </dd>
<dt class="col-12">{{ object|help_text:'persons_responsible_structures' }}</dt> <dt class="col-sm-6">{{ object|help_text:'persons_responsible_structures' }}</dt>
<dd class="col-12"> <dd class="col-sm-6">
{{ object.persons_responsible_structures.name|default:'N/A'|linebreaks }} {{ object.persons_responsible_structures.name|default:'N/A'|linebreaks }}
</dd> </dd>
<dt class="col-12">{{ object|help_text:'rigging_plan'|safe }}</dt> <dt class="col-6">{{ object|help_text:'rigging_plan'|safe }}</dt>
<dd class="col-12"> <dd class="col-6">
{{ object.rigging_plan|linkornone|default:'N/A' }} {{ object.rigging_plan|linkornone }}
</dd> </dd>
</dl> </dl>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,17 +5,26 @@
{% load nice_errors from filters %} {% load nice_errors from filters %}
{% block css %} {% block css %}
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/> {{ block.super }}
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
{% endblock %} {% endblock %}
{% block preload_js %} {% block preload_js %}
<script src="{% static 'js/selects.js' %}" async></script> {{ block.super }}
<script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script src="{% static 'js/autocompleter.js' %}"></script> {{ block.super }}
<script src="{% static 'js/jquery-ui.js' %}"></script><!--TODO optimise--->
<script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/modal.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script> <script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/autocompleter.js' %}"></script>
<script> <script>
function parseBool(str) { function parseBool(str) {
return str.toLowerCase() == 'true'; return str.toLowerCase() == 'true';

View File

@@ -114,8 +114,10 @@ def orderby(request, field, attr):
return dict_.urlencode() return dict_.urlencode()
# Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
@register.filter(needs_autoescape=True) # Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
@register.filter(needs_autoescape=True)
def get_field(obj, field, autoescape=True): def get_field(obj, field, autoescape=True):
value = getattr(obj, field) value = getattr(obj, field)
if(isinstance(value, bool)): if(isinstance(value, bool)):
@@ -171,7 +173,7 @@ def title_spaced(string):
@register.filter(needs_autoescape=True) @register.filter(needs_autoescape=True)
def namewithnotes(obj, url, autoescape=True): def namewithnotes(obj, url, autoescape=True):
if hasattr(obj, 'notes') and obj.notes is not None and len(obj.notes) > 0: if hasattr(obj, 'notes') and obj.notes is not None and len(obj.notes) > 0:
return mark_safe(obj.name + " <a href='{}'><span class='fas fa-sticky-note'></span></a>".format(reverse(url, kwargs={'pk': obj.pk}))) return mark_safe(obj.name + " <a href='{}'><span class='far fa-sticky-note'></span></a>".format(reverse(url, kwargs={'pk': obj.pk})))
else: else:
return obj.name return obj.name
@@ -210,25 +212,8 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
clazz += " btn-primary " clazz += " btn-primary "
icon = "fa-plus" icon = "fa-plus"
text = "New" text = "New"
elif type == 'copy':
return {'copy': True, 'id': id, 'style': style}
elif type == 'search': elif type == 'search':
return {'submit': True, 'class': 'btn-info', 'icon': 'fa-search', 'text': 'Search', 'id': id, 'style': style} return {'submit': True, 'class': 'btn-info', 'icon': 'fa-search', 'text': 'Search', 'id': id, 'style': style}
elif type == 'submit': elif type == 'submit':
return {'submit': True, 'class': 'btn-primary', 'icon': 'fa-save', 'text': 'Save', 'id': id, 'style': style} return {'submit': True, 'class': 'btn-primary', 'icon': 'fa-save', 'text': 'Save', 'id': id, 'style': style}
return {'target': url, 'pk': pk, 'class': clazz, 'icon': icon, 'text': text, 'id': id, 'style': style} return {'target': url, 'pk': pk, 'class': clazz, 'icon': icon, 'text': text, 'id': id, 'style': style}
@register.simple_tag # TODO Can these be done with annotation/aggregation?
def invoices_waiting():
return len(models.Event.objects.waiting_invoices())
@register.simple_tag
def invoices_outstanding():
return len(models.Invoice.objects.outstanding_invoices())
@register.simple_tag
def total_invoices_todo():
return len(models.Event.objects.waiting_invoices()) + len(models.Invoice.objects.outstanding_invoices())

View File

@@ -1,56 +0,0 @@
from bs4 import BeautifulSoup
from django import template
from django.utils.safestring import mark_safe
import markdown
__author__ = 'ghost'
register = template.Library()
@register.filter(name="markdown")
def markdown_filter(text, input_format='html'):
# markdown library can't handle text=None
if text is None:
return text
html = markdown.markdown(text, extensions=['markdown.extensions.nl2br'])
# Convert format to RML
soup = BeautifulSoup(html, "html.parser")
# Prevent code injection
for script in soup('script'):
script.string = "Your script shall not pass!"
if input_format == 'html':
return mark_safe('<div class="markdown">' + str(soup) + '</div>')
elif input_format == 'rml':
# Image aren't supported so remove them
for img in soup('img'):
img.parent.extract()
# <code> should become <font>
for c in soup('code'):
c.name = 'font'
c['face'] = "Courier"
# blockquotes don't exist but we can still do something to show
for bq in soup('blockquote'):
bq.name = 'pre'
bq.string = bq.text
for alist in soup.find_all(['ul', 'ol']):
alist['style'] = alist.name
for li in alist.find_all('li', recursive=False):
text = li.find(text=True)
text.wrap(soup.new_tag('p'))
if alist.parent.name != 'li':
indent = soup.new_tag('indent')
indent['left'] = '0.6cm'
alist.wrap(indent)
# Paragraphs have a different tag
for p in soup('p'):
p.name = 'para'
return mark_safe(str(soup))

View File

@@ -25,15 +25,6 @@ def ra(basic_event, admin_user):
ra.delete() ra.delete()
@pytest.fixture
def medium_ra(ra):
ra.big_power = True
ra.save()
yield ra
ra.big_power = False
ra.save()
@pytest.fixture @pytest.fixture
def venue(db): def venue(db):
venue = models.Venue.objects.create(name="Venue 1") venue = models.Venue.objects.create(name="Venue 1")
@@ -42,7 +33,7 @@ def venue(db):
@pytest.fixture # TODO parameterise with Event sizes @pytest.fixture # TODO parameterise with Event sizes
def checklist(basic_event, venue, admin_user, ra): def checklist(basic_event, venue, admin_user):
checklist = models.EventChecklist.objects.create(event=basic_event, power_mic=admin_user, safe_parking=False, checklist = models.EventChecklist.objects.create(event=basic_event, power_mic=admin_user, safe_parking=False,
safe_packing=False, exits=False, trip_hazard=False, warning_signs=False, safe_packing=False, exits=False, trip_hazard=False, warning_signs=False,
ear_plugs=False, hs_location="Locked away safely", ear_plugs=False, hs_location="Locked away safely",
@@ -53,7 +44,7 @@ def checklist(basic_event, venue, admin_user, ra):
@pytest.fixture @pytest.fixture
def many_events(db, admin_user, scope="class"): def many_events(db, scope="class"):
many_events = { many_events = {
# produce 7 normal events - 5 current # produce 7 normal events - 5 current
1: models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6), 1: models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6),
@@ -83,12 +74,12 @@ def many_events(db, admin_user, scope="class"):
10: models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, 10: models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True,
description="dryhire today"), description="dryhire today"),
11: models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, 11: models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True,
checked_in_by=admin_user, checked_in_by=cls.profile,
description="dryhire today, checked in"), description="dryhire today, checked in"),
12: models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True, 12: models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True,
status=models.Event.BOOKED, description="dryhire past"), status=models.Event.BOOKED, description="dryhire past"),
13: models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True, 13: models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
checked_in_by=admin_user, description="dryhire past checked in"), checked_in_by=cls.profile, description="dryhire past checked in"),
14: models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True, 14: models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True,
status=models.Event.CANCELLED, description="dryhire today cancelled"), status=models.Event.CANCELLED, description="dryhire today cancelled"),

View File

@@ -53,7 +53,7 @@ class EventDetail(BasePage):
# TODO Refactor into regions to match template fragmentation # TODO Refactor into regions to match template fragmentation
_event_name_selector = (By.XPATH, '//h2') _event_name_selector = (By.XPATH, '//h2')
_person_panel_selector = (By.XPATH, '//div[contains(text(), "Person Details")]/..') _person_panel_selector = (By.XPATH, '//div[contains(text(), "Contact Details")]/..')
_name_selector = (By.XPATH, '//dt[text()="Person"]/following-sibling::dd[1]') _name_selector = (By.XPATH, '//dt[text()="Person"]/following-sibling::dd[1]')
_email_selector = (By.XPATH, '//dt[text()="Email"]/following-sibling::dd[1]') _email_selector = (By.XPATH, '//dt[text()="Email"]/following-sibling::dd[1]')
_phone_selector = (By.XPATH, '//dt[text()="Phone Number"]/following-sibling::dd[1]') _phone_selector = (By.XPATH, '//dt[text()="Phone Number"]/following-sibling::dd[1]')
@@ -96,7 +96,7 @@ class CreateEvent(FormPage):
_warning_selector = (By.XPATH, '/html/body/div[1]/div[1]') _warning_selector = (By.XPATH, '/html/body/div[1]/div[1]')
form_items = { form_items = {
'description': (regions.SimpleMDETextArea, (By.ID, 'id_description')), 'description': (regions.TextBox, (By.ID, 'id_description')),
'name': (regions.TextBox, (By.ID, 'id_name')), 'name': (regions.TextBox, (By.ID, 'id_name')),
'start_date': (regions.DatePicker, (By.ID, 'id_start_date')), 'start_date': (regions.DatePicker, (By.ID, 'id_start_date')),
@@ -110,7 +110,7 @@ class CreateEvent(FormPage):
'collected_by': (regions.TextBox, (By.ID, 'id_collector')), 'collected_by': (regions.TextBox, (By.ID, 'id_collector')),
'po': (regions.TextBox, (By.ID, 'id_purchase_order')), 'po': (regions.TextBox, (By.ID, 'id_purchase_order')),
'notes': (regions.SimpleMDETextArea, (By.ID, 'id_notes')) 'notes': (regions.TextBox, (By.ID, 'id_notes'))
} }
def select_event_type(self, type_name): def select_event_type(self, type_name):
@@ -230,11 +230,9 @@ class CreateEventChecklist(FormPage):
URL_TEMPLATE = 'event/{event_id}/checklist' URL_TEMPLATE = 'event/{event_id}/checklist'
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]") _submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
_power_mic_selector = (By.XPATH, "//div[select[@id='id_power_mic']]") _power_mic_selector = (By.XPATH, "//div[@id='id_power_mic-group']//div[contains(@class, 'bootstrap-select')]")
_add_vehicle_locator = (By.XPATH, "//button[contains(., 'Vehicle')]") _add_vehicle_locator = (By.XPATH, "//button[contains(., 'Vehicle')]")
_add_crew_locator = (By.XPATH, "//button[contains(., 'Crew')]") _add_crew_locator = (By.XPATH, "//button[contains(., 'Crew')]")
_vehicle_row_locator = ('xpath', "//tr[@id[starts-with(., 'vehicle') and not(contains(.,'new'))]]")
_crew_row_locator = ('xpath', "//tr[@id[starts-with(., 'crew') and not(contains(.,'new'))]]")
form_items = { form_items = {
'safe_parking': (regions.CheckBox, (By.ID, 'id_safe_parking')), 'safe_parking': (regions.CheckBox, (By.ID, 'id_safe_parking')),
@@ -273,61 +271,11 @@ class CreateEventChecklist(FormPage):
def power_mic(self): def power_mic(self):
return regions.BootstrapSelectElement(self, self.find_element(*self._power_mic_selector)) return regions.BootstrapSelectElement(self, self.find_element(*self._power_mic_selector))
@property
def vehicles(self):
return [self.VehicleRow(self, el) for el in self.find_elements(*self._vehicle_row_locator)]
class VehicleRow(Region):
_name_locator = ('xpath', ".//input")
_select_locator = ('xpath', ".//div[contains(@class,'bootstrap-select')]/..")
@property
def name(self):
return regions.TextBox(self, self.root.find_element(*self._name_locator))
@property
def vehicle(self):
return regions.BootstrapSelectElement(self, self.root.find_element(*self._select_locator))
@property
def crew(self):
return [self.CrewRow(self, el) for el in self.find_elements(*self._crew_row_locator)]
class CrewRow(Region):
_select_locator = ('xpath', ".//div[contains(@class,'bootstrap-select')]/..")
_start_time_locator = ('xpath', ".//input[@name[starts-with(., 'start') and not(contains(.,'new'))]]")
_end_time_locator = ('xpath', ".//input[@name[starts-with(., 'end') and not(contains(.,'new'))]]")
_role_locator = ('xpath', ".//input[@name[starts-with(., 'role') and not(contains(.,'new'))]]")
@property
def crewmember(self):
return regions.BootstrapSelectElement(self, self.root.find_element(*self._select_locator))
@property
def start_time(self):
return regions.DateTimePicker(self, self.root.find_element(*self._start_time_locator))
@property
def end_time(self):
return regions.DateTimePicker(self, self.root.find_element(*self._end_time_locator))
@property
def role(self):
return regions.TextBox(self, self.root.find_element(*self._role_locator))
@property @property
def success(self): def success(self):
return '{event_id}' not in self.driver.current_url return '{event_id}' not in self.driver.current_url
class EditEventChecklist(CreateEventChecklist):
URL_TEMPLATE = '/event/checklist/{pk}/edit'
@property
def success(self):
return 'edit' not in self.driver.current_url
class GenericList(BasePage): class GenericList(BasePage):
_search_selector = (By.CSS_SELECTOR, 'div.input-group:nth-child(2) > input:nth-child(1)') _search_selector = (By.CSS_SELECTOR, 'div.input-group:nth-child(2) > input:nth-child(1)')
_search_go_selector = (By.ID, 'id_search') _search_go_selector = (By.ID, 'id_search')

View File

@@ -1,7 +1,7 @@
from pypom import Region from pypom import Region
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from PyRIGS.tests.regions import TextBox, Modal, SimpleMDETextArea from PyRIGS.tests.regions import TextBox, Modal
class Header(Region): class Header(Region):
@@ -42,7 +42,7 @@ class ItemModal(Modal):
form_items = { form_items = {
'name': (TextBox, (By.ID, 'item_name')), 'name': (TextBox, (By.ID, 'item_name')),
'description': (SimpleMDETextArea, (By.ID, 'item_description')), 'description': (TextBox, (By.ID, 'item_description')),
'quantity': (TextBox, (By.ID, 'item_quantity')), 'quantity': (TextBox, (By.ID, 'item_quantity')),
'price': (TextBox, (By.ID, 'item_cost')) 'price': (TextBox, (By.ID, 'item_cost'))
} }

View File

@@ -16,7 +16,6 @@ from RIGS import models
from RIGS.tests import regions from RIGS.tests import regions
from . import pages from . import pages
import pytest import pytest
import time as t
pytestmark = pytest.mark.django_db(transaction=True) pytestmark = pytest.mark.django_db(transaction=True)
@@ -638,190 +637,270 @@ class TestCalendar(BaseRigboardTest):
else: else:
self.assertNotContains(response, "TE E" + str(test) + " ") self.assertNotContains(response, "TE E" + str(test) + " ")
def test_calendar_buttons(self): # If FullCalendar fails to load for whatever reason, the buttons don't work
self.page = pages.CalendarPage(self.driver, self.live_server_url).open()
self.assertIn(timezone.now().strftime("%Y-%m"), self.driver.current_url)
def test_calendar_buttons(logged_in_browser, live_server): # If FullCalendar fails to load for whatever reason, the buttons don't work target_date = datetime.date(2020, 1, 1)
page = pages.CalendarPage(logged_in_browser.driver, live_server.url).open() self.page.target_date.set_value(target_date)
assert timezone.now().strftime("%Y-%m") in logged_in_browser.url self.page.go()
self.assertIn(self.page.target_date.value.strftime("%Y-%m"), self.driver.current_url)
target_date = datetime.date(2020, 1, 1) self.page.next()
page.target_date.set_value(target_date) target_date += datetime.timedelta(days=32)
page.go() self.assertIn(target_date.strftime("%m"), self.driver.current_url)
assert page.target_date.value.strftime("%Y-%m") in logged_in_browser.url
page.next()
target_date += datetime.timedelta(days=32)
assert target_date.strftime("%m") in logged_in_browser.url
def test_ra_edit(logged_in_browser, live_server, ra): @screenshot_failure_cls
page = pages.EditRiskAssessment(logged_in_browser.driver, live_server.url, pk=ra.pk).open() class TestHealthAndSafety(BaseRigboardTest):
page.nonstandard_equipment = nse = True def setUp(self):
page.general_notes = gn = "There are some notes, but I've not written them here as that would be helpful" super().setUp()
page.submit() self.profile = models.Profile.objects.get_or_create(
assert not page.success first_name='Test',
page.supervisor_consulted = True last_name='TEC User',
page.submit() username='eventtest',
assert page.success email='teccie@functional.test',
# Check that data is right is_superuser=True # lazily grant all permissions
ra = models.RiskAssessment.objects.get(pk=ra.pk) )[0]
assert ra.general_notes == gn self.venue = models.Venue.objects.create(name="Venue 1")
assert ra.nonstandard_equipment == nse
self.testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=6),
description="start future no end",
purchase_order='TESTPO',
person=self.client,
venue=self.venue)
self.testEvent2 = models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=6),
description="start future no end",
purchase_order='TESTPO',
person=self.client,
venue=self.venue)
self.testEvent3 = models.Event.objects.create(name="TE E3", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=6),
description="start future no end",
purchase_order='TESTPO',
person=self.client,
venue=self.venue)
self.testRA = models.RiskAssessment.objects.create(event=self.testEvent2, supervisor_consulted=False, nonstandard_equipment=False,
nonstandard_use=False,
contractors=False,
other_companies=False,
crew_fatigue=False,
big_power=False,
generators=False,
other_companies_power=False,
nonstandard_equipment_power=False,
multiple_electrical_environments=False,
noise_monitoring=False,
known_venue=True,
safe_loading=True,
safe_storage=True,
area_outside_of_control=False,
barrier_required=False,
nonstandard_emergency_procedure=False,
special_structures=False,
suspended_structures=False,
outside=False)
self.testRA2 = models.RiskAssessment.objects.create(event=self.testEvent3, supervisor_consulted=False, nonstandard_equipment=False,
nonstandard_use=False,
contractors=False,
other_companies=False,
crew_fatigue=False,
big_power=True,
generators=False,
other_companies_power=False,
nonstandard_equipment_power=False,
multiple_electrical_environments=False,
noise_monitoring=False,
known_venue=True,
safe_loading=True,
safe_storage=True,
area_outside_of_control=False,
barrier_required=False,
nonstandard_emergency_procedure=False,
special_structures=False,
suspended_structures=False,
outside=False)
self.page = pages.EventDetail(self.driver, self.live_server_url, event_id=self.testEvent.pk).open()
def small_ec(page, admin_user): # TODO Can I loop through all the boolean fields and test them at once?
page.safe_parking = True def test_ra_creation(self):
page.safe_packing = True self.page = pages.CreateRiskAssessment(self.driver, self.live_server_url, event_id=self.testEvent.pk).open()
page.exits = True
page.trip_hazard = True
page.warning_signs = True
page.ear_plugs = True
page.hs_location = "The Moon"
page.extinguishers_location = "With the rest of the fire"
# If we do this first the search fails, for ... reasons
page.power_mic.search(admin_user.name)
page.power_mic.toggle()
assert not page.power_mic.is_open
page.earthing = True
page.rcds = True
page.supply_test = True
page.pat = True
# Check there are no defaults
self.assertIsNone(self.page.nonstandard_equipment)
def test_ec_create_small(logged_in_browser, live_server, admin_user, ra): # No database side validation, only HTML5.
page = pages.CreateEventChecklist(logged_in_browser.driver, live_server.url, event_id=ra.event.pk).open()
small_ec(page, admin_user)
page.submit()
assert page.success
self.page.nonstandard_equipment = False
self.page.nonstandard_use = False
self.page.contractors = False
self.page.other_companies = False
self.page.crew_fatigue = False
self.page.general_notes = "There are no notes."
self.page.big_power = False
self.page.outside = False
self.page.power_mic.search(self.profile.name)
self.page.power_mic.set_option(self.profile.name, True)
# TODO This should not be necessary, normally closes automatically
self.page.power_mic.toggle()
self.assertFalse(self.page.power_mic.is_open)
self.page.generators = False
self.page.other_companies_power = False
self.page.nonstandard_equipment_power = False
self.page.multiple_electrical_environments = False
self.page.power_notes = "Remember to bring some power"
self.page.noise_monitoring = False
self.page.sound_notes = "Loud, but not too loud"
self.page.known_venue = False
self.page.safe_loading = False
self.page.safe_storage = False
self.page.area_outside_of_control = False
self.page.barrier_required = False
self.page.nonstandard_emergency_procedure = False
self.page.special_structures = False
# self.page.persons_responsible_structures = "Nobody and her cat, She"
def test_ec_create_medium(logged_in_browser, live_server, admin_user, medium_ra): self.page.suspended_structures = True
page = pages.CreateEventChecklist(logged_in_browser.driver, live_server.url, event_id=medium_ra.event.pk).open() # TODO Test for this proper
self.page.rigging_plan = "https://nottinghamtec.sharepoint.com/test/"
self.page.submit()
self.assertFalse(self.page.success)
page.safe_parking = True self.page.suspended_structures = False
page.safe_packing = True self.page.submit()
page.exits = True self.assertTrue(self.page.success)
page.trip_hazard = True
page.warning_signs = True
page.ear_plugs = True
page.hs_location = "Death Valley"
page.extinguishers_location = "With the rest of the fire"
# If we do this first the search fails, for ... reasons
page.power_mic.search(admin_user.name)
page.power_mic.toggle()
assert not page.power_mic.is_open
# Gotta scroll to make the button clickable # Test that we can't make another one
logged_in_browser.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") self.page = pages.CreateRiskAssessment(self.driver, self.live_server_url, event_id=self.testEvent.pk).open()
self.assertIn('edit', self.driver.current_url)
page.earthing = True def test_ra_edit(self):
page.pat = True self.page = pages.EditRiskAssessment(self.driver, self.live_server_url, pk=self.testRA.pk).open()
page.source_rcd = True self.page.nonstandard_equipment = nse = True
page.labelling = True self.page.general_notes = gn = "There are some notes, but I've not written them here as that would be helpful"
page.fd_voltage_l1 = 240 self.page.submit()
page.fd_voltage_l2 = 235 self.assertFalse(self.page.success)
page.fd_voltage_l3 = 0 self.page.supervisor_consulted = True
page.fd_phase_rotation = True self.page.submit()
page.fd_earth_fault = "1.21" self.assertTrue(self.page.success)
page.fd_pssc = 1984 # Check that data is right
page.w1_description = "In the carpark, by the bins" ra = models.RiskAssessment.objects.get(pk=self.testRA.pk)
page.w1_polarity = True self.assertEqual(ra.general_notes, gn)
page.w1_voltage = 240 self.assertEqual(ra.nonstandard_equipment, nse)
page.w1_earth_fault = "0.42"
page.submit() def test_ec_create_small(self):
assert page.success self.page = pages.CreateEventChecklist(self.driver, self.live_server_url, event_id=self.testEvent2.pk).open()
self.page.safe_parking = True
self.page.safe_packing = True
self.page.exits = True
self.page.trip_hazard = True
self.page.warning_signs = True
self.page.ear_plugs = True
self.page.hs_location = "The Moon"
self.page.extinguishers_location = "With the rest of the fire"
# If we do this first the search fails, for ... reasons
self.page.power_mic.search(self.profile.name)
self.page.power_mic.toggle()
self.assertFalse(self.page.power_mic.is_open)
def test_ec_create_vehicle(logged_in_browser, live_server, admin_user, checklist): # Gotta scroll to make the button clickable
page = pages.EditEventChecklist(logged_in_browser.driver, live_server.url, pk=checklist.pk).open() self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
small_ec(page, admin_user)
page.add_vehicle()
assert len(page.vehicles) == 1
vehicle_name = 'Brian'
page.vehicles[0].name.set_value(vehicle_name)
# Appears we're moving too fast for javascript...
t.sleep(1)
page.vehicles[0].vehicle.search(admin_user.first_name)
t.sleep(1)
page.submit()
assert page.success
# Check data is correct
checklist.refresh_from_db()
vehicle = models.EventChecklistVehicle.objects.get(checklist=checklist.pk)
assert vehicle_name == vehicle.vehicle
self.page.earthing = True
self.page.rcds = True
self.page.supply_test = True
self.page.pat = True
# TODO Test validation of end before start self.page.submit()
def test_ec_create_crew(logged_in_browser, live_server, admin_user, checklist): self.assertTrue(self.page.success)
page = pages.EditEventChecklist(logged_in_browser.driver, live_server.url, pk=checklist.pk).open()
small_ec(page, admin_user)
page.add_crew()
assert len(page.crew) == 1
role = "MIC"
start_time = timezone.make_aware(datetime.datetime(2015, 1, 1, 9, 0))
end_time = timezone.make_aware(datetime.datetime(2015, 1, 1, 10, 30))
crew = page.crew[0]
t.sleep(2)
crew.crewmember.search(admin_user.first_name)
t.sleep(2)
crew.role.set_value(role)
crew.start_time.set_value(start_time)
crew.end_time.set_value(end_time)
page.submit()
assert page.success
# Check data is correct
crew_obj = models.EventChecklistCrew.objects.get(checklist=checklist.pk)
assert admin_user.pk == crew_obj.crewmember.pk
assert role == crew_obj.role
assert start_time == crew_obj.start
assert end_time == crew_obj.end
def test_ec_create_medium(self):
self.page = pages.CreateEventChecklist(self.driver, self.live_server_url, event_id=self.testEvent3.pk).open()
# TODO Can I loop through all the boolean fields and test them at once? self.page.safe_parking = True
def test_ra_creation(logged_in_browser, live_server, admin_user, basic_event): self.page.safe_packing = True
page = pages.CreateRiskAssessment(logged_in_browser.driver, live_server.url, event_id=basic_event.pk).open() self.page.exits = True
self.page.trip_hazard = True
self.page.warning_signs = True
self.page.ear_plugs = True
self.page.hs_location = "Death Valley"
self.page.extinguishers_location = "With the rest of the fire"
# If we do this first the search fails, for ... reasons
self.page.power_mic.search(self.profile.name)
self.page.power_mic.toggle()
self.assertFalse(self.page.power_mic.is_open)
# Check there are no defaults # Gotta scroll to make the button clickable
assert page.nonstandard_equipment is None self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# No database side validation, only HTML5. self.page.earthing = True
page.nonstandard_equipment = False self.page.pat = True
page.nonstandard_use = False self.page.source_rcd = True
page.contractors = False self.page.labelling = True
page.other_companies = False self.page.fd_voltage_l1 = 240
page.crew_fatigue = False self.page.fd_voltage_l2 = 235
page.general_notes = "There are no notes." self.page.fd_voltage_l3 = 0
page.big_power = False self.page.fd_phase_rotation = True
page.outside = False self.page.fd_earth_fault = 666
page.power_mic.search(admin_user.first_name) self.page.fd_pssc = 1984
page.generators = False self.page.w1_description = "In the carpark, by the bins"
page.other_companies_power = False self.page.w1_polarity = True
page.nonstandard_equipment_power = False self.page.w1_voltage = 240
page.multiple_electrical_environments = False self.page.w1_earth_fault = 333
page.power_notes = "Remember to bring some power"
page.noise_monitoring = False
page.sound_notes = "Loud, but not too loud"
page.known_venue = False
page.safe_loading = False
page.safe_storage = False
page.area_outside_of_control = False
page.barrier_required = False
page.nonstandard_emergency_procedure = False
page.special_structures = False
# self.page.persons_responsible_structures = "Nobody and her cat, She"
page.suspended_structures = True self.page.submit()
# TODO Test for this proper self.assertTrue(self.page.success)
page.rigging_plan = "https://nottinghamtec.sharepoint.com/test/"
page.submit()
assert not page.success
page.suspended_structures = False def test_ec_create_extras(self):
page.submit() eid = self.testEvent2.pk
assert page.success self.page = pages.CreateEventChecklist(self.driver, self.live_server_url, event_id=eid).open()
self.page.add_vehicle()
self.page.add_crew()
self.page.safe_parking = True
self.page.safe_packing = True
self.page.exits = True
self.page.trip_hazard = True
self.page.warning_signs = True
self.page.ear_plugs = True
self.page.hs_location = "The Moon"
self.page.extinguishers_location = "With the rest of the fire"
# If we do this first the search fails, for ... reasons
self.page.power_mic.search("Test") # FIXME
self.page.power_mic.toggle()
self.assertFalse(self.page.power_mic.is_open)
def test_ra_no_duplicates(logged_in_browser, live_server, ra): vehicle_name = 'Brian'
# Test that we can't make another one self.driver.find_element(By.XPATH, '//*[@name="vehicle_-1"]').send_keys(vehicle_name)
page = pages.CreateRiskAssessment(logged_in_browser.driver, live_server.url, event_id=ra.event.pk).open() driver = base_regions.BootstrapSelectElement(self.page, self.driver.find_element(By.XPATH, '//tr[@id="vehicles_-1"]//div[contains(@class, "bootstrap-select")]'))
assert 'edit' in logged_in_browser.url driver.search(self.profile.name)
crew = self.profile
role = "MIC"
crew_select = base_regions.BootstrapSelectElement(self.page, self.driver.find_element(By.XPATH, '//tr[@id="crew_-1"]//div[contains(@class, "bootstrap-select")]'))
start_time = base_regions.DateTimePicker(self.page, self.driver.find_element(By.XPATH, '//*[@name="start_-1"]'))
end_time = base_regions.DateTimePicker(self.page, self.driver.find_element(By.XPATH, '//*[@name="end_-1"]'))
start_time.set_value(timezone.make_aware(datetime.datetime(2015, 1, 1, 9, 0)))
# TODO Test validation of end before start
end_time.set_value(timezone.make_aware(datetime.datetime(2015, 1, 1, 10, 30)))
crew_select.search(crew.name)
self.driver.find_element(By.XPATH, '//*[@name="role_-1"]').send_keys(role)
self.page.earthing = True
self.page.rcds = True
self.page.supply_test = True
self.page.pat = True
self.page.submit()
self.assertTrue(self.page.success)
checklist = models.EventChecklist.objects.get(event=eid)
vehicle = models.EventChecklistVehicle.objects.get(checklist=checklist.pk)
self.assertEqual(vehicle_name, vehicle.vehicle)
crew_obj = models.EventChecklistCrew.objects.get(checklist=checklist.pk)
self.assertEqual(crew.pk, crew_obj.crewmember.pk)
self.assertEqual(role, crew_obj.role)

View File

@@ -3,8 +3,6 @@ from datetime import date
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.safestring import SafeText
from RIGS.templatetags.markdown_tags import markdown_filter
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from pytest_django.asserts import assertRedirects, assertNotContains, assertContains from pytest_django.asserts import assertRedirects, assertNotContains, assertContains
@@ -172,7 +170,6 @@ class TestInvoiceDelete(TestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
is_active=True, is_staff=True) is_active=True, is_staff=True)
cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
cls.events = { cls.events = {
1: models.Event.objects.create(name="TE E1", start_date=date.today()), 1: models.Event.objects.create(name="TE E1", start_date=date.today()),
2: models.Event.objects.create(name="TE E2", start_date=date.today()) 2: models.Event.objects.create(name="TE E2", start_date=date.today())
@@ -284,11 +281,11 @@ def test_xframe_headers(admin_client, basic_event):
response = admin_client.get(event_url, follow=True) response = admin_client.get(event_url, follow=True)
with pytest.raises(KeyError): with pytest.raises(KeyError):
response.headers["X-Frame-Options"] response._headers["X-Frame-Options"]
response = admin_client.get(login_url, follow=True) response = admin_client.get(login_url, follow=True)
with pytest.raises(KeyError): with pytest.raises(KeyError):
response.headers["X-Frame-Options"] response._headers["X-Frame-Options"]
def test_oembed(client, basic_event): def test_oembed(client, basic_event):
@@ -366,215 +363,6 @@ def test_checklist_review(admin_client, admin_user, checklist):
def test_ra_redirect(admin_client, admin_user, ra): def test_ra_redirect(admin_client, admin_user, ra):
request_url = reverse('event_ra', kwargs={'pk': ra.event.pk}) request_url = reverse('event_ra', kwargs={'pk': ra.event.pk})
expected_url = reverse('ra_edit', kwargs={'pk': ra.pk}) expected_url = reverse('ra_edit', kwargs={'pk': ra.pk})
response = admin_client.get(request_url, follow=True) response = admin_client.get(request_url, follow=True)
assertRedirects(response, expected_url, status_code=302, target_status_code=200) assertRedirects(response, expected_url, status_code=302, target_status_code=200)
class TestMarkdownTemplateTags(TestCase):
markdown = """
An h1 header
============
Paragraphs are separated by a blank line.
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
look like:
* this one
* that one
* the other one
Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
> Block quotes are
> written like so.
>
> They can span multiple paragraphs,
> if you like.
Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all
in chapters 12--14"). Three dots ... will be converted to an ellipsis.
Unicode is supported.
An h2 header
------------
Here's a numbered list:
1. first item
2. second item
3. third item
Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here's a code sample:
# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:
~~~
define foobar() {
print "Welcome to flavor country!";
}
~~~
(which makes copying & pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:
~~~python
import time
# Quick, count to ten!
for i in range(10):
# (but not *too* quick)
time.sleep(0.5)
print i
~~~
### An h3 header ###
Now a nested list:
1. First, get these ingredients:
* carrots
* celery
* lentils
2. Boil some water.
3. Dump everything in the pot and follow
this algorithm:
find wooden spoon
uncover pot
stir
cover pot
balance wooden spoon precariously on pot handle
wait 10 minutes
goto first step (or shut off burner when done)
Do not bump wooden spoon or it will fall.
Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).
Here's a link to [a website](http://foo.bar). Here's a footnote [^1].
[^1]: Footnote text goes here.
Tables can look like this:
size material color
---- ------------ ------------
9 leather brown
10 hemp canvas natural
11 glass transparent
Table: Shoes, their sizes, and what they're made of
(The above is the caption for the table.) Pandoc also supports
multi-line tables:
-------- -----------------------
keyword text
-------- -----------------------
red Sunsets, apples, and
other red or reddish
things.
green Leaves, grass, frogs
and other things it's
not easy being.
-------- -----------------------
A horizontal rule follows.
***
Here's a definition list:
apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There's no "e" in tomatoe.
Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)
Here's a "line block":
| Line one
| Line too
| Line tree
and images can be specified like so:
![example image](example-image.jpg "An exemplary image")
Inline math equations go in like so: $\\omega = d\\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:
$$I = \\int \rho R^{2} dV$$
And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: \\`foo\\`, \\*bar\\*, etc.
"""
def test_html_safe(self):
html = markdown_filter(self.markdown)
self.assertIsInstance(html, SafeText)
def test_img_strip(self):
rml = markdown_filter(self.markdown, 'rml')
self.assertNotIn("<img", rml)
def test_code(self):
rml = markdown_filter(self.markdown, 'rml')
self.assertIn('<font face="Courier">monospace</font>', rml)
def test_blockquote(self):
rml = markdown_filter(self.markdown, 'rml')
self.assertIn("<pre>\nBlock quotes", rml)
def test_lists(self):
rml = markdown_filter(self.markdown, 'rml')
self.assertIn("<li><para>second item</para></li>", rml) # <ol>
self.assertIn("<li><para>that one</para></li>", rml) # <ul>
def test_in_print(self):
event = models.Event.objects.create(
name="MD Print Test",
description=self.markdown,
start_date='2016-01-01',
)
user = models.Profile.objects.create(
username='RML test',
is_superuser=True, # Don't care about permissions
is_active=True,
)
user.set_password('rmltester')
user.save()
self.assertTrue(self.client.login(username=user.username, password='rmltester'))
response = self.client.get(reverse('event_print', kwargs={'pk': event.pk}))
self.assertEqual(response.status_code, 200)
# By the time we have a PDF it should be larger than the original by some margin
# RML hard fails if something doesn't work
self.assertGreater(len(response.content), len(self.markdown))
def test_nonetype(self):
html = markdown_filter(None)
self.assertIsNone(html)
def test_linebreaks(self):
html = markdown_filter(self.markdown)
self.assertIn("Itemized lists<br/>\nlook like", html)

View File

@@ -132,8 +132,6 @@ urlpatterns = [
name='event_authorise_preview'), name='event_authorise_preview'),
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(), re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
name='event_authorise'), name='event_authorise'),
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/preview/$', rigboard.EventAuthorise.as_view(preview=True),
name='event_authorise_form_preview'),
# ICS Calendar - API key authentication # ICS Calendar - API key authentication
re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),

View File

@@ -6,7 +6,7 @@ class PersonList(GenericListView):
model = models.Person model = models.Person
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(PersonList, self).get_context_data(**kwargs)
context['page_title'] = "People" context['page_title'] = "People"
context['create'] = 'person_create' context['create'] = 'person_create'
context['edit'] = 'person_update' context['edit'] = 'person_update'
@@ -19,7 +19,7 @@ class PersonDetail(GenericDetailView):
model = models.Person model = models.Person
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(PersonDetail, self).get_context_data(**kwargs)
context['history_link'] = 'person_history' context['history_link'] = 'person_history'
context['detail_link'] = 'person_detail' context['detail_link'] = 'person_detail'
context['update_link'] = 'person_update' context['update_link'] = 'person_update'
@@ -49,7 +49,7 @@ class OrganisationList(GenericListView):
model = models.Organisation model = models.Organisation
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(OrganisationList, self).get_context_data(**kwargs)
context['create'] = 'organisation_create' context['create'] = 'organisation_create'
context['edit'] = 'organisation_update' context['edit'] = 'organisation_update'
context['can_edit'] = self.request.user.has_perm('RIGS.change_organisation') context['can_edit'] = self.request.user.has_perm('RIGS.change_organisation')
@@ -62,7 +62,7 @@ class OrganisationDetail(GenericDetailView):
model = models.Organisation model = models.Organisation
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(OrganisationDetail, self).get_context_data(**kwargs)
context['history_link'] = 'organisation_history' context['history_link'] = 'organisation_history'
context['detail_link'] = 'organisation_detail' context['detail_link'] = 'organisation_detail'
context['update_link'] = 'organisation_update' context['update_link'] = 'organisation_update'
@@ -92,7 +92,7 @@ class VenueList(GenericListView):
model = models.Venue model = models.Venue
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(VenueList, self).get_context_data(**kwargs)
context['create'] = 'venue_create' context['create'] = 'venue_create'
context['edit'] = 'venue_update' context['edit'] = 'venue_update'
context['can_edit'] = self.request.user.has_perm('RIGS.change_venue') context['can_edit'] = self.request.user.has_perm('RIGS.change_venue')
@@ -104,7 +104,7 @@ class VenueDetail(GenericDetailView):
model = models.Venue model = models.Venue
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super(VenueDetail, self).get_context_data(**kwargs)
context['history_link'] = 'venue_history' context['history_link'] = 'venue_history'
context['detail_link'] = 'venue_detail' context['detail_link'] = 'venue_detail'
context['update_link'] = 'venue_update' context['update_link'] = 'venue_update'

View File

@@ -0,0 +1 @@
default_app_config = 'assets.apps.AssetsAppConfig'

View File

@@ -1,24 +0,0 @@
import urllib.parse
class AssetIDConverter: # Forces lowercase to uppercase
regex = '[^/]+'
def to_python(self, value):
return str(value).upper()
def to_url(self, value):
return str(value).upper()
class ListConverter:
regex = '[^/]+'
def to_python(self, value):
return value.split(',')
def to_url(self, value):
string = ""
for i in value:
string += "," + str(i)
return string[1:]

View File

@@ -32,8 +32,6 @@ class AssetSearchForm(forms.Form):
q = forms.CharField(required=False) q = forms.CharField(required=False)
category = forms.ModelMultipleChoiceField(models.AssetCategory.objects.all(), required=False) category = forms.ModelMultipleChoiceField(models.AssetCategory.objects.all(), required=False)
status = forms.ModelMultipleChoiceField(models.AssetStatus.objects.all(), required=False) status = forms.ModelMultipleChoiceField(models.AssetStatus.objects.all(), required=False)
is_cable = forms.BooleanField(required=False)
date_acquired = forms.DateField(required=False)
class SupplierForm(forms.ModelForm): class SupplierForm(forms.ModelForm):
@@ -46,3 +44,11 @@ class CableTypeForm(forms.ModelForm):
class Meta: class Meta:
model = models.CableType model = models.CableType
fields = '__all__' fields = '__all__'
def clean(self):
form_data = self.cleaned_data
queryset = models.CableType.objects.filter(Q(plug=form_data['plug']) & Q(socket=form_data['socket']) & Q(circuits=form_data['circuits']) & Q(cores=form_data['cores']))
# Being identical to itself shouldn't count...
if queryset.exists() and self.instance.pk != queryset[0].pk:
raise forms.ValidationError("A cable type that exactly matches this one already exists, please use that instead.", code="notunique")
return form_data

View File

@@ -20,7 +20,6 @@ class Command(BaseCommand):
assets = [] assets = []
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
print("Generating sample assets data")
from django.conf import settings from django.conf import settings
if not (settings.DEBUG or settings.STAGING): if not (settings.DEBUG or settings.STAGING):
@@ -35,7 +34,6 @@ class Command(BaseCommand):
self.create_assets() self.create_assets()
self.create_connectors() self.create_connectors()
self.create_cables() self.create_cables()
print("Done generating sample assets data")
def create_categories(self): def create_categories(self):
choices = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging'] choices = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging']

View File

@@ -8,9 +8,9 @@ def add_default(apps, schema_editor):
Connector = apps.get_model('assets', 'Connector') Connector = apps.get_model('assets', 'Connector')
for cable_type in CableType.objects.all(): for cable_type in CableType.objects.all():
if cable_type.plug is None: if cable_type.plug is None:
cable_type.plug = Connector.objects.first() cable_type.plug = Connector.first()
if cable_type.socket is None: if cable_type.socket is None:
cable_type.socket = Connector.objects.first() cable_type.socket = Connector.first()
cable_type.save() cable_type.save()

View File

@@ -1,4 +1,4 @@
# Generated by Django 3.1.7 on 2021-03-02 12:04 # Generated by Django 3.1.5 on 2021-02-08 16:03
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@@ -7,7 +7,7 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('assets', '0020_auto_20210302_1201'), ('assets', '0019_fix_cabletype'),
] ]
operations = [ operations = [

View File

@@ -1,23 +0,0 @@
# Generated by Django 3.1.7 on 2021-03-02 12:01
from django.db import migrations
def postgres_migration_prep(apps, schema_editor):
model = apps.get_model("assets", "Supplier")
fields = ["address", "email", "notes", "phone"]
for field in fields:
filter_param = {"{}__isnull".format(field): True}
update_param = {field: ""}
model.objects.filter(**filter_param).update(**update_param)
class Migration(migrations.Migration):
dependencies = [
('assets', '0019_fix_cabletype'),
]
operations = [
migrations.RunPython(postgres_migration_prep, migrations.RunPython.noop)
]

View File

@@ -1,17 +0,0 @@
# Generated by Django 3.2.11 on 2022-01-12 19:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0021_auto_20210302_1204'),
]
operations = [
migrations.AlterUniqueTogether(
name='cabletype',
unique_together={('plug', 'socket', 'circuits', 'cores')},
),
]

View File

@@ -6,8 +6,7 @@ from django.urls import reverse
from reversion import revisions as reversion from reversion import revisions as reversion
from reversion.models import Version from reversion.models import Version
from RIGS.models import Profile from RIGS.models import RevisionMixin, Profile
from versioning.versioning import RevisionMixin
class AssetCategory(models.Model): class AssetCategory(models.Model):
@@ -50,7 +49,7 @@ class Supplier(models.Model, RevisionMixin):
ordering = ['name'] ordering = ['name']
def get_absolute_url(self): def get_absolute_url(self):
return reverse('supplier_detail', kwargs={'pk': self.pk}) return reverse('supplier_list')
def __str__(self): def __str__(self):
return self.name return self.name
@@ -76,17 +75,13 @@ class CableType(models.Model):
class Meta: class Meta:
ordering = ['plug', 'socket', '-circuits'] ordering = ['plug', 'socket', '-circuits']
unique_together = ['plug', 'socket', 'circuits', 'cores']
def __str__(self): def __str__(self):
if self.plug and self.socket: if self.plug and self.socket:
return f"{self.plug.description}{self.socket.description}" return "%s%s" % (self.plug.description, self.socket.description)
else: else:
return "Unknown" return "Unknown"
def get_absolute_url(self):
return reverse('cable_type_detail', kwargs={'pk': self.pk})
def get_available_asset_id(wanted_prefix=""): def get_available_asset_id(wanted_prefix=""):
sql = """ sql = """
@@ -104,7 +99,6 @@ def get_available_asset_id(wanted_prefix=""):
return 9000 return 9000
else: else:
return row[0] return row[0]
cursor.close()
@reversion.register @reversion.register
@@ -149,7 +143,7 @@ class Asset(models.Model, RevisionMixin):
] ]
def __str__(self): def __str__(self):
return f"{self.asset_id} | {self.description}" return "{} | {}".format(self.asset_id, self.description)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('asset_detail', kwargs={'pk': self.asset_id}) return reverse('asset_detail', kwargs={'pk': self.asset_id})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -64,16 +64,16 @@
<div class="form-group form-row"> <div class="form-group form-row">
{% include 'partials/form_field.html' with field=form.length append=form.length.help_text col="col-6" %} {% include 'partials/form_field.html' with field=form.length append=form.length.help_text col="col-6" %}
<div class="col-4"> <div class="col-4">
<button class="btn btn-danger" onclick="setFieldValue('{{ form.length.id_for_label }}','5');" tabindex="-1" type="button">5{{ form.length.help_text }}</button> <button class="btn btn-danger" onclick="setFieldValue('{{ form.length.id_for_label }}','5');" tabindex="-1">5{{ form.length.help_text }}</button>
<button class="btn btn-success" onclick="setFieldValue('{{ form.length.id_for_label }}','10');" tabindex="-1" type="button">10{{ form.length.help_text }}</button> <button class="btn btn-success" onclick="setFieldValue('{{ form.length.id_for_label }}','10');" tabindex="-1">10{{ form.length.help_text }}</button>
<button class="btn btn-info" onclick="setFieldValue('{{ form.length.id_for_label }}','20');" tabindex="-1" type="button">20{{ form.length.help_text }}</button> <button class="btn btn-info" onclick="setFieldValue('{{ form.length.id_for_label }}','20');" tabindex="-1">20{{ form.length.help_text }}</button>
</div> </div>
</div> </div>
<div class="form-group form-row"> <div class="form-group form-row">
{% include 'partials/form_field.html' with field=form.csa append=form.csa.help_text title='CSA' col="col-6" %} {% include 'partials/form_field.html' with field=form.csa append=form.csa.help_text title='CSA' col="col-6" %}
<div class="col-4"> <div class="col-4">
<button class="btn btn-secondary" onclick="setFieldValue('{{ form.csa.id_for_label }}', '1.5');" tabindex="-1" type="button">1.5{{ form.csa.help_text }}</button> <button class="btn btn-secondary" onclick="setFieldValue('{{ form.csa.id_for_label }}', '1.5');" tabindex="-1">1.5{{ form.csa.help_text }}</button>
<button class="btn btn-secondary" onclick="setFieldValue('{{ form.csa.id_for_label }}', '2.5');" tabindex="-1" type="button">2.5{{ form.csa.help_text }}</button> <button class="btn btn-secondary" onclick="setFieldValue('{{ form.csa.id_for_label }}', '2.5');" tabindex="-1">2.5{{ form.csa.help_text }}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -4,6 +4,9 @@
{% load widget_tweaks %} {% load widget_tweaks %}
{% block js %} {% block js %}
<script src="{% static 'js/jquery-ui.js' %}"></script>
<script src="{% static "js/interaction.js" %}"></script>
<script src="{% static "js/modal.js" %}"></script>
<script> <script>
$('document').ready(function(){ $('document').ready(function(){
$('#asset-search-form').submit(function () { $('#asset-search-form').submit(function () {
@@ -12,7 +15,9 @@
}); });
$('#searchButton').click(function (e) { $('#searchButton').click(function (e) {
e.preventDefault(); e.preventDefault();
var url = "{% url 'asset_audit' None %}".replace('None', $("#{{form.q.id_for_label}}").val()); var url = "{% url 'asset_audit' None %}";
var id = $("#{{form.q.id_for_label}}").val();
url = url.replace('None', id);
$.ajax({ $.ajax({
url: url, url: url,
success: function(){ success: function(){

View File

@@ -2,9 +2,6 @@
{% load widget_tweaks %} {% load widget_tweaks %}
{% block content %} {% block content %}
<div class="row justify-content-end">
{% include 'partials/asset_buttons.html' %}
</div>
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
{% include 'partials/asset_detail_form.html' %} {% include 'partials/asset_detail_form.html' %}

View File

@@ -3,20 +3,13 @@
{% load static %} {% load static %}
{% block css %} {% block css %}
{{ block.super }} <link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/> <link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'css/simplemde.min.css' %}">
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}"></script>
<script src="{% static 'js/simplemde.min.js' %}"></script>
<script src="{% static 'js/interaction.js' %}"></script>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ block.super }} <script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
<script src="{% static 'js/autocompleter.js' %}"></script> <script src="{% static 'js/autocompleter.js' %}"></script>
<script> <script>
const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches; const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches;
@@ -37,7 +30,7 @@
}) })
.ajaxSelectPicker({ .ajaxSelectPicker({
ajax: { ajax: {
url: "{% url 'asset_search_json' %}", url: '{% url 'asset_search_json' %}',
type: "GET", type: "GET",
data: function () { data: function () {
let params = { let params = {
@@ -75,11 +68,6 @@
preserveSelected: false preserveSelected: false
}); });
</script> </script>
<script>
$(document).ready(function () {
setupMDE('#id_comments');
});
</script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -1,22 +1,20 @@
{% extends 'base_assets.html' %} {% extends 'base_assets.html' %}
{% load paginator from filters %} {% load paginator from filters %}
{% load button from filters %} {% load button from filters %}
{% load ids_from_objects from asset_tags %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% block css %} {% block css %}
{{ block.super }} <link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/> <link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
{% endblock %} {% endblock %}
{% block preload_js %} {% block preload_js %}
{{ block.super }} <script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/selects.js' %}" async></script> <script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ block.super }}
<script> <script>
//Get querystring value //Get querystring value
function getParameterByName(name) { function getParameterByName(name) {
@@ -61,54 +59,27 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col px-0"> <div class="col px-0">
<form id="asset-search-form" method="GET"> <form id="asset-search-form" method="GET" class="form-inline justify-content-end">
<div class="form-row"> <div class="input-group px-1 mb-2 mb-sm-0 flex-nowrap">
<div class="col"> {% render_field form.q|add_class:'form-control' placeholder='Enter Asset ID/Desc/Serial' %}
<div class="input-group px-1 mb-2 mb-sm-0 flex-nowrap"> <label for="q" class="sr-only">Asset ID/Description/Serial Number:</label>
{% render_field form.q|add_class:'form-control' placeholder='Enter Asset ID/Desc/Serial' %} <span class="input-group-append">{% button 'search' id="id_search" %}</span>
<label for="q" class="sr-only">Asset ID/Description/Serial Number:</label> </div>
<span class="input-group-append">{% button 'search' id="id_search" %}</span> <div id="category-group" class="form-group px-1" style="margin-bottom: 0;">
</div> <label for="category" class="sr-only">Category</label>
</div> {% render_field form.category|attr:'multiple'|add_class:'form-control custom-select selectpicker col-sm' data-none-selected-text="Categories" data-header="Categories" data-actions-box="true" %}
</div> </div>
<div class="form-row mt-2"> <div id="status-group" class="form-group px-1" style="margin-bottom: 0;">
<div class="col"> <label for="status" class="sr-only">Status</label>
<div id="category-group" class="form-group px-1" style="margin-bottom: 0;"> {% render_field form.status|attr:'multiple'|add_class:'form-control custom-select selectpicker col-sm' data-none-selected-text="Statuses" data-header="Statuses" data-actions-box="true" %}
<label for="category" class="sr-only">Category</label> </div>
{% render_field form.category|attr:'multiple'|add_class:'form-control custom-select selectpicker col-sm' data-none-selected-text="Categories" data-header="Categories" data-actions-box="true" %} <button id="filter-submit" type="submit" class="btn btn-secondary" style="width: 6em">Filter</button>
</div>
</div>
<div class="col">
<div id="status-group" class="form-group px-1" style="margin-bottom: 0;">
<label for="status" class="sr-only">Status</label>
{% render_field form.status|attr:'multiple'|add_class:'form-control custom-select selectpicker col-sm' data-none-selected-text="Statuses" data-header="Statuses" data-actions-box="true" %}
</div>
</div>
<div class="col mt-2">
<div class="form-check form-check-inline">
{% render_field form.is_cable|add_class:'form-check-input' %}
<label class="form-check-label" for="is_cable">Only Cables?</label>
</div>
</div>
<div class="col-auto">
<div class="form-group d-flex flex-nowrap">
<label for="date_acquired" class="text-nowrap mt-auto">Date Acquired</label>
{% render_field form.date_acquired|add_class:'form-control mx-2' %}
</div>
</div>
<div class="col-auto mr-auto">
<button id="filter-submit" type="submit" class="btn btn-secondary" style="width: 6em">Filter</button>
</div>
</div>
</form> </form>
</div> </div>
</div> </div>
<div class="row my-2"> <div class="row my-2">
<div class="col text-right px-0"> <div class="col text-right px-0">
{% button 'new' 'asset_create' style="width: 6em" %} {% button 'new' 'asset_create' style="width: 6em" %}
{% if object_list %}
<a class="btn btn-primary" href="{% url 'generate_labels' object_list|ids_from_objects %}"><span class="fas fa-barcode"></span> Generate Labels</a>
{% endif %}
</div> </div>
</div> </div>
<div class="row my-2"> <div class="row my-2">

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE document SYSTEM "rml.dtd">
{% load multiply from filters %}
{% load index from asset_tags %}
<document filename="{{filename}}">
<template>
<pageTemplate id="main">
<pageGraphics>
</pageGraphics>
<frame id="first" x1="5" y1="-10" width="581" height="842"/>
</pageTemplate>
</template>
<stylesheet>
<blockTableStyle id="table">
<!-- show a grid: this also comes in handy for debugging your tables.-->
<lineStyle kind="GRID" colorName="black" thickness="1" start="0,0" stop="-1,-1" />
</blockTableStyle>
</stylesheet>
<story>
<blockTable style="table">
{% for i in images0 %}
<tr>
<td>{% with images0|index:forloop.counter0 as image %}{% if image %}<illustration width="120" height="35"><image file="data:image/png;base64,{{image}}" x="0" y="0"
width="120" height="35"/></illustration>{% endif %}{% endwith %}</td>
<td>{% with images1|index:forloop.counter0 as image %}{% if image %}<illustration width="120" height="35"><image file="data:image/png;base64,{{image}}" x="0" y="0"
width="120" height="35"/></illustration>{% endif %}{% endwith %}</td>
<td>{% with images2|index:forloop.counter0 as image %}{% if image %}<illustration width="120" height="35"><image file="data:image/png;base64,{{image}}" x="0" y="0"
width="120" height="35"/></illustration>{% endif %}{% endwith %}</td>
<td>{% with images3|index:forloop.counter0 as image %}{% if image %}<illustration width="120" height="35"><image file="data:image/png;base64,{{image}}" x="0" y="0"
width="120" height="35"/></illustration>{% endif %}{% endwith %}</td>
</tr>
{% endfor %}
</blockTable>
</story>
</document>

View File

@@ -5,14 +5,13 @@
{% button 'submit' %} {% button 'submit' %}
{% elif duplicate %} {% elif duplicate %}
<!--duplicate--> <!--duplicate-->
<button type="submit" class="btn btn-success"><span class="fas fa-check"></span> Create Duplicate</button> <button type="submit" class="btn btn-success"><i class="fas fa-check"></i> Create Duplicate</button>
{% else %} {% else %}
<!--detail view--> <!--detail view-->
<div class="btn-group"> <div class="btn-group">
{% button 'edit' url='asset_update' pk=object.asset_id %} {% button 'edit' url='asset_update' pk=object.asset_id %}
{% button 'duplicate' url='asset_duplicate' pk=object.asset_id %} {% button 'duplicate' url='asset_duplicate' pk=object.asset_id %}
<a type="button" class="btn btn-info" href="{% url 'asset_audit' object.asset_id %}"><span class="fas fa-certificate"></span> Audit</a> <a type="button" class="btn btn-info" href="{% url 'asset_audit' object.asset_id %}"><i class="fas fa-certificate"></i> Audit</a>
<a type="button" class="btn btn-primary" href="{% url 'generate_label' object.asset_id %}"><span class="fas fa-barcode"></span> Generate Label</a>
</div> </div>
{% endif %} {% endif %}
{% if create or edit or duplicate %} {% if create or edit or duplicate %}

View File

@@ -1,6 +1,4 @@
{% load widget_tweaks %} {% load widget_tweaks %}
{% load markdown_tags %}
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
Asset Details Asset Details
@@ -40,14 +38,14 @@
<!---TODO: Lower default number of lines in comments box--> <!---TODO: Lower default number of lines in comments box-->
<div class="form-group"> <div class="form-group">
<label for="{{ form.comments.id_for_label }}">Comments</label> <label for="{{ form.comments.id_for_label }}">Comments</label>
{% render_field form.comments|add_class:'form-control md-enabled' %} {% render_field form.comments|add_class:'form-control' %}
</div> </div>
{% else %} {% else %}
<dt>Asset ID</dt> <dt>Asset ID</dt>
<dd>{{ object.asset_id }}</dd> <dd>{{ object.asset_id }}</dd>
<dt>Description</dt> <dt>Description</dt>
<dd>{{ object.description }}</dd> <dd style="overflow-wrap: break-word;">{{ object.description }}</dd>
<dt>Category</dt> <dt>Category</dt>
<dd>{{ object.category }}</dd> <dd>{{ object.category }}</dd>
@@ -59,7 +57,7 @@
<dd>{{ object.serial_number|default:'-' }}</dd> <dd>{{ object.serial_number|default:'-' }}</dd>
<dt>Comments</dt> <dt>Comments</dt>
<dd style="overflow-wrap: break-word;">{{ object.comments|default:'-'|markdown }}</dd> <dd style="overflow-wrap: break-word;">{{ object.comments|default:'-'|linebreaksbr }}</dd>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@@ -17,9 +17,11 @@
{% else %} {% else %}
<dl> <dl>
<dt>Cable Type</dt> <dt>Cable Type</dt>
<dd>{% if object.cable_type %}<a href="{{object.cable_type.get_absolute_url}}">{{ object.cable_type }}</a>{%else%}-{%endif%}</dd> <dd>{{ object.cable_type|default_if_none:'-' }}</dd>
<dt>Length</dt> <dt>Length</dt>
<dd>{{ object.length|default_if_none:'-' }}m</dd> <dd>{{ object.length|default_if_none:'-' }}m</dd>
<dt>Cross Sectional Area</dt> <dt>Cross Sectional Area</dt>
<dd>{{ object.csa|default_if_none:'-' }}mm²</dd> <dd>{{ object.csa|default_if_none:'-' }}mm²</dd>
</dl> </dl>

View File

@@ -28,13 +28,13 @@
<dt>Children</dt> <dt>Children</dt>
{% if object.asset_parent.all %} {% if object.asset_parent.all %}
<div style="max-height: 200px; overflow-y: auto; -webkit-overflow-scrolling: touch; ">
{% for child in object.asset_parent.all %} {% for child in object.asset_parent.all %}
<dd> <dd>
<a href="{% url 'asset_detail' child.asset_id %}">{{ child }}</a> <a href="{% url 'asset_detail' child.asset_id %}">
{{ child.asset_id }} - {{ child.description }}
</a>
</dd> </dd>
{% endfor %} {% endfor %}
</div>
{% else %} {% else %}
<dd><span>-</span></dd> <dd><span>-</span></dd>
{% endif %} {% endif %}

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