Compare commits

..

90 Commits

Author SHA1 Message Date
d6c789d90a Whoops 2021-03-02 11:24:22 +00:00
4fe7b874a4 Implement django doctor suggestions 2021-03-02 11:21:44 +00:00
ac0425740e Revert "Very initial pass at reimplementing event table fully responsive"
This reverts commit f5d875d153.
2021-03-02 11:11:18 +00:00
5382d8ae00 Revert "Further work at grid-table, including tablet layout"
This reverts commit b3939d8426.
2021-03-02 11:11:18 +00:00
08a55644c5 Delete unused signature template 2021-02-25 14:07:40 +00:00
b3939d8426 Further work at grid-table, including tablet layout 2021-02-25 14:02:34 +00:00
f775cee7dd Should fix CI 2021-02-24 22:42:30 +00:00
f5d875d153 Very initial pass at reimplementing event table fully responsive 2021-02-24 22:41:15 +00:00
f88f418503 Fixed a couple of places where profiles should have been linked but weren't 2021-02-24 18:20:51 +00:00
f3d9646607 Enable preview of event authorisation 2021-02-23 16:01:05 +00:00
798689403d Another go at CI caching 2021-02-23 13:58:03 +00:00
d1069a1745 That was silly 2021-02-23 01:27:42 +00:00
238d53a547 Try reimplementing caching on CI 2021-02-23 01:25:32 +00:00
8756e7f880 Oops 2021-02-23 01:19:38 +00:00
25c9cf25f7 Disable asset cache on ci, try to get assets working on heroku
Whyyy do things just randomly fall over
2021-02-23 01:11:33 +00:00
ad66f434de bah staticfiles 2021-02-23 01:01:36 +00:00
1aae2bff28 Damn you pipeline 2021-02-23 00:40:27 +00:00
9302cccd58 Swithc to pathlib in settings 2021-02-23 00:34:27 +00:00
123c8bfd80 Serve logo in next gen format 2021-02-22 23:53:53 +00:00
02af3870aa Can't async jquery 2021-02-22 23:48:23 +00:00
6d3a861df5 Preload fontawesome 2021-02-22 23:46:20 +00:00
0e07c50c18 Prevent search engines indexing us
Might help cut down the spam
2021-02-22 23:41:58 +00:00
8505d5e93a Squash jquery and popper together 2021-02-22 23:41:42 +00:00
ff780f2042 >.> 2021-02-22 14:00:06 +00:00
4a6d69c002 Drop jquery-ui in favour of html5sortable
10x smaller dependency!
2021-02-22 13:42:39 +00:00
28a70667c2 Fixes 2021-02-22 12:54:28 +00:00
5edb61f243 Fix dark theme test when no user is available 2021-02-22 12:17:40 +00:00
4e4492bc01 Load dark css only when required 2021-02-22 11:26:37 +00:00
697024e91b Slimmer way of including FOntAwesome 2021-02-22 11:00:09 +00:00
b5e80382b9 Various fixes for prior 2021-02-22 10:43:11 +00:00
ffbcfe28a9 Concat select styles/js 2021-02-22 01:03:27 +00:00
10af465a06 Use moment to keep cached timeagos up to date
Blerugh.
2021-02-22 00:43:45 +00:00
f1af5925b1 Concat 1st and 3rd party base js 2021-02-22 00:32:46 +00:00
b3adadceff Minify base js 2021-02-21 18:13:15 +00:00
2044cbdac2 Use pip installed fontawesome and css/webfont loading rather than JS 2021-02-21 02:15:16 +00:00
a789184c1c Disable template coverage plugin
Is that what's crashing??? Plausibly: https://github.com/suda/pytest_django_coverage_test
2021-02-21 01:04:47 +00:00
cebff5adda When in doubt, sleep 2021-02-21 00:22:51 +00:00
cc538c659c poke 2021-02-17 14:54:33 +00:00
f3409d0680 Turn up the verbosity of CI tests 2021-02-15 18:03:59 +00:00
1a30a418b1 Whoops 2021-02-15 17:49:52 +00:00
3aeafde96e Port RA interaction test(s) to pytest 2021-02-15 17:40:50 +00:00
f4a163f63c Minor futzing with status display 2021-02-15 16:45:45 +00:00
e14e250896 Fix crew test 2021-02-15 16:38:36 +00:00
59a9fd5bb4 Oops 2021-02-15 00:45:31 +00:00
925498be02 Fix database locking shenanigans 2021-02-15 00:44:26 +00:00
6c9e360927 More regions for checklist interaction tests 2021-02-14 22:50:22 +00:00
c02e2e6bbf Partial refactor of event checklist tests 2021-02-14 19:00:30 +00:00
be5aa892f0 Middle align homepage list icons 2021-02-14 18:37:35 +00:00
23ac9fb62a etc 2021-02-14 11:15:39 +00:00
7f05468483 argh 2021-02-14 02:36:07 +00:00
5a36e33bf0 etc 2021-02-14 02:19:28 +00:00
b3ceed777e etc 2021-02-14 02:07:46 +00:00
a7119599ca more poking 2021-02-14 01:37:06 +00:00
2396e27943 Potentially fix tests, init splinter 2021-02-13 18:42:56 +00:00
8204fdae1f Minor template fix 2021-02-13 18:42:48 +00:00
0b0043f6f7 Fix PEBKAC error 2021-02-11 02:18:22 +00:00
5a8011a8e3 Less threads equals more better? 2021-02-09 21:57:48 +00:00
7062ccd5f8 Fix silly javascript thing 2021-02-08 23:20:27 +00:00
9b525759f4 Fix IDI0T error 2021-02-08 23:20:19 +00:00
a9b034255e Fix pycodestyle, experiment with custom buildpack 2021-02-08 19:20:38 +00:00
6676183443 Remove dark mode switch from gulpfile 2021-02-08 18:21:56 +00:00
3f93cebf41 Much prefetch/select related optimisations 2021-02-08 18:18:16 +00:00
603e919ad0 Fix sql efficency for rigboard index 2021-02-08 16:49:02 +00:00
a0b70a3cac Minor template tweaks 2021-02-08 16:33:15 +00:00
a11e32252f Make some improvements as suggested by DjangoDoctor 2021-02-08 16:27:57 +00:00
e48e016cb9 Tests actually work again 2021-02-08 15:18:20 +00:00
ef1d9868da Revert to old method of sample data gen
bulk_create is super quick, but no autoincrement on sqlite is killer when trying to run tests.
2021-02-08 12:57:08 +00:00
788fb3efe6 Yet more test shenanigans
Can you tell I'm getting fed up?
2021-02-07 02:58:34 +00:00
4f912932ca Make dark theme a user level property, lazy load dark CSS
- Also now respects the colour-scheme media query
- Added meta tag to tell the browser we support dark theme, allowing dark UA stylesheet if the user sends said media query
- Means you only have to set it once per account rather than once per machine
- Dark themed embeds!
2021-02-06 16:48:10 +00:00
0598612c15 First pass at updating event embed
Seems I forgot about those in BS4 port, oops
2021-02-06 01:02:25 +00:00
656f9fdd25 Stop browsersync automatically opening
Annoying focus stealing be gone
2021-02-06 00:45:23 +00:00
ccda38918c Properly migrate to Sentry from Raven 2021-02-06 00:42:11 +00:00
a1edf80dd0 Minor test futzing 2021-02-05 03:16:19 +00:00
83fe526cbd Init signals.py for assets 2021-02-05 02:34:25 +00:00
1d63bd940d More test munging 2021-02-05 01:17:23 +00:00
c090163f40 SQL efficiency on asset list 2021-02-05 00:58:30 +00:00
baa3b2c9c6 More migration to fixtures 2021-02-05 00:04:15 +00:00
462a16ec42 Move user/group setup into new generateSampleUserData command 2021-02-04 16:08:18 +00:00
6cb3d1855a Fix model tests for vat rate fixture 2021-02-04 13:41:53 +00:00
9279131edf Migrate to pipenv
Closes #384
2021-02-04 13:17:05 +00:00
3853ad0871 Much test refactoring 2021-02-04 13:06:23 +00:00
7eea868575 Derp fixes 2021-02-01 15:47:19 +00:00
fc6e66c7f5 Cache static directory on CI, skip npm install and gulp build on hit
Should speed up CI runs a lot
2021-02-01 15:39:35 +00:00
01ed05ecd9 Fix fonts, better JS compression, remove unused print.scss 2021-02-01 15:31:10 +00:00
20e5d25130 Add smol tec logo to navbar 2021-02-01 15:16:46 +00:00
11db880ac3 Optimise generateSampleData by ~10x
Does remove reversion creation for now...
2021-02-01 14:09:14 +00:00
87caab6c8e Serve minified css 2021-02-01 02:46:28 +00:00
d79366d2e6 Enable HTMLMin, further Whitenoise config 2021-02-01 02:30:17 +00:00
10f2152d8b Fix some unnecessary CRSF exemptions 2021-01-31 21:43:30 +00:00
0154ecb6d8 Deduplicate OEmbed view 2021-01-31 21:32:32 +00:00
138 changed files with 12166 additions and 4979 deletions

View File

@@ -17,7 +17,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9.1
python-version: 3.9
- uses: actions/cache@v2
id: pcache
with:
@@ -27,7 +27,8 @@ jobs:
${{ runner.os }}-pipenv-
- name: Install Dependencies
run: |
python -m pip install --upgrade pip pipenv
python -m pip install --upgrade pip
pip install pipenv
pipenv install -d
# if: steps.pcache.outputs.cache-hit != 'true'
- name: Cache Static Files

1
.gitignore vendored
View File

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

23
Pipfile
View File

