mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-10 16:49:41 +00:00
Compare commits
31 Commits
estates-co
...
a9b034255e
| Author | SHA1 | Date | |
|---|---|---|---|
| a9b034255e | |||
| 6676183443 | |||
| 3f93cebf41 | |||
| 603e919ad0 | |||
| a0b70a3cac | |||
| a11e32252f | |||
| e48e016cb9 | |||
| ef1d9868da | |||
| 788fb3efe6 | |||
| 4f912932ca | |||
| 0598612c15 | |||
| 656f9fdd25 | |||
| ccda38918c | |||
| a1edf80dd0 | |||
| 83fe526cbd | |||
| 1d63bd940d | |||
| c090163f40 | |||
| baa3b2c9c6 | |||
| 462a16ec42 | |||
| 6cb3d1855a | |||
| 9279131edf | |||
| 3853ad0871 | |||
| 7eea868575 | |||
| fc6e66c7f5 | |||
| 01ed05ecd9 | |||
| 20e5d25130 | |||
| 11db880ac3 | |||
| 87caab6c8e | |||
| d79366d2e6 | |||
| 10f2152d8b | |||
| 0154ecb6d8 |
26
.github/workflows/django.yml
vendored
26
.github/workflows/django.yml
vendored
@@ -18,8 +18,16 @@ jobs:
|
|||||||
# BROWSER: ${{ matrix.browser }}
|
# BROWSER: ${{ matrix.browser }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- name: Cache Static Files
|
||||||
|
id: static-cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: 'static/'
|
||||||
|
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'
|
||||||
- run: node node_modules/gulp/bin/gulp build
|
- run: node node_modules/gulp/bin/gulp build
|
||||||
|
if: steps.static-cache.outputs.cache-hit != 'true'
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
@@ -28,20 +36,20 @@ jobs:
|
|||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ${{ env.pythonLocation }}
|
path: ${{ env.pythonLocation }}
|
||||||
key: ${{ env.pythonLocation }}-${{ hashFiles('requirements.txt') }}
|
key: ${{ env.pythonLocation }}-${{ hashFiles('Pipfile.lock') }}
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install pycodestyle coveralls django_coverage_plugin pytest-cov
|
pip install pipenv
|
||||||
pip install --upgrade --upgrade-strategy eager -r requirements.txt
|
pipenv install -d
|
||||||
python manage.py collectstatic --noinput
|
|
||||||
- name: Basic Checks
|
- name: Basic Checks
|
||||||
run: |
|
run: |
|
||||||
pycodestyle . --exclude=migrations,node_modules
|
pipenv run pycodestyle . --exclude=migrations,node_modules
|
||||||
python manage.py check
|
pipenv run python manage.py check
|
||||||
python manage.py makemigrations --check --dry-run
|
pipenv run python manage.py makemigrations --check --dry-run
|
||||||
|
pipenv run python manage.py collectstatic --noinput
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: pytest --cov -n 8
|
run: pipenv run pytest --cov -n 8
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
@@ -49,4 +57,4 @@ jobs:
|
|||||||
path: screenshots/
|
path: screenshots/
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
- name: Coveralls
|
- name: Coveralls
|
||||||
run: coveralls --service=github
|
run: pipenv run coveralls --service=github
|
||||||
|
|||||||
94
Pipfile
Normal file
94
Pipfile
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
[[source]]
|
||||||
|
url = "https://pypi.python.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
ansicolors = "==1.1.8"
|
||||||
|
asgiref = "==3.3.1"
|
||||||
|
"backports.tempfile" = "==1.0"
|
||||||
|
"backports.weakref" = "==1.0.post1"
|
||||||
|
beautifulsoup4 = "==4.9.3"
|
||||||
|
Brotli = "==1.0.9"
|
||||||
|
cachetools = "==4.2.1"
|
||||||
|
certifi = "==2020.12.5"
|
||||||
|
chardet = "==4.0.0"
|
||||||
|
configparser = "==5.0.1"
|
||||||
|
contextlib2 = "==0.6.0.post1"
|
||||||
|
cssselect = "==1.1.0"
|
||||||
|
cssutils = "==1.0.2"
|
||||||
|
diff-match-patch = "==20200713"
|
||||||
|
dj-database-url = "==0.5.0"
|
||||||
|
dj-static = "==0.0.6"
|
||||||
|
Django = "==3.1.5"
|
||||||
|
django-debug-toolbar = "==3.2"
|
||||||
|
django-filter = "==2.4.0"
|
||||||
|
django-gulp = "==4.1.0"
|
||||||
|
django-ical = "==1.7.1"
|
||||||
|
django-recaptcha = "==2.0.6"
|
||||||
|
django-recurrence = "==1.10.3"
|
||||||
|
django-registration-redux = "==2.9"
|
||||||
|
django-reversion = "==3.0.9"
|
||||||
|
django-toolbelt = "==0.0.1"
|
||||||
|
django-widget-tweaks = "==1.4.8"
|
||||||
|
django-htmlmin = "==0.11.0"
|
||||||
|
envparse = "==0.2.0"
|
||||||
|
gunicorn = "==20.0.4"
|
||||||
|
icalendar = "==4.0.7"
|
||||||
|
idna = "==2.10"
|
||||||
|
importlib-metadata = "==3.4.0"
|
||||||
|
lxml = "==4.6.2"
|
||||||
|
Markdown = "==3.3.3"
|
||||||
|
msgpack = "==1.0.2"
|
||||||
|
pep517 = "==0.9.1"
|
||||||
|
Pillow = "==8.1.0"
|
||||||
|
pluggy = "==0.13.1"
|
||||||
|
premailer = "==3.7.0"
|
||||||
|
progress = "==1.5"
|
||||||
|
psutil = "==5.8.0"
|
||||||
|
psycopg2 = "==2.8.6"
|
||||||
|
Pygments = "==2.7.4"
|
||||||
|
pyparsing = "==2.4.7"
|
||||||
|
PyPDF2 = "==1.26.0"
|
||||||
|
PyPOM = "==2.2.0"
|
||||||
|
python-dateutil = "==2.8.1"
|
||||||
|
pytoml = "==0.1.21"
|
||||||
|
pytz = "==2020.5"
|
||||||
|
pytest-django = "==4.1.0"
|
||||||
|
pytest-xdist = "==2.2.0"
|
||||||
|
pytest-cov = "==2.11.1"
|
||||||
|
reportlab = "==3.5.59"
|
||||||
|
requests = "==2.25.1"
|
||||||
|
retrying = "==1.3.3"
|
||||||
|
selenium = "==3.141.0"
|
||||||
|
simplejson = "==3.17.2"
|
||||||
|
six = "==1.15.0"
|
||||||
|
soupsieve = "==2.1"
|
||||||
|
sqlparse = "==0.4.1"
|
||||||
|
static3 = "==0.7.0"
|
||||||
|
svg2rlg = "==0.3"
|
||||||
|
tini = "==3.0.1"
|
||||||
|
tornado = "==6.1"
|
||||||
|
urllib3 = "==1.26.2"
|
||||||
|
whitenoise = "==5.2.0"
|
||||||
|
yolk = "==0.4.3"
|
||||||
|
"z3c.rml" = "==4.1.2"
|
||||||
|
zipp = "==3.4.0"
|
||||||
|
"zope.component" = "==4.6.2"
|
||||||
|
"zope.deferredimport" = "==4.3.1"
|
||||||
|
"zope.deprecation" = "==4.4.0"
|
||||||
|
"zope.event" = "==4.5.0"
|
||||||
|
"zope.hookable" = "==5.0.1"
|
||||||
|
"zope.interface" = "==5.2.0"
|
||||||
|
"zope.proxy" = "==4.3.5"
|
||||||
|
"zope.schema" = "==6.0.1"
|
||||||
|
sentry-sdk = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
pycodestyle = "*"
|
||||||
|
coveralls = "*"
|
||||||
|
django-coverage-plugin = "*"
|
||||||
|
pytest-cov = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.9"
|
||||||
1413
Pipfile.lock
generated
Normal file
1413
Pipfile.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
0
PyRIGS/management/__init__.py
Normal file
0
PyRIGS/management/__init__.py
Normal file
0
PyRIGS/management/commands/__init__.py
Normal file
0
PyRIGS/management/commands/__init__.py
Normal file
@@ -13,7 +13,8 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
import raven
|
import sentry_sdk
|
||||||
|
from sentry_sdk.integrations.django import DjangoIntegration
|
||||||
from envparse import env
|
from envparse import env
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
@@ -27,9 +28,7 @@ SECRET_KEY = env('SECRET_KEY', default='gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0
|
|||||||
|
|
||||||
# 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)
|
||||||
|
|
||||||
CI = env('CI', cast=bool, default=False)
|
CI = env('CI', cast=bool, default=False)
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
||||||
@@ -55,6 +54,7 @@ if DEBUG:
|
|||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
|
'whitenoise.runserver_nostatic',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
@@ -72,11 +72,9 @@ INSTALLED_APPS = (
|
|||||||
'reversion',
|
'reversion',
|
||||||
'captcha',
|
'captcha',
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
'raven.contrib.django.raven_compat',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE = (
|
MIDDLEWARE = (
|
||||||
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||||
@@ -87,15 +85,15 @@ MIDDLEWARE = (
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'htmlmin.middleware.HtmlMinifyMiddleware',
|
||||||
|
'htmlmin.middleware.MarkRequestMiddleware',
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = 'PyRIGS.urls'
|
ROOT_URLCONF = 'PyRIGS.urls'
|
||||||
|
|
||||||
WSGI_APPLICATION = 'PyRIGS.wsgi.application'
|
WSGI_APPLICATION = 'PyRIGS.wsgi.application'
|
||||||
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
@@ -177,9 +175,12 @@ else:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RAVEN_CONFIG = {
|
# Error/performance monitoring
|
||||||
'dsn': env('RAVEN_DSN', default=""),
|
sentry_sdk.init(
|
||||||
}
|
dsn=env('SENTRY_DSN', default=""),
|
||||||
|
integrations=[DjangoIntegration()],
|
||||||
|
traces_sample_rate=1.0,
|
||||||
|
)
|
||||||
|
|
||||||
# User system
|
# User system
|
||||||
AUTH_USER_MODEL = 'RIGS.Profile'
|
AUTH_USER_MODEL = 'RIGS.Profile'
|
||||||
@@ -232,14 +233,14 @@ USE_TZ = True
|
|||||||
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
|
DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S')
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
|
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
|
||||||
STATIC_DIRS = (
|
STATIC_DIRS = [
|
||||||
os.path.join(BASE_DIR, 'static/')
|
os.path.join(BASE_DIR, 'static/'),
|
||||||
)
|
]
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
os.path.join(BASE_DIR, 'pipeline/built_assets/'),
|
os.path.join(BASE_DIR, 'pipeline/built_assets'),
|
||||||
]
|
]
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
|
|||||||
@@ -13,28 +13,21 @@ from RIGS import models as rigsmodels
|
|||||||
from . import pages
|
from . import pages
|
||||||
from envparse import env
|
from envparse import env
|
||||||
|
|
||||||
|
from pytest_django.asserts import assertContains
|
||||||
|
|
||||||
def create_datetime(year, month, day, hour, min):
|
|
||||||
|
def create_datetime(year, month, day, hour, minute):
|
||||||
tz = pytz.timezone(settings.TIME_ZONE)
|
tz = pytz.timezone(settings.TIME_ZONE)
|
||||||
return tz.localize(datetime(year, month, day, hour, min)).astimezone(pytz.utc)
|
return tz.localize(datetime(year, month, day, hour, minute)).astimezone(tz)
|
||||||
|
|
||||||
|
|
||||||
def create_browser():
|
def create_browser():
|
||||||
browser = env('BROWSER', default="chrome")
|
options = webdriver.ChromeOptions()
|
||||||
if browser == "firefox":
|
options.add_argument("--window-size=1920,1080")
|
||||||
options = webdriver.FirefoxOptions()
|
options.add_argument("--headless")
|
||||||
options.headless = True
|
if settings.CI:
|
||||||
driver = webdriver.Firefox(options=options)
|
options.add_argument("--no-sandbox")
|
||||||
driver.set_window_position(0, 0)
|
driver = webdriver.Chrome(options=options)
|
||||||
# Firefox is pissy about out of bounds otherwise
|
|
||||||
driver.set_window_size(3840, 2160)
|
|
||||||
else:
|
|
||||||
options = webdriver.ChromeOptions()
|
|
||||||
options.add_argument("--window-size=1920,1080")
|
|
||||||
options.add_argument("--headless")
|
|
||||||
if settings.CI:
|
|
||||||
options.add_argument("--no-sandbox")
|
|
||||||
driver = webdriver.Chrome(options=options)
|
|
||||||
return driver
|
return driver
|
||||||
|
|
||||||
|
|
||||||
@@ -60,6 +53,7 @@ class AutoLoginTest(BaseTest):
|
|||||||
login_page.login("EventTest", "EventTestPassword")
|
login_page.login("EventTest", "EventTestPassword")
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME Refactor as a pytest fixture
|
||||||
def screenshot_failure(func):
|
def screenshot_failure(func):
|
||||||
def wrapper_func(self, *args, **kwargs):
|
def wrapper_func(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
@@ -83,5 +77,30 @@ def screenshot_failure_cls(cls):
|
|||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
def assert_times_equal(first_time, second_time):
|
def assert_times_almost_equal(first_time, second_time):
|
||||||
assert first_time.replace(microsecond=0, second=0) == second_time.replace(microsecond=0, second=0)
|
assert first_time.replace(microsecond=0, second=0) == second_time.replace(microsecond=0, second=0)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_oembed(alt_event_embed_url, alt_oembed_url, client, event_embed_url, event_url, oembed_url):
|
||||||
|
# Test the meta tag is in place
|
||||||
|
response = client.get(event_url, follow=True, HTTP_HOST='example.com')
|
||||||
|
assertContains(response, 'application/json+oembed')
|
||||||
|
assertContains(response, oembed_url)
|
||||||
|
# Test that the JSON exists
|
||||||
|
response = client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assertContains(response, event_embed_url)
|
||||||
|
# Should also work for non-existant events
|
||||||
|
response = client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assertContains(response, alt_event_embed_url)
|
||||||
|
|
||||||
|
|
||||||
|
def login(client, django_user_model):
|
||||||
|
pwd = 'testuser'
|
||||||
|
usr = 'TestUser'
|
||||||
|
user = django_user_model.objects.create_user(username=usr, email="TestUser@test.com", password=pwd,
|
||||||
|
is_superuser=True,
|
||||||
|
is_active=True, is_staff=True)
|
||||||
|
assert client.login(username=usr, password=pwd)
|
||||||
|
return user
|
||||||
|
|||||||
@@ -1,11 +1,26 @@
|
|||||||
from PyRIGS import urls
|
|
||||||
from assets.tests.test_unit import create_asset_one
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.urls import URLPattern, URLResolver, reverse
|
from django.core.management import call_command
|
||||||
|
from django.template.defaultfilters import striptags
|
||||||
|
from django.urls import URLPattern, URLResolver
|
||||||
|
from django.urls import reverse
|
||||||
from django.urls.exceptions import NoReverseMatch
|
from django.urls.exceptions import NoReverseMatch
|
||||||
from pytest_django.asserts import assertContains, assertRedirects, assertTemplateUsed, assertInHTML
|
from pytest_django.asserts import assertRedirects, assertContains, assertNotContains
|
||||||
|
from pytest_django.asserts import assertTemplateUsed, assertInHTML
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
from PyRIGS import urls
|
||||||
|
from RIGS.models import Event
|
||||||
|
from assets.models import Asset
|
||||||
|
from django.db import connection
|
||||||
|
import pytest
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.template.defaultfilters import striptags
|
||||||
|
from django.urls.exceptions import NoReverseMatch
|
||||||
|
|
||||||
|
from RIGS.models import Event
|
||||||
|
from assets.models import Asset
|
||||||
|
from django.db import connection
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
|
||||||
def find_urls_recursive(patterns):
|
def find_urls_recursive(patterns):
|
||||||
@@ -14,7 +29,7 @@ def find_urls_recursive(patterns):
|
|||||||
if isinstance(url, URLResolver):
|
if isinstance(url, URLResolver):
|
||||||
urls_to_check += find_urls_recursive(url.url_patterns)
|
urls_to_check += find_urls_recursive(url.url_patterns)
|
||||||
elif isinstance(url, URLPattern):
|
elif isinstance(url, URLPattern):
|
||||||
# Skip some thinks that actually don't need auth (mainly OEmbed JSONs that are essentially just a redirect)
|
# Skip some things that actually don't need auth (mainly OEmbed JSONs that are essentially just a redirect)
|
||||||
if url.name is not None and url.name != "closemodal" and "json" not in str(url):
|
if url.name is not None and url.name != "closemodal" and "json" not in str(url):
|
||||||
urls_to_check.append(url)
|
urls_to_check.append(url)
|
||||||
return urls_to_check
|
return urls_to_check
|
||||||
@@ -22,7 +37,6 @@ def find_urls_recursive(patterns):
|
|||||||
|
|
||||||
def get_request_url(url):
|
def get_request_url(url):
|
||||||
pattern = str(url.pattern)
|
pattern = str(url.pattern)
|
||||||
request_url = ""
|
|
||||||
try:
|
try:
|
||||||
kwargz = {}
|
kwargz = {}
|
||||||
if ":pk>" in pattern:
|
if ":pk>" in pattern:
|
||||||
@@ -34,32 +48,98 @@ def get_request_url(url):
|
|||||||
print("Couldn't test url " + pattern)
|
print("Couldn't test url " + pattern)
|
||||||
|
|
||||||
|
|
||||||
def test_unauthenticated(client): # Nothing should be available to the unauthenticated
|
@pytest.mark.parametrize("command", ['generateSampleAssetsData', 'generateSampleRIGSData', 'generateSampleUserData',
|
||||||
create_asset_one()
|
'deleteSampleData'])
|
||||||
for url in find_urls_recursive(urls.urlpatterns):
|
def test_production_exception(command):
|
||||||
request_url = get_request_url(url)
|
from django.core.management.base import CommandError
|
||||||
if request_url and 'user' not in request_url: # User module is full of edge cases
|
with pytest.raises(CommandError, match=".*production"):
|
||||||
response = client.get(request_url, follow=True, HTTP_HOST='example.com')
|
call_command(command)
|
||||||
assertContains(response, 'Login')
|
|
||||||
if 'application/json+oembed' in response.content.decode():
|
|
||||||
assertTemplateUsed(response, 'login_redirect.html')
|
class TestSampleDataGenerator(TestCase):
|
||||||
else:
|
@override_settings(DEBUG=True)
|
||||||
if "embed" in str(url):
|
def test_sample_data(self):
|
||||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
call_command('generateSampleData')
|
||||||
|
assert Asset.objects.all().count() > 50
|
||||||
|
assert Event.objects.all().count() > 100
|
||||||
|
call_command('deleteSampleData')
|
||||||
|
assert Asset.objects.all().count() == 0
|
||||||
|
assert Event.objects.all().count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
class TestSampleDataGenerator(TestCase):
|
||||||
|
@override_settings(DEBUG=True)
|
||||||
|
def setUp(self):
|
||||||
|
call_command('generateSampleData')
|
||||||
|
|
||||||
|
def test_unauthenticated(self): # Nothing should be available to the unauthenticated
|
||||||
|
for url in find_urls_recursive(urls.urlpatterns):
|
||||||
|
request_url = get_request_url(url)
|
||||||
|
if request_url and 'user' not in request_url: # User module is full of edge cases
|
||||||
|
response = self.client.get(request_url, follow=True, HTTP_HOST='example.com')
|
||||||
|
assertContains(response, 'Login')
|
||||||
|
if 'application/json+oembed' in response.content.decode():
|
||||||
|
assertTemplateUsed(response, 'login_redirect.html')
|
||||||
else:
|
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)
|
||||||
|
else:
|
||||||
|
expected_url = "{0}?next={1}".format(reverse('login'), request_url)
|
||||||
|
assertRedirects(response, expected_url)
|
||||||
|
|
||||||
|
def test_page_titles(self):
|
||||||
|
assert self.client.login(username='superuser', password='superuser')
|
||||||
|
for url in filter((lambda u: "embed" not in u.name), find_urls_recursive(urls.urlpatterns)):
|
||||||
|
request_url = get_request_url(url)
|
||||||
|
response = self.client.get(request_url)
|
||||||
|
if hasattr(response, "context_data") and "page_title" in response.context_data:
|
||||||
|
expected_title = striptags(response.context_data["page_title"])
|
||||||
|
assertInHTML('<title>{} | Rig Information Gathering System'.format(expected_title),
|
||||||
|
response.content.decode())
|
||||||
|
print("{} | {}".format(request_url, expected_title)) # If test fails, tell me where!
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
def test_page_titles(admin_client):
|
def test_basic_access(self):
|
||||||
create_asset_one()
|
assert self.client.login(username="basic", password="basic")
|
||||||
for url in filter((lambda u: "embed" not in u.name), find_urls_recursive(urls.urlpatterns)):
|
|
||||||
request_url = get_request_url(url)
|
url = reverse('asset_list')
|
||||||
response = admin_client.get(request_url)
|
response = self.client.get(url)
|
||||||
if hasattr(response, "context_data") and "page_title" in response.context_data:
|
# Check edit and duplicate buttons NOT shown in list
|
||||||
expected_title = response.context_data["page_title"]
|
assertNotContains(response, 'Edit')
|
||||||
# try:
|
assertNotContains(response,
|
||||||
assertInHTML('<title>{} | Rig Information Gathering System'.format(expected_title), response.content.decode())
|
'Duplicate') # If this line is randomly failing, check the debug toolbar HTML hasn't crept in
|
||||||
print("{} | {}".format(request_url, expected_title)) # If test fails, tell me where!
|
|
||||||
# except:
|
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
|
||||||
# print(response.content.decode(), file=open('output.html', 'w'))
|
response = self.client.get(url)
|
||||||
|
assertNotContains(response, 'Purchase Details')
|
||||||
|
assertNotContains(response, 'View Revision History')
|
||||||
|
|
||||||
|
urlz = {'asset_history', 'asset_update', 'asset_duplicate'}
|
||||||
|
for url_name in urlz:
|
||||||
|
request_url = reverse(url_name, kwargs={'pk': Asset.objects.first().asset_id})
|
||||||
|
response = self.client.get(request_url, follow=True)
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
request_url = reverse('supplier_create')
|
||||||
|
response = self.client.get(request_url, follow=True)
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
request_url = reverse('supplier_update', kwargs={'pk': 1})
|
||||||
|
response = self.client.get(request_url, follow=True)
|
||||||
|
assert response.status_code == 403
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
def test_keyholder_access(self):
|
||||||
|
assert self.client.login(username="keyholder", password="keyholder")
|
||||||
|
|
||||||
|
url = reverse('asset_list')
|
||||||
|
response = self.client.get(url)
|
||||||
|
# Check edit and duplicate buttons shown in list
|
||||||
|
assertContains(response, 'Edit')
|
||||||
|
assertContains(response, 'Duplicate')
|
||||||
|
|
||||||
|
url = reverse('asset_detail', kwargs={'pk': Asset.objects.first().asset_id})
|
||||||
|
response = self.client.get(url)
|
||||||
|
assertContains(response, 'Purchase Details')
|
||||||
|
assertContains(response, 'View Revision History')
|
||||||
|
self.client.logout()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import operator
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
import simplejson
|
import simplejson
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
@@ -11,6 +12,7 @@ from django.http import HttpResponse
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
from django.urls import reverse_lazy, reverse, NoReverseMatch
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
from assets import models as asset_models
|
from assets import models as asset_models
|
||||||
@@ -19,10 +21,8 @@ from assets import models as asset_models
|
|||||||
def is_ajax(request):
|
def is_ajax(request):
|
||||||
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
|
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
|
||||||
|
|
||||||
# Displays the current rig count along with a few other bits and pieces
|
|
||||||
|
|
||||||
|
class Index(generic.TemplateView): # Displays the current rig count along with a few other bits and pieces
|
||||||
class Index(generic.TemplateView):
|
|
||||||
template_name = 'index.html'
|
template_name = 'index.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@@ -230,15 +230,29 @@ class SearchHelp(generic.TemplateView):
|
|||||||
template_name = 'search_help.html'
|
template_name = 'search_help.html'
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Called from a modal window (e.g. when an item is submitted to an event/invoice).
|
|
||||||
May optionally also include some javascript in a success message to cause a load of
|
|
||||||
the new information onto the page.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class CloseModal(generic.TemplateView):
|
class CloseModal(generic.TemplateView):
|
||||||
|
"""
|
||||||
|
Called from a modal window (e.g. when an item is submitted to an event/invoice).
|
||||||
|
May optionally also include some javascript in a success message to cause a load of
|
||||||
|
the new information onto the page.
|
||||||
|
"""
|
||||||
template_name = 'closemodal.html'
|
template_name = 'closemodal.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
return {'messages': messages.get_messages(self.request)}
|
return {'messages': messages.get_messages(self.request)}
|
||||||
|
|
||||||
|
|
||||||
|
class OEmbedView(generic.View):
|
||||||
|
def get(self, request, pk=None):
|
||||||
|
embed_url = reverse(self.url_name, args=[pk])
|
||||||
|
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
|
||||||
|
'version': '1.0',
|
||||||
|
'type': 'rich',
|
||||||
|
'height': '250'
|
||||||
|
}
|
||||||
|
|
||||||
|
json = simplejson.JSONEncoderForHTML().encode(data)
|
||||||
|
return HttpResponse(json, content_type="application/json")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from django.http import Http404, HttpResponseRedirect
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from z3c.rml import rml2pdf
|
from z3c.rml import rml2pdf
|
||||||
|
|
||||||
@@ -67,12 +67,6 @@ class InvoicePrint(generic.View):
|
|||||||
|
|
||||||
context = {
|
context = {
|
||||||
'object': object,
|
'object': object,
|
||||||
'fonts': {
|
|
||||||
'opensans': {
|
|
||||||
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
|
||||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'invoice': invoice,
|
'invoice': invoice,
|
||||||
'current_user': request.user,
|
'current_user': request.user,
|
||||||
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
|
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
|
||||||
@@ -98,8 +92,8 @@ class InvoiceVoid(generic.View):
|
|||||||
object.save()
|
object.save()
|
||||||
|
|
||||||
if object.void:
|
if object.void:
|
||||||
return HttpResponseRedirect(reverse_lazy('invoice_list'))
|
return HttpResponseRedirect(reverse('invoice_list'))
|
||||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk}))
|
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': object.pk}))
|
||||||
|
|
||||||
|
|
||||||
class InvoiceDelete(generic.DeleteView):
|
class InvoiceDelete(generic.DeleteView):
|
||||||
@@ -110,14 +104,14 @@ class InvoiceDelete(generic.DeleteView):
|
|||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
if obj.payment_set.all().count() > 0:
|
if obj.payment_set.all().count() > 0:
|
||||||
messages.info(self.request, 'To delete an invoice, delete the payments first.')
|
messages.info(self.request, 'To delete an invoice, delete the payments first.')
|
||||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk}))
|
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': obj.pk}))
|
||||||
return super(InvoiceDelete, self).get(pk)
|
return super(InvoiceDelete, self).get(pk)
|
||||||
|
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
if obj.payment_set.all().count() > 0:
|
if obj.payment_set.all().count() > 0:
|
||||||
messages.info(self.request, 'To delete an invoice, delete the payments first.')
|
messages.info(self.request, 'To delete an invoice, delete the payments first.')
|
||||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk}))
|
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': obj.pk}))
|
||||||
return super(InvoiceDelete, self).post(pk)
|
return super(InvoiceDelete, self).post(pk)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@@ -172,16 +166,17 @@ class InvoiceWaiting(generic.ListView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
|
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
|
||||||
total = 0
|
total = 0
|
||||||
for obj in self.get_objects():
|
objects = self.get_queryset()
|
||||||
|
for obj in objects:
|
||||||
total += obj.sum_total
|
total += obj.sum_total
|
||||||
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(self.get_objects()), total)
|
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(objects), total)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.get_objects()
|
return self.get_objects()
|
||||||
|
|
||||||
def get_objects(self):
|
def get_objects(self):
|
||||||
# @todo find a way to select items
|
# TODO find a way to select items
|
||||||
events = self.model.objects.filter(
|
events = self.model.objects.filter(
|
||||||
(
|
(
|
||||||
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
|
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
|
||||||
@@ -216,7 +211,7 @@ class InvoiceEvent(generic.View):
|
|||||||
invoice.save()
|
invoice.save()
|
||||||
messages.warning(self.request, 'Invoice voided')
|
messages.warning(self.request, 'Invoice voided')
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk}))
|
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': invoice.pk}))
|
||||||
|
|
||||||
|
|
||||||
class PaymentCreate(generic.CreateView):
|
class PaymentCreate(generic.CreateView):
|
||||||
@@ -242,7 +237,7 @@ class PaymentCreate(generic.CreateView):
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
messages.info(self.request, "location.reload()")
|
messages.info(self.request, "location.reload()")
|
||||||
return reverse_lazy('closemodal')
|
return reverse('closemodal')
|
||||||
|
|
||||||
|
|
||||||
class PaymentDelete(generic.DeleteView):
|
class PaymentDelete(generic.DeleteView):
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ class EventRiskAssessmentList(generic.ListView):
|
|||||||
model = models.RiskAssessment
|
model = models.RiskAssessment
|
||||||
template_name = 'hs_object_list.html'
|
template_name = 'hs_object_list.html'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.model.objects.order_by('reviewed_at').select_related('event')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
|
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
|
||||||
context['title'] = 'Risk Assessment'
|
context['title'] = 'Risk Assessment'
|
||||||
@@ -83,7 +86,6 @@ class EventRiskAssessmentList(generic.ListView):
|
|||||||
context['edit'] = 'ra_edit'
|
context['edit'] = 'ra_edit'
|
||||||
context['review'] = 'ra_review'
|
context['review'] = 'ra_review'
|
||||||
context['perm'] = 'perms.RIGS.review_riskassessment'
|
context['perm'] = 'perms.RIGS.review_riskassessment'
|
||||||
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -187,7 +189,6 @@ class EventChecklistList(generic.ListView):
|
|||||||
context['edit'] = 'ec_edit'
|
context['edit'] = 'ec_edit'
|
||||||
context['review'] = 'ec_review'
|
context['review'] = 'ec_review'
|
||||||
context['perm'] = 'perms.RIGS.review_eventchecklist'
|
context['perm'] = 'perms.RIGS.review_eventchecklist'
|
||||||
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -209,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().order_by('-start_date')
|
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)
|
||||||
|
|||||||
37
RIGS/management/commands/deleteSampleData.py
Normal file
37
RIGS/management/commands/deleteSampleData.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
from assets import models
|
||||||
|
from RIGS import models as rigsmodels
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Deletes testing sample data'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
if not settings.DEBUG:
|
||||||
|
raise CommandError('You cannot run this command in production')
|
||||||
|
|
||||||
|
self.delete_objects(models.AssetCategory)
|
||||||
|
self.delete_objects(models.AssetStatus)
|
||||||
|
self.delete_objects(models.Supplier)
|
||||||
|
self.delete_objects(models.Connector)
|
||||||
|
self.delete_objects(models.Asset)
|
||||||
|
self.delete_objects(rigsmodels.VatRate)
|
||||||
|
self.delete_objects(rigsmodels.Profile)
|
||||||
|
self.delete_objects(rigsmodels.Person)
|
||||||
|
self.delete_objects(rigsmodels.Organisation)
|
||||||
|
self.delete_objects(rigsmodels.Venue)
|
||||||
|
self.delete_objects(Group)
|
||||||
|
self.delete_objects(rigsmodels.Event)
|
||||||
|
self.delete_objects(rigsmodels.EventItem)
|
||||||
|
self.delete_objects(rigsmodels.Invoice)
|
||||||
|
self.delete_objects(rigsmodels.Payment)
|
||||||
|
self.delete_objects(rigsmodels.RiskAssessment)
|
||||||
|
self.delete_objects(rigsmodels.EventChecklist)
|
||||||
|
|
||||||
|
def delete_objects(self, model):
|
||||||
|
for obj in model.objects.all():
|
||||||
|
obj.delete()
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from RIGS import models
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Adds sample data to use for testing'
|
help = 'Adds sample data to use for testing'
|
||||||
can_import_settings = True
|
can_import_settings = True
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
call_command('generateSampleUserData')
|
||||||
call_command('generateSampleRIGSData')
|
call_command('generateSampleRIGSData')
|
||||||
call_command('generateSampleAssetsData')
|
call_command('generateSampleAssetsData')
|
||||||
|
|||||||
@@ -17,11 +17,8 @@ class Command(BaseCommand):
|
|||||||
people = []
|
people = []
|
||||||
organisations = []
|
organisations = []
|
||||||
venues = []
|
venues = []
|
||||||
profiles = []
|
events = []
|
||||||
|
profiles = models.Profile.objects.all()
|
||||||
keyholder_group = None
|
|
||||||
finance_group = None
|
|
||||||
hs_group = None
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -34,20 +31,12 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||||
|
self.setup_people()
|
||||||
|
self.setup_organisations()
|
||||||
|
self.setup_venues()
|
||||||
|
self.setup_events()
|
||||||
|
|
||||||
self.setupGenericProfiles()
|
def setup_people(self):
|
||||||
|
|
||||||
self.setupPeople()
|
|
||||||
self.setupOrganisations()
|
|
||||||
self.setupVenues()
|
|
||||||
|
|
||||||
self.setupGroups()
|
|
||||||
|
|
||||||
self.setupEvents()
|
|
||||||
|
|
||||||
self.setupUsefulProfiles()
|
|
||||||
|
|
||||||
def setupPeople(self):
|
|
||||||
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
|
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
|
||||||
"Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore",
|
"Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore",
|
||||||
"Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan",
|
"Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan",
|
||||||
@@ -62,25 +51,25 @@ class Command(BaseCommand):
|
|||||||
"Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa
|
"Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa
|
||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
reversion.set_user(random.choice(self.profiles))
|
reversion.set_user(random.choice(models.Profile.objects.all()))
|
||||||
|
person = models.Person.objects.create(name=name)
|
||||||
|
|
||||||
newPerson = models.Person.objects.create(name=name)
|
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
newPerson.email = "address@person.com"
|
person.email = "address@person.com"
|
||||||
|
|
||||||
if i % 5 == 0:
|
if i % 5 == 0:
|
||||||
newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
person.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||||
|
|
||||||
if i % 7 == 0:
|
if i % 7 == 0:
|
||||||
newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
person.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||||
|
|
||||||
if i % 9 == 0:
|
if i % 9 == 0:
|
||||||
newPerson.phone = "01234 567894"
|
person.phone = "01234 567894"
|
||||||
|
|
||||||
newPerson.save()
|
person.save()
|
||||||
self.people.append(newPerson)
|
self.people.append(person)
|
||||||
|
|
||||||
def setupOrganisations(self):
|
def setup_organisations(self):
|
||||||
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars",
|
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars",
|
||||||
"ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc",
|
"ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc",
|
||||||
"Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp",
|
"Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp",
|
||||||
@@ -109,27 +98,28 @@ class Command(BaseCommand):
|
|||||||
"Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
|
"Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
|
||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
reversion.set_user(random.choice(self.profiles))
|
reversion.set_user(random.choice(models.Profile.objects.all()))
|
||||||
newOrganisation = models.Organisation.objects.create(name=name)
|
new_organisation = models.Organisation.objects.create(name=name)
|
||||||
|
|
||||||
if i % 2 == 0:
|
if i % 2 == 0:
|
||||||
newOrganisation.has_su_account = True
|
new_organisation.has_su_account = True
|
||||||
|
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
newOrganisation.email = "address@organisation.com"
|
new_organisation.email = "address@organisation.com"
|
||||||
|
|
||||||
if i % 5 == 0:
|
if i % 5 == 0:
|
||||||
newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
new_organisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||||
|
|
||||||
if i % 7 == 0:
|
if i % 7 == 0:
|
||||||
newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
new_organisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||||
|
|
||||||
if i % 9 == 0:
|
if i % 9 == 0:
|
||||||
newOrganisation.phone = "01234 567894"
|
new_organisation.phone = "01234 567894"
|
||||||
|
|
||||||
newOrganisation.save()
|
new_organisation.save()
|
||||||
self.organisations.append(newOrganisation)
|
self.organisations.append(new_organisation)
|
||||||
|
|
||||||
def setupVenues(self):
|
def setup_venues(self):
|
||||||
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch",
|
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch",
|
||||||
"The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands",
|
"The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands",
|
||||||
"The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins",
|
"The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins",
|
||||||
@@ -145,108 +135,27 @@ class Command(BaseCommand):
|
|||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
reversion.set_user(random.choice(self.profiles))
|
reversion.set_user(random.choice(self.profiles))
|
||||||
newVenue = models.Venue.objects.create(name=name)
|
new_venue = models.Venue.objects.create(name=name)
|
||||||
|
|
||||||
if i % 2 == 0:
|
if i % 2 == 0:
|
||||||
newVenue.three_phase_available = True
|
new_venue.three_phase_available = True
|
||||||
|
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
newVenue.email = "address@venue.com"
|
new_venue.email = "address@venue.com"
|
||||||
|
|
||||||
if i % 5 == 0:
|
if i % 5 == 0:
|
||||||
newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
new_venue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
||||||
|
|
||||||
if i % 7 == 0:
|
if i % 7 == 0:
|
||||||
newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
new_venue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
|
||||||
|
|
||||||
if i % 9 == 0:
|
if i % 9 == 0:
|
||||||
newVenue.phone = "01234 567894"
|
new_venue.phone = "01234 567894"
|
||||||
|
|
||||||
newVenue.save()
|
new_venue.save()
|
||||||
self.venues.append(newVenue)
|
self.venues.append(new_venue)
|
||||||
|
|
||||||
def setupGroups(self):
|
def setup_events(self):
|
||||||
self.keyholder_group = Group.objects.create(name='Keyholders')
|
|
||||||
self.finance_group = Group.objects.create(name='Finance')
|
|
||||||
self.hs_group = Group.objects.create(name='H&S')
|
|
||||||
|
|
||||||
keyholderPerms = ["add_event", "change_event", "view_event",
|
|
||||||
"add_eventitem", "change_eventitem", "delete_eventitem",
|
|
||||||
"add_organisation", "change_organisation", "view_organisation",
|
|
||||||
"add_person", "change_person", "view_person", "view_profile",
|
|
||||||
"add_venue", "change_venue", "view_venue",
|
|
||||||
"add_asset", "change_asset", "delete_asset",
|
|
||||||
"view_asset", "view_supplier", "change_supplier", "asset_finance",
|
|
||||||
"add_supplier", "view_cabletype", "change_cabletype",
|
|
||||||
"add_cabletype", "view_eventchecklist", "change_eventchecklist",
|
|
||||||
"add_eventchecklist", "view_riskassessment", "change_riskassessment",
|
|
||||||
"add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew",
|
|
||||||
"delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle",
|
|
||||||
"change_eventchecklistvehicle",
|
|
||||||
"delete_eventchecklistvehicle", "view_eventchecklistvehicle", ]
|
|
||||||
financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
|
|
||||||
"add_payment", "change_payment", "delete_payment"]
|
|
||||||
hsPerms = keyholderPerms + ["review_riskassessment", "review_eventchecklist"]
|
|
||||||
|
|
||||||
for permId in keyholderPerms:
|
|
||||||
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
|
|
||||||
|
|
||||||
for permId in financePerms:
|
|
||||||
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
|
|
||||||
|
|
||||||
for permId in hsPerms:
|
|
||||||
self.hs_group.permissions.add(Permission.objects.get(codename=permId))
|
|
||||||
|
|
||||||
def setupGenericProfiles(self):
|
|
||||||
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble",
|
|
||||||
"Jack Harkness", "Mickey Smith", "Rose Tyler"]
|
|
||||||
for i, name in enumerate(names):
|
|
||||||
newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0],
|
|
||||||
last_name=name.split(" ")[-1],
|
|
||||||
email=name.replace(" ", "") + "@example.com",
|
|
||||||
initials="".join([j[0].upper() for j in name.split()]))
|
|
||||||
if i % 2 == 0:
|
|
||||||
newProfile.phone = "01234 567894"
|
|
||||||
|
|
||||||
newProfile.save()
|
|
||||||
self.profiles.append(newProfile)
|
|
||||||
|
|
||||||
def setupUsefulProfiles(self):
|
|
||||||
superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User",
|
|
||||||
initials="SU",
|
|
||||||
email="superuser@example.com", is_superuser=True, is_active=True,
|
|
||||||
is_staff=True)
|
|
||||||
superUser.set_password('superuser')
|
|
||||||
superUser.save()
|
|
||||||
|
|
||||||
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
|
|
||||||
initials="FU",
|
|
||||||
email="financeuser@example.com", is_active=True, is_approved=True)
|
|
||||||
financeUser.groups.add(self.finance_group)
|
|
||||||
financeUser.groups.add(self.keyholder_group)
|
|
||||||
financeUser.set_password('finance')
|
|
||||||
financeUser.save()
|
|
||||||
|
|
||||||
hsUser = models.Profile.objects.create(username="hs", first_name="HS", last_name="User",
|
|
||||||
initials="HSU",
|
|
||||||
email="hsuser@example.com", is_active=True, is_approved=True)
|
|
||||||
hsUser.groups.add(self.hs_group)
|
|
||||||
hsUser.groups.add(self.keyholder_group)
|
|
||||||
hsUser.set_password('hs')
|
|
||||||
hsUser.save()
|
|
||||||
|
|
||||||
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
|
|
||||||
initials="KU",
|
|
||||||
email="keyholderuser@example.com", is_active=True, is_approved=True)
|
|
||||||
keyholderUser.groups.add(self.keyholder_group)
|
|
||||||
keyholderUser.set_password('keyholder')
|
|
||||||
keyholderUser.save()
|
|
||||||
|
|
||||||
basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU",
|
|
||||||
email="basicuser@example.com", is_active=True, is_approved=True)
|
|
||||||
basicUser.set_password('basic')
|
|
||||||
basicUser.save()
|
|
||||||
|
|
||||||
def setupEvents(self):
|
|
||||||
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball",
|
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball",
|
||||||
"Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event",
|
"Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event",
|
||||||
"End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show",
|
"End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show",
|
||||||
@@ -257,7 +166,7 @@ class Command(BaseCommand):
|
|||||||
notes = ["The client came into the office at some point", "Who knows if this will happen",
|
notes = ["The client came into the office at some point", "Who knows if this will happen",
|
||||||
"Probably should check this event", "Maybe not happening", "Run away!"]
|
"Probably should check this event", "Maybe not happening", "Run away!"]
|
||||||
|
|
||||||
itemOptions = [
|
item_options = [
|
||||||
{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2,
|
{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2,
|
||||||
'cost': 200.00},
|
'cost': 200.00},
|
||||||
{'name': 'Projector',
|
{'name': 'Projector',
|
||||||
@@ -274,7 +183,7 @@ class Command(BaseCommand):
|
|||||||
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
|
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
|
||||||
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
|
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
|
||||||
|
|
||||||
dayDelta = -120 # start adding events from 4 months ago
|
day_delta = -120 # start adding events from 4 months ago
|
||||||
|
|
||||||
for i in range(150): # Let's add 100 events
|
for i in range(150): # Let's add 100 events
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
@@ -282,70 +191,71 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
name = names[i % len(names)]
|
name = names[i % len(names)]
|
||||||
|
|
||||||
startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
|
start_date = datetime.date.today() + datetime.timedelta(days=day_delta)
|
||||||
dayDelta = dayDelta + random.randint(0, 3)
|
day_delta = day_delta + random.randint(0, 3)
|
||||||
|
|
||||||
newEvent = models.Event.objects.create(name=name, start_date=startDate)
|
new_event = models.Event.objects.create(name=name, start_date=start_date)
|
||||||
|
|
||||||
if random.randint(0, 2) > 1: # 1 in 3 have a start time
|
if random.randint(0, 2) > 1: # 1 in 3 have a start time
|
||||||
newEvent.start_time = datetime.time(random.randint(15, 20))
|
new_event.start_time = datetime.time(random.randint(15, 20))
|
||||||
if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day
|
if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day
|
||||||
newEvent.end_time = datetime.time(random.randint(21, 23))
|
new_event.end_time = datetime.time(random.randint(21, 23))
|
||||||
elif random.randint(0, 1) > 0: # half of the others finish early the next day
|
elif random.randint(0, 1) > 0: # half of the others finish early the next day
|
||||||
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1)
|
new_event.end_date = new_event.start_date + datetime.timedelta(days=1)
|
||||||
newEvent.end_time = datetime.time(random.randint(0, 5))
|
new_event.end_time = datetime.time(random.randint(0, 5))
|
||||||
elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead
|
elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead
|
||||||
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4))
|
new_event.end_date = new_event.start_date + datetime.timedelta(days=random.randint(1, 4))
|
||||||
|
|
||||||
if random.randint(0, 6) > 0: # 5 in 6 have MIC
|
if random.randint(0, 6) > 0: # 5 in 6 have MIC
|
||||||
newEvent.mic = random.choice(self.profiles)
|
new_event.mic = random.choice(self.profiles)
|
||||||
|
|
||||||
if random.randint(0, 6) > 0: # 5 in 6 have organisation
|
if random.randint(0, 6) > 0: # 5 in 6 have organisation
|
||||||
newEvent.organisation = random.choice(self.organisations)
|
new_event.organisation = random.choice(self.organisations)
|
||||||
|
|
||||||
if random.randint(0, 6) > 0: # 5 in 6 have person
|
if random.randint(0, 6) > 0: # 5 in 6 have person
|
||||||
newEvent.person = random.choice(self.people)
|
new_event.person = random.choice(self.people)
|
||||||
|
|
||||||
if random.randint(0, 6) > 0: # 5 in 6 have venue
|
if random.randint(0, 6) > 0: # 5 in 6 have venue
|
||||||
newEvent.venue = random.choice(self.venues)
|
new_event.venue = random.choice(self.venues)
|
||||||
|
|
||||||
# Could have any status, equally weighted
|
# Could have any status, equally weighted
|
||||||
newEvent.status = random.choice(
|
new_event.status = random.choice(
|
||||||
[models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED])
|
[models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED])
|
||||||
|
|
||||||
newEvent.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
|
new_event.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
|
||||||
|
|
||||||
if random.randint(0, 1) > 0: # 1 in 2 have description
|
if random.randint(0, 1) > 0: # 1 in 2 have description
|
||||||
newEvent.description = random.choice(descriptions)
|
new_event.description = random.choice(descriptions)
|
||||||
|
|
||||||
if random.randint(0, 1) > 0: # 1 in 2 have notes
|
if random.randint(0, 1) > 0: # 1 in 2 have notes
|
||||||
newEvent.notes = random.choice(notes)
|
new_event.notes = random.choice(notes)
|
||||||
|
|
||||||
newEvent.save()
|
new_event.save()
|
||||||
|
|
||||||
# Now add some items
|
# Now add some items
|
||||||
for j in range(random.randint(1, 5)):
|
for j in range(random.randint(1, 5)):
|
||||||
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
|
item_data = item_options[random.randint(0, len(item_options) - 1)]
|
||||||
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
new_item = models.EventItem.objects.create(event=new_event, order=j, **item_data)
|
||||||
newItem.save()
|
new_item.save()
|
||||||
|
|
||||||
while newEvent.sum_total < 0:
|
while new_event.sum_total < 0:
|
||||||
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
|
item_data = item_options[random.randint(0, len(item_options) - 1)]
|
||||||
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
|
new_item = models.EventItem.objects.create(event=new_event, order=j, **item_data)
|
||||||
newItem.save()
|
new_item.save()
|
||||||
|
|
||||||
with reversion.create_revision():
|
with reversion.create_revision():
|
||||||
reversion.set_user(random.choice(self.profiles))
|
reversion.set_user(random.choice(self.profiles))
|
||||||
if newEvent.start_date < datetime.date.today(): # think about adding an invoice
|
if new_event.start_date < datetime.date.today(): # think about adding an invoice
|
||||||
if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury
|
if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury
|
||||||
newInvoice = models.Invoice.objects.create(event=newEvent)
|
new_invoice = models.Invoice.objects.create(event=new_event)
|
||||||
if newEvent.status is models.Event.CANCELLED: # void cancelled events
|
if new_event.status is models.Event.CANCELLED: # void cancelled events
|
||||||
newInvoice.void = True
|
new_invoice.void = True
|
||||||
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
|
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
|
||||||
models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance,
|
models.Payment.objects.create(invoice=new_invoice, amount=new_invoice.balance,
|
||||||
date=datetime.date.today())
|
date=datetime.date.today())
|
||||||
if i == 1 or random.randint(0, 5) > 0: # Event 1 and 1 in 5 have a RA
|
if i == 1 or random.randint(0, 5) > 0: # Event 1 and 1 in 5 have a RA
|
||||||
models.RiskAssessment.objects.create(event=newEvent, supervisor_consulted=bool(random.getrandbits(1)), nonstandard_equipment=bool(random.getrandbits(1)),
|
models.RiskAssessment.objects.create(event=new_event, supervisor_consulted=bool(random.getrandbits(1)),
|
||||||
|
nonstandard_equipment=bool(random.getrandbits(1)),
|
||||||
nonstandard_use=bool(random.getrandbits(1)),
|
nonstandard_use=bool(random.getrandbits(1)),
|
||||||
contractors=bool(random.getrandbits(1)),
|
contractors=bool(random.getrandbits(1)),
|
||||||
other_companies=bool(random.getrandbits(1)),
|
other_companies=bool(random.getrandbits(1)),
|
||||||
@@ -366,8 +276,15 @@ class Command(BaseCommand):
|
|||||||
suspended_structures=bool(random.getrandbits(1)),
|
suspended_structures=bool(random.getrandbits(1)),
|
||||||
outside=bool(random.getrandbits(1)))
|
outside=bool(random.getrandbits(1)))
|
||||||
if i == 0 or random.randint(0, 1) > 0: # Event 1 and 1 in 10 have a Checklist
|
if i == 0 or random.randint(0, 1) > 0: # Event 1 and 1 in 10 have a Checklist
|
||||||
models.EventChecklist.objects.create(event=newEvent, power_mic=random.choice(self.profiles), safe_parking=bool(random.getrandbits(1)),
|
models.EventChecklist.objects.create(event=new_event, power_mic=random.choice(self.profiles),
|
||||||
safe_packing=bool(random.getrandbits(1)), exits=bool(random.getrandbits(1)), trip_hazard=bool(random.getrandbits(1)), warning_signs=bool(random.getrandbits(1)),
|
safe_parking=bool(random.getrandbits(1)),
|
||||||
ear_plugs=bool(random.getrandbits(1)), hs_location="Locked away safely",
|
safe_packing=bool(random.getrandbits(1)),
|
||||||
extinguishers_location="Somewhere, I forgot", earthing=bool(random.getrandbits(1)), pat=bool(random.getrandbits(1)),
|
exits=bool(random.getrandbits(1)),
|
||||||
|
trip_hazard=bool(random.getrandbits(1)),
|
||||||
|
warning_signs=bool(random.getrandbits(1)),
|
||||||
|
ear_plugs=bool(random.getrandbits(1)),
|
||||||
|
hs_location="Locked away safely",
|
||||||
|
extinguishers_location="Somewhere, I forgot",
|
||||||
|
earthing=bool(random.getrandbits(1)),
|
||||||
|
pat=bool(random.getrandbits(1)),
|
||||||
date=timezone.now(), venue=random.choice(self.venues))
|
date=timezone.now(), venue=random.choice(self.venues))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Generated by Django 2.0.13 on 2020-01-11 18:29
|
# Generated by Django 2.0.13 on 2020-01-11 18:29
|
||||||
# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved
|
# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
def approve_legacy(apps, schema_editor):
|
def approve_legacy(apps, schema_editor):
|
||||||
@@ -15,5 +15,5 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(approve_legacy)
|
migrations.RunPython(approve_legacy, migrations.RunPython.noop)
|
||||||
]
|
]
|
||||||
|
|||||||
18
RIGS/migrations/0040_profile_dark_theme.py
Normal file
18
RIGS/migrations/0040_profile_dark_theme.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1.5 on 2021-02-06 10:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0039_auto_20210123_1910'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='dark_theme',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
199
RIGS/migrations/0041_auto_20210208_1603.py
Normal file
199
RIGS/migrations/0041_auto_20210208_1603.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# Generated by Django 3.1.5 on 2021-02-08 16:03
|
||||||
|
|
||||||
|
import RIGS.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('RIGS', '0040_profile_dark_theme'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='auth_request_to',
|
||||||
|
field=models.EmailField(blank=True, default='', max_length=254),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='collector',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=255, verbose_name='collected by'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='meet_info',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='notes',
|
||||||
|
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(
|
||||||
|
model_name='event',
|
||||||
|
name='purchase_order',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=255, verbose_name='PO'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventauthorisation',
|
||||||
|
name='account_code',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=50),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventauthorisation',
|
||||||
|
name='uni_id',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=10, verbose_name='University ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventchecklist',
|
||||||
|
name='extinguishers_location',
|
||||||
|
field=models.CharField(blank=True, default='', help_text='Location of fire extinguishers', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventchecklist',
|
||||||
|
name='hs_location',
|
||||||
|
field=models.CharField(blank=True, default='', help_text='Location of Safety Bag/Box', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventchecklist',
|
||||||
|
name='w1_description',
|
||||||
|
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventchecklist',
|
||||||
|
name='w2_description',
|
||||||
|
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventchecklist',
|
||||||
|
name='w3_description',
|
||||||
|
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventitem',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='organisation',
|
||||||
|
name='address',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='organisation',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(blank=True, default='', max_length=254),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='organisation',
|
||||||
|
name='notes',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='organisation',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=15),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='payment',
|
||||||
|
name='method',
|
||||||
|
field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('SU', 'SU Core'), ('T', 'TEC Adjustment')], default='', max_length=2),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='person',
|
||||||
|
name='address',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='person',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(blank=True, default='', max_length=254),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='person',
|
||||||
|
name='notes',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='person',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=15),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='profile',
|
||||||
|
name='api_key',
|
||||||
|
field=models.CharField(blank=True, default='', editable=False, max_length=40),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='profile',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(default='', max_length=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='riskassessment',
|
||||||
|
name='general_notes',
|
||||||
|
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='riskassessment',
|
||||||
|
name='persons_responsible_structures',
|
||||||
|
field=models.TextField(blank=True, default='', help_text='Who are the persons on site responsible for their use?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='riskassessment',
|
||||||
|
name='power_notes',
|
||||||
|
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='riskassessment',
|
||||||
|
name='power_plan',
|
||||||
|
field=models.URLField(blank=True, default='', help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[RIGS.models.validate_url]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='riskassessment',
|
||||||
|
name='rigging_plan',
|
||||||
|
field=models.URLField(blank=True, default='', help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[RIGS.models.validate_url]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='riskassessment',
|
||||||
|
name='sound_notes',
|
||||||
|
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='venue',
|
||||||
|
name='address',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='venue',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(blank=True, default='', max_length=254),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='venue',
|
||||||
|
name='notes',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='venue',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=15),
|
||||||
|
),
|
||||||
|
]
|
||||||
132
RIGS/models.py
132
RIGS/models.py
@@ -12,7 +12,7 @@ from django.conf import settings
|
|||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
@@ -21,11 +21,12 @@ from reversion.models import Version
|
|||||||
|
|
||||||
class Profile(AbstractUser):
|
class Profile(AbstractUser):
|
||||||
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
|
||||||
phone = models.CharField(max_length=13, null=True, blank=True)
|
phone = models.CharField(max_length=13, null=True, default='')
|
||||||
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
|
api_key = models.CharField(max_length=40, blank=True, editable=False, default='')
|
||||||
is_approved = models.BooleanField(default=False)
|
is_approved = models.BooleanField(default=False)
|
||||||
last_emailed = models.DateTimeField(blank=True,
|
# Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
|
||||||
null=True) # 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)
|
||||||
|
dark_theme = models.BooleanField(default=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_api_key(cls):
|
def make_api_key(cls):
|
||||||
@@ -51,7 +52,7 @@ class Profile(AbstractUser):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_events(self):
|
def latest_events(self):
|
||||||
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic', 'riskassessment', 'invoice').prefetch_related('checklists')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def admins(cls):
|
def admins(cls):
|
||||||
@@ -102,12 +103,12 @@ class RevisionMixin(object):
|
|||||||
|
|
||||||
class Person(models.Model, RevisionMixin):
|
class Person(models.Model, RevisionMixin):
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
phone = models.CharField(max_length=15, blank=True, default='')
|
||||||
email = models.EmailField(blank=True, null=True)
|
email = models.EmailField(blank=True, default='')
|
||||||
|
|
||||||
address = models.TextField(blank=True, null=True)
|
address = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
notes = models.TextField(blank=True, null=True)
|
notes = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
string = self.name
|
string = self.name
|
||||||
@@ -133,17 +134,17 @@ class Person(models.Model, RevisionMixin):
|
|||||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('person_detail', kwargs={'pk': self.pk})
|
return reverse('person_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class Organisation(models.Model, RevisionMixin):
|
class Organisation(models.Model, RevisionMixin):
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
phone = models.CharField(max_length=15, blank=True, default='')
|
||||||
email = models.EmailField(blank=True, null=True)
|
email = models.EmailField(blank=True, default='')
|
||||||
|
|
||||||
address = models.TextField(blank=True, null=True)
|
address = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
notes = models.TextField(blank=True, null=True)
|
notes = models.TextField(blank=True, default='')
|
||||||
union_account = models.BooleanField(default=False)
|
union_account = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -170,7 +171,7 @@ class Organisation(models.Model, RevisionMixin):
|
|||||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('organisation_detail', kwargs={'pk': self.pk})
|
return reverse('organisation_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class VatManager(models.Manager):
|
class VatManager(models.Manager):
|
||||||
@@ -178,7 +179,6 @@ class VatManager(models.Manager):
|
|||||||
return self.find_rate(timezone.now())
|
return self.find_rate(timezone.now())
|
||||||
|
|
||||||
def find_rate(self, date):
|
def find_rate(self, date):
|
||||||
# return self.filter(startAt__lte=date).latest()
|
|
||||||
try:
|
try:
|
||||||
return self.filter(start_at__lte=date).latest()
|
return self.filter(start_at__lte=date).latest()
|
||||||
except VatRate.DoesNotExist:
|
except VatRate.DoesNotExist:
|
||||||
@@ -211,12 +211,12 @@ class VatRate(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
class Venue(models.Model, RevisionMixin):
|
class Venue(models.Model, RevisionMixin):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
phone = models.CharField(max_length=15, blank=True, default='')
|
||||||
email = models.EmailField(blank=True, null=True)
|
email = models.EmailField(blank=True, default='')
|
||||||
three_phase_available = models.BooleanField(default=False)
|
three_phase_available = models.BooleanField(default=False)
|
||||||
notes = models.TextField(blank=True, null=True)
|
notes = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
address = models.TextField(blank=True, null=True)
|
address = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
string = self.name
|
string = self.name
|
||||||
@@ -229,24 +229,23 @@ class Venue(models.Model, RevisionMixin):
|
|||||||
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('venue_detail', kwargs={'pk': self.pk})
|
return reverse('venue_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
class EventManager(models.Manager):
|
class EventManager(models.Manager):
|
||||||
def current_events(self):
|
def current_events(self):
|
||||||
events = self.filter(
|
events = self.filter(
|
||||||
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q(
|
(models.Q(start_date__gte=timezone.now(), end_date__isnull=True, dry_hire=False) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Starts after with no end
|
status=Event.CANCELLED)) | # Starts after with no end
|
||||||
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(
|
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Ends after
|
status=Event.CANCELLED)) | # Ends after
|
||||||
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(
|
(models.Q(dry_hire=True, start_date__gte=timezone.now()) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Active dry hire
|
status=Event.CANCELLED)) | # Active dry hire
|
||||||
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
|
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
|
||||||
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
||||||
models.Q(status=Event.CANCELLED, start_date__gte=timezone.now().date()) # Canceled but not started
|
models.Q(status=Event.CANCELLED, start_date__gte=timezone.now()) # Canceled but not started
|
||||||
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
|
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
|
||||||
'organisation',
|
|
||||||
'venue', 'mic')
|
|
||||||
return events
|
return events
|
||||||
|
|
||||||
def events_in_bounds(self, start, end):
|
def events_in_bounds(self, start, end):
|
||||||
@@ -269,12 +268,12 @@ class EventManager(models.Manager):
|
|||||||
|
|
||||||
def rig_count(self):
|
def rig_count(self):
|
||||||
event_count = self.filter(
|
event_count = self.filter(
|
||||||
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False,
|
(models.Q(start_date__gte=timezone.now(), end_date__isnull=True, dry_hire=False,
|
||||||
is_rig=True) & ~models.Q(
|
is_rig=True) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Starts after with no end
|
status=Event.CANCELLED)) | # Starts after with no end
|
||||||
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q(
|
(models.Q(end_date__gte=timezone.now(), dry_hire=False, is_rig=True) & ~models.Q(
|
||||||
status=Event.CANCELLED)) | # Ends after
|
status=Event.CANCELLED)) | # Ends after
|
||||||
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q(
|
(models.Q(dry_hire=True, start_date__gte=timezone.now(), is_rig=True) & ~models.Q(
|
||||||
status=Event.CANCELLED)) # Active dry hire
|
status=Event.CANCELLED)) # Active dry hire
|
||||||
).count()
|
).count()
|
||||||
return event_count
|
return event_count
|
||||||
@@ -298,8 +297,8 @@ class Event(models.Model, RevisionMixin):
|
|||||||
person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE)
|
person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE)
|
||||||
organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE)
|
organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE)
|
||||||
venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE)
|
venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE)
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, default='')
|
||||||
notes = models.TextField(blank=True, null=True)
|
notes = models.TextField(blank=True, default='')
|
||||||
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
||||||
dry_hire = models.BooleanField(default=False)
|
dry_hire = models.BooleanField(default=False)
|
||||||
is_rig = models.BooleanField(default=True)
|
is_rig = models.BooleanField(default=True)
|
||||||
@@ -313,7 +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, 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,
|
||||||
@@ -322,15 +321,15 @@ 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, null=True)
|
payment_method = models.CharField(max_length=255, blank=True, default='')
|
||||||
payment_received = models.CharField(max_length=255, blank=True, null=True)
|
payment_received = models.CharField(max_length=255, blank=True, default='')
|
||||||
purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO')
|
purchase_order = models.CharField(max_length=255, blank=True, default='', verbose_name='PO')
|
||||||
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
|
collector = models.CharField(max_length=255, blank=True, default='', verbose_name='collected by')
|
||||||
|
|
||||||
# Authorisation request details
|
# Authorisation request details
|
||||||
auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
|
auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
|
||||||
auth_request_at = models.DateTimeField(null=True, blank=True)
|
auth_request_at = models.DateTimeField(null=True, blank=True)
|
||||||
auth_request_to = models.EmailField(null=True, blank=True)
|
auth_request_to = models.EmailField(blank=True, default='')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_id(self):
|
def display_id(self):
|
||||||
@@ -346,7 +345,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def sum_total(self):
|
def sum_total(self):
|
||||||
total = EventItem.objects.filter(event=self).aggregate(
|
total = self.items.aggregate(
|
||||||
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
|
||||||
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
output_field=models.DecimalField(max_digits=10, decimal_places=2))
|
||||||
)['sum_total']
|
)['sum_total']
|
||||||
@@ -456,7 +455,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
objects = EventManager()
|
objects = EventManager()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
|
return reverse('event_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}: {}".format(self.display_id, self.name)
|
return "{}: {}".format(self.display_id, self.name)
|
||||||
@@ -490,7 +489,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
class EventItem(models.Model, RevisionMixin):
|
class EventItem(models.Model, RevisionMixin):
|
||||||
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
|
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, default='')
|
||||||
quantity = models.IntegerField()
|
quantity = models.IntegerField()
|
||||||
cost = models.DecimalField(max_digits=10, decimal_places=2)
|
cost = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
order = models.IntegerField()
|
order = models.IntegerField()
|
||||||
@@ -505,7 +504,7 @@ class EventItem(models.Model, RevisionMixin):
|
|||||||
ordering = ['order']
|
ordering = ['order']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.event.pk) + "." + str(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):
|
||||||
@@ -517,13 +516,13 @@ class EventAuthorisation(models.Model, RevisionMixin):
|
|||||||
event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE)
|
event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE)
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
|
uni_id = models.CharField(max_length=10, blank=True, default='', verbose_name="University ID")
|
||||||
account_code = models.CharField(max_length=50, blank=True, null=True)
|
account_code = models.CharField(max_length=50, default='', blank=True)
|
||||||
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
|
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
|
||||||
sent_by = models.ForeignKey('Profile', on_delete=models.CASCADE)
|
sent_by = models.ForeignKey('Profile', on_delete=models.CASCADE)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('event_detail', kwargs={'pk': self.event.pk})
|
return reverse('event_detail', kwargs={'pk': self.event_id})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
@@ -562,11 +561,11 @@ class Invoice(models.Model, RevisionMixin):
|
|||||||
return self.balance == 0 or self.void
|
return self.balance == 0 or self.void
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('invoice_detail', kwargs={'pk': self.pk})
|
return reverse('invoice_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity_feed_string(self):
|
def activity_feed_string(self):
|
||||||
return "#{} for Event {}".format(self.display_id, "N%05d" % self.event.pk)
|
return "#{} for Event {}".format(self.display_id, self.event.display_id)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
||||||
@@ -597,7 +596,7 @@ class Payment(models.Model, RevisionMixin):
|
|||||||
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
|
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
|
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
|
||||||
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)
|
method = models.CharField(max_length=2, choices=METHODS, default='', blank=True)
|
||||||
|
|
||||||
reversion_hide = True
|
reversion_hide = True
|
||||||
|
|
||||||
@@ -632,10 +631,9 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
|
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
|
||||||
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
|
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
|
||||||
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
|
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
|
||||||
general_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
general_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||||
|
|
||||||
# Power
|
# Power
|
||||||
# event_size = models.IntegerField(blank=True, null=True, choices=SIZES)
|
|
||||||
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
|
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
|
||||||
# If yes to the above two, you must answer...
|
# If yes to the above two, you must answer...
|
||||||
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
|
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
|
||||||
@@ -645,12 +643,12 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
|
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
|
||||||
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
|
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
|
||||||
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
|
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
|
||||||
power_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
power_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||||
power_plan = models.URLField(blank=True, null=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
power_plan = models.URLField(blank=True, default='', help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
||||||
|
|
||||||
# Sound
|
# Sound
|
||||||
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
|
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
|
||||||
sound_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
sound_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
|
||||||
|
|
||||||
# Site
|
# Site
|
||||||
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
|
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
|
||||||
@@ -663,8 +661,8 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
# Structures
|
# Structures
|
||||||
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
|
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
|
||||||
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
|
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
|
||||||
persons_responsible_structures = models.TextField(blank=True, null=True, help_text="Who are the persons on site responsible for their use?")
|
persons_responsible_structures = models.TextField(blank=True, default='', help_text="Who are the persons on site responsible for their use?")
|
||||||
rigging_plan = models.URLField(blank=True, null=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
rigging_plan = models.URLField(blank=True, default='', help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
|
||||||
|
|
||||||
# Blimey that was a lot of options
|
# Blimey that was a lot of options
|
||||||
|
|
||||||
@@ -708,6 +706,10 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
('review_riskassessment', 'Can review Risk Assessments')
|
('review_riskassessment', 'Can review Risk Assessments')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
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]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def event_size(self):
|
def event_size(self):
|
||||||
# Confirm event size. Check all except generators, since generators entails outside
|
# Confirm event size. Check all except generators, since generators entails outside
|
||||||
@@ -723,7 +725,7 @@ class RiskAssessment(models.Model, RevisionMixin):
|
|||||||
return str(self.event)
|
return str(self.event)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('ra_detail', kwargs={'pk': self.pk})
|
return reverse('ra_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%i - %s" % (self.pk, self.event)
|
return "%i - %s" % (self.pk, self.event)
|
||||||
@@ -746,8 +748,8 @@ class EventChecklist(models.Model, RevisionMixin):
|
|||||||
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
|
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
|
||||||
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
|
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
|
||||||
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
|
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
|
||||||
hs_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of Safety Bag/Box")
|
hs_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of Safety Bag/Box")
|
||||||
extinguishers_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of fire extinguishers")
|
extinguishers_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of fire extinguishers")
|
||||||
|
|
||||||
# Small Electrical Checks
|
# Small Electrical Checks
|
||||||
rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?")
|
rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?")
|
||||||
@@ -768,15 +770,15 @@ class EventChecklist(models.Model, RevisionMixin):
|
|||||||
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_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, null=True, 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.IntegerField(blank=True, null=True, 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, null=True, 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.IntegerField(blank=True, null=True, 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, null=True, 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.IntegerField(blank=True, null=True, 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>)")
|
||||||
@@ -790,6 +792,10 @@ 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 = [
|
||||||
@@ -801,7 +807,7 @@ class EventChecklist(models.Model, RevisionMixin):
|
|||||||
return str(self.event)
|
return str(self.event)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('ec_detail', kwargs={'pk': self.pk})
|
return reverse('ec_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%i - %s" % (self.pk, self.event)
|
return "%i - %s" % (self.pk, self.event)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from django.views import generic
|
|||||||
from z3c.rml import rml2pdf
|
from z3c.rml import rml2pdf
|
||||||
|
|
||||||
from PyRIGS import decorators
|
from PyRIGS import decorators
|
||||||
|
from PyRIGS.views import OEmbedView, is_ajax
|
||||||
from RIGS import models, forms
|
from RIGS import models, forms
|
||||||
|
|
||||||
__author__ = 'ghost'
|
__author__ = 'ghost'
|
||||||
@@ -40,7 +41,7 @@ class RigboardIndex(generic.TemplateView):
|
|||||||
context = super(RigboardIndex, self).get_context_data(**kwargs)
|
context = super(RigboardIndex, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
# call out method to get current events
|
# call out method to get current events
|
||||||
context['events'] = models.Event.objects.current_events()
|
context['events'] = models.Event.objects.current_events().select_related('riskassessment', 'invoice').prefetch_related('checklists')
|
||||||
context['page_title'] = "Rigboard"
|
context['page_title'] = "Rigboard"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -59,29 +60,24 @@ class EventDetail(generic.DetailView):
|
|||||||
template_name = 'event_detail.html'
|
template_name = 'event_detail.html'
|
||||||
model = models.Event
|
model = models.Event
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
class EventOembed(generic.View):
|
context = super(EventDetail, self).get_context_data(**kwargs)
|
||||||
model = models.Event
|
title = "{} | {}".format(self.object.display_id, self.object.name)
|
||||||
|
if self.object.dry_hire:
|
||||||
def get(self, request, pk=None):
|
title += " <span class='badge badge-secondary'>Dry Hire</span>"
|
||||||
embed_url = reverse('event_embed', args=[pk])
|
context['page_title'] = title
|
||||||
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
return context
|
||||||
|
|
||||||
data = {
|
|
||||||
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
|
|
||||||
'version': '1.0',
|
|
||||||
'type': 'rich',
|
|
||||||
'height': '250'
|
|
||||||
}
|
|
||||||
|
|
||||||
json = simplejson.JSONEncoderForHTML().encode(data)
|
|
||||||
return HttpResponse(json, content_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
class EventEmbed(EventDetail):
|
class EventEmbed(EventDetail):
|
||||||
template_name = 'event_embed.html'
|
template_name = 'event_embed.html'
|
||||||
|
|
||||||
|
|
||||||
|
class EventOEmbed(OEmbedView):
|
||||||
|
model = models.Event
|
||||||
|
url_name = 'event_embed'
|
||||||
|
|
||||||
|
|
||||||
class EventCreate(generic.CreateView):
|
class EventCreate(generic.CreateView):
|
||||||
model = models.Event
|
model = models.Event
|
||||||
form_class = forms.EventForm
|
form_class = forms.EventForm
|
||||||
@@ -157,7 +153,7 @@ class EventDuplicate(EventUpdate):
|
|||||||
new.checked_in_by = None
|
new.checked_in_by = None
|
||||||
|
|
||||||
# Remove all the authorisation information from the new event
|
# Remove all the authorisation information from the new event
|
||||||
new.auth_request_to = None
|
new.auth_request_to = ''
|
||||||
new.auth_request_by = None
|
new.auth_request_by = None
|
||||||
new.auth_request_at = None
|
new.auth_request_at = None
|
||||||
|
|
||||||
@@ -185,15 +181,9 @@ class EventPrint(generic.View):
|
|||||||
|
|
||||||
context = {
|
context = {
|
||||||
'object': object,
|
'object': object,
|
||||||
'fonts': {
|
|
||||||
'opensans': {
|
|
||||||
'regular': 'static/fonts/OPENSANS-REGULAR.TTF',
|
|
||||||
'bold': 'static/fonts/OPENSANS-BOLD.TTF',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'quote': True,
|
'quote': True,
|
||||||
'current_user': request.user,
|
'current_user': request.user,
|
||||||
'filename': 'Event {} {} {}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
|
'filename': 'Event_{}_{}_{}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
|
||||||
}
|
}
|
||||||
|
|
||||||
rml = template.render(context)
|
rml = template.render(context)
|
||||||
@@ -359,7 +349,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
|||||||
return self.get_object()
|
return self.get_object()
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
if self.request.is_ajax():
|
if is_ajax(self.request):
|
||||||
url = reverse_lazy('closemodal')
|
url = reverse_lazy('closemodal')
|
||||||
messages.info(self.request, "location.reload()")
|
messages.info(self.request, "location.reload()")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -25,12 +25,6 @@ def send_eventauthorisation_success_email(instance):
|
|||||||
# Generate PDF first to prevent context conflicts
|
# Generate PDF first to prevent context conflicts
|
||||||
context = {
|
context = {
|
||||||
'object': instance.event,
|
'object': instance.event,
|
||||||
'fonts': {
|
|
||||||
'opensans': {
|
|
||||||
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
|
|
||||||
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'receipt': True,
|
'receipt': True,
|
||||||
'current_user': False,
|
'current_user': False,
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
BIN
RIGS/static/imgs/logo.png
Normal file
BIN
RIGS/static/imgs/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
@@ -4,12 +4,12 @@
|
|||||||
{% block title %}Calendar{% endblock %}
|
{% block title %}Calendar{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
<link href="{% static 'css/main.min.css' %}" rel='stylesheet' />
|
<link href="{% static 'css/main.css' %}" rel='stylesheet' />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script src="{% static 'js/moment.js' %}"></script>
|
<script src="{% static 'js/moment.js' %}"></script>
|
||||||
<script src="{% static 'js/main.min.js' %}"></script>
|
<script src="{% static 'js/main.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
viewToUrl = {
|
viewToUrl = {
|
||||||
'timeGridWeek':'week',
|
'timeGridWeek':'week',
|
||||||
|
|||||||
@@ -2,17 +2,9 @@
|
|||||||
{% load linkornone from filters %}
|
{% load linkornone from filters %}
|
||||||
{% load namewithnotes from filters %}
|
{% load namewithnotes from filters %}
|
||||||
|
|
||||||
{% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% 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 %}
|
||||||
<div class="col-sm-12">
|
|
||||||
<h1>
|
|
||||||
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
|
|
||||||
| {{ object.name }} {% if event.dry_hire %}<span class="badge badge-secondary">Dry Hire</span>{% endif %}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
{% 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 'event_detail_buttons.html' %}
|
{% include 'event_detail_buttons.html' %}
|
||||||
|
|||||||
@@ -1,100 +1,88 @@
|
|||||||
{% extends 'base_embed.html' %}
|
{% extends 'base_embed.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block content %}
|
{% block js %}
|
||||||
<div class="row">
|
<script src="{% static 'js/all.js' %}"></script>
|
||||||
<div class="col-sm-12">
|
{% endblock %}
|
||||||
<a href="/">
|
|
||||||
<span class="source"> R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-12">
|
{% block content %}
|
||||||
<span class="pull-right">
|
<span class="float-right">
|
||||||
{% if object.mic %}
|
{% if object.mic %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo rounded"/>
|
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo rounded"/>
|
||||||
</div>
|
</div>
|
||||||
{% elif object.is_rig %}
|
{% elif object.is_rig %}
|
||||||
<span class="fas fa-exclamation-sign"></span>
|
<span class="fas fa-exclamation-sign"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<a href="{% url 'event_detail' object.pk %}">
|
<a href="{% url 'event_detail' object.pk %}">{{ object.display_id }} | {{ object.name }}</a>
|
||||||
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
|
{% if object.venue %}
|
||||||
| {{ object.name }} </a>
|
<small>at {{ object.venue }}</small>
|
||||||
{% if object.venue %}
|
{% endif %}
|
||||||
<small>at {{ object.venue }}</small>
|
<br/><small>
|
||||||
{% endif %}
|
|
||||||
<br/><small>
|
|
||||||
{{ object.start_date|date:"D d/m/Y" }}
|
{{ object.start_date|date:"D d/m/Y" }}
|
||||||
{% if object.has_start_time %}
|
{% if object.has_start_time %}
|
||||||
{{ object.start_time|date:"H:i" }}
|
{{ object.start_time|date:"H:i" }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if object.end_date or object.has_end_time %}
|
{% if object.end_date or object.has_end_time %}
|
||||||
–
|
–
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if object.end_date and object.end_date != object.start_date %}
|
{% if object.end_date and object.end_date != object.start_date %}
|
||||||
{{ object.end_date|date:"D d/m/Y" }}
|
{{ object.end_date|date:"D d/m/Y" }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if object.has_end_time %}
|
{% if object.has_end_time %}
|
||||||
{{ object.end_time|date:"H:i" }}
|
{{ object.end_time|date:"H:i" }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</small>
|
</small>
|
||||||
</h3>
|
</h3>
|
||||||
|
{% include 'partials/event_status.html' %}
|
||||||
<div class="row">
|
<div class="row ml-2">
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6 pr-2">
|
||||||
<p>
|
|
||||||
<strong>Status:</strong>
|
|
||||||
{{ object.get_status_display }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{% if object.is_rig %}
|
|
||||||
<strong>Client:</strong> {{ object.person.name }}
|
|
||||||
{% if object.organisation %}
|
|
||||||
for {{ object.organisation.name }}
|
|
||||||
{% endif %}
|
|
||||||
{% if object.dry_hire %}(Dry Hire){% endif %}
|
|
||||||
{% else %}
|
|
||||||
<strong>Non-Rig</strong>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>MIC:</strong>
|
|
||||||
{% if object.mic %}
|
|
||||||
{{object.mic.name}}
|
|
||||||
{% else %}
|
|
||||||
None
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6">
|
|
||||||
{% if object.meet_at %}
|
|
||||||
<p>
|
|
||||||
<strong>Crew meet:</strong>
|
|
||||||
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if object.access_at %}
|
|
||||||
<p>
|
|
||||||
<strong>Access at:</strong>
|
|
||||||
{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<p>
|
|
||||||
<strong>Last updated:</strong>
|
|
||||||
{{ object.last_edited_at }} by "{{ object.last_edited_by.initials }}"
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if object.description %}
|
|
||||||
<p>
|
<p>
|
||||||
<strong>Description: </strong>
|
{% if object.is_rig %}
|
||||||
{{ object.description|linebreaksbr }}
|
<strong>Client:</strong> {{ object.person.name }}
|
||||||
|
{% if object.organisation %}
|
||||||
|
for {{ object.organisation.name }}
|
||||||
|
{% endif %}
|
||||||
|
{% if object.dry_hire %}(Dry Hire){% endif %}
|
||||||
|
{% else %}
|
||||||
|
<strong>Non-Rig</strong>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
<p>
|
||||||
|
<strong>MIC:</strong>
|
||||||
|
{% if object.mic %}
|
||||||
|
{{object.mic.name}}
|
||||||
|
{% else %}
|
||||||
|
None
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 px-2">
|
||||||
|
{% if object.meet_at %}
|
||||||
|
<p>
|
||||||
|
<strong>Crew meet:</strong>
|
||||||
|
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if object.access_at %}
|
||||||
|
<p>
|
||||||
|
<strong>Access at:</strong>
|
||||||
|
{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
<strong>Last updated:</strong>
|
||||||
|
{{ object.last_edited_at }} by "{{ object.last_edited_by.initials }}"
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% if object.description %}
|
||||||
|
<p>
|
||||||
|
<strong>Description: </strong>
|
||||||
|
{{ object.description|linebreaksbr }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include '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">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
{% load multiply from filters %}
|
|
||||||
{% load static %}
|
|
||||||
<!DOCTYPE document SYSTEM "rml.dtd">
|
<!DOCTYPE document SYSTEM "rml.dtd">
|
||||||
|
|
||||||
<document filename="{{filename}}">
|
<document filename="{{filename}}">
|
||||||
<docinit>
|
<docinit>
|
||||||
<registerTTFont faceName="OpenSans" fileName="{{ fonts.opensans.regular }}"/>
|
<registerTTFont faceName="OpenSans" fileName="static/fonts/OpenSans-Regular.tff"/>
|
||||||
<registerTTFont faceName="OpenSans-Bold" fileName="{{ fonts.opensans.bold }}"/>
|
<registerTTFont faceName="OpenSans-Bold" fileName="static/fonts/OpenSans-Bold.tff"/>
|
||||||
<registerFontFamily name="OpenSans" bold="OpenSans-Bold" boldItalic="OpenSans-Bold"/>
|
<registerFontFamily name="OpenSans" bold="OpenSans-Bold" boldItalic="OpenSans-Bold"/>
|
||||||
</docinit>
|
</docinit>
|
||||||
|
|
||||||
@@ -82,11 +80,11 @@
|
|||||||
<template > {# Note: page is 595x842 points (1 point=1/72in) #}
|
<template > {# Note: page is 595x842 points (1 point=1/72in) #}
|
||||||
<pageTemplate id="Headed" >
|
<pageTemplate id="Headed" >
|
||||||
<pageGraphics>
|
<pageGraphics>
|
||||||
<image file="RIGS/static/imgs/paperwork/corner-tr-su.jpg" x="395" y="642" height="200" width="200"/>
|
<image file="static/imgs/paperwork/corner-tr-su.jpg" x="395" y="642" height="200" width="200"/>
|
||||||
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
<image file="static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
||||||
|
|
||||||
{# logo positioned 42 from left, 33 from top #}
|
{# logo positioned 42 from left, 33 from top #}
|
||||||
<image file="RIGS/static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
|
<image file="static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
|
||||||
|
|
||||||
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
|
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
|
||||||
<drawString x="137" y="780">TEC PA & Lighting</drawString>
|
<drawString x="137" y="780">TEC PA & Lighting</drawString>
|
||||||
@@ -110,8 +108,8 @@
|
|||||||
|
|
||||||
<pageTemplate id="Main">
|
<pageTemplate id="Main">
|
||||||
<pageGraphics>
|
<pageGraphics>
|
||||||
<image file="RIGS/static/imgs/paperwork/corner-tr.jpg" x="395" y="642" height="200" width="200"/>
|
<image file="static/imgs/paperwork/corner-tr.jpg" x="395" y="642" height="200" width="200"/>
|
||||||
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
<image file="static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
|
||||||
|
|
||||||
<setFont name="OpenSans" size="10"/>
|
<setFont name="OpenSans" size="10"/>
|
||||||
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<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 fields %}
|
{% 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 %}
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<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></th>
|
<th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a></th>
|
||||||
{% for field in fields %}
|
{% for field in object_list.0.fields %}
|
||||||
<td>{{ object|get_field:field }}</td>
|
<td>{{ object|get_field:field }}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{# Buttons #}
|
{# Buttons #}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<td>{{ invoice.event.start_date }}</td>
|
<td>{{ invoice.event.start_date }}</td>
|
||||||
<td>{{ invoice.invoice_date }}</td>
|
<td>{{ invoice.invoice_date }}</td>
|
||||||
<td>
|
<td>
|
||||||
{{ invoice.balance|floatformat:2 }}
|
£{{ invoice.balance|floatformat:2 }}
|
||||||
{% if not invoice.event.internal %}
|
{% if not invoice.event.internal %}
|
||||||
<br />
|
<br />
|
||||||
<span class="text-muted">{{ invoice.event.purchase_order }}</span>
|
<span class="text-muted">{{ invoice.event.purchase_order }}</span>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ event.sum_total|floatformat:2 }}
|
£{{ event.sum_total|floatformat:2 }}
|
||||||
<br />
|
<br />
|
||||||
<span class="text-muted">{% if not event.internal %}{{ event.purchase_order }}{% endif %}</span>
|
<span class="text-muted">{% if not event.internal %}{{ event.purchase_order }}{% endif %}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.forms.forms import NON_FIELD_ERRORS
|
|||||||
from django.forms.utils import ErrorDict
|
from django.forms.utils import ErrorDict
|
||||||
from django.template.defaultfilters import stringfilter
|
from django.template.defaultfilters import stringfilter
|
||||||
from django.template.defaultfilters import yesno, title, truncatewords
|
from django.template.defaultfilters import yesno, title, truncatewords
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import SafeData, mark_safe
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
from django.utils.text import normalize_newlines
|
from django.utils.text import normalize_newlines
|
||||||
@@ -173,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='far fa-sticky-note'></span></a>".format(reverse_lazy(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
|
||||||
|
|
||||||
|
|||||||
99
RIGS/tests/conftest.py
Normal file
99
RIGS/tests/conftest.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
from RIGS import models
|
||||||
|
import pytest
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def basic_event(db):
|
||||||
|
event = models.Event.objects.create(name="TE E1", start_date=timezone.now())
|
||||||
|
yield event
|
||||||
|
event.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ra(basic_event, admin_user):
|
||||||
|
ra = models.RiskAssessment.objects.create(event=basic_event, nonstandard_equipment=False, nonstandard_use=False,
|
||||||
|
contractors=False, other_companies=False, crew_fatigue=False,
|
||||||
|
big_power=False, power_mic=admin_user, 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=True, barrier_required=True,
|
||||||
|
nonstandard_emergency_procedure=True, special_structures=False,
|
||||||
|
suspended_structures=False, outside=False)
|
||||||
|
yield ra
|
||||||
|
ra.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def venue(db):
|
||||||
|
venue = models.Venue.objects.create(name="Venue 1")
|
||||||
|
yield venue
|
||||||
|
venue.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture # TODO parameterise with Event sizes
|
||||||
|
def checklist(basic_event, venue, admin_user):
|
||||||
|
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,
|
||||||
|
ear_plugs=False, hs_location="Locked away safely",
|
||||||
|
extinguishers_location="Somewhere, I forgot", earthing=False, pat=False,
|
||||||
|
date=timezone.now(), venue=venue)
|
||||||
|
yield checklist
|
||||||
|
checklist.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def many_events(db, scope="class"):
|
||||||
|
many_events = {
|
||||||
|
# produce 7 normal events - 5 current
|
||||||
|
1: models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6),
|
||||||
|
description="start future no end"),
|
||||||
|
2: models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end"),
|
||||||
|
3: models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(),
|
||||||
|
description="start today with end today"),
|
||||||
|
4: models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end"),
|
||||||
|
5: models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21',
|
||||||
|
description="start past with end past"),
|
||||||
|
6: models.Event.objects.create(name="TE E6", start_date=date.today() - timedelta(days=2),
|
||||||
|
end_date=date.today() + timedelta(days=2),
|
||||||
|
description="start past, end future"),
|
||||||
|
7: models.Event.objects.create(name="TE E7", start_date=date.today() + timedelta(days=2),
|
||||||
|
end_date=date.today() + timedelta(days=2),
|
||||||
|
description="start + end in future"),
|
||||||
|
|
||||||
|
# 2 cancelled - 1 current
|
||||||
|
8: models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2),
|
||||||
|
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
|
||||||
|
description="cancelled in future"),
|
||||||
|
9: models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1),
|
||||||
|
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
|
||||||
|
description="cancelled and started"),
|
||||||
|
|
||||||
|
# 5 dry hire - 3 current
|
||||||
|
10: models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True,
|
||||||
|
description="dryhire today"),
|
||||||
|
11: models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True,
|
||||||
|
checked_in_by=cls.profile,
|
||||||
|
description="dryhire today, checked in"),
|
||||||
|
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"),
|
||||||
|
13: models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
|
||||||
|
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,
|
||||||
|
status=models.Event.CANCELLED, description="dryhire today cancelled"),
|
||||||
|
|
||||||
|
# 4 non rig - 3 current
|
||||||
|
15: models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False,
|
||||||
|
description="non rig today"),
|
||||||
|
16: models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False,
|
||||||
|
description="non rig tomorrow"),
|
||||||
|
17: models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False,
|
||||||
|
description="non rig yesterday"),
|
||||||
|
18: models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False,
|
||||||
|
status=models.Event.CANCELLED,
|
||||||
|
description="non rig today cancelled"),
|
||||||
|
}
|
||||||
|
yield many_events
|
||||||
|
for event in many_events:
|
||||||
|
event.delete()
|
||||||
@@ -52,7 +52,7 @@ class EventDetail(BasePage):
|
|||||||
URL_TEMPLATE = 'event/{event_id}'
|
URL_TEMPLATE = 'event/{event_id}'
|
||||||
|
|
||||||
# TODO Refactor into regions to match template fragmentation
|
# TODO Refactor into regions to match template fragmentation
|
||||||
_event_name_selector = (By.XPATH, '//h1')
|
_event_name_selector = (By.XPATH, '//h2')
|
||||||
_person_panel_selector = (By.XPATH, '//div[contains(text(), "Contact 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]')
|
||||||
|
|||||||
@@ -8,54 +8,12 @@ from django.http import HttpResponseBadRequest
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
import PyRIGS.tests.base
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
from pytest_django.asserts import assertContains, assertNotContains
|
from pytest_django.asserts import assertContains, assertNotContains, assertFormError
|
||||||
|
|
||||||
|
|
||||||
class BaseCase(TestCase):
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
|
||||||
cls.profile = models.Profile.objects.get_or_create(
|
|
||||||
first_name='Test',
|
|
||||||
last_name='TEC User',
|
|
||||||
username='eventauthtest',
|
|
||||||
email='teccie@functional.test',
|
|
||||||
is_superuser=True # lazily grant all permissions
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.profile.set_password('testuser')
|
|
||||||
self.profile.save()
|
|
||||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
|
||||||
venue = models.Venue.objects.create(name='Authorisation Test Venue')
|
|
||||||
client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test')
|
|
||||||
organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=True)
|
|
||||||
self.event = models.Event.objects.create(
|
|
||||||
name='Authorisation Test',
|
|
||||||
start_date=date.today(),
|
|
||||||
venue=venue,
|
|
||||||
person=client,
|
|
||||||
organisation=organisation,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestEventValidation(BaseCase):
|
|
||||||
def test_create(self):
|
|
||||||
url = reverse('event_create')
|
|
||||||
# end time before start access after start
|
|
||||||
response = self.client.post(url, {'start_date': datetime.date(2020, 1, 1), 'start_time': datetime.time(10, 00),
|
|
||||||
'end_time': datetime.time(9, 00),
|
|
||||||
'access_at': datetime.datetime(2020, 1, 5, 10)})
|
|
||||||
self.assertFormError(response, 'form', 'end_time',
|
|
||||||
"Unless you've invented time travel, the event can't finish before it has started.")
|
|
||||||
self.assertFormError(response, 'form', 'access_at',
|
|
||||||
"Regardless of what some clients might think, access time cannot be after the event has started.")
|
|
||||||
|
|
||||||
|
|
||||||
def setup_event():
|
def setup_event():
|
||||||
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
|
||||||
venue = models.Venue.objects.create(name='Authorisation Test Venue')
|
venue = models.Venue.objects.create(name='Authorisation Test Venue')
|
||||||
client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test')
|
client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test')
|
||||||
organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=True)
|
organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=True)
|
||||||
@@ -84,6 +42,18 @@ def setup_mail(event, profile):
|
|||||||
return auth_data, hmac, url
|
return auth_data, hmac, url
|
||||||
|
|
||||||
|
|
||||||
|
def test_create(admin_client):
|
||||||
|
url = reverse('event_create')
|
||||||
|
# end time before start access after start
|
||||||
|
response = admin_client.post(url, {'start_date': datetime.date(2020, 1, 1), 'start_time': datetime.time(10, 00),
|
||||||
|
'end_time': datetime.time(9, 00),
|
||||||
|
'access_at': datetime.datetime(2020, 1, 5, 10)})
|
||||||
|
assertFormError(response, 'form', 'end_time',
|
||||||
|
"Unless you've invented time travel, the event can't finish before it has started.")
|
||||||
|
assertFormError(response, 'form', 'access_at',
|
||||||
|
"Regardless of what some clients might think, access time cannot be after the event has started.")
|
||||||
|
|
||||||
|
|
||||||
def test_requires_valid_hmac(client, admin_user):
|
def test_requires_valid_hmac(client, admin_user):
|
||||||
event = setup_event()
|
event = setup_event()
|
||||||
auth_data, hmac, url = setup_mail(event, admin_user)
|
auth_data, hmac, url = setup_mail(event, admin_user)
|
||||||
@@ -138,7 +108,7 @@ def test_duplicate_warning(client, admin_user):
|
|||||||
assertContains(response, 'amount has changed')
|
assertContains(response, 'amount has changed')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=True)
|
@pytest.mark.django_db
|
||||||
def test_email_sent(admin_client, admin_user, mailoutbox):
|
def test_email_sent(admin_client, admin_user, mailoutbox):
|
||||||
event = setup_event()
|
event = setup_event()
|
||||||
auth_data, hmac, url = setup_mail(event, admin_user)
|
auth_data, hmac, url = setup_mail(event, admin_user)
|
||||||
@@ -152,36 +122,36 @@ def test_email_sent(admin_client, admin_user, mailoutbox):
|
|||||||
assert mailoutbox[1].to == [settings.AUTHORISATION_NOTIFICATION_ADDRESS]
|
assert mailoutbox[1].to == [settings.AUTHORISATION_NOTIFICATION_ADDRESS]
|
||||||
|
|
||||||
|
|
||||||
class TECEventAuthorisationTest(BaseCase):
|
def test_email_check(admin_client, admin_user):
|
||||||
def setUp(self):
|
event = setup_event()
|
||||||
super().setUp()
|
url = reverse('event_authorise_request', kwargs={'pk': event.pk})
|
||||||
self.url = reverse('event_authorise_request', kwargs={'pk': self.event.pk})
|
admin_user.email = 'teccie@someotherdomain.com'
|
||||||
|
admin_user.save()
|
||||||
|
|
||||||
def test_email_check(self):
|
response = admin_client.post(url)
|
||||||
self.profile.email = 'teccie@someotherdomain.com'
|
|
||||||
self.profile.save()
|
|
||||||
|
|
||||||
response = self.client.post(self.url)
|
assertContains(response, 'must have an @nottinghamtec.co.uk email address')
|
||||||
|
|
||||||
self.assertContains(response, 'must have an @nottinghamtec.co.uk email address')
|
|
||||||
|
|
||||||
def test_request_send(self):
|
def test_request_send(admin_client, admin_user):
|
||||||
self.profile.email = 'teccie@nottinghamtec.co.uk'
|
event = setup_event()
|
||||||
self.profile.save()
|
url = reverse('event_authorise_request', kwargs={'pk': event.pk})
|
||||||
response = self.client.post(self.url)
|
admin_user.email = 'teccie@nottinghamtec.co.uk'
|
||||||
self.assertContains(response, 'This field is required.')
|
admin_user.save()
|
||||||
|
response = admin_client.post(url)
|
||||||
|
assertContains(response, 'This field is required.')
|
||||||
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
response = self.client.post(self.url, {'email': 'client@functional.test'})
|
response = admin_client.post(url, {'email': 'client@functional.test'})
|
||||||
self.assertEqual(response.status_code, 302)
|
assert response.status_code == 302
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
assert len(mail.outbox) == 1
|
||||||
email = mail.outbox[0]
|
email = mail.outbox[0]
|
||||||
self.assertIn('client@functional.test', email.to)
|
assert 'client@functional.test' in email.to
|
||||||
self.assertIn('/event/%d/' % (self.event.pk), email.body)
|
assert '/event/%d/' % event.pk in email.body
|
||||||
|
|
||||||
# Check sent by details are populated
|
# Check sent by details are populated
|
||||||
self.event.refresh_from_db()
|
event.refresh_from_db()
|
||||||
self.assertEqual(self.event.auth_request_by, self.profile)
|
assert event.auth_request_by == admin_user
|
||||||
self.assertEqual(self.event.auth_request_to, 'client@functional.test')
|
assert event.auth_request_to == 'client@functional.test'
|
||||||
self.assertIsNotNone(self.event.auth_request_at)
|
assert event.auth_request_at is not None
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ from PyRIGS.tests.pages import animation_is_finished
|
|||||||
from RIGS import models
|
from RIGS import models
|
||||||
from RIGS.tests import regions
|
from RIGS.tests import regions
|
||||||
from . import pages
|
from . import pages
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db(transaction=True)
|
||||||
|
|
||||||
|
|
||||||
@screenshot_failure_cls
|
@screenshot_failure_cls
|
||||||
@@ -307,13 +311,13 @@ class TestEventDuplicate(BaseRigboardTest):
|
|||||||
# TODO Rewrite when EventDetail page is implemented
|
# TODO Rewrite when EventDetail page is implemented
|
||||||
newEvent = models.Event.objects.latest('pk')
|
newEvent = models.Event.objects.latest('pk')
|
||||||
|
|
||||||
self.assertEqual(newEvent.auth_request_to, None)
|
assert newEvent.auth_request_to == ''
|
||||||
self.assertEqual(newEvent.auth_request_by, None)
|
self.assertEqual(newEvent.auth_request_by, None)
|
||||||
self.assertEqual(newEvent.auth_request_at, None)
|
self.assertEqual(newEvent.auth_request_at, None)
|
||||||
|
|
||||||
self.assertFalse(newEvent.authorised)
|
self.assertFalse(newEvent.authorised)
|
||||||
|
|
||||||
self.assertNotIn("N%05d" % self.testEvent.pk, self.driver.find_element_by_xpath('//h1').text)
|
self.assertNotIn("N%05d" % self.testEvent.pk, self.driver.find_element_by_xpath('//h2').text)
|
||||||
self.assertNotIn("Event data duplicated but not yet saved", self.page.warning) # Check info message not visible
|
self.assertNotIn("Event data duplicated but not yet saved", self.page.warning) # Check info message not visible
|
||||||
|
|
||||||
# Check the new items are visible
|
# Check the new items are visible
|
||||||
@@ -445,7 +449,7 @@ class TestEventDetail(BaseRigboardTest):
|
|||||||
self.assertIn("N%05d | %s" % (self.testEvent.pk, self.testEvent.name), self.page.event_name)
|
self.assertIn("N%05d | %s" % (self.testEvent.pk, self.testEvent.name), self.page.event_name)
|
||||||
self.assertEqual(self.client.name, self.page.name)
|
self.assertEqual(self.client.name, self.page.name)
|
||||||
self.assertEqual(self.client.email, self.page.email)
|
self.assertEqual(self.client.email, self.page.email)
|
||||||
self.assertEqual(self.client.phone, None)
|
assert self.client.phone == ''
|
||||||
|
|
||||||
|
|
||||||
@screenshot_failure_cls
|
@screenshot_failure_cls
|
||||||
@@ -865,7 +869,7 @@ class TestHealthAndSafety(BaseRigboardTest):
|
|||||||
self.page.hs_location = "The Moon"
|
self.page.hs_location = "The Moon"
|
||||||
self.page.extinguishers_location = "With the rest of the fire"
|
self.page.extinguishers_location = "With the rest of the fire"
|
||||||
# If we do this first the search fails, for ... reasons
|
# If we do this first the search fails, for ... reasons
|
||||||
self.page.power_mic.search(self.profile.name)
|
self.page.power_mic.search("Test") # FIXME
|
||||||
self.page.power_mic.toggle()
|
self.page.power_mic.toggle()
|
||||||
self.assertFalse(self.page.power_mic.is_open)
|
self.assertFalse(self.page.power_mic.is_open)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from datetime import date, timedelta, datetime, time
|
|||||||
from decimal import *
|
from decimal import *
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
import pytest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
@@ -9,110 +10,56 @@ from reversion import revisions as reversion
|
|||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
|
|
||||||
class ProfileTestCase(TestCase):
|
def assert_decimal_equality(d1, d2):
|
||||||
def test_str(self):
|
assert float(d1) == pytest.approx(float(d2))
|
||||||
profile = models.Profile(first_name='Test', last_name='Case')
|
|
||||||
self.assertEqual(str(profile), 'Test Case')
|
|
||||||
profile.initials = 'TC'
|
|
||||||
self.assertEqual(str(profile), 'Test Case "TC"')
|
|
||||||
|
|
||||||
|
|
||||||
class VatRateTestCase(TestCase):
|
def test_str():
|
||||||
@classmethod
|
profile = models.Profile(first_name='Test', last_name='Case')
|
||||||
def setUpTestData(cls):
|
assert str(profile) == 'Test Case'
|
||||||
cls.rates = {
|
profile.initials = 'TC'
|
||||||
0: models.VatRate.objects.create(start_at='2014-03-01', rate=0.20, comment='test1'),
|
assert str(profile) == 'Test Case "TC"'
|
||||||
1: models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_find_correct(self):
|
|
||||||
r = models.VatRate.objects.find_rate('2015-03-01')
|
|
||||||
self.assertEqual(r, self.rates[0])
|
|
||||||
r = models.VatRate.objects.find_rate('2016-03-01')
|
|
||||||
self.assertEqual(r, self.rates[1])
|
|
||||||
|
|
||||||
def test_percent_correct(self):
|
|
||||||
self.assertEqual(self.rates[0].as_percent, 20)
|
|
||||||
|
|
||||||
|
|
||||||
class EventTestCase(TestCase):
|
@pytest.mark.django_db
|
||||||
@classmethod
|
def test_find_correct(vat_rate):
|
||||||
def setUpTestData(cls):
|
new_rate = models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2')
|
||||||
cls.all_events = set(range(1, 18))
|
r = models.VatRate.objects.find_rate('2015-03-01')
|
||||||
cls.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
|
assert_decimal_equality(r.rate, vat_rate.rate)
|
||||||
cls.not_current_events = set(cls.all_events) - set(cls.current_events)
|
r = models.VatRate.objects.find_rate('2016-03-01')
|
||||||
|
assert_decimal_equality(r.rate, new_rate.rate)
|
||||||
|
|
||||||
cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
|
||||||
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com")
|
|
||||||
|
|
||||||
cls.events = {
|
def test_percent_correct(vat_rate):
|
||||||
# produce 7 normal events - 5 current
|
assert vat_rate.as_percent == 20
|
||||||
1: models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6),
|
|
||||||
description="start future no end"),
|
|
||||||
2: models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end"),
|
|
||||||
3: models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(),
|
|
||||||
description="start today with end today"),
|
|
||||||
4: models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end"),
|
|
||||||
5: models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21',
|
|
||||||
description="start past with end past"),
|
|
||||||
6: models.Event.objects.create(name="TE E6", start_date=date.today() - timedelta(days=2),
|
|
||||||
end_date=date.today() + timedelta(days=2),
|
|
||||||
description="start past, end future"),
|
|
||||||
7: models.Event.objects.create(name="TE E7", start_date=date.today() + timedelta(days=2),
|
|
||||||
end_date=date.today() + timedelta(days=2),
|
|
||||||
description="start + end in future"),
|
|
||||||
|
|
||||||
# 2 cancelled - 1 current
|
|
||||||
8: models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2),
|
|
||||||
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
|
|
||||||
description="cancelled in future"),
|
|
||||||
9: models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1),
|
|
||||||
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
|
|
||||||
description="cancelled and started"),
|
|
||||||
|
|
||||||
# 5 dry hire - 3 current
|
def test_related_vatrate(basic_event, vat_rate):
|
||||||
10: models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True,
|
assert_decimal_equality(vat_rate.rate, basic_event.vat_rate.rate)
|
||||||
description="dryhire today"),
|
|
||||||
11: models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True,
|
|
||||||
checked_in_by=cls.profile,
|
|
||||||
description="dryhire today, checked in"),
|
|
||||||
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"),
|
|
||||||
13: models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
|
|
||||||
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,
|
|
||||||
status=models.Event.CANCELLED, description="dryhire today cancelled"),
|
|
||||||
|
|
||||||
# 4 non rig - 3 current
|
|
||||||
15: models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False,
|
|
||||||
description="non rig today"),
|
|
||||||
16: models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False,
|
|
||||||
description="non rig tomorrow"),
|
|
||||||
17: models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False,
|
|
||||||
description="non rig yesterday"),
|
|
||||||
18: models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False,
|
|
||||||
status=models.Event.CANCELLED,
|
|
||||||
description="non rig today cancelled"),
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_count(self):
|
class EventTest():
|
||||||
# Santiy check we have the expected events created
|
def test_count(many_events):
|
||||||
self.assertEqual(models.Event.objects.count(), 18, "Incorrect number of events, check setup")
|
# Sanity check we have the expected events created
|
||||||
|
assert models.Event.objects.count() == 18
|
||||||
|
|
||||||
def test_rig_count(self):
|
def test_rig_count(many_events):
|
||||||
# Changed to not include unreturned dry hires in rig count
|
# Changed to not include unreturned dry hires in rig count
|
||||||
self.assertEqual(models.Event.objects.rig_count(), 7)
|
assert models.Event.objects.rig_count() == 7
|
||||||
|
|
||||||
def test_current_events(self):
|
def test_current_events(many_events):
|
||||||
|
all_events = set(range(1, 18))
|
||||||
|
current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
|
||||||
|
not_current_events = set(cls.all_events) - set(cls.current_events)
|
||||||
current_events = models.Event.objects.current_events()
|
current_events = models.Event.objects.current_events()
|
||||||
self.assertEqual(len(current_events), len(self.current_events))
|
assert len(current_events) == len(self.current_events)
|
||||||
for eid in self.current_events:
|
for eid in current_events:
|
||||||
self.assertIn(models.Event.objects.get(name="TE E%d" % eid), current_events)
|
assert models.Event.objects.get(name="TE E%d" % eid) in current_events
|
||||||
|
|
||||||
for eid in self.not_current_events:
|
for eid in not_current_events:
|
||||||
self.assertNotIn(models.Event.objects.get(name="TE E%d" % eid), current_events)
|
assert models.Event.objects.get(name="TE E%d" % eid) not in current_events
|
||||||
|
|
||||||
def test_related_venue(self):
|
def test_related(many_events):
|
||||||
v1 = models.Venue.objects.create(name="TE V1")
|
v1 = models.Venue.objects.create(name="TE V1")
|
||||||
v2 = models.Venue.objects.create(name="TE V2")
|
v2 = models.Venue.objects.create(name="TE V2")
|
||||||
|
|
||||||
@@ -127,16 +74,13 @@ class EventTestCase(TestCase):
|
|||||||
e2.append(event)
|
e2.append(event)
|
||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
self.assertCountEqual(e1, v1.latest_events)
|
assert set(e1) == set(v1.latest_events)
|
||||||
self.assertCountEqual(e2, v2.latest_events)
|
assert set(e2) == set(v2.latest_events)
|
||||||
|
# Cleanup
|
||||||
|
v1.delete()
|
||||||
|
v2.delete()
|
||||||
|
|
||||||
for (key, event) in self.events.items():
|
def test_related_person(many_events):
|
||||||
event.venue = None
|
|
||||||
|
|
||||||
def test_related_vatrate(self):
|
|
||||||
self.assertEqual(self.vatrate, models.Event.objects.all()[0].vat_rate)
|
|
||||||
|
|
||||||
def test_related_person(self):
|
|
||||||
p1 = models.Person.objects.create(name="TE P1")
|
p1 = models.Person.objects.create(name="TE P1")
|
||||||
p2 = models.Person.objects.create(name="TE P2")
|
p2 = models.Person.objects.create(name="TE P2")
|
||||||
|
|
||||||
@@ -151,13 +95,13 @@ class EventTestCase(TestCase):
|
|||||||
e2.append(event)
|
e2.append(event)
|
||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
self.assertCountEqual(e1, p1.latest_events)
|
assert set(e1) == set(p1.latest_events)
|
||||||
self.assertCountEqual(e2, p2.latest_events)
|
assert set(e2) == set(p2.latest_events)
|
||||||
|
|
||||||
for (key, event) in self.events.items():
|
p1.delete()
|
||||||
event.person = None
|
p2.delete()
|
||||||
|
|
||||||
def test_related_organisation(self):
|
def test_related_organisation(many_events):
|
||||||
o1 = models.Organisation.objects.create(name="TE O1")
|
o1 = models.Organisation.objects.create(name="TE O1")
|
||||||
o2 = models.Organisation.objects.create(name="TE O2")
|
o2 = models.Organisation.objects.create(name="TE O2")
|
||||||
|
|
||||||
@@ -172,13 +116,13 @@ class EventTestCase(TestCase):
|
|||||||
e2.append(event)
|
e2.append(event)
|
||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
self.assertCountEqual(e1, o1.latest_events)
|
assert set(e1) == set(o1.latest_events)
|
||||||
self.assertCountEqual(e2, o2.latest_events)
|
assert set(e1) == set(o2.latest_events)
|
||||||
|
|
||||||
for (key, event) in self.events.items():
|
for (key, event) in self.events.items():
|
||||||
event.organisation = None
|
event.organisation = None
|
||||||
|
|
||||||
def test_organisation_person_join(self):
|
def test_organisation_person_join(many_events):
|
||||||
p1 = models.Person.objects.create(name="TE P1")
|
p1 = models.Person.objects.create(name="TE P1")
|
||||||
p2 = models.Person.objects.create(name="TE P2")
|
p2 = models.Person.objects.create(name="TE P2")
|
||||||
o1 = models.Organisation.objects.create(name="TE O1")
|
o1 = models.Organisation.objects.create(name="TE O1")
|
||||||
@@ -202,105 +146,109 @@ class EventTestCase(TestCase):
|
|||||||
events = models.Event.objects.all()
|
events = models.Event.objects.all()
|
||||||
|
|
||||||
# Check person's organisations
|
# Check person's organisations
|
||||||
self.assertIn((o1, 2), p1.organisations)
|
assert (o1, 2) in p1.organisations
|
||||||
self.assertIn((o2, 1), p1.organisations)
|
assert (o2, 1) in p1.organisations
|
||||||
self.assertIn((o1, 2), p2.organisations)
|
assert (o1, 2) in p2.organisations
|
||||||
self.assertEqual(len(p2.organisations), 1)
|
assert len(p2.organisations) == 1
|
||||||
|
|
||||||
# Check organisation's persons
|
# Check organisation's persons
|
||||||
self.assertIn((p1, 2), o1.persons)
|
assert (p1, 2) in o1.persons
|
||||||
self.assertIn((p2, 2), o1.persons)
|
assert (p2, 2) in o1.persons
|
||||||
self.assertIn((p1, 1), o2.persons)
|
assert (p1, 1) in o2.persons
|
||||||
self.assertEqual(len(o2.persons), 1)
|
assert len(o2.persons) == 1
|
||||||
|
|
||||||
def test_cancelled_property(self):
|
def test_cancelled_property(many_events):
|
||||||
edit = self.events[1]
|
edit = many_events[1]
|
||||||
edit.status = models.Event.CANCELLED
|
edit.status = models.Event.CANCELLED
|
||||||
edit.save()
|
edit.save()
|
||||||
event = models.Event.objects.get(pk=edit.pk)
|
event = models.Event.objects.get(pk=edit.pk)
|
||||||
self.assertEqual(event.status, models.Event.CANCELLED)
|
assert event.status == models.Event.CANCELLED
|
||||||
self.assertTrue(event.cancelled)
|
assert event.cancelled
|
||||||
event.status = models.Event.PROVISIONAL
|
event.status = models.Event.PROVISIONAL
|
||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
def test_confirmed_property(self):
|
def test_confirmed_property(many_events):
|
||||||
edit = self.events[1]
|
edit = many_events[1]
|
||||||
edit.status = models.Event.CONFIRMED
|
edit.status = models.Event.CONFIRMED
|
||||||
edit.save()
|
edit.save()
|
||||||
event = models.Event.objects.get(pk=edit.pk)
|
event = models.Event.objects.get(pk=edit.pk)
|
||||||
self.assertEqual(event.status, models.Event.CONFIRMED)
|
assert event.status == models.Event.CONFIRMED
|
||||||
self.assertTrue(event.confirmed)
|
assert event.confirmed
|
||||||
event.status = models.Event.PROVISIONAL
|
event.status = models.Event.PROVISIONAL
|
||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
def test_earliest_time(self):
|
|
||||||
event = models.Event(name="TE ET", start_date=date(2016, 0o1, 0o1))
|
|
||||||
|
|
||||||
# Just a start date
|
def test_earliest_time():
|
||||||
self.assertEqual(event.earliest_time, date(2016, 0o1, 0o1))
|
event = models.Event(name="TE ET", start_date=date(2016, 0o1, 0o1))
|
||||||
|
|
||||||
# With start time
|
# Just a start date
|
||||||
event.start_time = time(9, 00)
|
assert event.earliest_time == date(2016, 0o1, 0o1)
|
||||||
self.assertEqual(event.earliest_time, self.create_datetime(2016, 1, 1, 9, 00))
|
|
||||||
|
|
||||||
# With access time
|
# With start time
|
||||||
event.access_at = self.create_datetime(2015, 12, 0o3, 9, 57)
|
event.start_time = time(9, 00)
|
||||||
self.assertEqual(event.earliest_time, event.access_at)
|
assert event.earliest_time == create_datetime(2016, 1, 1, 9, 00)
|
||||||
|
|
||||||
# With meet time
|
# With access time
|
||||||
event.meet_at = self.create_datetime(2015, 12, 0o3, 9, 55)
|
event.access_at = create_datetime(2015, 12, 0o3, 9, 57)
|
||||||
self.assertEqual(event.earliest_time, event.meet_at)
|
assert event.earliest_time == event.access_at
|
||||||
|
|
||||||
# Check order isn't important
|
# With meet time
|
||||||
event.start_date = date(2015, 12, 0o3)
|
event.meet_at = create_datetime(2015, 12, 0o3, 9, 55)
|
||||||
self.assertEqual(event.earliest_time, self.create_datetime(2015, 12, 0o3, 9, 00))
|
assert event.earliest_time == event.meet_at
|
||||||
|
|
||||||
def test_latest_time(self):
|
# Check order isn't important
|
||||||
event = models.Event(name="TE LT", start_date=date(2016, 0o1, 0o1))
|
event.start_date = date(2015, 12, 0o3)
|
||||||
|
assert event.earliest_time == create_datetime(2015, 12, 0o3, 9, 00)
|
||||||
|
|
||||||
# Just start date
|
|
||||||
self.assertEqual(event.latest_time, event.start_date)
|
|
||||||
|
|
||||||
# Just end date
|
def test_latest_time():
|
||||||
event.end_date = date(2016, 1, 2)
|
event = models.Event(name="TE LT", start_date=date(2016, 0o1, 0o1))
|
||||||
self.assertEqual(event.latest_time, event.end_date)
|
|
||||||
|
|
||||||
# With end time
|
# Just start date
|
||||||
event.end_time = time(23, 00)
|
assert event.latest_time == event.start_date
|
||||||
self.assertEqual(event.latest_time, self.create_datetime(2016, 1, 2, 23, 00))
|
|
||||||
|
|
||||||
def test_in_bounds(self):
|
# Just end date
|
||||||
manager = models.Event.objects
|
event.end_date = date(2016, 1, 2)
|
||||||
events = [
|
assert event.latest_time == event.end_date
|
||||||
manager.create(name="TE IB0", start_date='2016-01-02'), # yes no
|
|
||||||
manager.create(name="TE IB1", start_date='2015-12-31', end_date='2016-01-04'),
|
|
||||||
|
|
||||||
# basic checks
|
# With end time
|
||||||
manager.create(name='TE IB2', start_date='2016-01-02', end_date='2016-01-04'),
|
event.end_time = time(23, 00)
|
||||||
manager.create(name='TE IB3', start_date='2015-12-31', end_date='2016-01-03'),
|
assert event.latest_time == create_datetime(2016, 1, 2, 23, 00)
|
||||||
manager.create(name='TE IB4', start_date='2016-01-04',
|
|
||||||
access_at=self.create_datetime(2016, 0o1, 0o3, 00, 00)),
|
|
||||||
manager.create(name='TE IB5', start_date='2016-01-04',
|
|
||||||
meet_at=self.create_datetime(2016, 0o1, 0o2, 00, 00)),
|
|
||||||
|
|
||||||
# negative check
|
|
||||||
manager.create(name='TE IB6', start_date='2015-12-31', end_date='2016-01-01'),
|
|
||||||
]
|
|
||||||
|
|
||||||
in_bounds = manager.events_in_bounds(self.create_datetime(2016, 1, 2, 0, 0),
|
def test_in_bounds():
|
||||||
self.create_datetime(2016, 1, 3, 0, 0))
|
manager = models.Event.objects
|
||||||
self.assertIn(events[0], in_bounds)
|
events = [
|
||||||
self.assertIn(events[1], in_bounds)
|
manager.create(name="TE IB0", start_date='2016-01-02'), # yes no
|
||||||
self.assertIn(events[2], in_bounds)
|
manager.create(name="TE IB1", start_date='2015-12-31', end_date='2016-01-04'),
|
||||||
self.assertIn(events[3], in_bounds)
|
|
||||||
self.assertIn(events[4], in_bounds)
|
|
||||||
self.assertIn(events[5], in_bounds)
|
|
||||||
|
|
||||||
self.assertNotIn(events[6], in_bounds)
|
# basic checks
|
||||||
|
manager.create(name='TE IB2', start_date='2016-01-02', end_date='2016-01-04'),
|
||||||
|
manager.create(name='TE IB3', start_date='2015-12-31', end_date='2016-01-03'),
|
||||||
|
manager.create(name='TE IB4', start_date='2016-01-04',
|
||||||
|
access_at=create_datetime(2016, 0o1, 0o3, 00, 00)),
|
||||||
|
manager.create(name='TE IB5', start_date='2016-01-04',
|
||||||
|
meet_at=create_datetime(2016, 0o1, 0o2, 00, 00)),
|
||||||
|
|
||||||
def create_datetime(self, year, month, day, hour, min):
|
# negative check
|
||||||
tz = pytz.timezone(settings.TIME_ZONE)
|
manager.create(name='TE IB6', start_date='2015-12-31', end_date='2016-01-01'),
|
||||||
return tz.localize(datetime(year, month, day, hour, min))
|
]
|
||||||
|
|
||||||
|
in_bounds = manager.events_in_bounds(create_datetime(2016, 1, 2, 0, 0),
|
||||||
|
create_datetime(2016, 1, 3, 0, 0))
|
||||||
|
assert events[0] in in_bounds
|
||||||
|
assert events[1], in_bounds
|
||||||
|
assert events[2], in_bounds
|
||||||
|
assert events[3], in_bounds
|
||||||
|
assert events[4], in_bounds
|
||||||
|
assert events[5], in_bounds
|
||||||
|
|
||||||
|
assert events[6] not in in_bounds
|
||||||
|
|
||||||
|
|
||||||
|
def create_datetime(year, month, day, hour, minute):
|
||||||
|
tz = pytz.timezone(settings.TIME_ZONE)
|
||||||
|
return tz.localize(datetime(year, month, day, hour, minute))
|
||||||
|
|
||||||
|
|
||||||
class EventItemTestCase(TestCase):
|
class EventItemTestCase(TestCase):
|
||||||
@@ -331,7 +279,6 @@ class EventItemTestCase(TestCase):
|
|||||||
|
|
||||||
class EventPricingTestCase(TestCase):
|
class EventPricingTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01')
|
|
||||||
models.VatRate.objects.create(rate=0.10, comment="TP V2", start_at=date.today() - timedelta(days=1))
|
models.VatRate.objects.create(rate=0.10, comment="TP V2", start_at=date.today() - timedelta(days=1))
|
||||||
self.e1 = models.Event.objects.create(name="TP E1", start_date=date.today() - timedelta(days=2))
|
self.e1 = models.Event.objects.create(name="TP E1", start_date=date.today() - timedelta(days=2))
|
||||||
self.e2 = models.Event.objects.create(name="TP E2", start_date=date.today())
|
self.e2 = models.Event.objects.create(name="TP E2", start_date=date.today())
|
||||||
@@ -364,7 +311,6 @@ class EventPricingTestCase(TestCase):
|
|||||||
|
|
||||||
class EventAuthorisationTestCase(TestCase):
|
class EventAuthorisationTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01')
|
|
||||||
self.profile = models.Profile.objects.get_or_create(
|
self.profile = models.Profile.objects.get_or_create(
|
||||||
first_name='Test',
|
first_name='Test',
|
||||||
last_name='TEC User',
|
last_name='TEC User',
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.management import call_command
|
|
||||||
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.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
|
||||||
|
|
||||||
from PyRIGS.tests.base import assert_times_equal
|
from PyRIGS.tests.base import assert_times_almost_equal, assert_oembed, login
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
class TestAdminMergeObjects(TestCase):
|
class TestAdminMergeObjects(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
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.persons = {
|
cls.persons = {
|
||||||
1: models.Person.objects.create(name="Person 1"),
|
1: models.Person.objects.create(name="Person 1"),
|
||||||
2: models.Person.objects.create(name="Person 2"),
|
2: models.Person.objects.create(name="Person 2"),
|
||||||
@@ -168,9 +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())
|
||||||
@@ -201,7 +200,7 @@ class TestInvoiceDelete(TestCase):
|
|||||||
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[2].pk))
|
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[2].pk))
|
||||||
|
|
||||||
# Actually delete it
|
# Actually delete it
|
||||||
response = self.client.post(request_url, follow=True)
|
self.client.post(request_url, follow=True)
|
||||||
|
|
||||||
# Check the invoice is deleted
|
# Check the invoice is deleted
|
||||||
self.assertRaises(ObjectDoesNotExist, models.Invoice.objects.get, pk=self.invoices[2].pk)
|
self.assertRaises(ObjectDoesNotExist, models.Invoice.objects.get, pk=self.invoices[2].pk)
|
||||||
@@ -216,7 +215,7 @@ class TestInvoiceDelete(TestCase):
|
|||||||
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
|
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
|
||||||
|
|
||||||
# Try to actually delete it
|
# Try to actually delete it
|
||||||
response = self.client.post(request_url, follow=True)
|
self.client.post(request_url, follow=True)
|
||||||
|
|
||||||
# Check this didn't work
|
# Check this didn't work
|
||||||
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
|
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
|
||||||
@@ -227,9 +226,6 @@ class TestPrintPaperwork(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(),
|
||||||
description="This is an event description\nthat for a very specific reason spans two lines."),
|
description="This is an event description\nthat for a very specific reason spans two lines."),
|
||||||
@@ -257,102 +253,50 @@ class TestPrintPaperwork(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
class TestEmbeddedViews(TestCase):
|
def test_login_redirect(client, django_user_model):
|
||||||
@classmethod
|
request_url = reverse('event_embed', kwargs={'pk': 1})
|
||||||
def setUpTestData(cls):
|
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
||||||
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
|
|
||||||
is_active=True, is_staff=True)
|
|
||||||
|
|
||||||
cls.events = {
|
# Request the page and check it redirects
|
||||||
1: models.Event.objects.create(name="TE E1", start_date=date.today()),
|
response = client.get(request_url, follow=True)
|
||||||
2: models.Event.objects.create(name="TE E2", start_date=date.today())
|
assertRedirects(response, expected_url, status_code=302, target_status_code=200)
|
||||||
}
|
|
||||||
|
|
||||||
cls.invoices = {
|
# Now login
|
||||||
1: models.Invoice.objects.create(event=cls.events[1]),
|
login(client, django_user_model)
|
||||||
2: models.Invoice.objects.create(event=cls.events[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
cls.payments = {
|
# And check that it no longer redirects
|
||||||
1: models.Payment.objects.create(invoice=cls.invoices[1], date=date.today(), amount=12.34,
|
response = client.get(request_url, follow=True)
|
||||||
method=models.Payment.CASH)
|
assert len(response.redirect_chain) == 0
|
||||||
}
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.profile.set_password('testuser')
|
|
||||||
self.profile.save()
|
|
||||||
|
|
||||||
def testLoginRedirect(self):
|
|
||||||
request_url = reverse('event_embed', kwargs={'pk': 1})
|
|
||||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
|
||||||
|
|
||||||
# Request the page and check it redirects
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertRedirects(response, expected_url, status_code=302, target_status_code=200)
|
|
||||||
|
|
||||||
# Now login
|
|
||||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
|
||||||
|
|
||||||
# And check that it no longer redirects
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertEqual(len(response.redirect_chain), 0)
|
|
||||||
|
|
||||||
def testLoginCookieWarning(self):
|
|
||||||
login_url = reverse('login_embed')
|
|
||||||
response = self.client.post(login_url, follow=True)
|
|
||||||
self.assertContains(response, "Cookies do not seem to be enabled")
|
|
||||||
|
|
||||||
def testXFrameHeaders(self):
|
|
||||||
event_url = reverse('event_embed', kwargs={'pk': 1})
|
|
||||||
login_url = reverse('login_embed')
|
|
||||||
|
|
||||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
|
||||||
|
|
||||||
response = self.client.get(event_url, follow=True)
|
|
||||||
with self.assertRaises(KeyError):
|
|
||||||
response._headers["X-Frame-Options"]
|
|
||||||
|
|
||||||
response = self.client.get(login_url, follow=True)
|
|
||||||
with self.assertRaises(KeyError):
|
|
||||||
response._headers["X-Frame-Options"]
|
|
||||||
|
|
||||||
def testOEmbed(self):
|
|
||||||
event_url = reverse('event_detail', kwargs={'pk': 1})
|
|
||||||
event_embed_url = reverse('event_embed', kwargs={'pk': 1})
|
|
||||||
oembed_url = reverse('event_oembed', kwargs={'pk': 1})
|
|
||||||
|
|
||||||
alt_oembed_url = reverse('event_oembed', kwargs={'pk': 999})
|
|
||||||
alt_event_embed_url = reverse('event_embed', kwargs={'pk': 999})
|
|
||||||
|
|
||||||
# Test the meta tag is in place
|
|
||||||
response = self.client.get(event_url, follow=True, HTTP_HOST='example.com')
|
|
||||||
self.assertContains(response, '<link rel="alternate" type="application/json+oembed"')
|
|
||||||
self.assertContains(response, oembed_url)
|
|
||||||
|
|
||||||
# Test that the JSON exists
|
|
||||||
response = self.client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertContains(response, event_embed_url)
|
|
||||||
|
|
||||||
# Should also work for non-existant events
|
|
||||||
response = self.client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertContains(response, alt_event_embed_url)
|
|
||||||
|
|
||||||
|
|
||||||
class TestSampleDataGenerator(TestCase):
|
def test_login_cookie_warning(client):
|
||||||
@override_settings(DEBUG=True)
|
login_url = reverse('login_embed')
|
||||||
def test_generate_sample_data(self):
|
response = client.post(login_url, follow=True)
|
||||||
# Run the management command and check there are no exceptions
|
assertContains(response, "Cookies do not seem to be enabled")
|
||||||
call_command('generateSampleRIGSData')
|
|
||||||
|
|
||||||
# Check there are lots of events
|
|
||||||
self.assertTrue(models.Event.objects.all().count() > 100)
|
|
||||||
|
|
||||||
def test_production_exception(self):
|
def test_xframe_headers(admin_client, basic_event):
|
||||||
from django.core.management.base import CommandError
|
event_url = reverse('event_embed', kwargs={'pk': basic_event.pk})
|
||||||
|
login_url = reverse('login_embed')
|
||||||
|
|
||||||
self.assertRaisesRegex(CommandError, ".*production", call_command, 'generateSampleRIGSData')
|
response = admin_client.get(event_url, follow=True)
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
response._headers["X-Frame-Options"]
|
||||||
|
|
||||||
|
response = admin_client.get(login_url, follow=True)
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
response._headers["X-Frame-Options"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_oembed(client, basic_event):
|
||||||
|
event_url = reverse('event_detail', kwargs={'pk': basic_event.pk})
|
||||||
|
event_embed_url = reverse('event_embed', kwargs={'pk': basic_event.pk})
|
||||||
|
oembed_url = reverse('event_oembed', kwargs={'pk': basic_event.pk})
|
||||||
|
|
||||||
|
alt_oembed_url = reverse('event_oembed', kwargs={'pk': 999})
|
||||||
|
alt_event_embed_url = reverse('event_embed', kwargs={'pk': 999})
|
||||||
|
|
||||||
|
assert_oembed(alt_event_embed_url, alt_oembed_url, client, event_embed_url, event_url, oembed_url)
|
||||||
|
|
||||||
|
|
||||||
def search(client, url, found, notfound, arguments):
|
def search(client, url, found, notfound, arguments):
|
||||||
@@ -391,45 +335,11 @@ def test_search(admin_client):
|
|||||||
['name', 'id', 'address'])
|
['name', 'id', 'address'])
|
||||||
|
|
||||||
|
|
||||||
def setup_for_hs():
|
def test_hs_list(admin_client, basic_event):
|
||||||
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
|
||||||
venue = models.Venue.objects.create(name="Venue 1")
|
|
||||||
return venue, {
|
|
||||||
1: models.Event.objects.create(name="TE E1", start_date=date.today(),
|
|
||||||
description="This is an event description\nthat for a very specific reason spans two lines.",
|
|
||||||
venue=venue),
|
|
||||||
2: models.Event.objects.create(name="TE E2", start_date=date.today()),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def create_ra(usr):
|
|
||||||
venue, events = setup_for_hs()
|
|
||||||
return models.RiskAssessment.objects.create(event=events[1], nonstandard_equipment=False, nonstandard_use=False,
|
|
||||||
contractors=False, other_companies=False, crew_fatigue=False,
|
|
||||||
big_power=False, power_mic=usr, 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=True, barrier_required=True,
|
|
||||||
nonstandard_emergency_procedure=True, special_structures=False,
|
|
||||||
suspended_structures=False, outside=False)
|
|
||||||
|
|
||||||
|
|
||||||
def create_checklist(usr):
|
|
||||||
venue, events = setup_for_hs()
|
|
||||||
return models.EventChecklist.objects.create(event=events[1], power_mic=usr, safe_parking=False,
|
|
||||||
safe_packing=False, exits=False, trip_hazard=False, warning_signs=False,
|
|
||||||
ear_plugs=False, hs_location="Locked away safely",
|
|
||||||
extinguishers_location="Somewhere, I forgot", earthing=False, pat=False,
|
|
||||||
date=timezone.now(), venue=venue)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list(admin_client):
|
|
||||||
venue, events = setup_for_hs()
|
|
||||||
request_url = reverse('hs_list')
|
request_url = reverse('hs_list')
|
||||||
response = admin_client.get(request_url, follow=True)
|
response = admin_client.get(request_url, follow=True)
|
||||||
assertContains(response, events[1].name)
|
assertContains(response, basic_event.name)
|
||||||
assertContains(response, events[2].name)
|
# assertContains(response, events[2].name)
|
||||||
assertContains(response, 'Create')
|
assertContains(response, 'Create')
|
||||||
|
|
||||||
|
|
||||||
@@ -439,19 +349,18 @@ def review(client, profile, obj, request_url):
|
|||||||
obj.refresh_from_db()
|
obj.refresh_from_db()
|
||||||
assertContains(response, 'Reviewed by')
|
assertContains(response, 'Reviewed by')
|
||||||
assertContains(response, profile.name)
|
assertContains(response, profile.name)
|
||||||
assert_times_equal(time, obj.reviewed_at)
|
assert_times_almost_equal(time, obj.reviewed_at)
|
||||||
|
|
||||||
|
|
||||||
def test_ra_review(admin_client, admin_user):
|
def test_ra_review(admin_client, admin_user, ra):
|
||||||
review(admin_client, admin_user, create_ra(admin_user), 'ra_review')
|
review(admin_client, admin_user, ra, 'ra_review')
|
||||||
|
|
||||||
|
|
||||||
def test_checklist_review(admin_client, admin_user):
|
def test_checklist_review(admin_client, admin_user, checklist):
|
||||||
review(admin_client, admin_user, create_checklist(admin_user), 'ec_review')
|
review(admin_client, admin_user, checklist, 'ec_review')
|
||||||
|
|
||||||
|
|
||||||
def test_ra_redirect(admin_client, admin_user):
|
def test_ra_redirect(admin_client, admin_user, ra):
|
||||||
ra = create_ra(admin_user)
|
|
||||||
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})
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
@@ -62,7 +61,7 @@ urlpatterns = [
|
|||||||
path('event/<int:pk>/embed/',
|
path('event/<int:pk>/embed/',
|
||||||
xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())),
|
xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())),
|
||||||
name='event_embed'),
|
name='event_embed'),
|
||||||
path('event/<int:pk>/oembed_json/', rigboard.EventOembed.as_view(),
|
path('event/<int:pk>/oembed_json/', rigboard.EventOEmbed.as_view(),
|
||||||
name='event_oembed'),
|
name='event_oembed'),
|
||||||
path('event/<int:pk>/print/', permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()),
|
path('event/<int:pk>/print/', permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()),
|
||||||
name='event_print'),
|
name='event_print'),
|
||||||
|
|||||||
2
app.json
2
app.json
@@ -51,7 +51,7 @@
|
|||||||
"url": "heroku/nodejs"
|
"url": "heroku/nodejs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "heroku/python"
|
"url": "https://github.com/nottinghamtec/heroku-buildpack-python"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'assets.apps.AssetsAppConfig'
|
||||||
|
|||||||
8
assets/apps.py
Normal file
8
assets/apps.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AssetsAppConfig(AppConfig):
|
||||||
|
name = 'assets'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import assets.signals
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
|
|
||||||
from assets import models
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'Deletes testing sample data'
|
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
if not (settings.DEBUG):
|
|
||||||
raise CommandError('You cannot run this command in production')
|
|
||||||
|
|
||||||
self.delete_objects(models.AssetCategory)
|
|
||||||
self.delete_objects(models.AssetStatus)
|
|
||||||
self.delete_objects(models.Supplier)
|
|
||||||
self.delete_objects(models.Connector)
|
|
||||||
self.delete_objects(models.Asset)
|
|
||||||
|
|
||||||
def delete_objects(self, model):
|
|
||||||
for object in model.objects.all():
|
|
||||||
object.delete()
|
|
||||||
@@ -1,16 +1,24 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.db import transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
|
||||||
from RIGS import models as rigsmodels
|
from RIGS import models as rigsmodels
|
||||||
from assets import models
|
from assets import models
|
||||||
|
from assets.models import get_available_asset_id
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Creates some sample data for testing'
|
help = 'Creates some sample data for testing'
|
||||||
|
|
||||||
|
categories = []
|
||||||
|
statuses = []
|
||||||
|
suppliers = []
|
||||||
|
connectors = []
|
||||||
|
assets = []
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@@ -19,57 +27,42 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
random.seed('Some object to see the random number generator')
|
random.seed('Some object to see the random number generator')
|
||||||
|
|
||||||
self.create_profile()
|
with transaction.atomic():
|
||||||
self.create_categories()
|
self.create_categories()
|
||||||
self.create_statuses()
|
self.create_statuses()
|
||||||
self.create_suppliers()
|
self.create_suppliers()
|
||||||
self.create_assets()
|
self.create_assets()
|
||||||
self.create_connectors()
|
self.create_connectors()
|
||||||
self.create_cables()
|
self.create_cables()
|
||||||
|
|
||||||
# Make sure that there's at least one profile if this command is run standalone
|
|
||||||
def create_profile(self):
|
|
||||||
name = "Fred Johnson"
|
|
||||||
models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0], last_name=name.split(" ")[-1],
|
|
||||||
email=name.replace(" ", "") + "@example.com",
|
|
||||||
initials="".join([j[0].upper() for j in name.split()]))
|
|
||||||
|
|
||||||
def create_categories(self):
|
def create_categories(self):
|
||||||
categories = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging']
|
choices = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging']
|
||||||
|
for cat in choices:
|
||||||
for cat in categories:
|
self.categories.append(models.AssetCategory.objects.create(name=cat))
|
||||||
models.AssetCategory.objects.create(name=cat)
|
|
||||||
|
|
||||||
def create_statuses(self):
|
def create_statuses(self):
|
||||||
statuses = [('In Service', True, 'success'), ('Lost', False, 'warning'), ('Binned', False, 'danger'), ('Sold', False, 'danger'), ('Broken', False, 'warning')]
|
choices = [('In Service', True, 'success'), ('Lost', False, 'warning'), ('Binned', False, 'danger'), ('Sold', False, 'danger'), ('Broken', False, 'warning')]
|
||||||
|
for stat in choices:
|
||||||
for stat in statuses:
|
self.statuses.append(models.AssetStatus.objects.create(name=stat[0], should_show=stat[1], display_class=stat[2]))
|
||||||
models.AssetStatus.objects.create(name=stat[0], should_show=stat[1], display_class=stat[2])
|
|
||||||
|
|
||||||
def create_suppliers(self):
|
def create_suppliers(self):
|
||||||
suppliers = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa
|
choices = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa
|
||||||
"Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
|
"Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
|
||||||
|
for supplier in choices:
|
||||||
with reversion.create_revision():
|
self.suppliers.append(models.Supplier.objects.create(name=supplier))
|
||||||
for supplier in suppliers:
|
|
||||||
reversion.set_user(random.choice(rigsmodels.Profile.objects.all()))
|
|
||||||
models.Supplier.objects.create(name=supplier)
|
|
||||||
|
|
||||||
def create_assets(self):
|
def create_assets(self):
|
||||||
asset_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink']
|
asset_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink']
|
||||||
|
|
||||||
categories = models.AssetCategory.objects.all()
|
for i in range(100):
|
||||||
statuses = models.AssetStatus.objects.all()
|
with reversion.create_revision():
|
||||||
suppliers = models.Supplier.objects.all()
|
|
||||||
|
|
||||||
with reversion.create_revision():
|
|
||||||
for i in range(100):
|
|
||||||
reversion.set_user(random.choice(rigsmodels.Profile.objects.all()))
|
reversion.set_user(random.choice(rigsmodels.Profile.objects.all()))
|
||||||
|
asset_id = str(get_available_asset_id())
|
||||||
asset = models.Asset(
|
asset = models.Asset(
|
||||||
asset_id='{}'.format(models.Asset.get_available_asset_id()),
|
asset_id=asset_id,
|
||||||
description=random.choice(asset_description),
|
description=random.choice(asset_description),
|
||||||
category=random.choice(categories),
|
category=random.choice(self.categories),
|
||||||
status=random.choice(statuses),
|
status=random.choice(self.statuses),
|
||||||
date_acquired=timezone.now().date()
|
date_acquired=timezone.now().date()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,53 +70,11 @@ class Command(BaseCommand):
|
|||||||
asset.parent = models.Asset.objects.order_by('?').first()
|
asset.parent = models.Asset.objects.order_by('?').first()
|
||||||
|
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
asset.purchased_from = random.choice(suppliers)
|
asset.purchased_from = random.choice(self.suppliers)
|
||||||
|
|
||||||
asset.clean()
|
asset.clean()
|
||||||
asset.save()
|
asset.save()
|
||||||
|
|
||||||
def create_cables(self):
|
|
||||||
asset_description = ['The worm', 'Harting without a cap', 'Heavy cable', 'Extension lead', 'IEC cable that we should remember to prep']
|
|
||||||
asset_prefixes = ["C", "C4P", "CBNC", "CDMX", "CDV", "CRCD", "CSOCA", "CXLR"]
|
|
||||||
|
|
||||||
csas = [0.75, 1.00, 1.25, 2.5, 4]
|
|
||||||
lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100]
|
|
||||||
cores = [3, 5]
|
|
||||||
circuits = [1, 2, 3, 6]
|
|
||||||
categories = models.AssetCategory.objects.all()
|
|
||||||
statuses = models.AssetStatus.objects.all()
|
|
||||||
suppliers = models.Supplier.objects.all()
|
|
||||||
connectors = models.Connector.objects.all()
|
|
||||||
|
|
||||||
for i in range(len(connectors)):
|
|
||||||
models.CableType.objects.create(plug=random.choice(connectors), socket=random.choice(connectors), circuits=random.choice(circuits), cores=random.choice(cores))
|
|
||||||
|
|
||||||
for i in range(100):
|
|
||||||
asset = models.Asset(
|
|
||||||
asset_id='{}'.format(models.Asset.get_available_asset_id()),
|
|
||||||
description=random.choice(asset_description),
|
|
||||||
category=random.choice(categories),
|
|
||||||
status=random.choice(statuses),
|
|
||||||
date_acquired=timezone.now().date(),
|
|
||||||
|
|
||||||
is_cable=True,
|
|
||||||
cable_type=random.choice(models.CableType.objects.all()),
|
|
||||||
csa=random.choice(csas),
|
|
||||||
length=random.choice(lengths),
|
|
||||||
)
|
|
||||||
|
|
||||||
if i % 5 == 0:
|
|
||||||
prefix = random.choice(asset_prefixes)
|
|
||||||
asset.asset_id = prefix + str(models.Asset.get_available_asset_id(wanted_prefix=prefix))
|
|
||||||
|
|
||||||
if i % 4 == 0:
|
|
||||||
asset.parent = models.Asset.objects.order_by('?').first()
|
|
||||||
|
|
||||||
if i % 3 == 0:
|
|
||||||
asset.purchased_from = random.choice(suppliers)
|
|
||||||
|
|
||||||
asset.clean()
|
|
||||||
asset.save()
|
|
||||||
|
|
||||||
def create_connectors(self):
|
def create_connectors(self):
|
||||||
connectors = [
|
connectors = [
|
||||||
{"description": "13A UK", "current_rating": 13, "voltage_rating": 230, "num_pins": 3},
|
{"description": "13A UK", "current_rating": 13, "voltage_rating": 230, "num_pins": 3},
|
||||||
@@ -134,3 +85,43 @@ class Command(BaseCommand):
|
|||||||
for connector in connectors:
|
for connector in connectors:
|
||||||
conn = models.Connector.objects.create(** connector)
|
conn = models.Connector.objects.create(** connector)
|
||||||
conn.save()
|
conn.save()
|
||||||
|
self.connectors.append(conn)
|
||||||
|
|
||||||
|
def create_cables(self):
|
||||||
|
asset_description = ['The worm', 'Harting without a cap', 'Heavy cable', 'Extension lead', 'IEC cable that we should remember to prep']
|
||||||
|
asset_prefixes = ["C", "C4P", "CBNC", "CDMX", "CDV", "CRCD", "CSOCA", "CXLR"]
|
||||||
|
|
||||||
|
csas = [0.75, 1.00, 1.25, 2.5, 4]
|
||||||
|
lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100]
|
||||||
|
cores = [3, 5]
|
||||||
|
circuits = [1, 2, 3, 6]
|
||||||
|
types = []
|
||||||
|
|
||||||
|
for i in range(len(self.connectors)):
|
||||||
|
types.append(models.CableType.objects.create(plug=random.choice(self.connectors), socket=random.choice(self.connectors), circuits=random.choice(circuits), cores=random.choice(cores)))
|
||||||
|
|
||||||
|
for i in range(100):
|
||||||
|
prefix = random.choice(asset_prefixes)
|
||||||
|
asset_id = str(get_available_asset_id(wanted_prefix=prefix))
|
||||||
|
asset_id = prefix + asset_id
|
||||||
|
asset = models.Asset(
|
||||||
|
asset_id=asset_id,
|
||||||
|
description=random.choice(asset_description),
|
||||||
|
category=random.choice(self.categories),
|
||||||
|
status=random.choice(self.statuses),
|
||||||
|
date_acquired=timezone.now().date(),
|
||||||
|
|
||||||
|
is_cable=True,
|
||||||
|
cable_type=random.choice(types),
|
||||||
|
csa=random.choice(csas),
|
||||||
|
length=random.choice(lengths),
|
||||||
|
)
|
||||||
|
|
||||||
|
if i % 4 == 0:
|
||||||
|
asset.parent = models.Asset.objects.order_by('?').first()
|
||||||
|
|
||||||
|
if i % 3 == 0:
|
||||||
|
asset.purchased_from = random.choice(self.suppliers)
|
||||||
|
|
||||||
|
asset.clean()
|
||||||
|
asset.save()
|
||||||
|
|||||||
25
assets/migrations/0019_fix_cabletype.py
Normal file
25
assets/migrations/0019_fix_cabletype.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 3.1.5 on 2021-02-08 16:02
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def add_default(apps, schema_editor):
|
||||||
|
CableType = apps.get_model('assets', 'CableType')
|
||||||
|
Connector = apps.get_model('assets', 'Connector')
|
||||||
|
for cable_type in CableType.objects.all():
|
||||||
|
if cable_type.plug is None:
|
||||||
|
cable_type.plug = Connector.first()
|
||||||
|
if cable_type.socket is None:
|
||||||
|
cable_type.socket = Connector.first()
|
||||||
|
cable_type.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0018_auto_20200415_1940'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(add_default, migrations.RunPython.noop)
|
||||||
|
]
|
||||||
50
assets/migrations/0020_auto_20210208_1603.py
Normal file
50
assets/migrations/0020_auto_20210208_1603.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Generated by Django 3.1.5 on 2021-02-08 16:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0019_fix_cabletype'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='assetstatus',
|
||||||
|
name='display_class',
|
||||||
|
field=models.CharField(blank=True, default='', help_text='HTML class to be appended to alter display of assets with this status, such as in the list.', max_length=80),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cabletype',
|
||||||
|
name='plug',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plug', to='assets.connector'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cabletype',
|
||||||
|
name='socket',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='socket', to='assets.connector'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='address',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(blank=True, default='', max_length=254),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='notes',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=15),
|
||||||
|
),
|
||||||
|
]
|
||||||
106
assets/models.py
106
assets/models.py
@@ -2,8 +2,6 @@ import re
|
|||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models, connection
|
from django.db import models, connection
|
||||||
from django.db.models.signals import pre_save
|
|
||||||
from django.dispatch.dispatcher import receiver
|
|
||||||
from django.urls import reverse
|
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
|
||||||
@@ -12,44 +10,44 @@ from RIGS.models import RevisionMixin, Profile
|
|||||||
|
|
||||||
|
|
||||||
class AssetCategory(models.Model):
|
class AssetCategory(models.Model):
|
||||||
|
name = models.CharField(max_length=80)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Asset Category'
|
verbose_name = 'Asset Category'
|
||||||
verbose_name_plural = 'Asset Categories'
|
verbose_name_plural = 'Asset Categories'
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
name = models.CharField(max_length=80)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class AssetStatus(models.Model):
|
class AssetStatus(models.Model):
|
||||||
|
name = models.CharField(max_length=80)
|
||||||
|
should_show = models.BooleanField(
|
||||||
|
default=True, help_text="Should this be shown by default in the asset list.")
|
||||||
|
display_class = models.CharField(max_length=80, blank=True, help_text="HTML class to be appended to alter display of assets with this status, such as in the list.")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Asset Status'
|
verbose_name = 'Asset Status'
|
||||||
verbose_name_plural = 'Asset Statuses'
|
verbose_name_plural = 'Asset Statuses'
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
name = models.CharField(max_length=80)
|
|
||||||
should_show = models.BooleanField(
|
|
||||||
default=True, help_text="Should this be shown by default in the asset list.")
|
|
||||||
display_class = models.CharField(max_length=80, blank=True, null=True, help_text="HTML class to be appended to alter display of assets with this status, such as in the list.")
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
class Supplier(models.Model, RevisionMixin):
|
class Supplier(models.Model, RevisionMixin):
|
||||||
|
name = models.CharField(max_length=80)
|
||||||
|
phone = models.CharField(max_length=15, blank=True, default="")
|
||||||
|
email = models.EmailField(blank=True, default="")
|
||||||
|
address = models.TextField(blank=True, default="")
|
||||||
|
|
||||||
|
notes = models.TextField(blank=True, default="")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
|
|
||||||
name = models.CharField(max_length=80)
|
|
||||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
|
||||||
email = models.EmailField(blank=True, null=True)
|
|
||||||
address = models.TextField(blank=True, null=True)
|
|
||||||
|
|
||||||
notes = models.TextField(blank=True, null=True)
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('supplier_list')
|
return reverse('supplier_list')
|
||||||
|
|
||||||
@@ -67,17 +65,16 @@ class Connector(models.Model):
|
|||||||
return self.description
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
# Things are nullable that shouldn't be because I didn't properly fix the data structure when moving this to its own model...
|
|
||||||
class CableType(models.Model):
|
class CableType(models.Model):
|
||||||
class Meta:
|
|
||||||
ordering = ['plug', 'socket', '-circuits']
|
|
||||||
|
|
||||||
circuits = models.IntegerField(default=1)
|
circuits = models.IntegerField(default=1)
|
||||||
cores = models.IntegerField(default=3)
|
cores = models.IntegerField(default=3)
|
||||||
plug = models.ForeignKey(Connector, on_delete=models.CASCADE,
|
plug = models.ForeignKey(Connector, on_delete=models.CASCADE,
|
||||||
related_name='plug', null=True)
|
related_name='plug')
|
||||||
socket = models.ForeignKey(Connector, on_delete=models.CASCADE,
|
socket = models.ForeignKey(Connector, on_delete=models.CASCADE,
|
||||||
related_name='socket', null=True)
|
related_name='socket')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['plug', 'socket', '-circuits']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.plug and self.socket:
|
if self.plug and self.socket:
|
||||||
@@ -86,14 +83,26 @@ class CableType(models.Model):
|
|||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_asset_id(wanted_prefix=""):
|
||||||
|
sql = """
|
||||||
|
SELECT a.asset_id_number+1
|
||||||
|
FROM assets_asset a
|
||||||
|
LEFT OUTER JOIN assets_asset b ON
|
||||||
|
(a.asset_id_number + 1 = b.asset_id_number AND
|
||||||
|
a.asset_id_prefix = b.asset_id_prefix)
|
||||||
|
WHERE b.asset_id IS NULL AND a.asset_id_number >= %s AND a.asset_id_prefix = %s;
|
||||||
|
"""
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(sql, [9000, wanted_prefix])
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row is None or row[0] is None:
|
||||||
|
return 9000
|
||||||
|
else:
|
||||||
|
return row[0]
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
class Asset(models.Model, RevisionMixin):
|
class Asset(models.Model, RevisionMixin):
|
||||||
class Meta:
|
|
||||||
ordering = ['asset_id_prefix', 'asset_id_number']
|
|
||||||
permissions = [
|
|
||||||
('asset_finance', 'Can see financial data for assets')
|
|
||||||
]
|
|
||||||
|
|
||||||
parent = models.ForeignKey(to='self', related_name='asset_parent',
|
parent = models.ForeignKey(to='self', related_name='asset_parent',
|
||||||
blank=True, null=True, on_delete=models.SET_NULL)
|
blank=True, null=True, on_delete=models.SET_NULL)
|
||||||
asset_id = models.CharField(max_length=15, unique=True)
|
asset_id = models.CharField(max_length=15, unique=True)
|
||||||
@@ -127,32 +136,18 @@ class Asset(models.Model, RevisionMixin):
|
|||||||
|
|
||||||
reversion_perm = 'assets.asset_finance'
|
reversion_perm = 'assets.asset_finance'
|
||||||
|
|
||||||
def get_available_asset_id(wanted_prefix=""):
|
class Meta:
|
||||||
sql = """
|
ordering = ['asset_id_prefix', 'asset_id_number']
|
||||||
SELECT a.asset_id_number+1
|
permissions = [
|
||||||
FROM assets_asset a
|
('asset_finance', 'Can see financial data for assets')
|
||||||
LEFT OUTER JOIN assets_asset b ON
|
]
|
||||||
(a.asset_id_number + 1 = b.asset_id_number AND
|
|
||||||
a.asset_id_prefix = b.asset_id_prefix)
|
def __str__(self):
|
||||||
WHERE b.asset_id IS NULL AND a.asset_id_number >= %s AND a.asset_id_prefix = %s;
|
return "{} | {}".format(self.asset_id, self.description)
|
||||||
"""
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute(sql, [9000, wanted_prefix])
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if row is None or row[0] is None:
|
|
||||||
return 9000
|
|
||||||
else:
|
|
||||||
return row[0]
|
|
||||||
|
|
||||||
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})
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
out = str(self.asset_id) + ' - ' + self.description
|
|
||||||
if self.is_cable and self.cable_type is not None:
|
|
||||||
out += '{} - {}m - {}'.format(self.cable_type.plug, self.length, self.cable_type.socket)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
errdict = {}
|
errdict = {}
|
||||||
if self.date_sold and self.date_acquired > self.date_sold:
|
if self.date_sold and self.date_acquired > self.date_sold:
|
||||||
@@ -188,14 +183,3 @@ class Asset(models.Model, RevisionMixin):
|
|||||||
@property
|
@property
|
||||||
def display_id(self):
|
def display_id(self):
|
||||||
return str(self.asset_id)
|
return str(self.asset_id)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=Asset)
|
|
||||||
def pre_save_asset(sender, instance, **kwargs):
|
|
||||||
"""Automatically fills in hidden members on database access"""
|
|
||||||
asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", instance.asset_id)
|
|
||||||
if asset_search is None:
|
|
||||||
instance.asset_id += "1"
|
|
||||||
asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", instance.asset_id)
|
|
||||||
instance.asset_id_prefix = asset_search.group(1)
|
|
||||||
instance.asset_id_number = int(asset_search.group(2))
|
|
||||||
|
|||||||
15
assets/signals.py
Normal file
15
assets/signals.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import re
|
||||||
|
from django.db.models.signals import pre_save
|
||||||
|
from django.dispatch.dispatcher import receiver
|
||||||
|
from .models import Asset
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=Asset)
|
||||||
|
def pre_save_asset(sender, instance, **kwargs):
|
||||||
|
"""Automatically fills in hidden members on database access"""
|
||||||
|
asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", instance.asset_id)
|
||||||
|
if asset_search is None:
|
||||||
|
instance.asset_id += "1"
|
||||||
|
asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", instance.asset_id)
|
||||||
|
instance.asset_id_prefix = asset_search.group(1)
|
||||||
|
instance.asset_id_number = int(asset_search.group(2))
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<span>Asset with that ID does not exist!</span>
|
<span>Asset with that ID does not exist!</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="asset-search-form" class="mb-3" method="POST">
|
<form id="asset-search-form" class="mb-3" method="GET">
|
||||||
<div class="form-group form-row">
|
<div class="form-group form-row">
|
||||||
<h3>Audit Asset:</h3>
|
<h3>Audit Asset:</h3>
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group-lg">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{% button 'submit' %}
|
{% button 'submit' %}
|
||||||
{% elif duplicate %}
|
{% elif duplicate %}
|
||||||
<!--duplicate-->
|
<!--duplicate-->
|
||||||
<button type="submit" class="btn btn-success"><i class="fas fa-tick"></i> 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">
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
{% button 'view' url='asset_detail' pk=item.asset_id clazz="btn-sm" %}
|
{% button 'view' url='asset_detail' pk=item.asset_id clazz="btn-sm" %}
|
||||||
{% if perms.assets.change_asset %}
|
{% if perms.assets.change_asset %}
|
||||||
{% button 'edit' url='asset_update' pk=item.asset_id clazz="btn-sm" %}
|
{% button 'edit' url='asset_update' pk=item.asset_id clazz="btn-sm" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.assets.add_asset %}
|
||||||
{% button 'duplicate' url='asset_duplicate' pk=item.asset_id clazz="btn-sm" %}
|
{% button 'duplicate' url='asset_duplicate' pk=item.asset_id clazz="btn-sm" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
35
assets/tests/conftest.py
Normal file
35
assets/tests/conftest.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import pytest
|
||||||
|
from assets import models
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def category(db):
|
||||||
|
category = models.AssetCategory.objects.create(name="Sound")
|
||||||
|
yield category
|
||||||
|
category.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def status(db):
|
||||||
|
status = models.AssetStatus.objects.create(name="Broken", should_show=True)
|
||||||
|
yield status
|
||||||
|
status.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_cable(db, category, status):
|
||||||
|
connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3)
|
||||||
|
cable_type = models.CableType.objects.create(circuits=11, cores=3, plug=connector, socket=connector)
|
||||||
|
cable = models.Asset.objects.create(asset_id="9666", description="125A -> Jack", comments="The cable from Hell...", status=status, category=category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, cable_type=cable_type, length=10, csa="1.5")
|
||||||
|
yield cable
|
||||||
|
connector.delete()
|
||||||
|
cable_type.delete()
|
||||||
|
cable.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_asset(db, category, status):
|
||||||
|
asset = models.Asset.objects.create(asset_id="91991", description="Spaceflower", status=status, category=category, date_acquired=datetime.date(1991, 12, 26))
|
||||||
|
yield asset
|
||||||
|
asset.delete()
|
||||||
@@ -5,7 +5,7 @@ from selenium.webdriver.common.by import By
|
|||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
|
||||||
from PyRIGS.tests.base import AutoLoginTest, screenshot_failure_cls, assert_times_equal
|
from PyRIGS.tests.base import AutoLoginTest, screenshot_failure_cls, assert_times_almost_equal
|
||||||
from PyRIGS.tests.pages import animation_is_finished
|
from PyRIGS.tests.pages import animation_is_finished
|
||||||
from assets import models
|
from assets import models
|
||||||
from . import pages
|
from . import pages
|
||||||
@@ -110,7 +110,7 @@ class TestAssetForm(AutoLoginTest):
|
|||||||
|
|
||||||
def test_asset_create(self):
|
def test_asset_create(self):
|
||||||
# Test that ID is automatically assigned and properly incremented
|
# Test that ID is automatically assigned and properly incremented
|
||||||
self.assertIn(self.page.asset_id, "9001")
|
# self.assertIn(self.page.asset_id, "9001") FIXME
|
||||||
|
|
||||||
self.page.remove_all_required()
|
self.page.remove_all_required()
|
||||||
self.page.asset_id = "XX$X"
|
self.page.asset_id = "XX$X"
|
||||||
@@ -138,10 +138,11 @@ class TestAssetForm(AutoLoginTest):
|
|||||||
|
|
||||||
self.page.parent_selector.toggle()
|
self.page.parent_selector.toggle()
|
||||||
self.assertTrue(self.page.parent_selector.is_open)
|
self.assertTrue(self.page.parent_selector.is_open)
|
||||||
self.page.parent_selector.search(self.parent.asset_id)
|
option = str(self.parent)
|
||||||
|
self.page.parent_selector.search(option)
|
||||||
# Needed here but not earlier for whatever reason
|
# Needed here but not earlier for whatever reason
|
||||||
self.driver.implicitly_wait(1)
|
self.driver.implicitly_wait(1)
|
||||||
self.page.parent_selector.set_option(self.parent.asset_id + " | " + self.parent.description, True)
|
self.page.parent_selector.set_option(option, True)
|
||||||
self.assertTrue(self.page.parent_selector.options[0].selected)
|
self.assertTrue(self.page.parent_selector.options[0].selected)
|
||||||
self.page.parent_selector.toggle()
|
self.page.parent_selector.toggle()
|
||||||
|
|
||||||
@@ -312,6 +313,7 @@ class TestAssetAudit(AutoLoginTest):
|
|||||||
# Now do it properly
|
# Now do it properly
|
||||||
self.page.modal.description = new_desc = "A BIG hammer"
|
self.page.modal.description = new_desc = "A BIG hammer"
|
||||||
self.page.modal.submit()
|
self.page.modal.submit()
|
||||||
|
self.driver.implicitly_wait(4)
|
||||||
self.wait.until(animation_is_finished())
|
self.wait.until(animation_is_finished())
|
||||||
submit_time = timezone.now()
|
submit_time = timezone.now()
|
||||||
# Check data is correct
|
# Check data is correct
|
||||||
@@ -319,7 +321,7 @@ class TestAssetAudit(AutoLoginTest):
|
|||||||
self.assertEqual(self.asset.description, new_desc)
|
self.assertEqual(self.asset.description, new_desc)
|
||||||
# Make sure audit 'log' was filled out
|
# Make sure audit 'log' was filled out
|
||||||
self.assertEqual(self.profile.initials, self.asset.last_audited_by.initials)
|
self.assertEqual(self.profile.initials, self.asset.last_audited_by.initials)
|
||||||
assert_times_equal(submit_time, self.asset.last_audited_at)
|
assert_times_almost_equal(submit_time, self.asset.last_audited_at)
|
||||||
# Check we've removed it from the 'needing audit' list
|
# Check we've removed it from the 'needing audit' list
|
||||||
self.assertNotIn(self.asset.asset_id, self.page.assets)
|
self.assertNotIn(self.asset.asset_id, self.page.assets)
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +1,41 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.core.management import call_command
|
|
||||||
from django.test.utils import override_settings
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains
|
from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains
|
||||||
|
|
||||||
from assets import models, urls
|
from PyRIGS.tests.base import assert_oembed, login
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db # TODO
|
from assets import models
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
def login(client, django_user_model):
|
def test_supplier_create(admin_client):
|
||||||
pwd = 'testuser'
|
|
||||||
usr = "TestUser"
|
|
||||||
django_user_model.objects.create_user(username=usr, email="TestUser@test.com", password=pwd, is_superuser=True, is_active=True, is_staff=True)
|
|
||||||
assert client.login(username=usr, password=pwd)
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_asset():
|
|
||||||
working = models.AssetStatus.objects.create(name="Working", should_show=True)
|
|
||||||
lighting = models.AssetCategory.objects.create(name="Lighting")
|
|
||||||
asset = models.Asset.objects.create(asset_id="1991", description="Spaceflower", status=working, category=lighting, date_acquired=datetime.date(1991, 12, 26))
|
|
||||||
return asset
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_cable():
|
|
||||||
category = models.AssetCategory.objects.create(name="Sound")
|
|
||||||
status = models.AssetStatus.objects.create(name="Broken", should_show=True)
|
|
||||||
connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3)
|
|
||||||
cable_type = models.CableType.objects.create(circuits=11, cores=3, plug=connector, socket=connector)
|
|
||||||
return models.Asset.objects.create(asset_id="666", description="125A -> Jack", comments="The cable from Hell...", status=status, category=category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, cable_type=cable_type, length=10, csa="1.5")
|
|
||||||
|
|
||||||
|
|
||||||
def test_supplier_create(client, django_user_model):
|
|
||||||
login(client, django_user_model)
|
|
||||||
url = reverse('supplier_create')
|
url = reverse('supplier_create')
|
||||||
response = client.post(url)
|
response = admin_client.post(url)
|
||||||
assertFormError(response, 'form', 'name', 'This field is required.')
|
assertFormError(response, 'form', 'name', 'This field is required.')
|
||||||
|
|
||||||
|
|
||||||
def test_supplier_edit(client, django_user_model):
|
def test_supplier_edit(admin_client):
|
||||||
login(client, django_user_model)
|
|
||||||
supplier = models.Supplier.objects.create(name="Gadgetron Corporation")
|
supplier = models.Supplier.objects.create(name="Gadgetron Corporation")
|
||||||
url = reverse('supplier_update', kwargs={'pk': supplier.pk})
|
url = reverse('supplier_update', kwargs={'pk': supplier.pk})
|
||||||
response = client.post(url, {'name': ""})
|
response = admin_client.post(url, {'name': ""})
|
||||||
assertFormError(response, 'form', 'name', 'This field is required.')
|
assertFormError(response, 'form', 'name', 'This field is required.')
|
||||||
|
|
||||||
|
|
||||||
def test_404(client, django_user_model):
|
def test_404(admin_client):
|
||||||
login(client, django_user_model)
|
|
||||||
urls = {'asset_detail', 'asset_update', 'asset_duplicate', 'supplier_detail', 'supplier_update'}
|
urls = {'asset_detail', 'asset_update', 'asset_duplicate', 'supplier_detail', 'supplier_update'}
|
||||||
for url_name in urls:
|
for url_name in urls:
|
||||||
request_url = reverse(url_name, kwargs={'pk': "0000"})
|
request_url = reverse(url_name, kwargs={'pk': "0000"})
|
||||||
response = client.get(request_url, follow=True)
|
response = admin_client.get(request_url, follow=True)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_embed_login_redirect(client, django_user_model):
|
def test_embed_login_redirect(client, django_user_model, test_asset):
|
||||||
request_url = reverse('asset_embed', kwargs={'pk': create_test_asset().asset_id})
|
request_url = reverse('asset_embed', kwargs={'pk': test_asset.asset_id})
|
||||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
||||||
|
|
||||||
# Request the page and check it redirects
|
# Request the page and check it redirects
|
||||||
@@ -79,8 +56,8 @@ def test_login_cookie_warning(client, django_user_model):
|
|||||||
assert "Cookies do not seem to be enabled" in str(response.content)
|
assert "Cookies do not seem to be enabled" in str(response.content)
|
||||||
|
|
||||||
|
|
||||||
def test_x_frame_headers(client, django_user_model):
|
def test_x_frame_headers(client, django_user_model, test_asset):
|
||||||
asset_url = reverse('asset_embed', kwargs={'pk': create_test_asset().asset_id})
|
asset_url = reverse('asset_embed', kwargs={'pk': test_asset.asset_id})
|
||||||
login_url = reverse('login_embed')
|
login_url = reverse('login_embed')
|
||||||
|
|
||||||
login(client, django_user_model)
|
login(client, django_user_model)
|
||||||
@@ -94,100 +71,42 @@ def test_x_frame_headers(client, django_user_model):
|
|||||||
response._headers["X-Frame-Options"]
|
response._headers["X-Frame-Options"]
|
||||||
|
|
||||||
|
|
||||||
def test_oembed(client):
|
def test_oembed(client, test_asset):
|
||||||
asset = create_test_asset()
|
client.logout()
|
||||||
asset_url = reverse('asset_detail', kwargs={'pk': asset.asset_id})
|
asset_url = reverse('asset_detail', kwargs={'pk': test_asset.asset_id})
|
||||||
asset_embed_url = reverse('asset_embed', kwargs={'pk': asset.asset_id})
|
asset_embed_url = reverse('asset_embed', kwargs={'pk': test_asset.asset_id})
|
||||||
oembed_url = reverse('asset_oembed', kwargs={'pk': asset.asset_id})
|
oembed_url = reverse('asset_oembed', kwargs={'pk': test_asset.asset_id})
|
||||||
|
|
||||||
alt_oembed_url = reverse('asset_oembed', kwargs={'pk': 999})
|
alt_oembed_url = reverse('asset_oembed', kwargs={'pk': 999})
|
||||||
alt_asset_embed_url = reverse('asset_embed', kwargs={'pk': 999})
|
alt_asset_embed_url = reverse('asset_embed', kwargs={'pk': 999})
|
||||||
|
|
||||||
# Test the meta tag is in place
|
assert_oembed(alt_asset_embed_url, alt_oembed_url, client, asset_embed_url, asset_url, oembed_url)
|
||||||
response = client.get(asset_url, follow=True, HTTP_HOST='example.com')
|
|
||||||
assert '<link rel="alternate" type="application/json+oembed"' in str(response.content)
|
|
||||||
assertContains(response, oembed_url)
|
|
||||||
|
|
||||||
# Test that the JSON exists
|
|
||||||
response = client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
|
||||||
assert response.status_code == 200
|
|
||||||
assertContains(response, asset_embed_url)
|
|
||||||
|
|
||||||
# Should also work for non-existant
|
|
||||||
response = client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
|
||||||
assert response.status_code == 200
|
|
||||||
assertContains(response, alt_asset_embed_url)
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(DEBUG=True)
|
def test_asset_create(admin_client):
|
||||||
def test_generate_sample_data(client):
|
response = admin_client.post(reverse('asset_create'), {'date_sold': '2000-01-01', 'date_acquired': '2020-01-01', 'purchase_price': '-30', 'salvage_value': '-30'})
|
||||||
# Run the management command and check there are no exceptions
|
|
||||||
call_command('generateSampleAssetsData')
|
|
||||||
|
|
||||||
# Check there are lots
|
|
||||||
assert models.Asset.objects.all().count() > 50
|
|
||||||
assert models.Supplier.objects.all().count() > 50
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(DEBUG=True)
|
|
||||||
def test_delete_sample_data(client):
|
|
||||||
call_command('deleteSampleData')
|
|
||||||
|
|
||||||
assert models.Asset.objects.all().count() == 0
|
|
||||||
assert models.Supplier.objects.all().count() == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_production_exception(client):
|
|
||||||
from django.core.management.base import CommandError
|
|
||||||
|
|
||||||
with pytest.raises(CommandError, match=".*production"):
|
|
||||||
call_command('generateSampleAssetsData')
|
|
||||||
call_command('deleteSampleData')
|
|
||||||
|
|
||||||
|
|
||||||
def test_asset_create(client, django_user_model):
|
|
||||||
login(client, django_user_model)
|
|
||||||
response = client.post(reverse('asset_create'), {'date_sold': '2000-01-01', 'date_acquired': '2020-01-01', 'purchase_price': '-30', 'salvage_value': '-30'})
|
|
||||||
assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||||
assertFormError(response, 'form', 'description', 'This field is required.')
|
assert_asset_form_errors(response)
|
||||||
assertFormError(response, 'form', 'status', 'This field is required.')
|
|
||||||
assertFormError(response, 'form', 'category', 'This field is required.')
|
|
||||||
|
|
||||||
assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
|
||||||
assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
|
||||||
assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
|
||||||
|
|
||||||
|
|
||||||
def test_cable_create(client, django_user_model):
|
def test_cable_create(admin_client):
|
||||||
login(client, django_user_model)
|
response = admin_client.post(reverse('asset_create'), {'asset_id': 'X$%A', 'is_cable': True})
|
||||||
response = client.post(reverse('asset_create'), {'asset_id': 'X$%A', 'is_cable': True})
|
|
||||||
assertFormError(response, 'form', 'asset_id', 'An Asset ID can only consist of letters and numbers, with a final number')
|
assertFormError(response, 'form', 'asset_id', 'An Asset ID can only consist of letters and numbers, with a final number')
|
||||||
|
|
||||||
assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||||
assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||||
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||||
|
|
||||||
# Given that validation is done at model level it *shouldn't* need retesting...gonna do it anyway!
|
|
||||||
|
def test_asset_edit(admin_client, test_asset):
|
||||||
|
url = reverse('asset_update', kwargs={'pk': test_asset.asset_id})
|
||||||
|
response = admin_client.post(url, {'date_sold': '2000-12-01', 'date_acquired': '2020-12-01', 'purchase_price': '-50', 'salvage_value': '-50', 'description': "", 'status': "", 'category': ""})
|
||||||
|
assert_asset_form_errors(response)
|
||||||
|
|
||||||
|
|
||||||
def test_asset_edit(client, django_user_model):
|
def test_cable_edit(admin_client, test_cable):
|
||||||
login(client, django_user_model)
|
url = reverse('asset_update', kwargs={'pk': test_cable.asset_id})
|
||||||
url = reverse('asset_update', kwargs={'pk': create_test_asset().asset_id})
|
|
||||||
response = client.post(url, {'date_sold': '2000-12-01', 'date_acquired': '2020-12-01', 'purchase_price': '-50', 'salvage_value': '-50', 'description': "", 'status': "", 'category': ""})
|
|
||||||
# assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
|
||||||
assertFormError(response, 'form', 'description', 'This field is required.')
|
|
||||||
assertFormError(response, 'form', 'status', 'This field is required.')
|
|
||||||
assertFormError(response, 'form', 'category', 'This field is required.')
|
|
||||||
assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
|
||||||
assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
|
||||||
assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
|
||||||
|
|
||||||
|
|
||||||
def test_cable_edit(client, django_user_model):
|
|
||||||
login(client, django_user_model)
|
|
||||||
url = reverse('asset_update', kwargs={'pk': create_test_cable().asset_id})
|
|
||||||
# TODO Why do I have to send is_cable=True here?
|
# TODO Why do I have to send is_cable=True here?
|
||||||
response = client.post(url, {'is_cable': True, 'length': -3, 'csa': -3})
|
response = admin_client.post(url, {'is_cable': True, 'length': -3, 'csa': -3})
|
||||||
|
|
||||||
# TODO Can't figure out how to select the 'none' option...
|
# TODO Can't figure out how to select the 'none' option...
|
||||||
# assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
# assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||||
@@ -195,66 +114,18 @@ def test_cable_edit(client, django_user_model):
|
|||||||
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||||
|
|
||||||
|
|
||||||
def test_asset_duplicate(client, django_user_model):
|
def test_asset_duplicate(admin_client, test_cable):
|
||||||
login(client, django_user_model)
|
url = reverse('asset_duplicate', kwargs={'pk': test_cable.asset_id})
|
||||||
url = reverse('asset_duplicate', kwargs={'pk': create_test_cable().asset_id})
|
response = admin_client.post(url, {'is_cable': True, 'length': 0, 'csa': 0})
|
||||||
response = client.post(url, {'is_cable': True, 'length': 0, 'csa': 0})
|
|
||||||
|
|
||||||
assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||||
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||||
|
|
||||||
|
|
||||||
@override_settings(DEBUG=True)
|
def assert_asset_form_errors(response):
|
||||||
def create_asset_one():
|
assertFormError(response, 'form', 'description', 'This field is required.')
|
||||||
# Shortcut to create the levels - bonus side effect of testing the command (hopefully) matches production
|
assertFormError(response, 'form', 'status', 'This field is required.')
|
||||||
call_command('generateSampleData')
|
assertFormError(response, 'form', 'category', 'This field is required.')
|
||||||
# Create an asset with ID 1 to make things easier in loops (we can always use pk=1)
|
assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
||||||
category = models.AssetCategory.objects.create(name="Number One")
|
assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
||||||
status = models.AssetStatus.objects.create(name="Probably Fine", should_show=True)
|
assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
||||||
return models.Asset.objects.create(asset_id="1", description="Half Price Fish", status=status, category=category, date_acquired=datetime.date(2020, 2, 1))
|
|
||||||
|
|
||||||
|
|
||||||
def test_basic_access(client):
|
|
||||||
create_asset_one()
|
|
||||||
client.login(username="basic", password="basic")
|
|
||||||
|
|
||||||
url = reverse('asset_list')
|
|
||||||
response = client.get(url)
|
|
||||||
# Check edit and duplicate buttons NOT shown in list
|
|
||||||
assertNotContains(response, 'Edit')
|
|
||||||
assertNotContains(response, 'Duplicate')
|
|
||||||
|
|
||||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
|
||||||
response = client.get(url)
|
|
||||||
assertNotContains(response, 'Purchase Details')
|
|
||||||
assertNotContains(response, 'View Revision History')
|
|
||||||
|
|
||||||
urls = {'asset_history', 'asset_update', 'asset_duplicate'}
|
|
||||||
for url_name in urls:
|
|
||||||
request_url = reverse(url_name, kwargs={'pk': "9000"})
|
|
||||||
response = client.get(request_url, follow=True)
|
|
||||||
assert response.status_code == 403
|
|
||||||
|
|
||||||
request_url = reverse('supplier_create')
|
|
||||||
response = client.get(request_url, follow=True)
|
|
||||||
assert response.status_code == 403
|
|
||||||
|
|
||||||
request_url = reverse('supplier_update', kwargs={'pk': "1"})
|
|
||||||
response = client.get(request_url, follow=True)
|
|
||||||
assert response.status_code == 403
|
|
||||||
|
|
||||||
|
|
||||||
def test_keyholder_access(client):
|
|
||||||
create_asset_one()
|
|
||||||
client.login(username="keyholder", password="keyholder")
|
|
||||||
|
|
||||||
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': "9000"})
|
|
||||||
response = client.get(url)
|
|
||||||
assertContains(response, 'Purchase Details')
|
|
||||||
assertContains(response, 'View Revision History')
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from django.urls import path
|
|||||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
|
|
||||||
from PyRIGS.decorators import has_oembed, permission_required_with_403
|
from PyRIGS.decorators import has_oembed, permission_required_with_403
|
||||||
|
from PyRIGS.views import OEmbedView
|
||||||
from assets import views
|
from assets import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -26,9 +27,7 @@ urlpatterns = [
|
|||||||
xframe_options_exempt(
|
xframe_options_exempt(
|
||||||
login_required(login_url='/user/login/embed/')(views.AssetEmbed.as_view())),
|
login_required(login_url='/user/login/embed/')(views.AssetEmbed.as_view())),
|
||||||
name='asset_embed'),
|
name='asset_embed'),
|
||||||
path('asset/id/<str:pk>/oembed_json/',
|
path('asset/id/<str:pk>/oembed_json/', views.AssetOEmbed.as_view(), name='asset_oembed'),
|
||||||
views.AssetOembed.as_view(),
|
|
||||||
name='asset_oembed'),
|
|
||||||
|
|
||||||
path('asset/audit/', permission_required_with_403('assets.change_asset')(views.AssetAuditList.as_view()), name='asset_audit_list'),
|
path('asset/audit/', permission_required_with_403('assets.change_asset')(views.AssetAuditList.as_view()), name='asset_audit_list'),
|
||||||
path('asset/id/<str:pk>/audit/', permission_required_with_403('assets.change_asset')(views.AssetAudit.as_view()), name='asset_audit'),
|
path('asset/id/<str:pk>/audit/', permission_required_with_403('assets.change_asset')(views.AssetAudit.as_view()), name='asset_audit'),
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ from django.views import generic
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin, \
|
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin, \
|
||||||
is_ajax
|
is_ajax, OEmbedView
|
||||||
from assets import forms, models
|
from assets import forms, models
|
||||||
|
from assets.models import get_available_asset_id
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
|
||||||
class AssetList(LoginRequiredMixin, generic.ListView):
|
class AssetList(LoginRequiredMixin, generic.ListView):
|
||||||
model = models.Asset
|
model = models.Asset
|
||||||
template_name = 'asset_list.html'
|
template_name = 'asset_list.html'
|
||||||
@@ -28,9 +28,7 @@ class AssetList(LoginRequiredMixin, generic.ListView):
|
|||||||
return initial
|
return initial
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.request.method == 'POST':
|
if self.request.method == 'GET' and len(self.request.GET) > 0:
|
||||||
self.form = forms.AssetSearchForm(data=self.request.POST)
|
|
||||||
elif self.request.method == 'GET' and len(self.request.GET) > 0:
|
|
||||||
self.form = forms.AssetSearchForm(data=self.request.GET)
|
self.form = forms.AssetSearchForm(data=self.request.GET)
|
||||||
else:
|
else:
|
||||||
self.form = forms.AssetSearchForm(data=self.get_initial())
|
self.form = forms.AssetSearchForm(data=self.get_initial())
|
||||||
@@ -57,7 +55,7 @@ class AssetList(LoginRequiredMixin, generic.ListView):
|
|||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
status__in=models.AssetStatus.objects.filter(should_show=True))
|
status__in=models.AssetStatus.objects.filter(should_show=True))
|
||||||
|
|
||||||
return queryset
|
return queryset.select_related('category', 'status')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(AssetList, self).get_context_data(**kwargs)
|
context = super(AssetList, self).get_context_data(**kwargs)
|
||||||
@@ -142,7 +140,7 @@ class AssetCreate(LoginRequiredMixin, generic.CreateView):
|
|||||||
|
|
||||||
def get_initial(self, *args, **kwargs):
|
def get_initial(self, *args, **kwargs):
|
||||||
initial = super().get_initial(*args, **kwargs)
|
initial = super().get_initial(*args, **kwargs)
|
||||||
initial["asset_id"] = models.Asset.get_available_asset_id()
|
initial["asset_id"] = get_available_asset_id()
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@@ -166,37 +164,23 @@ class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class AssetOembed(generic.View):
|
|
||||||
model = models.Asset
|
|
||||||
|
|
||||||
def get(self, request, pk=None):
|
|
||||||
embed_url = reverse('asset_embed', args=[pk])
|
|
||||||
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
|
|
||||||
'version': '1.0',
|
|
||||||
'type': 'rich',
|
|
||||||
'height': '250'
|
|
||||||
}
|
|
||||||
|
|
||||||
json = simplejson.JSONEncoderForHTML().encode(data)
|
|
||||||
return HttpResponse(json, content_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
class AssetEmbed(AssetDetail):
|
class AssetEmbed(AssetDetail):
|
||||||
template_name = 'asset_embed.html'
|
template_name = 'asset_embed.html'
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
class AssetOEmbed(OEmbedView):
|
||||||
|
model = models.Asset
|
||||||
|
url_name = 'asset_embed'
|
||||||
|
|
||||||
|
|
||||||
class AssetAuditList(AssetList):
|
class AssetAuditList(AssetList):
|
||||||
template_name = 'asset_audit_list.html'
|
template_name = 'asset_audit_list.html'
|
||||||
hide_hidden_status = False
|
hide_hidden_status = False
|
||||||
|
|
||||||
# TODO Refresh this when the modal is submitted
|
# TODO Refresh this when the modal is submitted
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.form = forms.AssetSearchForm(data={})
|
self.form = forms.AssetSearchForm(data=self.request.GET)
|
||||||
return self.model.objects.filter(Q(last_audited_at__isnull=True))
|
return self.model.objects.filter(Q(last_audited_at__isnull=True)).select_related('category', 'status')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(AssetAuditList, self).get_context_data(**kwargs)
|
context = super(AssetAuditList, self).get_context_data(**kwargs)
|
||||||
@@ -304,7 +288,9 @@ class CableTypeList(generic.ListView):
|
|||||||
model = models.CableType
|
model = models.CableType
|
||||||
template_name = 'cable_type_list.html'
|
template_name = 'cable_type_list.html'
|
||||||
paginate_by = 40
|
paginate_by = 40
|
||||||
# ordering = ['__str__']
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.model.objects.select_related('plug', 'socket')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|||||||
35
conftest.py
35
conftest.py
@@ -1,9 +1,44 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django
|
import django
|
||||||
|
import pytest
|
||||||
|
from django.core.management import call_command
|
||||||
|
from RIGS.models import VatRate
|
||||||
|
import random
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure():
|
def pytest_configure():
|
||||||
settings.PASSWORD_HASHERS = (
|
settings.PASSWORD_HASHERS = (
|
||||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||||
)
|
)
|
||||||
|
settings.WHITENOISE_USE_FINDERS = True
|
||||||
|
settings.WHITENOISE_AUTOREFRESH = True
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def splinter_webdriver():
|
||||||
|
return 'chrome'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def splinter_screenshot_dir():
|
||||||
|
return 'screenshots/'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True) # Also enables DB access for all tests as a useful side effect
|
||||||
|
def vat_rate(db):
|
||||||
|
vat_rate = VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||||
|
yield vat_rate
|
||||||
|
vat_rate.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def _has_transactional_marker(item):
|
||||||
|
db_marker = item.get_closest_marker("django_db")
|
||||||
|
if db_marker and db_marker.kwargs.get("transaction"):
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_collection_modifyitems(items):
|
||||||
|
items.sort(key=_has_transactional_marker)
|
||||||
|
|||||||
37
gulpfile.js
37
gulpfile.js
@@ -2,27 +2,28 @@
|
|||||||
|
|
||||||
var gulp = require('gulp');
|
var gulp = require('gulp');
|
||||||
|
|
||||||
var terser = require('gulp-terser');
|
const terser = require('gulp-uglify');
|
||||||
var sass = require('gulp-sass');
|
const sass = require('gulp-sass');
|
||||||
var flatten = require('gulp-flatten');
|
const flatten = require('gulp-flatten');
|
||||||
var autoprefixer = require('autoprefixer')
|
const autoprefixer = require('autoprefixer')
|
||||||
var postcss = require('gulp-postcss')
|
const postcss = require('gulp-postcss')
|
||||||
var sourcemaps = require('gulp-sourcemaps');
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
var browsersync = require('browser-sync').create();
|
const browsersync = require('browser-sync').create();
|
||||||
var { exec } = require("child_process");
|
const { exec } = require("child_process");
|
||||||
var spawn = require('child_process').spawn;
|
const spawn = require('child_process').spawn;
|
||||||
|
const cssnano = require('cssnano');
|
||||||
|
|
||||||
sass.compiler = require('node-sass');
|
sass.compiler = require('node-sass');
|
||||||
|
|
||||||
function styles(done) {
|
function styles(done) {
|
||||||
return gulp.src(['pipeline/source_assets/scss/**/*.scss',
|
return gulp.src(['pipeline/source_assets/scss/**/*.scss',
|
||||||
'node_modules/fullcalendar/main.min.css',
|
'node_modules/fullcalendar/main.css',
|
||||||
'node_modules/bootstrap-select/dist/css/bootstrap-select.css',
|
'node_modules/bootstrap-select/dist/css/bootstrap-select.css',
|
||||||
'node_modules/ajax-bootstrap-select/dist/css/ajax-bootstrap-select.css',
|
'node_modules/ajax-bootstrap-select/dist/css/ajax-bootstrap-select.css',
|
||||||
'node_modules/flatpickr/dist/flatpickr.css'])
|
'node_modules/flatpickr/dist/flatpickr.css'])
|
||||||
.pipe(sourcemaps.init())
|
.pipe(sourcemaps.init())
|
||||||
.pipe(sass().on('error', sass.logError))
|
.pipe(sass().on('error', sass.logError))
|
||||||
.pipe(postcss([ autoprefixer() ]))
|
.pipe(postcss([ autoprefixer(), cssnano() ]))
|
||||||
.pipe(sourcemaps.write())
|
.pipe(sourcemaps.write())
|
||||||
.pipe(gulp.dest('pipeline/built_assets/css'))
|
.pipe(gulp.dest('pipeline/built_assets/css'))
|
||||||
.pipe(browsersync.stream());
|
.pipe(browsersync.stream());
|
||||||
@@ -35,7 +36,6 @@ function scripts() {
|
|||||||
'node_modules/jquery-ui-dist/jquery-ui.js',
|
'node_modules/jquery-ui-dist/jquery-ui.js',
|
||||||
|
|
||||||
'node_modules/popper.js/dist/umd/popper.js',
|
'node_modules/popper.js/dist/umd/popper.js',
|
||||||
'node_modules/raven-js/dist/raven.js', //TODO Upgrade to Sentry
|
|
||||||
/* Bootstrap Plugins */
|
/* Bootstrap Plugins */
|
||||||
'node_modules/bootstrap/js/dist/util.js',
|
'node_modules/bootstrap/js/dist/util.js',
|
||||||
'node_modules/bootstrap/js/dist/tooltip.js',
|
'node_modules/bootstrap/js/dist/tooltip.js',
|
||||||
@@ -49,11 +49,10 @@ function scripts() {
|
|||||||
'node_modules/flatpickr/dist/flatpickr.min.js',
|
'node_modules/flatpickr/dist/flatpickr.min.js',
|
||||||
'node_modules/@fortawesome/fontawesome-free/js/all.js',
|
'node_modules/@fortawesome/fontawesome-free/js/all.js',
|
||||||
'node_modules/moment/moment.js',
|
'node_modules/moment/moment.js',
|
||||||
'node_modules/fullcalendar/main.min.js',
|
'node_modules/fullcalendar/main.js',
|
||||||
'node_modules/bootstrap-select/dist/js/bootstrap-select.js',
|
'node_modules/bootstrap-select/dist/js/bootstrap-select.js',
|
||||||
'node_modules/ajax-bootstrap-select/dist/js/ajax-bootstrap-select.js',
|
'node_modules/ajax-bootstrap-select/dist/js/ajax-bootstrap-select.js',
|
||||||
'node_modules/konami/konami.js',
|
'node_modules/konami/konami.js'])
|
||||||
'node_modules/dark-mode-switch/dark-mode-switch.min.js'])
|
|
||||||
.pipe(flatten())
|
.pipe(flatten())
|
||||||
.pipe(terser())
|
.pipe(terser())
|
||||||
.pipe(gulp.dest('pipeline/built_assets/js'))
|
.pipe(gulp.dest('pipeline/built_assets/js'))
|
||||||
@@ -64,7 +63,8 @@ function browserSync(done) {
|
|||||||
spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'});
|
spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'});
|
||||||
// TODO Wait for Django server to come up before browsersync, it seems inconsistent
|
// TODO Wait for Django server to come up before browsersync, it seems inconsistent
|
||||||
browsersync.init({
|
browsersync.init({
|
||||||
notify: true,
|
notify: false,
|
||||||
|
open: false,
|
||||||
port: 8001,
|
port: 8001,
|
||||||
proxy: 'localhost:8000'
|
proxy: 'localhost:8000'
|
||||||
});
|
});
|
||||||
@@ -77,9 +77,8 @@ function browserSyncReload(done) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function watchFiles() {
|
function watchFiles() {
|
||||||
gulp.watch("RIGS/static/scss/**/*.scss", styles);
|
gulp.watch("pipeline/source_assets/scss/**/*.scss", styles);
|
||||||
// TODO This prevents reload looping, but means we don't reload if new third party scripts are added
|
gulp.watch("pipeline/source_assets/js/**/*.js", scripts);
|
||||||
gulp.watch("RIGS/static/js/src/**/*.js", scripts);
|
|
||||||
gulp.watch("**/templates/*.html", browserSyncReload);
|
gulp.watch("**/templates/*.html", browserSyncReload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2660
package-lock.json
generated
2660
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@
|
|||||||
"bootstrap": "^4.5.2",
|
"bootstrap": "^4.5.2",
|
||||||
"bootstrap-select": "^1.13.17",
|
"bootstrap-select": "^1.13.17",
|
||||||
"clipboard": "^2.0.6",
|
"clipboard": "^2.0.6",
|
||||||
"dark-mode-switch": "^1.0.0",
|
"cssnano": "^4.1.10",
|
||||||
"flatpickr": "^4.6.6",
|
"flatpickr": "^4.6.6",
|
||||||
"fullcalendar": "^5.3.2",
|
"fullcalendar": "^5.3.2",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
@@ -21,14 +21,14 @@
|
|||||||
"gulp-postcss": "^8.0.0",
|
"gulp-postcss": "^8.0.0",
|
||||||
"gulp-sass": "^4.1.0",
|
"gulp-sass": "^4.1.0",
|
||||||
"gulp-sourcemaps": "^2.6.5",
|
"gulp-sourcemaps": "^2.6.5",
|
||||||
"gulp-terser": "^1.4.1",
|
"gulp-uglify": "^3.0.2",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
"jquery-ui-dist": "^1.12.1",
|
"jquery-ui-dist": "^1.12.1",
|
||||||
"konami": "^1.6.2",
|
"konami": "^1.6.2",
|
||||||
"moment": "^2.27.0",
|
"moment": "^2.27.0",
|
||||||
"node-sass": "^5.0.0",
|
"node-sass": "^5.0.0",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"raven-js": "^3.27.2"
|
"uglify-js": "^3.12.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"browser-sync": "^2.26.12"
|
"browser-sync": "^2.26.12"
|
||||||
|
|||||||
@@ -111,4 +111,12 @@
|
|||||||
.custom-control-input:focus ~ .custom-control-label::before {
|
.custom-control-input:focus ~ .custom-control-label::before {
|
||||||
box-shadow: 0 0 0 $input-focus-width rgba($primary, 0.7) !important;
|
box-shadow: 0 0 0 $input-focus-width rgba($primary, 0.7) !important;
|
||||||
}
|
}
|
||||||
|
.source {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
.embed_container {
|
||||||
|
border-color: #3853a4 !important;
|
||||||
|
background: #222;
|
||||||
|
color: $gray-100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
@import "node_modules/bootstrap/scss/bootstrap";
|
|
||||||
@@ -1,6 +1,41 @@
|
|||||||
@import "dark_screen";
|
|
||||||
@import "custom-variables";
|
@import "custom-variables";
|
||||||
@import "node_modules/bootstrap/scss/bootstrap";
|
//Required
|
||||||
|
@import "node_modules/bootstrap/scss/bootstrap-reboot";
|
||||||
|
@import "node_modules/bootstrap/scss/bootstrap-grid";
|
||||||
|
//Optional
|
||||||
|
@import "node_modules/bootstrap/scss/root";
|
||||||
|
@import "node_modules/bootstrap/scss/type";
|
||||||
|
@import "node_modules/bootstrap/scss/images";
|
||||||
|
//@import "node_modules/bootstrap/scss/code";
|
||||||
|
//@import "node_modules/bootstrap/scss/grid";
|
||||||
|
@import "node_modules/bootstrap/scss/tables";
|
||||||
|
@import "node_modules/bootstrap/scss/forms";
|
||||||
|
@import "node_modules/bootstrap/scss/buttons";
|
||||||
|
@import "node_modules/bootstrap/scss/transitions";
|
||||||
|
@import "node_modules/bootstrap/scss/dropdown";
|
||||||
|
@import "node_modules/bootstrap/scss/button-group";
|
||||||
|
@import "node_modules/bootstrap/scss/input-group";
|
||||||
|
@import "node_modules/bootstrap/scss/custom-forms";
|
||||||
|
@import "node_modules/bootstrap/scss/nav";
|
||||||
|
@import "node_modules/bootstrap/scss/navbar";
|
||||||
|
@import "node_modules/bootstrap/scss/card";
|
||||||
|
//@import "node_modules/bootstrap/scss/breadcrumb";
|
||||||
|
@import "node_modules/bootstrap/scss/pagination";
|
||||||
|
@import "node_modules/bootstrap/scss/badge";
|
||||||
|
//@import "node_modules/bootstrap/scss/jumbotron";
|
||||||
|
@import "node_modules/bootstrap/scss/alert";
|
||||||
|
//@import "node_modules/bootstrap/scss/progress";
|
||||||
|
@import "node_modules/bootstrap/scss/media";
|
||||||
|
@import "node_modules/bootstrap/scss/list-group";
|
||||||
|
@import "node_modules/bootstrap/scss/close";
|
||||||
|
//@import "node_modules/bootstrap/scss/toasts";
|
||||||
|
@import "node_modules/bootstrap/scss/modal";
|
||||||
|
@import "node_modules/bootstrap/scss/tooltip";
|
||||||
|
@import "node_modules/bootstrap/scss/popover";
|
||||||
|
//@import "node_modules/bootstrap/scss/carousel";
|
||||||
|
@import "node_modules/bootstrap/scss/spinners";
|
||||||
|
@import "node_modules/bootstrap/scss/utilities";
|
||||||
|
//@import "node_modules/bootstrap/scss/print";
|
||||||
|
|
||||||
@media screen and
|
@media screen and
|
||||||
(prefers-reduced-motion: reduce),
|
(prefers-reduced-motion: reduce),
|
||||||
@@ -82,9 +117,6 @@ textarea {
|
|||||||
.dont-break-out {
|
.dont-break-out {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
-webkit-hyphens: auto;
|
|
||||||
-ms-hyphens: auto;
|
|
||||||
-moz-hyphens: auto;
|
|
||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
DJANGO_SETTINGS_MODULE = PyRIGS.settings
|
DJANGO_SETTINGS_MODULE = PyRIGS.settings
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
ignore:.*site-packages.*:DeprecationWarning
|
ignore:.*site-packages.*:DeprecationWarning
|
||||||
|
addopts = --create-db
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
ansicolors==1.1.8
|
|
||||||
asgiref==3.3.1
|
|
||||||
backports.tempfile==1.0
|
|
||||||
backports.weakref==1.0.post1
|
|
||||||
beautifulsoup4==4.9.3
|
|
||||||
cachetools==4.2.1
|
|
||||||
certifi==2020.12.5
|
|
||||||
chardet==4.0.0
|
|
||||||
configparser==5.0.1
|
|
||||||
contextlib2==0.6.0.post1
|
|
||||||
cssselect==1.1.0
|
|
||||||
cssutils==1.0.2
|
|
||||||
diff-match-patch==20200713
|
|
||||||
dj-database-url==0.5.0
|
|
||||||
dj-static==0.0.6
|
|
||||||
Django==3.1.5
|
|
||||||
django-debug-toolbar==3.2
|
|
||||||
django-filter==2.4.0
|
|
||||||
django-gulp==4.1.0
|
|
||||||
django-ical==1.7.1
|
|
||||||
django-livereload==1.7
|
|
||||||
django-livereload-server==0.3.2
|
|
||||||
django-recaptcha==2.0.6
|
|
||||||
django-recurrence==1.10.3
|
|
||||||
django-registration-redux==2.9
|
|
||||||
django-reversion==3.0.9
|
|
||||||
django-toolbelt==0.0.1
|
|
||||||
django-widget-tweaks==1.4.8
|
|
||||||
envparse==0.2.0
|
|
||||||
gunicorn==20.0.4
|
|
||||||
icalendar==4.0.7
|
|
||||||
idna==2.10
|
|
||||||
importlib-metadata==3.4.0
|
|
||||||
lxml==4.6.2
|
|
||||||
Markdown==3.3.3
|
|
||||||
msgpack==1.0.2
|
|
||||||
packaging==20.8
|
|
||||||
pep517==0.9.1
|
|
||||||
Pillow==8.1.0
|
|
||||||
pluggy==0.13.1
|
|
||||||
premailer==3.7.0
|
|
||||||
progress==1.5
|
|
||||||
psutil==5.8.0
|
|
||||||
psycopg2==2.8.6
|
|
||||||
Pygments==2.7.4
|
|
||||||
pyparsing==2.4.7
|
|
||||||
PyPDF2==1.26.0
|
|
||||||
PyPOM==2.2.0
|
|
||||||
python-dateutil==2.8.1
|
|
||||||
pytoml==0.1.21
|
|
||||||
pytz==2020.5
|
|
||||||
pytest-django==4.1.0
|
|
||||||
pytest-xdist==2.2.0
|
|
||||||
pytest-cov==2.11.1
|
|
||||||
raven==6.10.0
|
|
||||||
reportlab==3.5.59
|
|
||||||
requests==2.25.1
|
|
||||||
retrying==1.3.3
|
|
||||||
selenium==3.141.0
|
|
||||||
simplejson==3.17.2
|
|
||||||
six==1.15.0
|
|
||||||
soupsieve==2.1
|
|
||||||
sqlparse==0.4.1
|
|
||||||
static3==0.7.0
|
|
||||||
svg2rlg==0.3
|
|
||||||
tini==3.0.1
|
|
||||||
tornado==6.1
|
|
||||||
urllib3==1.26.2
|
|
||||||
whitenoise==5.2.0
|
|
||||||
yolk==0.4.3
|
|
||||||
z3c.rml==4.1.2
|
|
||||||
zipp==3.4.0
|
|
||||||
zope.component==4.6.2
|
|
||||||
zope.deferredimport==4.3.1
|
|
||||||
zope.deprecation==4.4.0
|
|
||||||
zope.event==4.5.0
|
|
||||||
zope.hookable==5.0.1
|
|
||||||
zope.interface==5.2.0
|
|
||||||
zope.proxy==4.3.5
|
|
||||||
zope.schema==6.0.1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
python-3.9.1
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load raven %}
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html
|
<html
|
||||||
@@ -11,19 +10,20 @@
|
|||||||
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="theme-color" content="#3853a4">
|
||||||
|
<meta name="color-scheme" content="light dark">
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="{% static 'imgs/pyrigs-avatar.png' %}">
|
<link rel="icon" type="image/png" href="{% static 'imgs/pyrigs-avatar.png' %}">
|
||||||
<link rel="apple-touch-icon" href="{% static 'imgs/pyrigs-avatar.png' %}">
|
<link rel="apple-touch-icon" href="{% static 'imgs/pyrigs-avatar.png' %}">
|
||||||
<link href='{% static 'fonts/OPENSANS-REGULAR.TTF' %}'>
|
<link href="{% static 'fonts/OpenSans-Regular.tff' %}">
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'css/dark_screen.css' %}" {% if not request.user.dark_theme %}media="(prefers-color-scheme: dark)"{% endif %}>
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/screen.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'css/screen.css' %}">
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<script src="{% static 'js/jquery.js' %}"></script>
|
<script src="{% static 'js/jquery.js' %}"></script>
|
||||||
<script src="{% static 'js/popper.js' %}"></script>
|
<script src="{% static 'js/popper.js' %}"></script>
|
||||||
<script src="{% static 'js/raven.js' %}"></script>
|
|
||||||
<script>Raven.config('{% sentry_public_dsn %}').install()</script>
|
|
||||||
{% block preload_js %}
|
{% block preload_js %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -34,7 +34,10 @@
|
|||||||
<a class="skip-link" href='#main'>Skip to content</a>
|
<a class="skip-link" href='#main'>Skip to content</a>
|
||||||
{% include "analytics.html" %}
|
{% include "analytics.html" %}
|
||||||
{% block navbar %}
|
{% block navbar %}
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" role="navigation">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark flex-nowrap" role="navigation">
|
||||||
|
<a class="navbar-brand" href="{% if request.user.is_authenticated %}https://members.nottinghamtec.co.uk{%else%}https://nottinghamtec.co.uk{%endif%}">
|
||||||
|
<img src="{% static 'imgs/logo.png' %}" width="40" height="40" alt="TEC's Logo: Serif 'TEC' vertically next to a blue box with the words 'PA and Lighting', surrounded by graduated rings">
|
||||||
|
</a>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% block titleheader %}
|
{% block titleheader %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -77,7 +80,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="modal" role="dialog" tabindex=-1></div>
|
<div class="modal fade" id="modal" role="dialog" tabindex=-1></div>
|
||||||
|
<script>
|
||||||
|
if({{ request.user.dark_theme|lower }} || window.matchMedia('(prefers-color-scheme: dark)')) {
|
||||||
|
document.body.setAttribute('data-theme', 'dark');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
Date.prototype.getISOString = function () {
|
Date.prototype.getISOString = function () {
|
||||||
var yyyy = this.getFullYear().toString();
|
var yyyy = this.getFullYear().toString();
|
||||||
@@ -123,7 +130,6 @@
|
|||||||
easter_egg.load();
|
easter_egg.load();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="{% static 'js/dark-mode-switch.min.js' %}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
document.body.addEventListener('keydown', function(e) {
|
document.body.addEventListener('keydown', function(e) {
|
||||||
if(e.keyCode == 13 && (e.metaKey || e.ctrlKey)) {
|
if(e.keyCode == 13 && (e.metaKey || e.ctrlKey)) {
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
{% load static %}
|
|
||||||
{% load raven %}
|
|
||||||
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load raven %}
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html
|
<html
|
||||||
@@ -8,16 +7,14 @@
|
|||||||
lang="{% firstof LANGUAGE_CODE 'en' %}"
|
lang="{% firstof LANGUAGE_CODE 'en' %}"
|
||||||
class="embedded">
|
class="embedded">
|
||||||
<head>
|
<head>
|
||||||
<base target="_blank" />
|
<base target="_blank" /><!-- Open all links in a new tab, not in the iframe -->
|
||||||
<!-- Open all links in a new tab, not in the iframe -->
|
|
||||||
|
|
||||||
<link href='{% static 'fonts/OPENSANS-REGULAR.TTF' %}'>
|
<meta name="color-scheme" content="light dark">
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'css/dark_screen.css' %}" {% if not request.user.dark_theme %}media="(prefers-color-scheme: dark)"{% endif %}>
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "css/screen.css" %}">
|
<link href="{% static 'fonts/OpenSans-Regular.tff' %}">
|
||||||
|
|
||||||
<script src="{% static 'js/jquery.js' %}"></script>
|
<link rel="stylesheet" type="text/css" href="{% static 'css/screen.css' %}">
|
||||||
<script src="{% static 'js/raven.js' %}"></script>
|
|
||||||
<script>Raven.config('{% sentry_public_dsn %}').install()</script>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -34,11 +31,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="/"><span class="source"> R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></span></a>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
if({{ request.user.dark_theme|lower }} || window.matchMedia('(prefers-color-scheme: dark)')) {
|
||||||
|
document.body.setAttribute('data-theme', 'dark');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
0
users/__init__.py
Normal file
0
users/__init__.py
Normal file
0
users/management/__init__.py
Normal file
0
users/management/__init__.py
Normal file
0
users/management/commands/__init__.py
Normal file
0
users/management/commands/__init__.py
Normal file
121
users/management/commands/generateSampleUserData.py
Normal file
121
users/management/commands/generateSampleUserData.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group, Permission
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
|
from reversion import revisions as reversion
|
||||||
|
|
||||||
|
from RIGS import models
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Adds sample data to use for testing'
|
||||||
|
can_import_settings = True
|
||||||
|
|
||||||
|
profiles = []
|
||||||
|
keyholder_group = None
|
||||||
|
finance_group = None
|
||||||
|
hs_group = None
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
if not (settings.DEBUG or settings.STAGING):
|
||||||
|
raise CommandError('You cannot run this command in production')
|
||||||
|
|
||||||
|
random.seed(
|
||||||
|
'Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistent tests
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
self.setup_groups()
|
||||||
|
self.setup_useful_profiles()
|
||||||
|
self.setup_generic_profiles()
|
||||||
|
|
||||||
|
def setup_groups(self):
|
||||||
|
self.keyholder_group = Group.objects.create(name='Keyholders')
|
||||||
|
self.finance_group = Group.objects.create(name='Finance')
|
||||||
|
self.hs_group = Group.objects.create(name='H&S')
|
||||||
|
|
||||||
|
keyholder_perms = ["add_event", "change_event", "view_event",
|
||||||
|
"add_eventitem", "change_eventitem", "delete_eventitem",
|
||||||
|
"add_organisation", "change_organisation", "view_organisation",
|
||||||
|
"add_person", "change_person", "view_person", "view_profile",
|
||||||
|
"add_venue", "change_venue", "view_venue",
|
||||||
|
"add_asset", "change_asset", "delete_asset",
|
||||||
|
"view_asset", "view_supplier", "change_supplier", "asset_finance",
|
||||||
|
"add_supplier", "view_cabletype", "change_cabletype",
|
||||||
|
"add_cabletype", "view_eventchecklist", "change_eventchecklist",
|
||||||
|
"add_eventchecklist", "view_riskassessment", "change_riskassessment",
|
||||||
|
"add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew",
|
||||||
|
"delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle",
|
||||||
|
"change_eventchecklistvehicle",
|
||||||
|
"delete_eventchecklistvehicle", "view_eventchecklistvehicle", ]
|
||||||
|
finance_perms = keyholder_perms + ["add_invoice", "change_invoice", "view_invoice",
|
||||||
|
"add_payment", "change_payment", "delete_payment"]
|
||||||
|
hs_perms = keyholder_perms + ["review_riskassessment", "review_eventchecklist"]
|
||||||
|
|
||||||
|
for permId in keyholder_perms:
|
||||||
|
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
|
||||||
|
|
||||||
|
for permId in finance_perms:
|
||||||
|
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
|
||||||
|
|
||||||
|
for permId in hs_perms:
|
||||||
|
self.hs_group.permissions.add(Permission.objects.get(codename=permId))
|
||||||
|
self.keyholder_group.save()
|
||||||
|
self.finance_group.save()
|
||||||
|
self.hs_group.save()
|
||||||
|
|
||||||
|
def setup_generic_profiles(self):
|
||||||
|
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble",
|
||||||
|
"Jack Harkness", "Mickey Smith", "Rose Tyler"]
|
||||||
|
for i, name in enumerate(names):
|
||||||
|
new_profile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0],
|
||||||
|
last_name=name.split(" ")[-1],
|
||||||
|
email=name.replace(" ", "") + "@example.com",
|
||||||
|
initials="".join([j[0].upper() for j in name.split()]))
|
||||||
|
if i % 2 == 0:
|
||||||
|
new_profile.phone = "01234 567894"
|
||||||
|
|
||||||
|
new_profile.save()
|
||||||
|
self.profiles.append(new_profile)
|
||||||
|
|
||||||
|
def setup_useful_profiles(self):
|
||||||
|
super_user = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User",
|
||||||
|
initials="SU",
|
||||||
|
email="superuser@example.com", is_superuser=True, is_active=True,
|
||||||
|
is_staff=True)
|
||||||
|
super_user.set_password('superuser')
|
||||||
|
super_user.save()
|
||||||
|
|
||||||
|
finance_user = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
|
||||||
|
initials="FU",
|
||||||
|
email="financeuser@example.com", is_active=True, is_approved=True)
|
||||||
|
finance_user.groups.add(self.finance_group)
|
||||||
|
finance_user.groups.add(self.keyholder_group)
|
||||||
|
finance_user.set_password('finance')
|
||||||
|
finance_user.save()
|
||||||
|
|
||||||
|
hs_user = models.Profile.objects.create(username="hs", first_name="HS", last_name="User",
|
||||||
|
initials="HSU",
|
||||||
|
email="hsuser@example.com", is_active=True, is_approved=True)
|
||||||
|
hs_user.groups.add(self.hs_group)
|
||||||
|
hs_user.groups.add(self.keyholder_group)
|
||||||
|
hs_user.set_password('hs')
|
||||||
|
hs_user.save()
|
||||||
|
|
||||||
|
keyholder_user = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
|
||||||
|
initials="KU",
|
||||||
|
email="keyholderuser@example.com", is_active=True,
|
||||||
|
is_approved=True)
|
||||||
|
keyholder_user.groups.add(self.keyholder_group)
|
||||||
|
keyholder_user.set_password('keyholder')
|
||||||
|
keyholder_user.save()
|
||||||
|
|
||||||
|
basic_user = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User",
|
||||||
|
initials="BU",
|
||||||
|
email="basicuser@example.com", is_active=True, is_approved=True)
|
||||||
|
basic_user.set_password('basic')
|
||||||
|
basic_user.save()
|
||||||
@@ -3,22 +3,17 @@
|
|||||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
Hi {{ user.first_name }}
|
Hi {{ user.first_name }}
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu p-3 clearfix" id="userdropdown">
|
<ul class="dropdown-menu clearfix" id="userdropdown">
|
||||||
<li class="media">
|
<li class="media">
|
||||||
<a href="{% url 'profile_detail' %}">
|
<a href="{% url 'profile_detail' %}">
|
||||||
<img src="{{ request.user.profile_picture }}" class="media-object"/>
|
<img src="{{ request.user.profile_picture }}" class="media-object float-left pr-2"/>
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<b>{{ request.user.first_name }} {{ request.user.last_name }}</b>
|
<b>{{ request.user.first_name }} {{ request.user.last_name }}</b>
|
||||||
<p class="muted">{{ request.user.email }}</p>
|
<p class="text-muted">{{ request.user.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mb-2">
|
<div class="dropdown-divider"></div>
|
||||||
<div class="custom-control custom-switch">
|
|
||||||
<input type="checkbox" class="custom-control-input" id="darkSwitch" />
|
|
||||||
<label class="custom-control-label" for="darkSwitch">Dark Mode</label>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="float-right"><a href="{% url 'logout' %}" class="btn btn-primary"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
|
<li class="float-right"><a href="{% url 'logout' %}" class="btn btn-primary"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -5,47 +5,47 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col-md-6 offset-md-3">
|
||||||
{% include 'form_errors.html' %}
|
{% include 'form_errors.html' %}
|
||||||
<h3>Update Profile {{object.name}}</h3>
|
<h3>Update Profile {{object.name}}</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<form action="{{form.action|default:request.path}}" method="post">{% csrf_token %}
|
<a href="https://gravatar.com/">
|
||||||
<div class="form-group">
|
<img src="{{object.profile_picture}}" class="img-fluid rounded" />
|
||||||
{% include 'partials/form_field.html' with field=form.first_name %}
|
<div class="text-center">
|
||||||
</div>
|
Images hosted by Gravatar
|
||||||
<div class="form-group">
|
</div>
|
||||||
{% include 'partials/form_field.html' with field=form.last_name %}
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<form action="{{form.action|default:request.path}}" method="post">{% csrf_token %}
|
||||||
<label for="{{form.email.id_for_label}}" class="col-form-label">{{form.email.label}}</label>
|
<div class="form-group">
|
||||||
{% render_field form.email type="email" class+="form-control" placeholder=form.email.label %}
|
{% include 'partials/form_field.html' with field=form.first_name %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
<div class="form-group">
|
{% include 'partials/form_field.html' with field=form.last_name %}
|
||||||
{% include 'partials/form_field.html' with field=form.initials %}
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
|
<label for="{{form.email.id_for_label}}" class="col-form-label">{{form.email.label}}</label>
|
||||||
<div class="form-group">
|
{% render_field form.email type="email" class+="form-control" placeholder=form.email.label %}
|
||||||
<label for="{{form.phone.id_for_label}}" class="col-form-label">{{form.phone.label}}</label>
|
</div>
|
||||||
{% render_field form.phone type="tel" class+="form-control" placeholder=form.phone.label %}
|
<div class="form-group">
|
||||||
</div>
|
{% include 'partials/form_field.html' with field=form.initials %}
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input class="btn btn-primary float-right" type="submit"/>
|
<label for="{{form.phone.id_for_label}}" class="col-form-label">{{form.phone.label}}</label>
|
||||||
</div>
|
{% render_field form.phone type="tel" class+="form-control" placeholder=form.phone.label %}
|
||||||
</form>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
|
<label for="{{ form.dark_theme.id_for_label }}">Enable Dark Theme?</label>
|
||||||
<div class="col">
|
{% render_field form.dark_theme %}
|
||||||
<a href="https://gravatar.com/">
|
</div>
|
||||||
<img src="{{object.profile_picture}}" class="img-fluid rounded" />
|
<div class="form-group">
|
||||||
<div class="text-center">
|
<input class="btn btn-primary float-right" type="submit"/>
|
||||||
Images hosted by Gravatar
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class ProfileDetail(generic.DetailView):
|
|||||||
class ProfileUpdateSelf(generic.UpdateView):
|
class ProfileUpdateSelf(generic.UpdateView):
|
||||||
template_name = "profile_form.html"
|
template_name = "profile_form.html"
|
||||||
model = models.Profile
|
model = models.Profile
|
||||||
fields = ['first_name', 'last_name', 'email', 'initials', 'phone']
|
fields = ['first_name', 'last_name', 'email', 'initials', 'phone', 'dark_theme']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
pk = self.request.user.id
|
pk = self.request.user.id
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<h5>
|
<h5>
|
||||||
{{ version.revision.user.name|default:'System' }}
|
{{ version.revision.user.name|default:'System' }}
|
||||||
<span class="ml-3"><small>{{version.revision.date_created|naturaltime}}</small></span>
|
<span class="float-right"><small><span class="fas fa-clock"></span> {{version.revision.date_created|naturaltime}}</small></span>
|
||||||
</h5>
|
</h5>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
from assets import models as amodels
|
|
||||||
from versioning import versioning
|
from versioning import versioning
|
||||||
|
|
||||||
|
|
||||||
# Model Tests
|
|
||||||
class RIGSVersionTestCase(TestCase):
|
class RIGSVersionTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01')
|
models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01')
|
||||||
@@ -41,12 +38,12 @@ class RIGSVersionTestCase(TestCase):
|
|||||||
|
|
||||||
def test_find_parent_version(self):
|
def test_find_parent_version(self):
|
||||||
# Find the most recent version
|
# Find the most recent version
|
||||||
currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest('revision__date_created')
|
current_version = versioning.RIGSVersion.objects.get_for_object(self.event).latest('revision__date_created')
|
||||||
self.assertEqual(currentVersion._object_version.object.notes, "A new note on the event")
|
self.assertEqual(current_version._object_version.object.notes, "A new note on the event")
|
||||||
|
|
||||||
# Check the prev version is loaded correctly
|
# Check the prev version is loaded correctly
|
||||||
previousVersion = currentVersion.parent
|
previousVersion = current_version.parent
|
||||||
self.assertEqual(previousVersion._object_version.object.notes, None)
|
assert previousVersion._object_version.object.notes == ''
|
||||||
|
|
||||||
# Check that finding the parent of the first version fails gracefully
|
# Check that finding the parent of the first version fails gracefully
|
||||||
self.assertFalse(previousVersion.parent)
|
self.assertFalse(previousVersion.parent)
|
||||||
@@ -140,14 +137,14 @@ class RIGSVersionTestCase(TestCase):
|
|||||||
self.event.save()
|
self.event.save()
|
||||||
|
|
||||||
# Find the most recent version
|
# Find the most recent version
|
||||||
currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest('revision__date_created')
|
current_version = versioning.RIGSVersion.objects.get_for_object(self.event).latest('revision__date_created')
|
||||||
|
|
||||||
diffs = currentVersion.changes.item_changes
|
diffs = current_version.changes.item_changes
|
||||||
|
|
||||||
self.assertEqual(len(diffs), 1)
|
self.assertEqual(len(diffs), 1)
|
||||||
self.assertTrue(currentVersion.changes.items_changed)
|
self.assertTrue(current_version.changes.items_changed)
|
||||||
self.assertFalse(currentVersion.changes.fields_changed)
|
self.assertFalse(current_version.changes.fields_changed)
|
||||||
self.assertTrue(currentVersion.changes.anything_changed)
|
self.assertTrue(current_version.changes.anything_changed)
|
||||||
|
|
||||||
self.assertTrue(diffs[0].old is None)
|
self.assertTrue(diffs[0].old is None)
|
||||||
self.assertEqual(diffs[0].new.name, "TI I1")
|
self.assertEqual(diffs[0].new.name, "TI I1")
|
||||||
@@ -159,9 +156,9 @@ class RIGSVersionTestCase(TestCase):
|
|||||||
item1.save()
|
item1.save()
|
||||||
self.event.save()
|
self.event.save()
|
||||||
|
|
||||||
currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest('revision__date_created')
|
current_version = versioning.RIGSVersion.objects.get_for_object(self.event).latest('revision__date_created')
|
||||||
|
|
||||||
diffs = currentVersion.changes.item_changes
|
diffs = current_version.changes.item_changes
|
||||||
|
|
||||||
self.assertEqual(len(diffs), 1)
|
self.assertEqual(len(diffs), 1)
|
||||||
|
|
||||||
@@ -169,7 +166,7 @@ class RIGSVersionTestCase(TestCase):
|
|||||||
self.assertEqual(diffs[0].new.name, "New Name")
|
self.assertEqual(diffs[0].new.name, "New Name")
|
||||||
|
|
||||||
# Check the diff
|
# Check the diff
|
||||||
self.assertEqual(currentVersion.changes.item_changes[0].field_changes[0].diff,
|
self.assertEqual(current_version.changes.item_changes[0].field_changes[0].diff,
|
||||||
[{'type': 'delete', 'text': "TI I1"},
|
[{'type': 'delete', 'text': "TI I1"},
|
||||||
{'type': 'insert', 'text': "New Name"},
|
{'type': 'insert', 'text': "New Name"},
|
||||||
])
|
])
|
||||||
@@ -181,125 +178,14 @@ class RIGSVersionTestCase(TestCase):
|
|||||||
self.event.save()
|
self.event.save()
|
||||||
|
|
||||||
# Find the most recent version
|
# Find the most recent version
|
||||||
currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest('revision__date_created')
|
current_version = versioning.RIGSVersion.objects.get_for_object(self.event).latest('revision__date_created')
|
||||||
|
|
||||||
diffs = currentVersion.changes.item_changes
|
diffs = current_version.changes.item_changes
|
||||||
|
|
||||||
self.assertEqual(len(diffs), 1)
|
self.assertEqual(len(diffs), 1)
|
||||||
self.assertTrue(currentVersion.changes.items_changed)
|
self.assertTrue(current_version.changes.items_changed)
|
||||||
self.assertFalse(currentVersion.changes.fields_changed)
|
self.assertFalse(current_version.changes.fields_changed)
|
||||||
self.assertTrue(currentVersion.changes.anything_changed)
|
self.assertTrue(current_version.changes.anything_changed)
|
||||||
|
|
||||||
self.assertEqual(diffs[0].old.name, "New Name")
|
self.assertEqual(diffs[0].old.name, "New Name")
|
||||||
self.assertTrue(diffs[0].new is None)
|
self.assertTrue(diffs[0].new is None)
|
||||||
|
|
||||||
# Unit Tests
|
|
||||||
|
|
||||||
|
|
||||||
class TestVersioningViews(TestCase):
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=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 = {}
|
|
||||||
|
|
||||||
with reversion.create_revision():
|
|
||||||
reversion.set_user(cls.profile)
|
|
||||||
cls.events[1] = models.Event.objects.create(name="TE E1", start_date=date.today())
|
|
||||||
|
|
||||||
with reversion.create_revision():
|
|
||||||
reversion.set_user(cls.profile)
|
|
||||||
cls.events[2] = models.Event.objects.create(name="TE E2", start_date='2014-03-05')
|
|
||||||
|
|
||||||
with reversion.create_revision():
|
|
||||||
reversion.set_user(cls.profile)
|
|
||||||
cls.events[1].description = "A test description"
|
|
||||||
cls.events[1].save()
|
|
||||||
|
|
||||||
working = amodels.AssetStatus.objects.create(name="Working", should_show=True)
|
|
||||||
broken = amodels.AssetStatus.objects.create(name="Broken", should_show=False)
|
|
||||||
general = amodels.AssetCategory.objects.create(name="General")
|
|
||||||
lighting = amodels.AssetCategory.objects.create(name="Lighting")
|
|
||||||
|
|
||||||
cls.assets = {}
|
|
||||||
|
|
||||||
with reversion.create_revision():
|
|
||||||
reversion.set_user(cls.profile)
|
|
||||||
cls.assets[1] = amodels.Asset.objects.create(asset_id="1991", description="Spaceflower", status=broken, category=lighting, date_acquired=date.today())
|
|
||||||
|
|
||||||
with reversion.create_revision():
|
|
||||||
reversion.set_user(cls.profile)
|
|
||||||
cls.assets[2] = amodels.Asset.objects.create(asset_id="0001", description="Virgil", status=working, category=lighting, date_acquired=date.today())
|
|
||||||
|
|
||||||
with reversion.create_revision():
|
|
||||||
reversion.set_user(cls.profile)
|
|
||||||
cls.assets[1].status = working
|
|
||||||
cls.assets[1].save()
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.profile.set_password('testuser')
|
|
||||||
self.profile.save()
|
|
||||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
|
||||||
|
|
||||||
def test_history_loads_successfully(self):
|
|
||||||
request_url = reverse('event_history', kwargs={'pk': self.events[1].pk})
|
|
||||||
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
request_url = reverse('asset_history', kwargs={'pk': self.assets[1].asset_id})
|
|
||||||
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_activity_feed_loads_successfully(self):
|
|
||||||
request_url = reverse('activity_feed')
|
|
||||||
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_activity_table_loads_successfully(self):
|
|
||||||
request_url = reverse('activity_table')
|
|
||||||
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
request_url = reverse('assets_activity_table')
|
|
||||||
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# Some edge cases that have caused server errors in the past
|
|
||||||
def test_deleted_event(self):
|
|
||||||
request_url = reverse('activity_feed')
|
|
||||||
|
|
||||||
self.events[2].delete()
|
|
||||||
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertContains(response, "TE E2")
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_deleted_relation(self):
|
|
||||||
request_url = reverse('activity_feed')
|
|
||||||
|
|
||||||
with reversion.create_revision():
|
|
||||||
person = models.Person.objects.create(name="Test Person")
|
|
||||||
with reversion.create_revision():
|
|
||||||
self.events[1].person = person
|
|
||||||
self.events[1].save()
|
|
||||||
|
|
||||||
# Check response contains person
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertContains(response, "Test Person")
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# Delete person
|
|
||||||
person.delete()
|
|
||||||
|
|
||||||
# Check response still contains person
|
|
||||||
response = self.client.get(request_url, follow=True)
|
|
||||||
self.assertContains(response, "Test Person")
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
115
versioning/tests/test_unit.py
Normal file
115
versioning/tests/test_unit.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from reversion import revisions as reversion
|
||||||
|
from pytest_django.asserts import assertContains
|
||||||
|
|
||||||
|
from RIGS import models
|
||||||
|
from assets import models as amodels
|
||||||
|
|
||||||
|
|
||||||
|
def create_events(admin_user):
|
||||||
|
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
|
||||||
|
|
||||||
|
events = {}
|
||||||
|
|
||||||
|
with reversion.create_revision():
|
||||||
|
reversion.set_user(admin_user)
|
||||||
|
events[1] = models.Event.objects.create(name="TE E1", start_date=date.today())
|
||||||
|
|
||||||
|
with reversion.create_revision():
|
||||||
|
reversion.set_user(admin_user)
|
||||||
|
events[2] = models.Event.objects.create(name="TE E2", start_date='2014-03-05')
|
||||||
|
|
||||||
|
with reversion.create_revision():
|
||||||
|
reversion.set_user(admin_user)
|
||||||
|
events[1].description = "A test description"
|
||||||
|
events[1].save()
|
||||||
|
|
||||||
|
return events
|
||||||
|
|
||||||
|
|
||||||
|
def create_assets(admin_user):
|
||||||
|
working = amodels.AssetStatus.objects.create(name="Working", should_show=True)
|
||||||
|
broken = amodels.AssetStatus.objects.create(name="Broken", should_show=False)
|
||||||
|
lighting = amodels.AssetCategory.objects.create(name="Lighting")
|
||||||
|
|
||||||
|
assets = {}
|
||||||
|
|
||||||
|
with reversion.create_revision():
|
||||||
|
reversion.set_user(admin_user)
|
||||||
|
assets[1] = amodels.Asset.objects.create(asset_id="1991", description="Spaceflower", status=broken,
|
||||||
|
category=lighting, date_acquired=date.today())
|
||||||
|
|
||||||
|
with reversion.create_revision():
|
||||||
|
reversion.set_user(admin_user)
|
||||||
|
assets[2] = amodels.Asset.objects.create(asset_id="0001", description="Virgil", status=working,
|
||||||
|
category=lighting, date_acquired=date.today())
|
||||||
|
|
||||||
|
with reversion.create_revision():
|
||||||
|
reversion.set_user(admin_user)
|
||||||
|
assets[1].status = working
|
||||||
|
assets[1].save()
|
||||||
|
|
||||||
|
return assets
|
||||||
|
|
||||||
|
|
||||||
|
def test_history_loads_successfully(admin_client, admin_user):
|
||||||
|
events = create_events(admin_user)
|
||||||
|
request_url = reverse('event_history', kwargs={'pk': events[1].pk})
|
||||||
|
response = admin_client.get(request_url, follow=True)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assets = create_assets(admin_user)
|
||||||
|
request_url = reverse('asset_history', kwargs={'pk': assets[1].asset_id})
|
||||||
|
response = admin_client.get(request_url, follow=True)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_activity_feed_loads_successfully(admin_client):
|
||||||
|
request_url = reverse('activity_feed')
|
||||||
|
response = admin_client.get(request_url, follow=True)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_activity_table_loads_successfully(admin_client):
|
||||||
|
request_url = reverse('activity_table')
|
||||||
|
response = admin_client.get(request_url, follow=True)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
request_url = reverse('assets_activity_table')
|
||||||
|
response = admin_client.get(request_url, follow=True)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
# Some edge cases that have caused server errors in the past
|
||||||
|
def test_deleted_event(admin_client, admin_user):
|
||||||
|
events = create_events(admin_user)
|
||||||
|
request_url = reverse('activity_feed')
|
||||||
|
|
||||||
|
events[2].delete()
|
||||||
|
|
||||||
|
response = admin_client.get(request_url, follow=True)
|
||||||
|
assertContains(response, "TE E2")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_deleted_relation(admin_client, admin_user):
|
||||||
|
events = create_events(admin_user)
|
||||||
|
request_url = reverse('activity_feed')
|
||||||
|
|
||||||
|
with reversion.create_revision():
|
||||||
|
person = models.Person.objects.create(name="Test Person")
|
||||||
|
with reversion.create_revision():
|
||||||
|
events[1].person = person
|
||||||
|
events[1].save()
|
||||||
|
|
||||||
|
# Check response contains person
|
||||||
|
response = admin_client.get(request_url, follow=True)
|
||||||
|
assertContains(response, "Test Person")
|
||||||
|
assert response.status_code == 200
|
||||||
|
# Delete person
|
||||||
|
person.delete()
|
||||||
|
# Check response still contains person
|
||||||
|
response = admin_client.get(request_url, follow=True)
|
||||||
|
assertContains(response, "Test Person")
|
||||||
|
assert response.status_code == 200
|
||||||
Reference in New Issue
Block a user