@@ -19,10 +19,11 @@ cssselect = "~=1.1.0"
cssutils = "~=1.0.2"
dj-database-url = "~=0.5.0"
dj-static = "~=0.0.6"
Django = "~=3.2"
Django = "~=3.1.5"
django-debug-toolbar = "~=3.2"
django-filter = "~=2.4.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"
@@ -34,11 +35,11 @@ gunicorn = "~=20.0.4"
icalendar = "~=4.0.7"
idna = "~=2.10"
importlib-metadata = "~=3.4.0"
lxml = "~=4.6.3"
lxml = "~=4.6.2"
Markdown = "~=3.3.3"
msgpack = "~=1.0.2"
pep517 = "~=0.9.1"
Pillow = "~=8.3.2"
Pillow = "~=8.1.0"
premailer = "~=3.7.0"
progress = "~=1.5"
psutil = "~=5.8.0"
@@ -56,12 +57,12 @@ retrying = "~=1.3.3"
simplejson = "~=3.17.2"
six = "~=1.15.0"
soupsieve = "~=2.1"
sqlparse = "~=0.4.2"
sqlparse = "~=0.4.1"
static3 = "~=0.7.0"
svg2rlg = "~=0.3"
tini = "~=3.0.1"
tornado = "~=6.1"
urllib3 = "~=1.26.5"
urllib3 = "~=1.26.2"
whitenoise = "~=5.2.0"
yolk = "~=0.4.3"
"z3c.rml" = "~=4.1.2"
@@ -76,8 +77,6 @@ zipp = "~=3.4.0"
"zope.schema" = "~=6.0.1"
sentry-sdk = "*"
diff-match-patch = "*"
python-barcode = "*"
django-hCaptcha = "*"
[dev-packages]
selenium = "~=3.141.0"
@@ -89,8 +88,14 @@ pytest-django = "*"
pluggy = "*"
pytest-splinter = "*"
pytest = "*"
pytest-xdist = {extras = [ "psutil",], version = "*"}
PyPOM = {extras = [ "splinter",], version = "*"}
[requires]
python_version = "3.9"
[dev-packages.pytest-xdist]
extras = [ "psutil",]
version = "*"
[dev-packages.PyPOM]
extras = [ "splinter",]
version = "*"

774
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -61,13 +61,12 @@ INSTALLED_APPS = (
'users',
'RIGS',
'assets',
'training',
'debug_toolbar',
'registration',
'reversion',
'captcha',
'widget_tweaks',
'hcaptcha',
)
MIDDLEWARE = (
@@ -187,9 +186,12 @@ LOGOUT_URL = '/user/logout/'
ACCOUNT_ACTIVATION_DAYS = 7
# CAPTCHA settings
HCAPTCHA_SITEKEY = env('HCAPTCHA_SITEKEY', '10000000-ffff-ffff-ffff-000000000001')
HCAPTCHA_SECRET = env('HCAPTCHA_SECRET', '0x0000000000000000000000000000000000000000')
# reCAPTCHA settings
RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY', default="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI") # If not set, use development key
RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PUBLIC_KEY', default="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe") # If not set, use development key
NOCAPTCHA = True
SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']
# Email
EMAILER_TEST = False
@@ -260,5 +262,3 @@ USE_GRAVATAR = True
TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

View File

@@ -117,15 +117,6 @@ class TextBox(Region):
self.root.send_keys(value)
class SimpleMDETextArea(Region):
@property
def value(self):
return self.driver.execute_script("return document.querySelector('#' + arguments[0]).nextSibling.nextSibling.CodeMirror.getDoc().getValue();", self.root.get_attribute("id"))
def set_value(self, value):
self.driver.execute_script("document.querySelector('#' + arguments[0]).nextSibling.nextSibling.CodeMirror.getDoc().setValue(arguments[1]);", self.root.get_attribute("id"), value)
class CheckBox(Region):
def toggle(self):
self.root.click()

View File

@@ -12,7 +12,6 @@ urlpatterns = [
path('', include('versioning.urls')),
path('', include('RIGS.urls')),
path('assets/', include('assets.urls')),
path('training/', include('training.urls')),
path('', login_required(views.Index.as_view()), name='index'),

View File

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

View File

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

View File

@@ -33,7 +33,20 @@ class InvoiceIndex(generic.ListView):
return context
def get_queryset(self):
return self.model.objects.outstanding_invoices()
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
sql = "SELECT * FROM " \
"(SELECT " \
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
"AS sub " \
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
"ORDER BY invoice_date"
query = self.model.objects.raw(sql)
return query
class InvoiceDetail(generic.DetailView):
@@ -42,13 +55,7 @@ class InvoiceDetail(generic.DetailView):
def get_context_data(self, **kwargs):
context = super(InvoiceDetail, self).get_context_data(**kwargs)
context['page_title'] = "Invoice {} ({}) ".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
if self.object.void:
context['page_title'] += "<span class='badge badge-warning float-right'>VOID</span>"
elif self.object.is_closed:
context['page_title'] += "<span class='badge badge-success float-right'>PAID</span>"
else:
context['page_title'] += "<span class='badge badge-info float-right'>OUTSTANDING</span>"
context['page_title'] = "Invoice {} ({})".format(self.object.display_id, self.object.invoice_date.strftime("%d/%m/%Y"))
return context
@@ -166,7 +173,24 @@ class InvoiceWaiting(generic.ListView):
return context
def get_queryset(self):
return self.model.objects.waiting_invoices()
return self.get_objects()
def get_objects(self):
# TODO find a way to select items
events = self.model.objects.filter(
(
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
Q(end_date__lte=datetime.date.today()) # Has end date, finishes before
) & Q(invoice__isnull=True) & # Has not already been invoiced
Q(is_rig=True) # Is a rig (not non-rig)
).order_by('start_date') \
.select_related('person',
'organisation',
'venue', 'mic') \
.prefetch_related('items')
return events
class InvoiceEvent(generic.View):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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),
),
]

View File

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

View File

@@ -1,4 +1,4 @@
# Generated by Django 3.1.13 on 2021-10-27 14:19
# Generated by Django 3.1.7 on 2021-03-02 11:21
from django.db import migrations, models
@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0042_auto_20211007_2338'),
('RIGS', '0041_auto_20210208_1603'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='initials',
field=models.CharField(max_length=5, null=True),
name='phone',
field=models.CharField(blank=True, default='', max_length=13),
),
]

View File

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

View File

@@ -20,7 +20,7 @@ from reversion.models import Version
class Profile(AbstractUser):
initials = models.CharField(max_length=5, null=True, blank=False)
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
phone = models.CharField(max_length=13, blank=True, default='')
api_key = models.CharField(max_length=40, blank=True, editable=False, default='')
is_approved = models.BooleanField(default=False)
@@ -28,8 +28,6 @@ class Profile(AbstractUser):
last_emailed = models.DateTimeField(blank=True, null=True)
dark_theme = models.BooleanField(default=False)
reversion_hide = True
@classmethod
def make_api_key(cls):
size = 20
@@ -67,6 +65,8 @@ class Profile(AbstractUser):
def __str__(self):
return self.name
# TODO move to versioning - currently get import errors with that
class RevisionMixin(object):
@property
@@ -278,19 +278,6 @@ class EventManager(models.Manager):
).count()
return event_count
def waiting_invoices(self):
events = self.filter(
(
models.Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
models.Q(end_date__lte=datetime.date.today()) # Or has end date, finishes before
) & models.Q(invoice__isnull=True) & # Has not already been invoiced
models.Q(is_rig=True) # Is a rig (not non-rig)
).order_by('start_date') \
.select_related('person', 'organisation', 'venue', 'mic') \
.prefetch_related('items')
return events
@reversion.register(follow=['items'])
class Event(models.Model, RevisionMixin):
@@ -325,6 +312,7 @@ class Event(models.Model, RevisionMixin):
end_time = models.TimeField(blank=True, null=True)
access_at = models.DateTimeField(blank=True, null=True)
meet_at = models.DateTimeField(blank=True, null=True)
meet_info = models.CharField(max_length=255, blank=True, default='')
# Crew management
checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True,
@@ -333,6 +321,8 @@ class Event(models.Model, RevisionMixin):
verbose_name="MIC", on_delete=models.CASCADE)
# Monies
payment_method = models.CharField(max_length=255, blank=True, default='')
payment_received = models.CharField(max_length=255, blank=True, default='')
purchase_order = models.CharField(max_length=255, blank=True, default='', verbose_name='PO')
collector = models.CharField(max_length=255, blank=True, default='', verbose_name='collected by')
@@ -343,13 +333,10 @@ class Event(models.Model, RevisionMixin):
@property
def display_id(self):
if self.pk:
if self.is_rig:
return str("N%05d" % self.pk)
else:
return self.pk
if self.is_rig:
return str("N%05d" % self.pk)
else:
return "????"
return self.pk
# Calculated values
"""
@@ -372,9 +359,6 @@ class Event(models.Model, RevisionMixin):
@property
def vat(self):
# No VAT is owed on internal transfers
if self.internal:
return 0
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01'))
"""
@@ -545,23 +529,6 @@ class EventAuthorisation(models.Model, RevisionMixin):
return "{} (requested by {})".format(self.event.display_id, self.sent_by.initials)
class InvoiceManager(models.Manager):
def outstanding_invoices(self):
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
sql = "SELECT * FROM " \
"(SELECT " \
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
"AS sub " \
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
"ORDER BY invoice_date"
query = self.raw(sql)
return query
@reversion.register(follow=['payment_set'])
class Invoice(models.Model, RevisionMixin):
event = models.OneToOneField('Event', on_delete=models.CASCADE)
@@ -570,8 +537,6 @@ class Invoice(models.Model, RevisionMixin):
reversion_perm = 'RIGS.view_invoice'
objects = InvoiceManager()
@property
def sum_total(self):
return self.event.sum_total
@@ -802,21 +767,21 @@ class EventChecklist(models.Model, RevisionMixin):
fd_voltage_l2 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L2-N", help_text="L2 - N")
fd_voltage_l3 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L3-N", help_text="L3 - N")
fd_phase_rotation = models.BooleanField(blank=True, null=True, verbose_name="Phase Rotation", help_text="Phase Rotation<br><small>(if required)</small>")
fd_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
fd_earth_fault = models.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
# Worst case points
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_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w1_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w1_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w2_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w2_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w2_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w3_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w3_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w3_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?<br><small>(using test button)</small>")
public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?<br><small>(using socket tester)</small>")

View File

@@ -151,7 +151,6 @@ class EventDuplicate(EventUpdate):
# Clear checked in by if it's a dry hire
if new.dry_hire is True:
new.checked_in_by = None
new.collector = None
# Remove all the authorisation information from the new event
new.auth_request_to = ''

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@@ -244,19 +244,12 @@
</div>
</div>
</div>
{% else %}
{% elif event.riskassessment.event_size == 1 %}
<div class="row my-3" id="size-1">
<div class="col-12">
{% if event.riskassessment.event_size == 1 %}
<div class="card border-warning">
<div class="card-header">Electrical Checks <small>for Medium TEC Events </small></div>
<div class="card-body">
{% else %}
<div class="card border-danger">
<div class="card-header">Electrical Checks <small>for Large TEC Events</small></div>
<div class="card-body">
<div class="alert alert-danger"><strong>Here be dragons. Ensure you have appeased the Power Gods before continuing... (If you didn't check with a Supervisor, <em>you cannot continue your event!</em>)</strong></div>
{% endif %}
{% include 'partials/checklist_checkbox.html' with formitem=form.source_rcd %}
{% include 'partials/checklist_checkbox.html' with formitem=form.labelling %}
{% include 'partials/checklist_checkbox.html' with formitem=form.earthing %}
@@ -346,6 +339,17 @@
</div>
</div>
</div>
{% else %}
<div class="row my-3" id="size-2">
<div class="col-12">
<div class="card border-danger">
<div class="card-header">Electrical Checks <small>for Large TEC Events</small></div>
<div class="card-body">
<p>Outside the scope of this assessment. <strong>I really hope you checked with a supervisor...</strong></p>
</div>
</div>
</div>
</div>
{% endif %}
<div class="row mt-3">
<div class="col-sm-12 text-right">

View File

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

View File

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

View File

@@ -74,14 +74,6 @@
<lineStyle kind="linebelow" start="3,0" stop="3,0" colorName="black"/>
<lineStyle kind="linebelow" start="5,0" stop="5,0" colorName="black"/>
</blockTableStyle>
<listStyle name="ol"
bulletFormat="%s."
bulletFontSize="10" />
<listStyle name="ul"
start="bulletchar"
bulletFontSize="10"/>
</stylesheet>
<template > {# Note: page is 595x842 points (1 point=1/72in) #}

View File

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

View File

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

View File

@@ -5,13 +5,15 @@
<p>Hi {{ to_name|default:"there" }},</p>
<p><b>{{ request.user.get_full_name }}</b> has requested that you authorise <b>{{ object.display_id }}
| {{ object.name }}</b>{% if not to_name %} on behalf of <b>{% if object.person %}{{ object.person.name }}{% else %}{{ object.organisation.name }}{% endif %}</b>{% endif %}.</p>
| {{ object.name }}</b>{% if not to_name %} on behalf of <b>{{ object.person.name }}</b>{% endif %}.</p>
<p>
Please find the link below to complete the event booking process.
Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward
this
email on.
{% if object.event.organisation and object.event.organisation.union_account %}{# internal #}
Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward
this
email on.
{% endif %}
</p>

View File

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

View File

@@ -1,48 +1,24 @@
{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %}
{% load static %}
{% load button from filters %}
{% block title %}Request Authorisation{% endblock %}
{% block js %}
<script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/popover.js' %}"></script>
<script src="{% static 'js/clipboard.min.js' %}"></script>
<script>
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
$(e.trigger).popover('show');
window.setTimeout(function () {$(e.trigger).popover('hide')}, 3000);
e.clearSelection();
});
</script>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="alert alert-warning pb-0">
<div class="alert alert-warning">
<h1>Send authorisation request email.</h1>
<p>Pressing send will email the address provided. <strong>Please triple check everything before continuing.</strong></p>
<p>Pressing send will email the address provided. Please triple check everything before continuing.</p>
</div>
<div class="alert alert-info pb-0">
{% if object.person.email or object.organisation.email %}
<div class="alert alert-info">
<dl class="dl-horizontal">
{% if object.person.email %}
<dt>Person Email</dt>
<dd><span id="person-email">{{ object.person.email }}</span>{% button 'copy' id='#person-email' %}</dd>
{% endif %}
{% if object.organisation.email %}
<dd>{{ object.person.email }}</dd>
<dt>Organisation Email</dt>
<dd><span id="org-email">{{ object.organisation.email }}</span>{% button 'copy' id='#org-email' %}</dd>
{% endif %}
<dd>{{ object.organisation.email }}</dd>
</dl>
{% else %}
<p>No email addresses saved to the client &#3232;_&#3232;</p>
{% endif %}
</div>
<form action="{{ form.action|default:request.path }}" method="POST" id="auth-request-form">
{% csrf_token %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -47,7 +47,5 @@
class="fas fa-pound-sign"></span>
<span class="d-none d-sm-inline">Invoice</span></a>
{% endif %}
<a href="https://docs.google.com/forms/d/e/1FAIpQLSf-TBOuJZCTYc2L8DWdAaC3_Werq0ulsUs8-6G85I6pA9WVsg/viewform" class="btn btn-danger"><span class="fas fa-file-invoice-dollar"></span> <span class="d-none d-sm-inline">Subhire Insurance Form</span></a>
{% endif %}
</div>

View File

@@ -1,5 +1,4 @@
{% load namewithnotes from filters %}
{% load markdown_tags %}
<div class="card card-info">
<div class="card-header">Event Info</div>
<div class="card-body">
@@ -47,7 +46,7 @@
<dd class="col-sm-12">&nbsp;</dd>
<dt class="col-sm-6">Event Description</dt>
<dd class="dont-break-out col-sm-12">{{ event.description|markdown }}</dd>
<dd class="dont-break-out col-sm-12">{{ event.description|linebreaksbr }}</dd>
<dd class="col-sm-12">&nbsp;</dd>

View File

@@ -6,10 +6,6 @@
<span class="badge badge-success">PO: {{ event.purchase_order }}</span>
{% elif event.authorised %}
<span class="badge badge-success">Authorisation: Complete <span class="fas fa-check"></span></span>
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
<span class="badge badge-warning"> Authorisation: Issue <span class="fas fa-exclamation-circle"></span></span>
{% elif event.auth_request_to %}
<span class="badge badge-info"> Authorisation: Sent <span class="fas fa-paper-plane"></span></span>
{% else %}
<span class="badge badge-danger">Authorisation: <span class="fas fa-times"></span></span>
{% endif %}

View File

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

View File

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

View File

@@ -5,14 +5,17 @@
{% load nice_errors from filters %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/>
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}" async></script>
{% endblock %}
{% block js %}
{{ block.super }}
<script src="{% static 'js/autocompleter.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>

View File

@@ -212,25 +212,8 @@ def button(type, url=None, pk=None, clazz="", icon=None, text="", id=None, style
clazz += " btn-primary "
icon = "fa-plus"
text = "New"
elif type == 'copy':
return {'copy': True, 'id': id, 'style': style}
elif type == 'search':
return {'submit': True, 'class': 'btn-info', 'icon': 'fa-search', 'text': 'Search', 'id': id, 'style': style}
elif type == 'submit':
return {'submit': True, 'class': 'btn-primary', 'icon': 'fa-save', 'text': 'Save', 'id': id, 'style': style}
return {'target': url, 'pk': pk, 'class': clazz, 'icon': icon, 'text': text, 'id': id, 'style': style}
@register.simple_tag
def invoices_waiting():
return len(models.Event.objects.waiting_invoices())
@register.simple_tag
def invoices_outstanding():
return len(models.Invoice.objects.outstanding_invoices())
@register.simple_tag
def total_invoices_todo():
return len(models.Event.objects.waiting_invoices()) + len(models.Invoice.objects.outstanding_invoices())

View File

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

View File

@@ -53,7 +53,7 @@ class EventDetail(BasePage):
# TODO Refactor into regions to match template fragmentation
_event_name_selector = (By.XPATH, '//h2')
_person_panel_selector = (By.XPATH, '//div[contains(text(), "Person Details")]/..')
_person_panel_selector = (By.XPATH, '//div[contains(text(), "Contact Details")]/..')
_name_selector = (By.XPATH, '//dt[text()="Person"]/following-sibling::dd[1]')
_email_selector = (By.XPATH, '//dt[text()="Email"]/following-sibling::dd[1]')
_phone_selector = (By.XPATH, '//dt[text()="Phone Number"]/following-sibling::dd[1]')
@@ -96,7 +96,7 @@ class CreateEvent(FormPage):
_warning_selector = (By.XPATH, '/html/body/div[1]/div[1]')
form_items = {
'description': (regions.SimpleMDETextArea, (By.ID, 'id_description')),
'description': (regions.TextBox, (By.ID, 'id_description')),
'name': (regions.TextBox, (By.ID, 'id_name')),
'start_date': (regions.DatePicker, (By.ID, 'id_start_date')),
@@ -110,7 +110,7 @@ class CreateEvent(FormPage):
'collected_by': (regions.TextBox, (By.ID, 'id_collector')),
'po': (regions.TextBox, (By.ID, 'id_purchase_order')),
'notes': (regions.SimpleMDETextArea, (By.ID, 'id_notes'))
'notes': (regions.TextBox, (By.ID, 'id_notes'))
}
def select_event_type(self, type_name):

View File

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

View File

@@ -721,12 +721,12 @@ def test_ec_create_medium(logged_in_browser, live_server, admin_user, medium_ra)
page.fd_voltage_l2 = 235
page.fd_voltage_l3 = 0
page.fd_phase_rotation = True
page.fd_earth_fault = "1.21"
page.fd_earth_fault = 666
page.fd_pssc = 1984
page.w1_description = "In the carpark, by the bins"
page.w1_polarity = True
page.w1_voltage = 240
page.w1_earth_fault = "0.42"
page.w1_earth_fault = 333
page.submit()
assert page.success

View File

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

View File

@@ -1,8 +0,0 @@
class AssetIDConverter: # Forces lowercase to uppercase
regex = '[^/]+'
def to_python(self, value):
return str(value).upper()
def to_url(self, value):
return str(value).upper()

View File

@@ -45,7 +45,7 @@ class CableTypeForm(forms.ModelForm):
model = models.CableType
fields = '__all__'
def clean(self): # TODO Does unique_together work better than this?
def clean(self):
form_data = self.cleaned_data
queryset = models.CableType.objects.filter(Q(plug=form_data['plug']) & Q(socket=form_data['socket']) & Q(circuits=form_data['circuits']) & Q(cores=form_data['cores']))
# Being identical to itself shouldn't count...

View File

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

View File

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

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ class Supplier(models.Model, RevisionMixin):
ordering = ['name']
def get_absolute_url(self):
return reverse('supplier_detail', kwargs={'pk': self.pk})
return reverse('supplier_list')
def __str__(self):
return self.name
@@ -82,9 +82,6 @@ class CableType(models.Model):
else:
return "Unknown"
def get_absolute_url(self):
return reverse('cable_type_detail', kwargs={'pk': self.pk})
def get_available_asset_id(wanted_prefix=""):
sql = """

View File

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

View File

@@ -12,7 +12,9 @@
});
$('#searchButton').click(function (e) {
e.preventDefault();
var url = "{% url 'asset_audit' None %}".replace('None', $("#{{form.q.id_for_label}}").val();
var url = "{% url 'asset_audit' None %}";
var id = $("#{{form.q.id_for_label}}").val();
url = url.replace('None', id);
$.ajax({
url: url,
success: function(){

View File

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

View File

@@ -5,14 +5,11 @@
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'css/simplemde.min.css' %}">
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}"></script>
<script src="{% static 'js/simplemde.min.js' %}"></script>
<script src="{% static 'js/interaction.js' %}"></script>
{% endblock %}
{% block js %}
@@ -37,7 +34,7 @@
})
.ajaxSelectPicker({
ajax: {
url: "{% url 'asset_search_json' %}",
url: '{% url 'asset_search_json' %}',
type: "GET",
data: function () {
let params = {
@@ -75,11 +72,6 @@
preserveSelected: false
});
</script>
<script>
$(document).ready(function () {
setupMDE('#id_comments');
});
</script>
{% endblock %}
{% block content %}

View File

@@ -11,10 +11,7 @@
<div class="btn-group">
{% button 'edit' url='asset_update' pk=object.asset_id %}
{% button 'duplicate' url='asset_duplicate' pk=object.asset_id %}
<a type="button" class="btn btn-info" href="{% url 'asset_audit' object.asset_id %}"><span class="fas fa-certificate"></span> Audit</a>
{% if object.is_cable %}
<a type="button" class="btn btn-primary" href="{% url 'generate_label' object.asset_id %}"><span class="fas fa-barcode"></span> Generate Label</a>
{% endif %}
<a type="button" class="btn btn-info" href="{% url 'asset_audit' object.asset_id %}"><i class="fas fa-certificate"></i> Audit</a>
</div>
{% endif %}
{% if create or edit or duplicate %}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
{% load widget_tweaks %}
{% load linkornone from filters %}
<div class="card mb-2">
<div class="card-header">
Purchase Details
@@ -8,26 +7,11 @@
{% if create or edit or duplicate %}
<div class="form-group" id="purchased-from-group">
<label for="{{ form.purchased_from.id_for_label }}">Supplier</label>
<div class="row">
<div class="col">
<select id="{{ form.purchased_from.id_for_label }}" name="{{ form.purchased_from.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='supplier' %}">
{% if object.purchased_from %}
<option value="{{form.purchased_from.value}}" selected="selected" data-update_url="{% url 'supplier_update' form.purchased_from.value %}">{{ object.purchased_from }}</option>
{% endif %}
</select>
</div>
<div class="col align-right">
<div class="btn-group">
<a href="{% url 'supplier_create' %}" class="btn btn-success modal-href"
data-target="#{{ form.purchased_from.id_for_label }}">
<span class="fas fa-plus"></span>
</a>
<a {% if form.supplier.value %}href="{% url 'supplier_update' form.purchased_from.value %}"{% endif %} class="btn btn-warning modal-href" id="{{ form.purchased_from.id_for_label }}-update" data-target="#{{ form.purchased_from.id_for_label }}">
<span class="fas fa-edit"></span>
</a>
</div>
</div>
</div>
<select id="{{ form.purchased_from.id_for_label }}" name="{{ form.purchased_from.name }}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='supplier' %}">
{% if object.purchased_from %}
<option value="{{form.purchased_from.value}}" selected="selected" data-update_url="{% url 'supplier_update' form.purchased_from.value %}">{{ object.purchased_from }}</option>
{% endif %}
</select>
</div>
<div class="form-group">
@@ -67,11 +51,14 @@
{% else %}
<dl>
<dt>Purchased From</dt>
<dd>{% if object.purchased_from %}<a href="{{object.purchased_from.get_absolute_url}}">{{ object.purchased_from }}</a>{%else%}-{%endif%}</dd>
<dd>{{ object.purchased_from|default_if_none:'-' }}</dd>
<dt>Purchase Price</dt>
<dd>£{{ object.purchase_price|default_if_none:'-' }}</dd>
<dt>Salvage Value</dt>
<dd>£{{ object.salvage_value|default_if_none:'-' }}</dd>
<dt>Date Acquired</dt>
<dd>{{ object.date_acquired|default_if_none:'-' }}</dd>
{% if object.date_sold %}

View File

@@ -70,14 +70,14 @@ class AssetList(BasePage):
class AssetForm(FormPage):
_purchased_from_select_locator = (By.XPATH, '//div[@id="purchased-from-group"]/div/div/div')
_purchased_from_select_locator = (By.CSS_SELECTOR, 'div#purchased-from-group>div.bootstrap-select')
_parent_select_locator = (By.CSS_SELECTOR, 'div#parent-group>div.bootstrap-select')
form_items = {
'asset_id': (regions.TextBox, (By.ID, 'id_asset_id')),
'description': (regions.TextBox, (By.ID, 'id_description')),
'is_cable': (regions.CheckBox, (By.ID, 'id_is_cable')),
'serial_number': (regions.TextBox, (By.ID, 'id_serial_number')),
'comments': (regions.SimpleMDETextArea, (By.ID, 'id_comments')),
'comments': (regions.TextBox, (By.ID, 'id_comments')),
'purchase_price': (regions.TextBox, (By.ID, 'id_purchase_price')),
'salvage_value': (regions.TextBox, (By.ID, 'id_salvage_value')),
'date_acquired': (regions.DatePicker, (By.ID, 'id_date_acquired')),

View File

@@ -1,24 +1,21 @@
from django.contrib.auth.decorators import login_required
from django.urls import path, register_converter
from django.urls import path
from django.views.decorators.clickjacking import xframe_options_exempt
from PyRIGS.decorators import has_oembed, permission_required_with_403
from PyRIGS.views import OEmbedView
from . import views, converters
register_converter(converters.AssetIDConverter, 'asset')
from assets import views
urlpatterns = [
path('', login_required(views.AssetList.as_view()), name='asset_index'),
path('asset/list/', login_required(views.AssetList.as_view()), name='asset_list'),
path('asset/id/<asset:pk>/', has_oembed(oembed_view="asset_oembed")(views.AssetDetail.as_view()), name='asset_detail'),
path('asset/id/<str:pk>/', has_oembed(oembed_view="asset_oembed")(views.AssetDetail.as_view()), name='asset_detail'),
path('asset/create/', permission_required_with_403('assets.add_asset')
(views.AssetCreate.as_view()), name='asset_create'),
path('asset/id/<asset:pk>/edit/', permission_required_with_403('assets.change_asset')
path('asset/id/<str:pk>/edit/', permission_required_with_403('assets.change_asset')
(views.AssetEdit.as_view()), name='asset_update'),
path('asset/id/<asset:pk>/duplicate/', permission_required_with_403('assets.add_asset')
path('asset/id/<str:pk>/duplicate/', permission_required_with_403('assets.add_asset')
(views.AssetDuplicate.as_view()), name='asset_duplicate'),
path('asset/id/<asset:pk>/label', login_required(views.GenerateLabel.as_view()), name='generate_label'),
path('cabletype/list/', login_required(views.CableTypeList.as_view()), name='cable_type_list'),
path('cabletype/create/', permission_required_with_403('assets.add_cable_type')(views.CableTypeCreate.as_view()), name='cable_type_create'),

View File

@@ -1,5 +1,4 @@
import simplejson
import random
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core import serializers
@@ -10,11 +9,6 @@ from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views import generic
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import get_object_or_404
from PIL import Image, ImageDraw, ImageFont
from barcode import Code39
from barcode.writer import ImageWriter
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin, \
is_ajax, OEmbedView
@@ -48,9 +42,9 @@ class AssetList(LoginRequiredMixin, generic.ListView):
queryset = self.model.objects.all()
elif len(query_string) >= 3:
queryset = self.model.objects.filter(
Q(asset_id__exact=query_string.upper()) | Q(description__icontains=query_string) | Q(serial_number__exact=query_string))
Q(asset_id__exact=query_string) | Q(description__icontains=query_string) | Q(serial_number__exact=query_string))
else:
queryset = self.model.objects.filter(Q(asset_id__exact=query_string.upper()))
queryset = self.model.objects.filter(Q(asset_id__exact=query_string))
if form.cleaned_data['category']:
queryset = queryset.filter(category__in=form.cleaned_data['category'])
@@ -344,37 +338,3 @@ class CableTypeUpdate(generic.UpdateView):
def get_success_url(self):
return reverse("cable_type_detail", kwargs={"pk": self.object.pk})
class GenerateLabel(generic.View):
def get(self, request, pk):
black = (0, 0, 0)
white = (255, 255, 255)
size = (700, 200)
font = ImageFont.truetype("static/fonts/OpenSans-Regular.tff", 20)
obj = get_object_or_404(models.Asset, asset_id=pk)
asset_id = "Asset: {}".format(obj.asset_id)
length = "Length: {}m".format(obj.length)
csa = "CSA: {}mm²".format(obj.csa)
image = Image.new("RGB", size, white)
logo = Image.open("static/imgs/square_logo.png")
draw = ImageDraw.Draw(image)
draw.text((210, 140), asset_id, fill=black, font=font)
draw.text((210, 170), length, fill=black, font=font)
draw.text((350, 170), csa, fill=black, font=font)
draw.multiline_text((500, 140), "TEC PA & Lighting\n(0115) 84 68720", fill=black, font=font)
barcode = Code39(str(obj.asset_id), writer=ImageWriter())
logo_size = (200, 200)
image.paste(logo.resize(logo_size, Image.ANTIALIAS))
barcode_image = barcode.render(writer_options={"quiet_zone": 0, "write_text": False})
width, height = barcode_image.size
image.paste(barcode_image.crop((0, 0, width, 135)), (int(((size[0] + logo_size[0]) - width) / 2), 0))
response = HttpResponse(content_type="image/png")
image.save(response, "PNG")
return response

View File

@@ -3,7 +3,7 @@
var gulp = require('gulp');
const terser = require('gulp-uglify');
const sass = require('gulp-sass')(require('node-sass'));
const sass = require('gulp-sass');
const flatten = require('gulp-flatten');
const autoprefixer = require('autoprefixer')
const postcss = require('gulp-postcss')
@@ -15,6 +15,8 @@ const cssnano = require('cssnano');
const con = require('gulp-concat');
const gulpif = require('gulp-if');
sass.compiler = require('node-sass');
function fonts(done) {
return gulp.src('node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.*')
.pipe(gulp.dest('pipeline/built_assets/fonts'))
@@ -27,9 +29,7 @@ function styles(done) {
'node_modules/fullcalendar/main.css',
'node_modules/bootstrap-select/dist/css/bootstrap-select.css',
'node_modules/ajax-bootstrap-select/dist/css/ajax-bootstrap-select.css',
'node_modules/flatpickr/dist/flatpickr.css',
'node_modules/simplemde/dist/simplemde.min.css'
])
'node_modules/flatpickr/dist/flatpickr.css',])
.pipe(sourcemaps.init())
.pipe(sass().on('error', sass.logError))
.pipe(gulpif(function(file) { return bs_select.includes(file.relative);}, con('selects.css')))
@@ -64,7 +64,6 @@ function scripts() {
'node_modules/fullcalendar/main.js',
'node_modules/bootstrap-select/dist/js/bootstrap-select.js',
'node_modules/ajax-bootstrap-select/dist/js/ajax-bootstrap-select.js',
'node_modules/simplemde/dist/simplemde.min.js',
'node_modules/konami/konami.js',
'pipeline/source_assets/js/**/*.js',])
.pipe(gulpif(function(file) { return base_scripts.includes(file.relative);}, con('base.js')))
@@ -84,7 +83,7 @@ function browserSync(done) {
notify: false,
open: false,
port: 8001,
proxy: '127.0.0.1:8000'
proxy: 'localhost:8000'
});
done();
}

12321
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,37 +5,35 @@
"author": "Tom Price",
"license": "Custom",
"dependencies": {
"@forevolve/bootstrap-dark": "^2.1.0",
"@fortawesome/fontawesome-free": "^5.15.4",
"@forevolve/bootstrap-dark": "^1.0.0-alpha.1075",
"@fortawesome/fontawesome-free": "^5.15.2",
"ajax-bootstrap-select": "^1.4.5",
"autocompleter": "^6.1.2",
"autoprefixer": "^10.4.0",
"autocompleter": "^6.0.3",
"autoprefixer": "^9.8.0",
"bootstrap": "^4.5.2",
"bootstrap-select": "^1.13.17",
"clipboard": "^2.0.8",
"cssnano": "^5.0.13",
"clipboard": "^2.0.6",
"cssnano": "^4.1.10",
"flatpickr": "^4.6.6",
"fullcalendar": "^5.10.1",
"fullcalendar": "^5.3.2",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-flatten": "^0.4.0",
"gulp-if": "^3.0.0",
"gulp-postcss": "^9.0.1",
"gulp-sass": "^5.0.0",
"gulp-sourcemaps": "^3.0.0",
"gulp-postcss": "^8.0.0",
"gulp-sass": "^4.1.0",
"gulp-sourcemaps": "^2.6.5",
"gulp-uglify": "^3.0.2",
"html5sortable": "^0.13.3",
"jquery": "^3.6.0",
"konami": "^1.6.3",
"html5sortable": "^0.10.0",
"jquery": "^3.5.1",
"konami": "^1.6.2",
"moment": "^2.27.0",
"node-sass": "^7.0.0",
"node-sass": "^5.0.0",
"popper.js": "^1.16.1",
"postcss": "^8.4.5",
"simplemde": "^1.11.2",
"uglify-js": "^3.14.5"
"uglify-js": "^3.12.6"
},
"devDependencies": {
"browser-sync": "^2.27.7"
"browser-sync": "^2.26.12"
},
"scripts": {
"gulp": "gulp",

View File

@@ -1,7 +1,3 @@
marked.setOptions({
breaks: true,
})
function setupItemTable(items_json) {
objectitems = JSON.parse(items_json)
$.each(objectitems, function (key, val) {
@@ -10,12 +6,12 @@ function setupItemTable(items_json) {
newitem = -1;
}
function nl2br(str, is_xhtml) {
function nl2br (str, is_xhtml) {
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
}
function escapeHtml(str) {
function escapeHtml (str) {
return $('<div/>').text(str).html();
}
@@ -36,16 +32,6 @@ function updatePrices() {
$('#total').text(parseFloat(sum + vat).toFixed(2));
}
function setupMDE(selector) {
editor = new SimpleMDE({
element: $(selector)[0],
forceSync: true,
toolbar: ["bold", "italic", "strikethrough", "|", "unordered-list", "ordered-list", "|", "link", "|", "preview", "guide"],
status: true,
});
$(selector).data('mde_editor',editor);
}
$('#item-table').on('click', '.item-delete', function () {
delete objectitems[$(this).data('pk')]
$('#item-' + $(this).data('pk')).remove();
@@ -120,7 +106,7 @@ $('body').on('submit', '#item-form', function (e) {
// update the table
$row = $('#item-' + pk);
$row.find('.name').html(escapeHtml(fields.name));
$row.find('.description').html(marked(fields.description));
$row.find('.description').html(nl2br(escapeHtml(fields.description)));
$row.find('.cost').html(parseFloat(fields.cost).toFixed(2));
$row.find('.quantity').html(fields.quantity);

View File

@@ -28,9 +28,6 @@
color: $gray-100 !important;
border-color: $darktheme;
}
.btn-link {
color: white;
}
.bs-popover-right > .arrow::after {
border-right-color: $darktheme;
}
@@ -122,35 +119,4 @@
background: #222;
color: $gray-100;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus {
border: 1px solid $info;
-webkit-text-fill-color: white;
-webkit-box-shadow: 0 0 0px 1000px rgba($info, .3) inset;
transition: background-color 5000s ease-in-out 0s;
}
.editor-toolbar > a {
color: white !important;
}
.editor-toolbar > a:hover {
background: transparent !important;
}
.editor-toolbar > a.active {
background: $info !important;
}
.cm-s-paper {
color: white;
background-color: $darktheme;
border-color: #bbb;
}
.CodeMirror-cursor {
border-color: white !important;
}
}

View File

@@ -177,11 +177,6 @@ svg {
white-space: no-wrap;
}
span.fas {
padding-left: 0.1em !important;
padding-right: 0.1em !important;
}
html.embedded {
display: flex;
flex-direction: column;
@@ -226,33 +221,3 @@ html.embedded {
max-width: 3em;
}
}
.markdown {
h1 {
font-size: $h1-font-size * 0.75;
}
h2 {
font-size: $h2-font-size * 0.8;
}
h3 {
font-size: $h3-font-size * 0.85;
}
h4 {
font-size: $h4-font-size * 0.9;
}
h5 {
font-size: $h5-font-size * 0.95;
}
img {
max-width: 100%;
}
}
#rigboard {
.markdown {
img {
max-width: 30rem;
}
}
}

View File

@@ -15,6 +15,7 @@
<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="preload" href="{% static 'fonts/fa-solid-900.woff2' %}" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" type="text/css" href="{% static 'css/screen.css' %}">
{% block css %}
@@ -31,22 +32,22 @@
<a class="skip-link" href='#main'>Skip to content</a>
{% include "analytics.html" %}
{% 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.webp' %}" width="40" height="40" alt="TEC's Logo: Serif 'TEC' vertically next to a blue box with the words 'PA and Lighting', surrounded by graduated rings">
</a>
<div class="container">
<a class="navbar-brand" style="position: absolute; left:0.5em; top: 2px;" href="{% if request.user.is_authenticated %}https://rigs.nottinghamtec.co.uk{%else%}https://nottinghamtec.co.uk{%endif%}">
<img src="{% static 'imgs/logo.webp' %}" width="40" height="40" alt="TEC's Logo: Serif 'TEC' vertically next to a blue box with the words 'PA and Lighting', surrounded by graduated rings" id="logo">
</a>
{% block titleheader %}
{% endblock %}
<button class="navbar-toggler ml-auto" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" onclick="document.getElementById('logo').classList.toggle('d-none');">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-between" id="navbarSupportedContent">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav">
{% block titleelements %}
{% endblock %}
</ul>
<ul class="navbar-nav align-self-end">
<ul class="navbar-nav ml-auto">
{% block titleelements_right %}
{% endblock %}
</ul>
@@ -77,11 +78,8 @@
</div>
<div class="modal fade" id="modal" role="dialog" tabindex=-1></div>
<script src="{% static 'js/base.js' %}"></script>
<script src="{% static 'js/marked.min.js' %}"></script>
{% include 'partials/dark_theme.html' %}
{% block js %}
{% endblock %}
</body>

View File

@@ -1,7 +1,7 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{page_title|safe}}{% block title %}{% endblock %}</h4>
<h4 class="modal-title">{{page_title}}{% block title %}{% endblock %}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>

View File

@@ -1,6 +1,5 @@
{% extends 'base_rigs.html' %}
{% load humanize %}
{% load static %}
{% block title %}RIGS{% endblock %}
@@ -8,9 +7,8 @@
<div class="row">
<h1 class="col-sm-12 pb-3">R<small class="text-muted">ig</small> I<small class="text-muted">nformation</small> G<small class="text-muted">athering</small> S<small class="text-muted">ystem</small></h1>
<h2 class="col-sm-12 pb-3">Welcome back {{ user.get_full_name }}, there {%if rig_count == 1 %}is one rig coming up{%else%}are {{ rig_count|apnumber }} rigs coming up.{%endif%}</h2>
<div class="col-sm-4 mb-3">
<div class="col-sm mb-3">
<div class="card">
<img class="card-img-top" src="{% static 'imgs/rigs.jpg' %}" alt="" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Rigboard</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'rigboard' %}"><span class="fas fa-list align-middle"></span><span class="align-middle"> Rigboard</span></a>
@@ -19,12 +17,6 @@
<a class="list-group-item list-group-item-action" href="{% url 'event_create' %}"><span class="fas fa-plus align-middle"></span><span class="align-middle"> New Event</span></a>
{% endif %}
</div>
</div>
</div>
<div class="col-sm-4 mb-3">
<div class="card">
{% now "m-d" as todays_date %}
<img class="card-img-top" src="{% if todays_date == '04-01' %}{% static 'imgs/tappytaptap.gif' %}{%else%}{% static 'imgs/assets.jpg' %}{%endif%}" alt="" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Asset Database</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'asset_index' %}"><span class="fas fa-tag align-middle"></span><span class="align-middle"> Asset List</span></a>
@@ -36,28 +28,11 @@
<a class="list-group-item list-group-item-action" href="{% url 'supplier_create' %}"><span class="fas fa-plus align-middle"></span><span class="align-middle"> New Supplier</span></a>
{% endif %}
</div>
</div>
</div>
<div class="col-sm-4 mb-3">
<div class="card">
<img class="card-img-top" src="{% static 'imgs/training.jpg' %}" alt="" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Training Database</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action text-info" href="{% url 'trainee_detail' request.user.pk %}"><span class="fas fa-file-signature align-middle"></span><span class="align-middle"> My Training Record</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'trainee_list' %}"><span class="fas fa-users"></span> Trainee List</a>
<a class="list-group-item list-group-item-action" href="{% url 'level_list' %}"><span class="fas fa-layer-group"></span> Level List</a></a>
<a class="list-group-item list-group-item-action" href="{% url 'item_list' %}"><span class="fas fa-sitemap"></span> Item List</a></a>
</div>
</div>
</div>
<div class="col-sm mb-3">
<div class="card">
<h4 class="card-header">Quick Links</h4>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="https://forum.nottinghamtec.co.uk" target="_blank" rel="noopener noreferrer"><span class="fas fa-comment-alt text-primary align-middle"></span><span class="align-middle"> TEC Forum</span></a>
<a class="list-group-item list-group-item-action" href="//nottinghamtec.sharepoint.com" target="_blank" rel="noopener noreferrer"><span class="fas fa-folder text-info align-middle"></span><span class="align-middle"> TEC Sharepoint</span></a>
<a class="list-group-item list-group-item-action" href="https://forum.nottinghamtec.co.uk" target="_blank" rel="noopener noreferrer"><span class="fas fa-comment-alt text-info align-middle"></span><span class="align-middle"> TEC Forum</span></a>
<a class="list-group-item list-group-item-action" href="//wiki.nottinghamtec.co.uk" target="_blank" rel="noopener noreferrer"><span class="fas fa-pen-square align-middle"></span><span class="align-middle"> TEC Wiki</span></a>
{% if perms.RIGS.change_event %}
{% if perms.RIGS.view_event %}
<a class="list-group-item list-group-item-action" href="//members.nottinghamtec.co.uk/price" target="_blank" rel="noopener noreferrer"><span class="fas fa-pound-sign text-warning align-middle"></span><span class="align-middle"> Price List</span></a>
{% endif %}
</div>

View File

@@ -2,8 +2,6 @@
<button type="submit" class="btn {{ class }}" title="{{ text }}" {% if id %}id="{{id}}"{%endif%} {% if style %}style="{{style}}"{%endif%}><span class="fas {{ icon }} align-middle"></span> <span class="d-none d-sm-inline align-middle">{{ text }}</span></button>
{% elif pk %}
<a href="{% url target pk %}" class="btn {{ class }}" {% if id %}id="{{id}}"{%endif%} {% if style %}style="{{style}}"{%endif%} {% if text == 'Print' %}target="_blank"{%endif%}><span class="fas {{ icon }} align-middle"></span> <span class="d-none d-sm-inline align-middle">{{ text }}</span></a>
{% elif copy %}
<button class="btn btn-secondary btn-sm mr-1" data-clipboard-target="{{id}}" data-content="Copied to clipboard!"><span class="fas fa-copy"></span></button>
{% else %}
<a href="{% url target %}" class="btn {{ class }}" {% if id %}id="{{id}}"{%endif%} {% if style %}style="{{style}}"{%endif%}><span class="fas {{ icon }} align-middle"></span> <span class="d-none d-sm-inline align-middle">{{ text }}</span></a>
{% endif %}

View File

@@ -1,10 +1,10 @@
{% if user.is_authenticated %}
<form id="searchForm" class="form-inline flex-nowrap mx-md-3 px-2 border border-light rounded w-75" role="form" method="GET" action="{% url 'event_archive' %}">
<form id="searchForm" class="form-inline flex-nowrap mx-md-3 px-2 border border-light rounded" role="form" method="GET" action="{% url 'event_archive' %}">
<div class="input-group input-group-sm flex-nowrap">
<div class="input-group-prepend">
<input id="id_search_input" type="search" name="q" class="form-control form-control-sm" placeholder="Search..." value="{{ request.GET.q }}" />
</div>
<select id="search-options" class="custom-select form-control" style="border-top-right-radius: 0px; border-bottom-right-radius: 0px; width: 15ch;">
<select id="search-options" class="custom-select form-control" style="border-top-right-radius: 0px; border-bottom-right-radius: 0px; width: 20ch;">
<option selected data-action="{% url 'event_archive' %}" href="#">Events</option>
<option data-action="{% url 'person_list' %}" href="#">People</option>
<option data-action="{% url 'organisation_list' %}" href="#">Organisations</option>
@@ -17,7 +17,7 @@
</select>
</div>
<button class="btn btn-info form-control form-control-sm btn-sm w-25" style="border-top-left-radius: 0px;border-bottom-left-radius: 0px;"><span class="fas fa-search"></span><span class="sr-only"> Search</span></button>
<a href="{% url 'search_help' %}" class="nav-link modal-href ml-1"><span class="fas fa-question-circle"></span></a>
<a href="{% url 'search_help' %}" class="nav-link modal-href ml-2"><span class="fas fa-question-circle"></span></a>
</form>
{% endif %}

View File

@@ -1,36 +1,29 @@
{% extends 'base_rigs.html' %}
{% load widget_tweaks %}
{% load static %}
{% block title %}Registration{% endblock %}
{% block content %}
<div style="background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url({% static 'imgs/wof2014-1-small.jpg' %}); background-repeat: no-repeat; background-size: cover; width: 100vw; height: 100vh; position: relative; left: 50%; right: 50%; margin-left: -50vw; margin-right: -50vw; margin-top: -24px; padding-top: 24px;">
<div class="container">
<div class="card">
<h3 class="card-header">New User Registration</h3>
<div class="card-body">
{% if form.errors or supplement_form.errors %}
<div class="alert alert-danger">
{{form.errors}}
{{supplement_form.errors}}
</div>
{% endif %}
<div class="col-sm-8 col-sm-offset-2">
<form method="post" role="form">{% csrf_token %}
{% for field in form %}
<div class="form-group form-row">
<label for="{{ field.id_for_label }}" class="col-form-label col-sm-4">{{ field.label }}</label>
<div class="controls col-sm-8">
{% render_field field class+="form-control" placeholder=field.label %}
</div>
</div>
{% endfor %}
<p><input type="submit" value="Register" class="btn btn-primary pull-right"></p>
</form>
</div>
</div>
</div>
</div>
<div class="col-sm-10 col-sm-offset-1">
<h3>New User Registration</h3>
{% if form.errors or supplement_form.errors %}
<div class="alert alert-danger">
{{form.errors}}
{{supplement_form.errors}}
</div>
{% endif %}
<div class="col-sm-8 col-sm-offset-2">
<form action="" method="post" class="" role="form">{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}" class="col-form-label col-sm-4">{{ field.label }}</label>
<div class="controls col-sm-8">
{% render_field field class+="form-control" placeholder=field.label %}
</div>
</div>
{% endfor %}
<p><input type="submit" value="Register" class="btn btn-primary pull-right"></p>
</form>
</div>
</div>
{% endblock %}
{% endblock %}

View File

View File

@@ -1,8 +0,0 @@
from django.contrib import admin
from training import models
from reversion.admin import VersionAdmin
# admin.site.register(models.Trainee, VersionAdmin)
admin.site.register(models.TrainingCategory, VersionAdmin)
admin.site.register(models.TrainingItem, VersionAdmin)
admin.site.register(models.TrainingLevel, VersionAdmin)

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class TrainingConfig(AppConfig):
name = 'training'

View File

@@ -1,49 +0,0 @@
from django import forms
from datetime import date
from training import models
from RIGS.models import Profile
class SessionLogForm(forms.Form):
pass
class QualificationForm(forms.ModelForm):
class Meta:
model = models.TrainingItemQualification
fields = '__all__'
def __init__(self, *args, **kwargs):
pk = kwargs.pop('pk', None)
super(QualificationForm, self).__init__(*args, **kwargs)
self.fields['trainee'].initial = Profile.objects.get(pk=pk)
self.fields['date'].widget.format = '%Y-%m-%d'
def clean_date(self):
date = self.cleaned_data['date']
if date > date.today():
raise forms.ValidationError('Qualification date may not be in the future')
return date
def clean_supervisor(self):
supervisor = self.cleaned_data['supervisor']
if supervisor.pk == self.cleaned_data['trainee'].pk:
raise forms.ValidationError('One may not supervise oneself...')
if not supervisor.is_supervisor:
raise forms.ValidationError('Selected supervisor must actually *be* a supervisor...')
return supervisor
class RequirementForm(forms.ModelForm):
depth = forms.ChoiceField(choices=models.TrainingItemQualification.CHOICES)
class Meta:
model = models.TrainingLevelRequirement
fields = '__all__'
def __init__(self, *args, **kwargs):
pk = kwargs.pop('pk', None)
super(RequirementForm, self).__init__(*args, **kwargs)
self.fields['level'].initial = models.TrainingLevel.objects.get(pk=pk)

View File

@@ -1,211 +0,0 @@
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 training import models
class Command(BaseCommand):
help = 'Adds sample data to use for testing'
can_import_settings = True
categories = []
items = []
levels = []
def handle(self, *args, **options):
print("Generating training data")
from django.conf import settings
if not (settings.DEBUG or settings.STAGING):
raise CommandError('You cannot run this command in production')
random.seed('otherwise it is done by time, which could lead to inconsistant tests')
with transaction.atomic():
self.setup_categories()
self.setup_items()
self.setup_levels()
self.setup_supervisor()
print("Done generating training data")
def setup_categories(self):
names = [(1, "Basic"), (2, "Sound"), (3, "Lighting"), (4, "Rigging"), (5, "Power"), (6, "Haulage")]
for i, name in names:
category = models.TrainingCategory.objects.create(reference_number=i, name=name)
category.save()
self.categories.append(category)
def setup_items(self):
names = [
"Motorised Power Towers",
"Catering",
"Forgetting Cables",
"Gazebo Construction",
"Balanced Audio",
"Unbalanced Audio",
"BBQ/Bin Interactions",
"Pushing Boxes",
"How Not To Die",
"Setting up projectors",
"Basketing truss",
"First Aid",
"Digging Trenches",
"Avoiding Bin Lorries",
"Getting cherry pickers stuck in mud",
"Crashing the Van",
"Getting pigs to fly",
"Basketing picnics",
"Python programming",
"Building Cables",
"Unbuilding Cables",
"Cat Herding",
"Pancake making",
"Tidying up",
"Reading Manuals",
"Bikeshedding",
"DJing",
"Partying",
"Teccie Gym",
"Putting dust covers on",
"Cleaning Lights",
"Water Skiing",
"Drinking",
"Fundamentals of Audio",
"Fundamentals of Photons",
"Social Interaction",
"Discourse Searching",
"Discord Searching",
"Coiling Cables",
"Kit Amnesties",
"Van Insurance",
"Subhire Insurance",
"Paperwork",
"More Paperwork",
"Second Aid",
"Being Old",
"Maxihoists",
"Sleazyhoists",
"Telehoists",
"Prolyte",
"Prolights",
"Making Phonecalls",
"Quoting For A Rig",
"Basic MIC",
"Advanced MIC",
"Avoiding MIC",
"Washing Cables",
"Cable Ramp",
"Van Loading",
"Trailer Loading",
"Storeroom Loading",
"Welding",
"Fire Extinguishers",
"Boring Conference AV",
"Flyaway",
"Short Leads",
"RF Systems",
"QLab",
"Use of Ladders",
"Working at Height",
"Organising Training",
"Organising Organising Training Training",
"Mental Health First Aid",
"Writing RAMS",
"Makros Runs",
"PAT",
"Kit Fixing",
"Kit Breaking",
"Replacing Lamps",
"Flying Pig Systems",
"Procrastination",
"Drinking Beer",
"Sending Emails",
"Email Signatures",
"Digital Sound Desks",
"Digital Lighting Desks",
"Painting PS10s",
"Chain Lubrication",
"Big Power",
"BIGGER POWER",
"Pixel Mapping",
"RDM",
"Ladder Inspections",
"Losing Crimpaz",
"Scrapping Trilite",
"Bin Diving",
"Wiki Editing"]
for i, name in enumerate(names):
item = models.TrainingItem.objects.create(category=random.choice(self.categories), reference_number=random.randint(0, 100), name=name)
self.items.append(item)
def setup_levels(self):
items = self.items.copy()
ta = models.TrainingLevel.objects.create(
level=models.TrainingLevel.TA,
description="Passion will hatred faithful evil suicide noble battle. Truth aversion gains grandeur noble. Dead play gains prejudice god ascetic grandeur zarathustra dead good. Faithful ultimate justice overcome love will mountains inexpedient.",
icon="address-card")
self.levels.append(ta)
tech_ccs = models.TrainingLevel.objects.create(
level=models.TrainingLevel.TECHNICIAN,
description="Technician Common Competencies. Spirit abstract endless insofar horror sexuality depths war decrepit against strong aversion revaluation free. Christianity reason joy sea law mountains transvaluation. Sea battle aversion dead ultimate morality self. Faithful morality.",
icon="book-reader")
tech_ccs.prerequisite_levels.add(ta)
super_ccs = models.TrainingLevel.objects.create(level=models.TrainingLevel.SUPERVISOR, description="Depths disgust hope faith of against hatred will victorious. Law...", icon="user-graduate")
for i in range(0, 5):
if len(items) == 0:
break
item = random.choice(items)
items.remove(item)
if i % 3 == 0:
models.TrainingLevelRequirement.objects.create(level=tech_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
else:
models.TrainingLevelRequirement.objects.create(level=super_ccs, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
icons = {
models.TrainingLevel.SOUND: ('microphone', 'microphone-alt'),
models.TrainingLevel.LIGHTING: ('lightbulb', 'traffic-light'),
models.TrainingLevel.POWER: ('plug', 'bolt'),
models.TrainingLevel.RIGGING: ('link', 'pallet'),
models.TrainingLevel.HAULAGE: ('truck', 'route'),
}
for i, name in models.TrainingLevel.DEPARTMENTS:
technician = models.TrainingLevel.objects.create(level=models.TrainingLevel.TECHNICIAN, department=i, description="Moral pinnacle derive ultimate war dead. Strong fearful joy contradict battle christian faithful enlightenment prejudice zarathustra moral.", icon=icons[i][0])
technician.prerequisite_levels.add(tech_ccs)
supervisor = models.TrainingLevel.objects.create(level=models.TrainingLevel.SUPERVISOR, department=i, description="Spirit holiest merciful mountains inexpedient reason value. Suicide ultimate hope.", icon=icons[i][1])
supervisor.prerequisite_levels.add(super_ccs, technician)
for i in range(0, 30):
if len(items) == 0:
break
item = random.choice(items)
items.remove(item)
if i % 3 == 0:
models.TrainingLevelRequirement.objects.create(level=technician, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
else:
models.TrainingLevelRequirement.objects.create(level=supervisor, item=item, depth=random.choice(models.TrainingItemQualification.CHOICES)[0])
self.levels.append(technician)
self.levels.append(supervisor)
def setup_supervisor(self):
supervisor = models.Profile.objects.create(username="supervisor", first_name="Super", last_name="Visor",
initials="SV",
email="supervisor@example.com", is_active=True,
is_staff=True, is_approved=True)
supervisor.set_password('supervisor')
supervisor.groups.add(Group.objects.get(name="Keyholders"))
supervisor.save()
models.TrainingLevelQualification.objects.create(
trainee=supervisor,
level=models.TrainingLevel.objects.filter(
level__gte=models.TrainingLevel.SUPERVISOR).exclude(
department=models.TrainingLevel.HAULAGE).exclude(
department__isnull=True).first(),
confirmed_on=timezone.now(),
confirmed_by=models.Trainee.objects.first())

View File

@@ -1,281 +0,0 @@
import os
import datetime
import re
import xml.etree.ElementTree as ET
from django.core.management.base import BaseCommand
from django.conf import settings
from django.db.utils import IntegrityError
from django.utils.timezone import make_aware
from training import models
from RIGS.models import Profile
class Command(BaseCommand):
epoch = datetime.date(1970, 1, 1)
id_map = {}
def handle(self, *args, **options):
self.import_Trainees()
self.import_TrainingCatagory()
self.import_TrainingItem()
self.import_TrainingItemQualification()
self.import_TrainingLevel()
self.import_TrainingLevelQualification()
self.import_TrainingLevelRequirements()
@staticmethod
def xml_path(file):
return os.path.join(settings.BASE_DIR, 'data/{}'.format(file))
@staticmethod
def parse_xml(file):
tree = ET.parse(file)
return tree.getroot()
def import_Trainees(self):
tally = [0, 0]
root = self.parse_xml(self.xml_path('Members.xml'))
for child in root:
try:
name = child.find('Member_x0020_Name').text
first_name = name.split()[0]
last_name = " ".join(name.split()[1:])
profile = Profile.objects.filter(first_name=first_name, last_name=last_name).first()
if profile:
self.id_map[child.find('ID').text] = profile.pk
tally[0] += 1
else:
# PYTHONIC, BABY
initials = first_name[0] + "".join([name_section[0] for name_section in re.split("\\s*-", last_name.replace("(", ""))])
# print(initials)
new_profile = Profile.objects.create(username=name.replace(" ", ""),
first_name=first_name,
last_name=last_name,
initials=initials)
self.id_map[child.find('ID').text] = new_profile.pk
tally[1] += 1
except AttributeError: # W.T.F
print("Trainee #{} is FUBAR".format(child.find('ID').text))
print('Trainees - Updated: {}, Created: {}'.format(tally[0], tally[1]))
def import_TrainingCatagory(self):
tally = [0, 0]
root = self.parse_xml(self.xml_path('Categories.xml'))
for child in root:
obj, created = models.TrainingCategory.objects.update_or_create(
pk=int(child.find('ID').text),
reference_number=int(child.find('Category_x0020_Number').text),
name=child.find('Category_x0020_Name').text
)
if created:
tally[1] += 1
else:
tally[0] += 1
print('Categories - Updated: {}, Created: {}'.format(tally[0], tally[1]))
def import_TrainingItem(self):
tally = [0, 0]
root = self.parse_xml(self.xml_path('Training Items.xml'))
for child in root:
if child.find('active').text == '0':
active = False
else:
active = True
number = int(child.find('Item_x0020_Number').text)
name = child.find('Item_x0020_Name').text
category = models.TrainingCategory.objects.get(pk=int(child.find('Category_x0020_ID').text))
try:
obj, created = models.TrainingItem.objects.update_or_create(
pk=int(child.find('ID').text),
reference_number=number,
name=name,
category=category,
active=active
)
except IntegrityError:
print("Training Item {}.{} {} has a duplicate reference number".format(category.reference_number, number, name))
if created:
tally[1] += 1
else:
tally[0] += 1
print('Training Items - Updated: {}, Created: {}'.format(tally[0], tally[1]))
def import_TrainingItemQualification(self):
tally = [0, 0, 0]
root = self.parse_xml(self.xml_path('Training Records.xml'))
for child in root:
depths = [("Training_Started", models.TrainingItemQualification.STARTED),
("Training_Complete", models.TrainingItemQualification.COMPLETE),
("Competency_Assessed", models.TrainingItemQualification.PASSED_OUT),]
for (depth, depth_index) in depths:
if child.find('{}_Date'.format(depth)) is not None:
if child.find('{}_Assessor_ID'.format(depth)) is None:
print("Training Record #{} had no supervisor. Assigning System User.".format(child.find('ID').text))
supervisor = Profile.objects.get(first_name="God")
continue
supervisor = Profile.objects.get(pk=self.id_map[child.find('{}_Assessor_ID'.format(depth)).text])
if child.find('Member_ID') is None:
print("Training Record #{} didn't train anybody and has been ignored. Dammit {}".format(child.find('ID').text, supervisor.name))
tally[2] += 1
continue
try:
obj, created = models.TrainingItemQualification.objects.update_or_create(
item=models.TrainingItem.objects.get(pk=int(child.find('Training_Item_ID').text)),
trainee=Profile.objects.get(pk=self.id_map[child.find('Member_ID').text]),
depth=depth_index,
date=child.find('{}_Date'.format(depth)).text[:-9], # Stored as datetime with time as midnight because fuck you I guess
supervisor=supervisor
)
notes = child.find('{}_Notes'.format(depth))
if notes is not None:
obj.notes = notes.text
obj.save()
if created:
tally[1] += 1
else:
tally[0] += 1
except IntegrityError: # Eh?
print("Training Record #{} is probably duplicate. ಠ_ಠ".format(child.find('ID').text))
except AttributeError:
print(child.find('ID').text)
print('Training Item Qualifications - Updated: {}, Created: {}, Broken: {}'.format(tally[0], tally[1], tally[2]))
def import_TrainingLevel(self):
tally = [0, 0]
root = self.parse_xml(self.xml_path('Training Levels.xml'))
for child in root:
name = child.find('Level_x0020_Name').text
if name == "Technical Assistant":
level = models.TrainingLevel.TA
depString = None
elif "Common" in name:
levelString = name.split()[0]
if levelString == "Technician":
level = models.TrainingLevel.TECHNICIAN
elif levelString == "Supervisor":
level = models.TrainingLevel.SUPERVISOR
depString = None
else:
depString = name.split()[-1]
levelString = name.split()[0]
if levelString == "Technician":
level = models.TrainingLevel.TECHNICIAN
elif levelString == "Supervisor":
level = models.TrainingLevel.SUPERVISOR
else:
print(levelString)
continue
for dep in models.TrainingLevel.DEPARTMENTS:
if dep[1] == depString:
department = dep[0]
desc = ""
if child.find('Desc') is not None:
desc = child.find('Desc').text
obj, created = models.TrainingLevel.objects.update_or_create(
pk=int(child.find('ID').text),
description=desc,
level=level
)
if depString is not None:
obj.department = department
obj.save()
if created:
tally[1] += 1
else:
tally[0] += 1
for level in models.TrainingLevel.objects.all():
if level.department is not None:
if level.level == models.TrainingLevel.TECHNICIAN:
level.prerequisite_levels.add(models.TrainingLevel.objects.get(level=models.TrainingLevel.TA), models.TrainingLevel.objects.get(level=models.TrainingLevel.TECHNICIAN, department=None))
elif level.level == models.TrainingLevel.SUPERVISOR:
level.prerequisite_levels.add(models.TrainingLevel.objects.get(level=models.TrainingLevel.TECHNICIAN, department=level.department), models.TrainingLevel.objects.get(level=models.TrainingLevel.SUPERVISOR, department=None))
print('Training Levels - Updated: {}, Created: {}'.format(tally[0], tally[1]))
def import_TrainingLevelQualification(self):
tally = [0, 0]
root = self.parse_xml(self.xml_path('Training Level Records.xml'))
for child in root:
try:
if child.find('Training_x0020_Level_x0020_ID') is None:
print('Training Level Qualification #{} does not qualify in any level. How?'.format(child.find('ID').text))
continue
if child.find('Member_x0020_ID') is None:
print('Training Level Qualification #{} does not qualify anyone. How?!'.format(child.find('ID').text))
continue
obj, created = models.TrainingLevelQualification.objects.update_or_create(
pk=int(child.find('ID').text),
trainee=Profile.objects.get(pk=self.id_map[child.find('Member_x0020_ID').text]),
level=models.TrainingLevel.objects.get(pk=int(child.find('Training_x0020_Level_x0020_ID').text))
)
if child.find('Date_x0020_Level_x0020_Awarded') is not None:
obj.confirmed_on = make_aware(datetime.datetime.strptime(child.find('Date_x0020_Level_x0020_Awarded').text.split('T')[0], "%Y-%m-%d"))
obj.save()
# confirmed by?
if created:
tally[1] += 1
else:
tally[0] += 1
except IntegrityError: # Eh?
print("Training Level Qualification #{} is duplicate. ಠ_ಠ".format(child.find('ID').text))
print('TrainingLevelQualifications - Updated: {}, Created: {}'.format(tally[0], tally[1]))
def import_TrainingLevelRequirements(self):
tally = [0, 0]
root = self.parse_xml(self.xml_path('Training Level Requirements.xml'))
for child in root:
items = child.find('Items').text.split(",")
for item in items:
try:
item = item.split('.')
obj, created = models.TrainingLevelRequirement.objects.update_or_create(
level=models.TrainingLevel.objects.get(
pk=int(
child.find('Level').text)), item=models.TrainingItem.objects.get(
active=True, reference_number=item[1], category=models.TrainingCategory.objects.get(
reference_number=item[0])), depth=int(
child.find('Depth').text))
if created:
tally[1] += 1
else:
tally[0] += 1
except models.TrainingItem.DoesNotExist:
print("Item with number {} does not exist".format(item))
except models.TrainingItem.MultipleObjectsReturned:
print(models.TrainingItem.objects.filter(reference_number=item[1], category=models.TrainingCategory.objects.get(reference_number=item[0])))
print('TrainingLevelRequirements - Updated: {}, Created: {}'.format(tally[0], tally[1]))

View File

@@ -1,80 +0,0 @@
# Generated by Django 3.1.5 on 2021-07-05 22:01
import RIGS.models
import django.contrib.auth.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('RIGS', '0041_auto_20210302_1204'),
]
operations = [
migrations.CreateModel(
name='TrainingCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('reference_number', models.CharField(max_length=3)),
('name', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='TrainingItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('reference_number', models.CharField(max_length=3)),
('name', models.CharField(max_length=50)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='items', to='training.trainingcategory')),
],
),
migrations.CreateModel(
name='TrainingLevel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('department', models.CharField(max_length=50, null=True)),
('level', models.IntegerField(choices=[(0, 'Technical Assistant'), (1, 'Technician'), (2, 'Supervisor')])),
],
bases=(models.Model, RIGS.models.RevisionMixin),
),
migrations.CreateModel(
name='Trainee',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('RIGS.profile',),
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='TrainingLevelQualification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('confirmed_on', models.DateTimeField()),
('confirmed_by', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='confirmer', to='training.trainee')),
('level', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='training.traininglevel')),
('trainee', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='levels', to='training.trainee')),
],
),
migrations.CreateModel(
name='TrainingItemQualification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('depth', models.IntegerField(choices=[(0, 'Training Started'), (1, 'Training Complete'), (2, 'Passed Out')])),
('date', models.DateField()),
('notes', models.TextField(blank=True)),
('item', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='training.trainingitem')),
('supervisor', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='qualifications_granted', to='training.trainee')),
('trainee', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='qualifications_obtained', to='training.trainee')),
],
),
]

View File

@@ -1,42 +0,0 @@
# Generated by Django 3.1.5 on 2021-07-05 23:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('training', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='trainingcategory',
options={'verbose_name_plural': 'Training Categories'},
),
migrations.AddField(
model_name='traininglevel',
name='description',
field=models.CharField(blank=True, max_length=120),
),
migrations.AddField(
model_name='traininglevel',
name='prerequisite_levels',
field=models.ManyToManyField(blank=True, related_name='prerequisites', to='training.TrainingLevel'),
),
migrations.AlterField(
model_name='traininglevel',
name='department',
field=models.IntegerField(choices=[(0, 'Sound'), (1, 'Lighting'), (2, 'Power'), (3, 'Rigging'), (4, 'Haulage')], null=True),
),
migrations.CreateModel(
name='TrainingLevelRequirement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('depth', models.IntegerField(verbose_name=((0, 'Training Started'), (1, 'Training Complete'), (2, 'Passed Out')))),
('item', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='training.trainingitem')),
('level', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='requirements', to='training.traininglevel')),
],
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 3.1.5 on 2021-07-16 00:50
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('training', '0002_auto_20210706_0053'),
]
operations = [
migrations.AlterField(
model_name='traininglevelqualification',
name='confirmed_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='confirmer', to='training.trainee'),
),
migrations.AlterField(
model_name='traininglevelqualification',
name='confirmed_on',
field=models.DateTimeField(null=True),
),
]

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