More optimisation and cleanup (#420)

This commit is contained in:
2021-03-02 11:29:57 +00:00
committed by GitHub
parent 2bf0175786
commit 911336ceec
113 changed files with 6472 additions and 2397 deletions

View File

@@ -10,7 +10,7 @@ from django.http import Http404, HttpResponseRedirect
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.template.loader import get_template
from django.urls import reverse_lazy
from django.urls import reverse
from django.views import generic
from z3c.rml import rml2pdf
@@ -67,12 +67,6 @@ class InvoicePrint(generic.View):
context = {
'object': object,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'invoice': invoice,
'current_user': request.user,
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
@@ -98,8 +92,8 @@ class InvoiceVoid(generic.View):
object.save()
if object.void:
return HttpResponseRedirect(reverse_lazy('invoice_list'))
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk}))
return HttpResponseRedirect(reverse('invoice_list'))
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': object.pk}))
class InvoiceDelete(generic.DeleteView):
@@ -110,14 +104,14 @@ class InvoiceDelete(generic.DeleteView):
obj = self.get_object()
if obj.payment_set.all().count() > 0:
messages.info(self.request, 'To delete an invoice, delete the payments first.')
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk}))
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': obj.pk}))
return super(InvoiceDelete, self).get(pk)
def post(self, request, pk):
obj = self.get_object()
if obj.payment_set.all().count() > 0:
messages.info(self.request, 'To delete an invoice, delete the payments first.')
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk}))
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': obj.pk}))
return super(InvoiceDelete, self).post(pk)
def get_success_url(self):
@@ -172,16 +166,17 @@ class InvoiceWaiting(generic.ListView):
def get_context_data(self, **kwargs):
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
total = 0
for obj in self.get_objects():
objects = self.get_queryset()
for obj in objects:
total += obj.sum_total
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(self.get_objects()), total)
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(objects), total)
return context
def get_queryset(self):
return self.get_objects()
def get_objects(self):
# @todo find a way to select items
# 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
@@ -216,7 +211,7 @@ class InvoiceEvent(generic.View):
invoice.save()
messages.warning(self.request, 'Invoice voided')
return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk}))
return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': invoice.pk}))
class PaymentCreate(generic.CreateView):
@@ -242,7 +237,7 @@ class PaymentCreate(generic.CreateView):
def get_success_url(self):
messages.info(self.request, "location.reload()")
return reverse_lazy('closemodal')
return reverse('closemodal')
class PaymentDelete(generic.DeleteView):

View File

@@ -76,6 +76,9 @@ class EventRiskAssessmentList(generic.ListView):
model = models.RiskAssessment
template_name = 'hs_object_list.html'
def get_queryset(self):
return self.model.objects.order_by('reviewed_at').select_related('event')
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
context['title'] = 'Risk Assessment'
@@ -83,7 +86,6 @@ class EventRiskAssessmentList(generic.ListView):
context['edit'] = 'ra_edit'
context['review'] = 'ra_review'
context['perm'] = 'perms.RIGS.review_riskassessment'
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
return context
@@ -187,7 +189,6 @@ class EventChecklistList(generic.ListView):
context['edit'] = 'ec_edit'
context['review'] = 'ec_review'
context['perm'] = 'perms.RIGS.review_eventchecklist'
context['fields'] = [n.name for n in list(self.model._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
return context
@@ -209,7 +210,7 @@ class HSList(generic.ListView):
template_name = 'hs_list.html'
def get_queryset(self):
return models.Event.objects.all().order_by('-start_date')
return models.Event.objects.all().order_by('-start_date').select_related('riskassessment').prefetch_related('checklists')
def get_context_data(self, **kwargs):
context = super(HSList, self).get_context_data(**kwargs)

View File

@@ -0,0 +1,37 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group
from assets import models
from RIGS import models as rigsmodels
class Command(BaseCommand):
help = 'Deletes testing sample data'
def handle(self, *args, **kwargs):
from django.conf import settings
if not settings.DEBUG:
raise CommandError('You cannot run this command in production')
self.delete_objects(models.AssetCategory)
self.delete_objects(models.AssetStatus)
self.delete_objects(models.Supplier)
self.delete_objects(models.Connector)
self.delete_objects(models.Asset)
self.delete_objects(rigsmodels.VatRate)
self.delete_objects(rigsmodels.Profile)
self.delete_objects(rigsmodels.Person)
self.delete_objects(rigsmodels.Organisation)
self.delete_objects(rigsmodels.Venue)
self.delete_objects(Group)
self.delete_objects(rigsmodels.Event)
self.delete_objects(rigsmodels.EventItem)
self.delete_objects(rigsmodels.Invoice)
self.delete_objects(rigsmodels.Payment)
self.delete_objects(rigsmodels.RiskAssessment)
self.delete_objects(rigsmodels.EventChecklist)
def delete_objects(self, model):
for obj in model.objects.all():
obj.delete()

View File

@@ -1,11 +1,14 @@
from django.core.management import call_command
from django.core.management.base import BaseCommand
from RIGS import models
class Command(BaseCommand):
help = 'Adds sample data to use for testing'
can_import_settings = True
def handle(self, *args, **options):
call_command('generateSampleUserData')
call_command('generateSampleRIGSData')
call_command('generateSampleAssetsData')

View File

@@ -17,11 +17,8 @@ class Command(BaseCommand):
people = []
organisations = []
venues = []
profiles = []
keyholder_group = None
finance_group = None
hs_group = None
events = []
profiles = models.Profile.objects.all()
def handle(self, *args, **options):
from django.conf import settings
@@ -34,20 +31,12 @@ class Command(BaseCommand):
with transaction.atomic():
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
self.setup_people()
self.setup_organisations()
self.setup_venues()
self.setup_events()
self.setupGenericProfiles()
self.setupPeople()
self.setupOrganisations()
self.setupVenues()
self.setupGroups()
self.setupEvents()
self.setupUsefulProfiles()
def setupPeople(self):
def setup_people(self):
names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe",
"Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore",
"Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan",
@@ -62,25 +51,25 @@ class Command(BaseCommand):
"Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa
for i, name in enumerate(names):
with reversion.create_revision():
reversion.set_user(random.choice(self.profiles))
reversion.set_user(random.choice(models.Profile.objects.all()))
person = models.Person.objects.create(name=name)
newPerson = models.Person.objects.create(name=name)
if i % 3 == 0:
newPerson.email = "address@person.com"
person.email = "address@person.com"
if i % 5 == 0:
newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
person.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
if i % 7 == 0:
newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
person.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
if i % 9 == 0:
newPerson.phone = "01234 567894"
person.phone = "01234 567894"
newPerson.save()
self.people.append(newPerson)
person.save()
self.people.append(person)
def setupOrganisations(self):
def setup_organisations(self):
names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars",
"ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc",
"Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp",
@@ -109,27 +98,28 @@ class Command(BaseCommand):
"Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
for i, name in enumerate(names):
with reversion.create_revision():
reversion.set_user(random.choice(self.profiles))
newOrganisation = models.Organisation.objects.create(name=name)
reversion.set_user(random.choice(models.Profile.objects.all()))
new_organisation = models.Organisation.objects.create(name=name)
if i % 2 == 0:
newOrganisation.has_su_account = True
new_organisation.has_su_account = True
if i % 3 == 0:
newOrganisation.email = "address@organisation.com"
new_organisation.email = "address@organisation.com"
if i % 5 == 0:
newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
new_organisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
if i % 7 == 0:
newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
new_organisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
if i % 9 == 0:
newOrganisation.phone = "01234 567894"
new_organisation.phone = "01234 567894"
newOrganisation.save()
self.organisations.append(newOrganisation)
new_organisation.save()
self.organisations.append(new_organisation)
def setupVenues(self):
def setup_venues(self):
names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch",
"The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands",
"The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins",
@@ -145,108 +135,27 @@ class Command(BaseCommand):
for i, name in enumerate(names):
with reversion.create_revision():
reversion.set_user(random.choice(self.profiles))
newVenue = models.Venue.objects.create(name=name)
new_venue = models.Venue.objects.create(name=name)
if i % 2 == 0:
newVenue.three_phase_available = True
new_venue.three_phase_available = True
if i % 3 == 0:
newVenue.email = "address@venue.com"
new_venue.email = "address@venue.com"
if i % 5 == 0:
newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
new_venue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
if i % 7 == 0:
newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
new_venue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
if i % 9 == 0:
newVenue.phone = "01234 567894"
new_venue.phone = "01234 567894"
newVenue.save()
self.venues.append(newVenue)
new_venue.save()
self.venues.append(new_venue)
def setupGroups(self):
self.keyholder_group = Group.objects.create(name='Keyholders')
self.finance_group = Group.objects.create(name='Finance')
self.hs_group = Group.objects.create(name='H&S')
keyholderPerms = ["add_event", "change_event", "view_event",
"add_eventitem", "change_eventitem", "delete_eventitem",
"add_organisation", "change_organisation", "view_organisation",
"add_person", "change_person", "view_person", "view_profile",
"add_venue", "change_venue", "view_venue",
"add_asset", "change_asset", "delete_asset",
"view_asset", "view_supplier", "change_supplier", "asset_finance",
"add_supplier", "view_cabletype", "change_cabletype",
"add_cabletype", "view_eventchecklist", "change_eventchecklist",
"add_eventchecklist", "view_riskassessment", "change_riskassessment",
"add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew",
"delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle",
"change_eventchecklistvehicle",
"delete_eventchecklistvehicle", "view_eventchecklistvehicle", ]
financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
"add_payment", "change_payment", "delete_payment"]
hsPerms = keyholderPerms + ["review_riskassessment", "review_eventchecklist"]
for permId in keyholderPerms:
self.keyholder_group.permissions.add(Permission.objects.get(codename=permId))
for permId in financePerms:
self.finance_group.permissions.add(Permission.objects.get(codename=permId))
for permId in hsPerms:
self.hs_group.permissions.add(Permission.objects.get(codename=permId))
def setupGenericProfiles(self):
names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble",
"Jack Harkness", "Mickey Smith", "Rose Tyler"]
for i, name in enumerate(names):
newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0],
last_name=name.split(" ")[-1],
email=name.replace(" ", "") + "@example.com",
initials="".join([j[0].upper() for j in name.split()]))
if i % 2 == 0:
newProfile.phone = "01234 567894"
newProfile.save()
self.profiles.append(newProfile)
def setupUsefulProfiles(self):
superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User",
initials="SU",
email="superuser@example.com", is_superuser=True, is_active=True,
is_staff=True)
superUser.set_password('superuser')
superUser.save()
financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User",
initials="FU",
email="financeuser@example.com", is_active=True, is_approved=True)
financeUser.groups.add(self.finance_group)
financeUser.groups.add(self.keyholder_group)
financeUser.set_password('finance')
financeUser.save()
hsUser = models.Profile.objects.create(username="hs", first_name="HS", last_name="User",
initials="HSU",
email="hsuser@example.com", is_active=True, is_approved=True)
hsUser.groups.add(self.hs_group)
hsUser.groups.add(self.keyholder_group)
hsUser.set_password('hs')
hsUser.save()
keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User",
initials="KU",
email="keyholderuser@example.com", is_active=True, is_approved=True)
keyholderUser.groups.add(self.keyholder_group)
keyholderUser.set_password('keyholder')
keyholderUser.save()
basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU",
email="basicuser@example.com", is_active=True, is_approved=True)
basicUser.set_password('basic')
basicUser.save()
def setupEvents(self):
def setup_events(self):
names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball",
"Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event",
"End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show",
@@ -257,7 +166,7 @@ class Command(BaseCommand):
notes = ["The client came into the office at some point", "Who knows if this will happen",
"Probably should check this event", "Maybe not happening", "Run away!"]
itemOptions = [
item_options = [
{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2,
'cost': 200.00},
{'name': 'Projector',
@@ -274,7 +183,7 @@ class Command(BaseCommand):
{'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
{'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
dayDelta = -120 # start adding events from 4 months ago
day_delta = -120 # start adding events from 4 months ago
for i in range(150): # Let's add 100 events
with reversion.create_revision():
@@ -282,70 +191,71 @@ class Command(BaseCommand):
name = names[i % len(names)]
startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
dayDelta = dayDelta + random.randint(0, 3)
start_date = datetime.date.today() + datetime.timedelta(days=day_delta)
day_delta = day_delta + random.randint(0, 3)
newEvent = models.Event.objects.create(name=name, start_date=startDate)
new_event = models.Event.objects.create(name=name, start_date=start_date)
if random.randint(0, 2) > 1: # 1 in 3 have a start time
newEvent.start_time = datetime.time(random.randint(15, 20))
new_event.start_time = datetime.time(random.randint(15, 20))
if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day
newEvent.end_time = datetime.time(random.randint(21, 23))
new_event.end_time = datetime.time(random.randint(21, 23))
elif random.randint(0, 1) > 0: # half of the others finish early the next day
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1)
newEvent.end_time = datetime.time(random.randint(0, 5))
new_event.end_date = new_event.start_date + datetime.timedelta(days=1)
new_event.end_time = datetime.time(random.randint(0, 5))
elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead
newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4))
new_event.end_date = new_event.start_date + datetime.timedelta(days=random.randint(1, 4))
if random.randint(0, 6) > 0: # 5 in 6 have MIC
newEvent.mic = random.choice(self.profiles)
new_event.mic = random.choice(self.profiles)
if random.randint(0, 6) > 0: # 5 in 6 have organisation
newEvent.organisation = random.choice(self.organisations)
new_event.organisation = random.choice(self.organisations)
if random.randint(0, 6) > 0: # 5 in 6 have person
newEvent.person = random.choice(self.people)
new_event.person = random.choice(self.people)
if random.randint(0, 6) > 0: # 5 in 6 have venue
newEvent.venue = random.choice(self.venues)
new_event.venue = random.choice(self.venues)
# Could have any status, equally weighted
newEvent.status = random.choice(
new_event.status = random.choice(
[models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED])
newEvent.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
new_event.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire
if random.randint(0, 1) > 0: # 1 in 2 have description
newEvent.description = random.choice(descriptions)
new_event.description = random.choice(descriptions)
if random.randint(0, 1) > 0: # 1 in 2 have notes
newEvent.notes = random.choice(notes)
new_event.notes = random.choice(notes)
newEvent.save()
new_event.save()
# Now add some items
for j in range(random.randint(1, 5)):
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
newItem.save()
item_data = item_options[random.randint(0, len(item_options) - 1)]
new_item = models.EventItem.objects.create(event=new_event, order=j, **item_data)
new_item.save()
while newEvent.sum_total < 0:
itemData = itemOptions[random.randint(0, len(itemOptions) - 1)]
newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData)
newItem.save()
while new_event.sum_total < 0:
item_data = item_options[random.randint(0, len(item_options) - 1)]
new_item = models.EventItem.objects.create(event=new_event, order=j, **item_data)
new_item.save()
with reversion.create_revision():
reversion.set_user(random.choice(self.profiles))
if newEvent.start_date < datetime.date.today(): # think about adding an invoice
if new_event.start_date < datetime.date.today(): # think about adding an invoice
if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury
newInvoice = models.Invoice.objects.create(event=newEvent)
if newEvent.status is models.Event.CANCELLED: # void cancelled events
newInvoice.void = True
new_invoice = models.Invoice.objects.create(event=new_event)
if new_event.status is models.Event.CANCELLED: # void cancelled events
new_invoice.void = True
elif random.randint(0, 2) > 1: # 1 in 3 have been paid
models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance,
models.Payment.objects.create(invoice=new_invoice, amount=new_invoice.balance,
date=datetime.date.today())
if i == 1 or random.randint(0, 5) > 0: # Event 1 and 1 in 5 have a RA
models.RiskAssessment.objects.create(event=newEvent, supervisor_consulted=bool(random.getrandbits(1)), nonstandard_equipment=bool(random.getrandbits(1)),
models.RiskAssessment.objects.create(event=new_event, supervisor_consulted=bool(random.getrandbits(1)),
nonstandard_equipment=bool(random.getrandbits(1)),
nonstandard_use=bool(random.getrandbits(1)),
contractors=bool(random.getrandbits(1)),
other_companies=bool(random.getrandbits(1)),
@@ -366,8 +276,15 @@ class Command(BaseCommand):
suspended_structures=bool(random.getrandbits(1)),
outside=bool(random.getrandbits(1)))
if i == 0 or random.randint(0, 1) > 0: # Event 1 and 1 in 10 have a Checklist
models.EventChecklist.objects.create(event=newEvent, power_mic=random.choice(self.profiles), safe_parking=bool(random.getrandbits(1)),
safe_packing=bool(random.getrandbits(1)), exits=bool(random.getrandbits(1)), trip_hazard=bool(random.getrandbits(1)), warning_signs=bool(random.getrandbits(1)),
ear_plugs=bool(random.getrandbits(1)), hs_location="Locked away safely",
extinguishers_location="Somewhere, I forgot", earthing=bool(random.getrandbits(1)), pat=bool(random.getrandbits(1)),
models.EventChecklist.objects.create(event=new_event, power_mic=random.choice(self.profiles),
safe_parking=bool(random.getrandbits(1)),
safe_packing=bool(random.getrandbits(1)),
exits=bool(random.getrandbits(1)),
trip_hazard=bool(random.getrandbits(1)),
warning_signs=bool(random.getrandbits(1)),
ear_plugs=bool(random.getrandbits(1)),
hs_location="Locked away safely",
extinguishers_location="Somewhere, I forgot",
earthing=bool(random.getrandbits(1)),
pat=bool(random.getrandbits(1)),
date=timezone.now(), venue=random.choice(self.venues))

View File

@@ -1,5 +1,5 @@
# Generated by Django 2.0.13 on 2020-01-11 18:29
# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved
# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved
from django.db import migrations
def approve_legacy(apps, schema_editor):
@@ -15,5 +15,5 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RunPython(approve_legacy)
migrations.RunPython(approve_legacy, 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

@@ -0,0 +1,199 @@
# Generated by Django 3.1.5 on 2021-02-08 16:03
import RIGS.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0040_profile_dark_theme'),
]
operations = [
migrations.AlterField(
model_name='event',
name='auth_request_to',
field=models.EmailField(blank=True, default='', max_length=254),
),
migrations.AlterField(
model_name='event',
name='collector',
field=models.CharField(blank=True, default='', max_length=255, verbose_name='collected by'),
),
migrations.AlterField(
model_name='event',
name='description',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='event',
name='meet_info',
field=models.CharField(blank=True, default='', max_length=255),
),
migrations.AlterField(
model_name='event',
name='notes',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='event',
name='payment_method',
field=models.CharField(blank=True, default='', max_length=255),
),
migrations.AlterField(
model_name='event',
name='payment_received',
field=models.CharField(blank=True, default='', max_length=255),
),
migrations.AlterField(
model_name='event',
name='purchase_order',
field=models.CharField(blank=True, default='', max_length=255, verbose_name='PO'),
),
migrations.AlterField(
model_name='eventauthorisation',
name='account_code',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.AlterField(
model_name='eventauthorisation',
name='uni_id',
field=models.CharField(blank=True, default='', max_length=10, verbose_name='University ID'),
),
migrations.AlterField(
model_name='eventchecklist',
name='extinguishers_location',
field=models.CharField(blank=True, default='', help_text='Location of fire extinguishers', max_length=255),
),
migrations.AlterField(
model_name='eventchecklist',
name='hs_location',
field=models.CharField(blank=True, default='', help_text='Location of Safety Bag/Box', max_length=255),
),
migrations.AlterField(
model_name='eventchecklist',
name='w1_description',
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
),
migrations.AlterField(
model_name='eventchecklist',
name='w2_description',
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
),
migrations.AlterField(
model_name='eventchecklist',
name='w3_description',
field=models.CharField(blank=True, default='', help_text='Description', max_length=255),
),
migrations.AlterField(
model_name='eventitem',
name='description',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='organisation',
name='address',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='organisation',
name='email',
field=models.EmailField(blank=True, default='', max_length=254),
),
migrations.AlterField(
model_name='organisation',
name='notes',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='organisation',
name='phone',
field=models.CharField(blank=True, default='', max_length=15),
),
migrations.AlterField(
model_name='payment',
name='method',
field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('SU', 'SU Core'), ('T', 'TEC Adjustment')], default='', max_length=2),
),
migrations.AlterField(
model_name='person',
name='address',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='person',
name='email',
field=models.EmailField(blank=True, default='', max_length=254),
),
migrations.AlterField(
model_name='person',
name='notes',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='person',
name='phone',
field=models.CharField(blank=True, default='', max_length=15),
),
migrations.AlterField(
model_name='profile',
name='api_key',
field=models.CharField(blank=True, default='', editable=False, max_length=40),
),
migrations.AlterField(
model_name='profile',
name='phone',
field=models.CharField(default='', max_length=13, null=True),
),
migrations.AlterField(
model_name='riskassessment',
name='general_notes',
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
),
migrations.AlterField(
model_name='riskassessment',
name='persons_responsible_structures',
field=models.TextField(blank=True, default='', help_text='Who are the persons on site responsible for their use?'),
),
migrations.AlterField(
model_name='riskassessment',
name='power_notes',
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
),
migrations.AlterField(
model_name='riskassessment',
name='power_plan',
field=models.URLField(blank=True, default='', help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[RIGS.models.validate_url]),
),
migrations.AlterField(
model_name='riskassessment',
name='rigging_plan',
field=models.URLField(blank=True, default='', help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[RIGS.models.validate_url]),
),
migrations.AlterField(
model_name='riskassessment',
name='sound_notes',
field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'),
),
migrations.AlterField(
model_name='venue',
name='address',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='venue',
name='email',
field=models.EmailField(blank=True, default='', max_length=254),
),
migrations.AlterField(
model_name='venue',
name='notes',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='venue',
name='phone',
field=models.CharField(blank=True, default='', max_length=15),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.7 on 2021-03-02 11:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RIGS', '0041_auto_20210208_1603'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='phone',
field=models.CharField(blank=True, default='', max_length=13),
),
]

View File

@@ -12,7 +12,7 @@ from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse_lazy
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
from reversion import revisions as reversion
@@ -21,11 +21,12 @@ from reversion.models import Version
class Profile(AbstractUser):
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
phone = models.CharField(max_length=13, null=True, blank=True)
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
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)
last_emailed = models.DateTimeField(blank=True,
null=True) # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
# Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that...
last_emailed = models.DateTimeField(blank=True, null=True)
dark_theme = models.BooleanField(default=False)
@classmethod
def make_api_key(cls):
@@ -51,7 +52,7 @@ class Profile(AbstractUser):
@property
def latest_events(self):
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
return self.event_mic.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic', 'riskassessment', 'invoice').prefetch_related('checklists')
@classmethod
def admins(cls):
@@ -102,12 +103,12 @@ class RevisionMixin(object):
class Person(models.Model, RevisionMixin):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=15, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
phone = models.CharField(max_length=15, blank=True, default='')
email = models.EmailField(blank=True, default='')
address = models.TextField(blank=True, null=True)
address = models.TextField(blank=True, default='')
notes = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, default='')
def __str__(self):
string = self.name
@@ -133,17 +134,17 @@ class Person(models.Model, RevisionMixin):
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
def get_absolute_url(self):
return reverse_lazy('person_detail', kwargs={'pk': self.pk})
return reverse('person_detail', kwargs={'pk': self.pk})
class Organisation(models.Model, RevisionMixin):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=15, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
phone = models.CharField(max_length=15, blank=True, default='')
email = models.EmailField(blank=True, default='')
address = models.TextField(blank=True, null=True)
address = models.TextField(blank=True, default='')
notes = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, default='')
union_account = models.BooleanField(default=False)
def __str__(self):
@@ -170,7 +171,7 @@ class Organisation(models.Model, RevisionMixin):
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
def get_absolute_url(self):
return reverse_lazy('organisation_detail', kwargs={'pk': self.pk})
return reverse('organisation_detail', kwargs={'pk': self.pk})
class VatManager(models.Manager):
@@ -178,7 +179,6 @@ class VatManager(models.Manager):
return self.find_rate(timezone.now())
def find_rate(self, date):
# return self.filter(startAt__lte=date).latest()
try:
return self.filter(start_at__lte=date).latest()
except VatRate.DoesNotExist:
@@ -211,12 +211,12 @@ class VatRate(models.Model, RevisionMixin):
class Venue(models.Model, RevisionMixin):
name = models.CharField(max_length=255)
phone = models.CharField(max_length=15, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
phone = models.CharField(max_length=15, blank=True, default='')
email = models.EmailField(blank=True, default='')
three_phase_available = models.BooleanField(default=False)
notes = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, default='')
address = models.TextField(blank=True, null=True)
address = models.TextField(blank=True, default='')
def __str__(self):
string = self.name
@@ -229,24 +229,23 @@ class Venue(models.Model, RevisionMixin):
return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic')
def get_absolute_url(self):
return reverse_lazy('venue_detail', kwargs={'pk': self.pk})
return reverse('venue_detail', kwargs={'pk': self.pk})
class EventManager(models.Manager):
def current_events(self):
events = self.filter(
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q(
(models.Q(start_date__gte=timezone.now(), end_date__isnull=True, dry_hire=False) & ~models.Q(
status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(
status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(
(models.Q(dry_hire=True, start_date__gte=timezone.now()) & ~models.Q(
status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
models.Q(status=Event.CANCELLED, start_date__gte=timezone.now().date()) # Canceled but not started
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
'organisation',
'venue', 'mic')
models.Q(status=Event.CANCELLED, start_date__gte=timezone.now()) # Canceled but not started
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
return events
def events_in_bounds(self, start, end):
@@ -269,12 +268,12 @@ class EventManager(models.Manager):
def rig_count(self):
event_count = self.filter(
(models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False,
(models.Q(start_date__gte=timezone.now(), end_date__isnull=True, dry_hire=False,
is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q(
(models.Q(end_date__gte=timezone.now(), dry_hire=False, is_rig=True) & ~models.Q(
status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q(
(models.Q(dry_hire=True, start_date__gte=timezone.now(), is_rig=True) & ~models.Q(
status=Event.CANCELLED)) # Active dry hire
).count()
return event_count
@@ -298,8 +297,8 @@ class Event(models.Model, RevisionMixin):
person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE)
organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE)
venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE)
description = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, default='')
notes = models.TextField(blank=True, default='')
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
dry_hire = models.BooleanField(default=False)
is_rig = models.BooleanField(default=True)
@@ -313,7 +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, 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,
@@ -322,15 +321,15 @@ class Event(models.Model, RevisionMixin):
verbose_name="MIC", on_delete=models.CASCADE)
# Monies
payment_method = models.CharField(max_length=255, blank=True, null=True)
payment_received = models.CharField(max_length=255, blank=True, null=True)
purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO')
collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by')
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')
# Authorisation request details
auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE)
auth_request_at = models.DateTimeField(null=True, blank=True)
auth_request_to = models.EmailField(null=True, blank=True)
auth_request_to = models.EmailField(blank=True, default='')
@property
def display_id(self):
@@ -346,7 +345,7 @@ class Event(models.Model, RevisionMixin):
@property
def sum_total(self):
total = EventItem.objects.filter(event=self).aggregate(
total = self.items.aggregate(
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
output_field=models.DecimalField(max_digits=10, decimal_places=2))
)['sum_total']
@@ -456,7 +455,7 @@ class Event(models.Model, RevisionMixin):
objects = EventManager()
def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.pk})
return reverse('event_detail', kwargs={'pk': self.pk})
def __str__(self):
return "{}: {}".format(self.display_id, self.name)
@@ -490,7 +489,7 @@ class Event(models.Model, RevisionMixin):
class EventItem(models.Model, RevisionMixin):
event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, default='')
quantity = models.IntegerField()
cost = models.DecimalField(max_digits=10, decimal_places=2)
order = models.IntegerField()
@@ -505,7 +504,7 @@ class EventItem(models.Model, RevisionMixin):
ordering = ['order']
def __str__(self):
return str(self.event.pk) + "." + str(self.order) + ": " + self.event.name + " | " + self.name
return "{}.{}: {} | {}".format(self.event_id, self.order, self.event.name, self.name)
@property
def activity_feed_string(self):
@@ -517,13 +516,13 @@ class EventAuthorisation(models.Model, RevisionMixin):
event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE)
email = models.EmailField()
name = models.CharField(max_length=255)
uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID")
account_code = models.CharField(max_length=50, blank=True, null=True)
uni_id = models.CharField(max_length=10, blank=True, default='', verbose_name="University ID")
account_code = models.CharField(max_length=50, default='', blank=True)
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount")
sent_by = models.ForeignKey('Profile', on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'pk': self.event.pk})
return reverse('event_detail', kwargs={'pk': self.event_id})
@property
def activity_feed_string(self):
@@ -562,11 +561,11 @@ class Invoice(models.Model, RevisionMixin):
return self.balance == 0 or self.void
def get_absolute_url(self):
return reverse_lazy('invoice_detail', kwargs={'pk': self.pk})
return reverse('invoice_detail', kwargs={'pk': self.pk})
@property
def activity_feed_string(self):
return "#{} for Event {}".format(self.display_id, "N%05d" % self.event.pk)
return "#{} for Event {}".format(self.display_id, self.event.display_id)
def __str__(self):
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
@@ -597,7 +596,7 @@ class Payment(models.Model, RevisionMixin):
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
date = models.DateField()
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)
method = models.CharField(max_length=2, choices=METHODS, default='', blank=True)
reversion_hide = True
@@ -632,10 +631,9 @@ class RiskAssessment(models.Model, RevisionMixin):
contractors = models.BooleanField(help_text="Are you using any external contractors?<br><small>i.e. Freelancers/Crewing Companies</small>")
other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?<br><small>e.g. TEC is providing the lighting while another company does sound</small>")
crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?")
general_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
general_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
# Power
# event_size = models.IntegerField(blank=True, null=True, choices=SIZES)
big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?")
# If yes to the above two, you must answer...
power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True,
@@ -645,12 +643,12 @@ class RiskAssessment(models.Model, RevisionMixin):
other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?")
nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?")
multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?")
power_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
power_plan = models.URLField(blank=True, null=True, help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
power_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
power_plan = models.URLField(blank=True, default='', help_text="Upload your power plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
# Sound
noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?")
sound_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
sound_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?")
# Site
known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?")
@@ -663,8 +661,8 @@ class RiskAssessment(models.Model, RevisionMixin):
# Structures
special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?")
suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?")
persons_responsible_structures = models.TextField(blank=True, null=True, help_text="Who are the persons on site responsible for their use?")
rigging_plan = models.URLField(blank=True, null=True, help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
persons_responsible_structures = models.TextField(blank=True, default='', help_text="Who are the persons on site responsible for their use?")
rigging_plan = models.URLField(blank=True, default='', help_text="Upload your rigging plan to the <a href='https://nottinghamtec.sharepoint.com/'>Sharepoint</a> and submit a link", validators=[validate_url])
# Blimey that was a lot of options
@@ -708,6 +706,10 @@ class RiskAssessment(models.Model, RevisionMixin):
('review_riskassessment', 'Can review Risk Assessments')
]
@cached_property
def fieldz(self):
return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
@property
def event_size(self):
# Confirm event size. Check all except generators, since generators entails outside
@@ -723,7 +725,7 @@ class RiskAssessment(models.Model, RevisionMixin):
return str(self.event)
def get_absolute_url(self):
return reverse_lazy('ra_detail', kwargs={'pk': self.pk})
return reverse('ra_detail', kwargs={'pk': self.pk})
def __str__(self):
return "%i - %s" % (self.pk, self.event)
@@ -746,8 +748,8 @@ class EventChecklist(models.Model, RevisionMixin):
trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?")
warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?<br><small>(strobe, smoke, power etc.)</small>")
ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?")
hs_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of Safety Bag/Box")
extinguishers_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of fire extinguishers")
hs_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of Safety Bag/Box")
extinguishers_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of fire extinguishers")
# Small Electrical Checks
rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?")
@@ -768,15 +770,15 @@ class EventChecklist(models.Model, RevisionMixin):
fd_earth_fault = models.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current")
# Worst case points
w1_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w1_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.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w2_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w2_description = models.CharField(blank=True, default='', max_length=255, help_text="Description")
w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?")
w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage")
w2_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
w3_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description")
w3_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.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (Z<small>S</small>)")
@@ -796,12 +798,16 @@ class EventChecklist(models.Model, RevisionMixin):
('review_eventchecklist', 'Can review Event Checklists')
]
@cached_property
def fieldz(self):
return [n.name for n in list(self._meta.get_fields()) if n.name != 'reviewed_at' and n.name != 'reviewed_by' and not n.is_relation and not n.auto_created]
@property
def activity_feed_string(self):
return str(self.event)
def get_absolute_url(self):
return reverse_lazy('ec_detail', kwargs={'pk': self.pk})
return reverse('ec_detail', kwargs={'pk': self.pk})
def __str__(self):
return "%i - %s" % (self.pk, self.event)

View File

@@ -11,7 +11,7 @@ import simplejson
from PyPDF2 import PdfFileMerger, PdfFileReader
from django.conf import settings
from django.contrib import messages
from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles import finders
from django.core import signing
from django.core.exceptions import SuspiciousOperation
from django.core.mail import EmailMultiAlternatives
@@ -27,6 +27,7 @@ from django.views import generic
from z3c.rml import rml2pdf
from PyRIGS import decorators
from PyRIGS.views import OEmbedView, is_ajax
from RIGS import models, forms
__author__ = 'ghost'
@@ -40,7 +41,7 @@ class RigboardIndex(generic.TemplateView):
context = super(RigboardIndex, self).get_context_data(**kwargs)
# call out method to get current events
context['events'] = models.Event.objects.current_events()
context['events'] = models.Event.objects.current_events().select_related('riskassessment', 'invoice').prefetch_related('checklists')
context['page_title'] = "Rigboard"
return context
@@ -59,29 +60,24 @@ class EventDetail(generic.DetailView):
template_name = 'event_detail.html'
model = models.Event
class EventOembed(generic.View):
model = models.Event
def get(self, request, pk=None):
embed_url = reverse('event_embed', args=[pk])
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url)
data = {
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url),
'version': '1.0',
'type': 'rich',
'height': '250'
}
json = simplejson.JSONEncoderForHTML().encode(data)
return HttpResponse(json, content_type="application/json")
def get_context_data(self, **kwargs):
context = super(EventDetail, self).get_context_data(**kwargs)
title = "{} | {}".format(self.object.display_id, self.object.name)
if self.object.dry_hire:
title += " <span class='badge badge-secondary'>Dry Hire</span>"
context['page_title'] = title
return context
class EventEmbed(EventDetail):
template_name = 'event_embed.html'
class EventOEmbed(OEmbedView):
model = models.Event
url_name = 'event_embed'
class EventCreate(generic.CreateView):
model = models.Event
form_class = forms.EventForm
@@ -157,7 +153,7 @@ class EventDuplicate(EventUpdate):
new.checked_in_by = None
# Remove all the authorisation information from the new event
new.auth_request_to = None
new.auth_request_to = ''
new.auth_request_by = None
new.auth_request_at = None
@@ -185,15 +181,9 @@ class EventPrint(generic.View):
context = {
'object': object,
'fonts': {
'opensans': {
'regular': 'static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'static/fonts/OPENSANS-BOLD.TTF',
}
},
'quote': True,
'current_user': request.user,
'filename': 'Event {} {} {}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
'filename': 'Event_{}_{}_{}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date)
}
rml = template.render(context)
@@ -284,6 +274,7 @@ class EventArchive(generic.ListView):
class EventAuthorise(generic.UpdateView):
template_name = 'eventauthorisation_form.html'
success_template = 'eventauthorisation_success.html'
preview = False
def form_valid(self, form):
self.object = form.save()
@@ -311,6 +302,7 @@ class EventAuthorise(generic.UpdateView):
context['page_title'] = "{}: {}".format(self.event.display_id, self.event.name)
if self.event.dry_hire:
context['page_title'] += ' <span class="badge badge-secondary align-top">Dry Hire</span>'
context['preview'] = self.preview
return context
def get(self, request, *args, **kwargs):
@@ -359,7 +351,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
return self.get_object()
def get_success_url(self):
if self.request.is_ajax():
if is_ajax(self.request):
url = reverse_lazy('closemodal')
messages.info(self.request, "location.reload()")
else:
@@ -397,7 +389,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
to=[email],
reply_to=[self.request.user.email],
)
css = staticfiles_storage.path('css/email.css')
css = finders.find('css/email.css')
html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context),
external_styles=css).transform()
msg.attach_alternative(html, 'text/html')
@@ -412,8 +404,7 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
model = models.Event
def render_to_response(self, context, **response_kwargs):
from django.contrib.staticfiles.storage import staticfiles_storage
css = staticfiles_storage.path('css/email.css')
css = finders.find('css/email.css')
response = super(EventAuthoriseRequestEmailPreview, self).render_to_response(context, **response_kwargs)
assert isinstance(response, HttpResponse)
response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
@@ -427,4 +418,5 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
'sent_by': self.request.user.pk,
})
context['to_name'] = self.request.GET.get('to_name', None)
context['target'] = 'event_authorise_form_preview'
return context

View File

@@ -6,7 +6,7 @@ from io import BytesIO
from PyPDF2 import PdfFileReader, PdfFileMerger
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles import finders
from django.core.cache import cache
from django.core.mail import EmailMessage, EmailMultiAlternatives
from django.db.models.signals import post_save
@@ -25,12 +25,6 @@ def send_eventauthorisation_success_email(instance):
# Generate PDF first to prevent context conflicts
context = {
'object': instance.event,
'fonts': {
'opensans': {
'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF',
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'receipt': True,
'current_user': False,
}
@@ -69,7 +63,7 @@ def send_eventauthorisation_success_email(instance):
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
)
css = staticfiles_storage.path('css/email.css')
css = finders.find('css/email.css')
html = Premailer(get_template("eventauthorisation_client_success.html").render(context),
external_styles=css).transform()
client_email.attach_alternative(html, 'text/html')
@@ -127,7 +121,7 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
to=[admin.email],
reply_to=[user.email],
)
css = staticfiles_storage.path('css/email.css')
css = finders.find('css/email.css')
html = Premailer(get_template("admin_awaiting_approval.html").render(context),
external_styles=css).transform()
email.attach_alternative(html, 'text/html')

BIN
RIGS/static/imgs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
RIGS/static/imgs/logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -74,6 +74,7 @@
{% endblock %}
{% block js %}
{{ block.super }}
<script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/popover.js' %}"></script>
<script>

View File

@@ -4,12 +4,12 @@
{% block title %}Calendar{% endblock %}
{% block css %}
<link href="{% static 'css/main.min.css' %}" rel='stylesheet' />
<link href="{% static 'css/main.css' %}" rel='stylesheet' />
{% endblock %}
{% block js %}
<script src="{% static 'js/moment.js' %}"></script>
<script src="{% static 'js/main.min.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
<script>
viewToUrl = {
'timeGridWeek':'week',

View File

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

View File

@@ -7,24 +7,18 @@
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/>
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
<script src="{% static 'js/selects.js' %}"></script>
{% endblock %}
{% block js %}
{{ block.super }}
<script src="{% static 'js/jquery-ui.js' %}"></script><!--TODO optimise-->
<script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/modal.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/autocompleter.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
{% include 'partials/datetime-fix.html' %}
@@ -134,14 +128,14 @@
<tbody id="vehiclest" data-pk="-1">
<tr id="vehicles_new" style="display: none;">
<td><input type="text" class="form-control" name="vehicle_new" disabled="true"/></td>
<td><select class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" name="driver_new" disabled="true"></select></td>
<td><select data-container="body" class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" name="driver_new" disabled="true"></select></td>
<td><button type="button" class="btn btn-danger btn-sm mt-1" data-action='delete' data-target='#vehicle'><span class="fas fa-times"></span></button></td>
</tr>
{% for i in object.vehicles.all %}
<tr id="vehicles_{{i.pk}}">
<td><input name="vehicle_{{i.pk}}" type="text" class="form-control" value="{{ i.vehicle }}"/></td>
<td>
<select name="driver_{{i.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
<select data-container="body" name="driver_{{i.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if i.driver != '' %}
<option value="{{i.driver.pk}}" selected="selected">{{ i.driver.name }}</option>
{% endif %}
@@ -202,7 +196,7 @@
<tbody id="crewmemberst" data-pk="-1">
<tr id="crew_new" style="display: none;">
<td>
<select name="crewmember_new" class="form-control" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" disabled="true"></select>
<select name="crewmember_new" class="form-control" data-container="body" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials" disabled="true"></select>
</td>
<td style="min-width: 15ch"><input name="start_new" type="datetime-local" class="form-control" value="{{ i.start }}" disabled=""/></td>
<td style="min-width: 15ch"><input name="role_new" type="text" class="form-control" value="{{ i.role }}" disabled="true"/></td>
@@ -212,7 +206,7 @@
{% for crew in object.crew.all %}
<tr id="crew_{{crew.pk}}">
<td>
<select name="crewmember_{{crew.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
<select data-container="body" name="crewmember_{{crew.pk}}" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='profile' %}?fields=first_name,last_name,initials">
{% if crew.crewmember != '' %}
<option value="{{crew.crewmember.pk}}" selected="selected">{{ crew.crewmember.name }}</option>
{% endif %}

View File

@@ -2,20 +2,12 @@
{% load linkornone from filters %}
{% load namewithnotes from filters %}
{% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %}
{% block content %}
<div class="row my-3 py-3">
{% if not request.is_ajax %}
<div class="col-sm-12">
<h1>
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
| {{ object.name }} {% if event.dry_hire %}<span class="badge badge-secondary">Dry Hire</span>{% endif %}
</h1>
</div>
{% if perms.RIGS.view_event %}
<div class="col-sm-12 text-right">
{% include 'event_detail_buttons.html' %}
{% include 'partials/event_detail_buttons.html' %}
</div>
{% endif %}
{% endif %}
@@ -85,7 +77,7 @@
{% endif %}
{% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right">
{% include 'event_detail_buttons.html' %}
{% include 'partials/event_detail_buttons.html' %}
</div>
{% endif %}
{% if event.is_rig %}
@@ -105,7 +97,7 @@
</div>
{% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right">
{% include 'event_detail_buttons.html' %}
{% include 'partials/event_detail_buttons.html' %}
</div>
{% endif %}
{% endif %}

View File

@@ -1,100 +1,89 @@
{% extends 'base_embed.html' %}
{% load static %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<a href="/">
<span class="source"> R<small>ig</small> I<small>nformation</small> G<small>athering</small> S<small>ystem</small></span>
</a>
</div>
{% block extra-head %}
<link href="{% static 'fontawesome_free/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'fontawesome_free/css/solid.css' %}" rel="stylesheet" type="text/css">
{% endblock %}
<div class="col-sm-12">
<span class="pull-right">
{% block content %}
<span class="float-right">
{% if object.mic %}
<div class="text-center">
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo rounded"/>
</div>
<div class="text-center">
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo rounded"/>
</div>
{% elif object.is_rig %}
<span class="fas fa-exclamation-sign"></span>
<span class="fas fa-exclamation-sign"></span>
{% endif %}
</span>
<h3>
<a href="{% url 'event_detail' object.pk %}">
{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %}
| {{ object.name }} </a>
{% if object.venue %}
<small>at {{ object.venue }}</small>
{% endif %}
<br/><small>
<h3>
<a href="{% url 'event_detail' object.pk %}">{{ object.display_id }} | {{ object.name }}</a>
{% if object.venue %}
<small>at {{ object.venue }}</small>
{% endif %}
<br/><small>
{{ object.start_date|date:"D d/m/Y" }}
{% if object.has_start_time %}
{{ object.start_time|date:"H:i" }}
{{ object.start_time|date:"H:i" }}
{% endif %}
{% if object.end_date or object.has_end_time %}
&ndash;
&ndash;
{% endif %}
{% if object.end_date and object.end_date != object.start_date %}
{{ object.end_date|date:"D d/m/Y" }}
{{ object.end_date|date:"D d/m/Y" }}
{% endif %}
{% if object.has_end_time %}
{{ object.end_time|date:"H:i" }}
{{ object.end_time|date:"H:i" }}
{% endif %}
</small>
</h3>
<div class="row">
<div class="col-xs-6">
<p>
<strong>Status:</strong>
{{ object.get_status_display }}
</p>
<p>
{% if object.is_rig %}
<strong>Client:</strong> {{ object.person.name }}
{% if object.organisation %}
for {{ object.organisation.name }}
{% endif %}
{% if object.dry_hire %}(Dry Hire){% endif %}
{% else %}
<strong>Non-Rig</strong>
{% endif %}
</p>
<p>
<strong>MIC:</strong>
{% if object.mic %}
{{object.mic.name}}
{% else %}
None
{% endif %}
</p>
</div>
<div class="col-xs-6">
{% if object.meet_at %}
<p>
<strong>Crew meet:</strong>
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
</p>
{% endif %}
{% if object.access_at %}
<p>
<strong>Access at:</strong>
{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
</p>
{% endif %}
<p>
<strong>Last updated:</strong>
{{ object.last_edited_at }} by "{{ object.last_edited_by.initials }}"
</p>
</div>
</div>
{% if object.description %}
</h3>
{% include 'partials/event_status.html' %}
<div class="row ml-2">
<div class="col-xs-6 pr-2">
<p>
<strong>Description: </strong>
{{ object.description|linebreaksbr }}
{% if object.is_rig %}
<strong>Client:</strong> {{ object.person.name }}
{% if object.organisation %}
for {{ object.organisation.name }}
{% endif %}
{% if object.dry_hire %}(Dry Hire){% endif %}
{% else %}
<strong>Non-Rig</strong>
{% endif %}
</p>
{% endif %}
<p>
<strong>MIC:</strong>
{% if object.mic %}
{{object.mic.name}}
{% else %}
None
{% endif %}
</p>
</div>
<div class="col-xs-6 px-2">
{% if object.meet_at %}
<p>
<strong>Crew meet:</strong>
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
</p>
{% endif %}
{% if object.access_at %}
<p>
<strong>Access at:</strong>
{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
</p>
{% endif %}
<p>
<strong>Last updated:</strong>
{{ object.last_edited_at }} by "{{ object.last_edited_by.initials }}"
</p>
</div>
</div>
</div>
{% if object.description %}
<p>
<strong>Description: </strong>
{{ object.description|linebreaksbr }}
</p>
{% endif %}
{% endblock %}

View File

@@ -7,25 +7,19 @@
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/flatpickr.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
<script src="{% static 'js/selects.js' %}"></script>
{% endblock %}
{% block js %}
{{ block.super }}
<script src="{% static 'js/jquery-ui.js' %}"></script><!--TODO optimise--->
<script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/modal.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/autocompleter.js' %}"></script>
<script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
{% include 'partials/datetime-fix.html' %}
@@ -79,7 +73,7 @@
{% block content %}
{% include 'item_modal.html' %}
<form class=" itemised_form" role="form" method="POST">
<form class="itemised_form" role="form" method="POST">
{% csrf_token %}
<div class="row">
<div class="col-12">

View File

@@ -1,12 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
{% load multiply from filters %}
{% load static %}
<!DOCTYPE document SYSTEM "rml.dtd">
<document filename="{{filename}}">
<docinit>
<registerTTFont faceName="OpenSans" fileName="{{ fonts.opensans.regular }}"/>
<registerTTFont faceName="OpenSans-Bold" fileName="{{ fonts.opensans.bold }}"/>
<registerTTFont faceName="OpenSans" fileName="static/fonts/OpenSans-Regular.tff"/>
<registerTTFont faceName="OpenSans-Bold" fileName="static/fonts/OpenSans-Bold.tff"/>
<registerFontFamily name="OpenSans" bold="OpenSans-Bold" boldItalic="OpenSans-Bold"/>
</docinit>
@@ -82,11 +79,11 @@
<template > {# Note: page is 595x842 points (1 point=1/72in) #}
<pageTemplate id="Headed" >
<pageGraphics>
<image file="RIGS/static/imgs/paperwork/corner-tr-su.jpg" x="395" y="642" height="200" width="200"/>
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
<image file="static/imgs/paperwork/corner-tr-su.jpg" x="395" y="642" height="200" width="200"/>
<image file="static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
{# logo positioned 42 from left, 33 from top #}
<image file="RIGS/static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
<image file="static/imgs/paperwork/tec-logo.jpg" x="42" y="719" height="90" width="84"/>
<setFont name="OpenSans-Bold" size="22.5" leading="10"/>
<drawString x="137" y="780">TEC PA &amp; Lighting</drawString>
@@ -110,8 +107,8 @@
<pageTemplate id="Main">
<pageGraphics>
<image file="RIGS/static/imgs/paperwork/corner-tr.jpg" x="395" y="642" height="200" width="200"/>
<image file="RIGS/static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
<image file="static/imgs/paperwork/corner-tr.jpg" x="395" y="642" height="200" width="200"/>
<image file="static/imgs/paperwork/corner-bl.jpg" x="0" y="0" height="200" width="200"/>
<setFont name="OpenSans" size="10"/>
<drawCenteredString x="302.5" y="38">[Page <pageNumber/> of <getName id="lastPage" default="0" />]</drawCenteredString>

View File

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

View File

@@ -1,10 +1,10 @@
{% extends 'base_client_email.html' %}
{% block content %}
<p>Hi {{ to_name|default:"there" }},</p>
<p><b>{{ request.user.get_full_name }}</b> has requested that you authorise <b>N{{ object.pk|stringformat:"05d" }}
<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>{{ object.person.name }}</b>{% endif %}.</p>
<p>
@@ -23,7 +23,7 @@
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="button" align="center">
<a href="{{ request.scheme }}://{{ request.get_host }}{% url 'event_authorise' object.pk hmac %}">
<a href="{{ request.scheme }}://{{ request.get_host }}{% url target|default:'event_authorise' object.pk hmac %}">
Complete Authorisation Form
</a>
</td>

View File

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

View File

@@ -30,14 +30,6 @@
{% render_field form.email type="email" class+="form-control" %}
</div>
</div>
<div class="text-right col-sm-3 offset-sm-9">
<div class="form-group">
<button type="submit" class="form-control btn btn-primary">
<i class="fas fa-paper-plane"></i>
Send
</button>
</div>
</div>
</form>
</div>
</div>
@@ -48,3 +40,14 @@
});
</script>
{% endblock %}
{% block footer %}
<div class="form-row">
<div class="btn-group" role="group">
<a type="button" target="_blank" href="{% url 'event_authorise_preview' object.pk %}" class="btn btn-info text-nowrap"><span class="fas fa-drafting-compass"></span> Preview</a>
<button type="submit" class="form-control btn btn-primary" form="auth-request-form">
<span class="fas fa-paper-plane"></span> Send
</button>
</div>
</div>
{% endblock %}

View File

@@ -21,7 +21,7 @@
<th scope="col">Event</th>
{# mmm hax #}
{% if object_list.0 != None %}
{% for field in fields %}
{% for field in object_list.0.fieldz %}
<th scope="col">{{ object_list.0|verbose_name:field|title }}</th>
{% endfor %}
{% endif %}
@@ -33,7 +33,7 @@
<tr class="{% if object.reviewed_by %}table-success{%endif%}">
{# General #}
<th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a></th>
{% for field in fields %}
{% for field in object_list.0.fieldz %}
<td>{{ object|get_field:field }}</td>
{% endfor %}
{# Buttons #}

View File

@@ -42,7 +42,7 @@
<td>{{ invoice.event.start_date }}</td>
<td>{{ invoice.invoice_date }}</td>
<td>
{{ invoice.balance|floatformat:2 }}
£{{ invoice.balance|floatformat:2 }}
{% if not invoice.event.internal %}
<br />
<span class="text-muted">{{ invoice.event.purchase_order }}</span>

View File

@@ -53,7 +53,7 @@
{% endif %}
</td>
<td>
{{ event.sum_total|floatformat:2 }}
£{{ event.sum_total|floatformat:2 }}
<br />
<span class="text-muted">{% if not event.internal %}{{ event.purchase_order }}{% endif %}</span>
</td>

View File

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

View File

@@ -9,7 +9,7 @@
{% if event.internal %}
<a class="btn item-add modal-href event-authorise-request
{% if event.authorised %}
btn-success
btn-success active
{% elif event.authorisation and event.authorisation.amount != event.total and event.authorisation.last_edited_at > event.auth_request_at %}
btn-warning
{% elif event.auth_request_to %}
@@ -19,7 +19,7 @@
{% endif %}
"
href="{% url 'event_authorise_request' object.pk %}">
<i class="fas fa-paper-plane"></i>
<span class="fas fa-paper-plane"></span>
<span class="d-none d-sm-inline">
{% if event.authorised %}
Authorised

View File

@@ -20,15 +20,7 @@
{% if event.is_rig %}
<dt class="col-sm-6">Event MIC</dt>
<dd class="col-sm-6">
{% if event.mic and perms.RIGS.view_profile %}
<a href="{% url 'profile_detail' event.mic.pk %}" class="modal-href">
{{ event.mic.name }}
</a>
{% else %}
{{ event.mic.name }}
{% endif %}
</dd>
<dd class="col-sm-6">{% include 'partials/linked_name.html' with profile=event.mic %}</dd>
{% endif %}
<dt class="col-sm-6">Status</dt>
@@ -71,7 +63,7 @@
{% if event.dry_hire %}
<dt class="col-sm-6">Checked In By</dt>
<dd class="col-sm-6">{{ object.checked_in_by.name }}</dd>
<dd class="col-sm-6">{% include 'partials/linked_name.html' with profile=event.checked_in_by %}</dd>
{% endif %}
{% if event.is_rig %}

View File

@@ -1,12 +1,14 @@
<h5>
<div>
<span class="badge badge-{% if event.confirmed %}success{% elif event.cancelled %}dark{% else %}warning{% endif %}">Status: {{ event.get_status_display }}</span>
{% if event.is_rig %}
{% if event.purchase_order %}
<span class="badge badge-success">PO: {{ event.purchase_order }}</span>
{% elif event.authorised %}
<span class="badge badge-success">Authorisation: Complete <span class="fas fa-check"></span></span>
{% else %}
<span class="badge badge-danger">Authorisation: <span class="fas fa-times"></span></span>
{% if event.sum_total > 0 %}
{% if event.purchase_order %}
<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>
{% else %}
<span class="badge badge-danger">Authorisation: <span class="fas fa-times"></span></span>
{% endif %}
{% endif %}
{% if not event.dry_hire %}
{% if event.riskassessment %}
@@ -14,8 +16,6 @@
{% else %}
<span class="badge badge-danger">RA: <span class="fas fa-times"></span></span>
{% endif %}
{% else %}
<span class="badge badge-secondary">RA: N/A</span>
{% endif %}
{% if not event.dry_hire %}
{% if event.hs_done %}
@@ -24,8 +24,6 @@
{% else %}
<span class="badge badge-danger">Checklist: <span class="fas fa-times"></span></span>
{% endif %}
{% else %}
<span class="badge badge-secondary">Checklist: N/A</span>
{% endif %}
{% if perms.RIGS.view_invoice %}
{% if event.invoice %}
@@ -41,4 +39,4 @@
{% endif %}
{% endif %}
{% endif %}
</h5>
</div>

View File

@@ -25,7 +25,7 @@
{% endif %}
{% else %}
table-warning
{% endif %}" id="event_row">
{% endif %}" {% if event.cancelled %}style="opacity: 50% !important;"{% endif %} id="event_row">
<!---Number-->
<th scope="row" id="event_number">{{ event.display_id }}</th>
<!--Dates & Times-->

View File

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

View File

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

View File

@@ -47,7 +47,7 @@
<dd class="col-sm-6">
{{ object.big_power|yesnoi:'invert' }}
</dd>
<dt class="col-sm-6">{{ object|help_text:'power_mic' }}</dt>
<dt class="col-sm-6">{{ object|help_text:'power_mic'|safe }}</dt>
<dd class="col-sm-6">
{{ object.power_mic.name|default:'None' }}
</dd>

View File

@@ -6,24 +6,18 @@
{% block css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/ajax-bootstrap-select.css' %}"/>
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/>
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/bootstrap-select.js' %}"></script>
<script src="{% static 'js/ajax-bootstrap-select.js' %}"></script>
<script src="{% static 'js/selects.js' %}" async></script>
{% endblock %}
{% block js %}
{{ block.super }}
<script src="{% static 'js/jquery-ui.js' %}"></script><!--TODO optimise--->
<script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/modal.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
<script src="{% static 'js/autocompleter.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
<script>
function parseBool(str) {

View File

@@ -4,7 +4,7 @@ from django.forms.forms import NON_FIELD_ERRORS
from django.forms.utils import ErrorDict
from django.template.defaultfilters import stringfilter
from django.template.defaultfilters import yesno, title, truncatewords
from django.urls import reverse_lazy
from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import SafeData, mark_safe
from django.utils.text import normalize_newlines
@@ -173,7 +173,7 @@ def title_spaced(string):
@register.filter(needs_autoescape=True)
def namewithnotes(obj, url, autoescape=True):
if hasattr(obj, 'notes') and obj.notes is not None and len(obj.notes) > 0:
return mark_safe(obj.name + " <a href='{}'><span class='far fa-sticky-note'></span></a>".format(reverse_lazy(url, kwargs={'pk': obj.pk})))
return mark_safe(obj.name + " <a href='{}'><span class='fas fa-sticky-note'></span></a>".format(reverse(url, kwargs={'pk': obj.pk})))
else:
return obj.name

108
RIGS/tests/conftest.py Normal file
View File

@@ -0,0 +1,108 @@
from RIGS import models
import pytest
from django.utils import timezone
@pytest.fixture
def basic_event(db):
event = models.Event.objects.create(name="TE E1", start_date=timezone.now())
yield event
event.delete()
@pytest.fixture
def ra(basic_event, admin_user):
ra = models.RiskAssessment.objects.create(event=basic_event, nonstandard_equipment=False, nonstandard_use=False,
contractors=False, other_companies=False, crew_fatigue=False,
big_power=False, power_mic=admin_user, generators=False,
other_companies_power=False, nonstandard_equipment_power=False,
multiple_electrical_environments=False, noise_monitoring=False,
known_venue=True, safe_loading=True, safe_storage=True,
area_outside_of_control=True, barrier_required=True,
nonstandard_emergency_procedure=True, special_structures=False,
suspended_structures=False, outside=False)
yield ra
ra.delete()
@pytest.fixture
def medium_ra(ra):
ra.big_power = True
ra.save()
yield ra
ra.big_power = False
ra.save()
@pytest.fixture
def venue(db):
venue = models.Venue.objects.create(name="Venue 1")
yield venue
venue.delete()
@pytest.fixture # TODO parameterise with Event sizes
def checklist(basic_event, venue, admin_user, ra):
checklist = models.EventChecklist.objects.create(event=basic_event, power_mic=admin_user, safe_parking=False,
safe_packing=False, exits=False, trip_hazard=False, warning_signs=False,
ear_plugs=False, hs_location="Locked away safely",
extinguishers_location="Somewhere, I forgot", earthing=False, pat=False,
date=timezone.now(), venue=venue)
yield checklist
checklist.delete()
@pytest.fixture
def many_events(db, admin_user, scope="class"):
many_events = {
# produce 7 normal events - 5 current
1: models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6),
description="start future no end"),
2: models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end"),
3: models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(),
description="start today with end today"),
4: models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end"),
5: models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21',
description="start past with end past"),
6: models.Event.objects.create(name="TE E6", start_date=date.today() - timedelta(days=2),
end_date=date.today() + timedelta(days=2),
description="start past, end future"),
7: models.Event.objects.create(name="TE E7", start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2),
description="start + end in future"),
# 2 cancelled - 1 current
8: models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled in future"),
9: models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled and started"),
# 5 dry hire - 3 current
10: models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True,
description="dryhire today"),
11: models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True,
checked_in_by=admin_user,
description="dryhire today, checked in"),
12: models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True,
status=models.Event.BOOKED, description="dryhire past"),
13: models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
checked_in_by=admin_user, description="dryhire past checked in"),
14: models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True,
status=models.Event.CANCELLED, description="dryhire today cancelled"),
# 4 non rig - 3 current
15: models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False,
description="non rig today"),
16: models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False,
description="non rig tomorrow"),
17: models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False,
description="non rig yesterday"),
18: models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False,
status=models.Event.CANCELLED,
description="non rig today cancelled"),
}
yield many_events
for event in many_events:
event.delete()

View File

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

View File

@@ -8,54 +8,12 @@ from django.http import HttpResponseBadRequest
from django.test import TestCase
from django.urls import reverse
import PyRIGS.tests.base
from RIGS import models
from pytest_django.asserts import assertContains, assertNotContains
class BaseCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
cls.profile = models.Profile.objects.get_or_create(
first_name='Test',
last_name='TEC User',
username='eventauthtest',
email='teccie@functional.test',
is_superuser=True # lazily grant all permissions
)[0]
def setUp(self):
super().setUp()
self.profile.set_password('testuser')
self.profile.save()
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
venue = models.Venue.objects.create(name='Authorisation Test Venue')
client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test')
organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=True)
self.event = models.Event.objects.create(
name='Authorisation Test',
start_date=date.today(),
venue=venue,
person=client,
organisation=organisation,
)
class TestEventValidation(BaseCase):
def test_create(self):
url = reverse('event_create')
# end time before start access after start
response = self.client.post(url, {'start_date': datetime.date(2020, 1, 1), 'start_time': datetime.time(10, 00),
'end_time': datetime.time(9, 00),
'access_at': datetime.datetime(2020, 1, 5, 10)})
self.assertFormError(response, 'form', 'end_time',
"Unless you've invented time travel, the event can't finish before it has started.")
self.assertFormError(response, 'form', 'access_at',
"Regardless of what some clients might think, access time cannot be after the event has started.")
from pytest_django.asserts import assertContains, assertNotContains, assertFormError
def setup_event():
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
venue = models.Venue.objects.create(name='Authorisation Test Venue')
client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test')
organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=True)
@@ -84,6 +42,18 @@ def setup_mail(event, profile):
return auth_data, hmac, url
def test_create(admin_client):
url = reverse('event_create')
# end time before start access after start
response = admin_client.post(url, {'start_date': datetime.date(2020, 1, 1), 'start_time': datetime.time(10, 00),
'end_time': datetime.time(9, 00),
'access_at': datetime.datetime(2020, 1, 5, 10)})
assertFormError(response, 'form', 'end_time',
"Unless you've invented time travel, the event can't finish before it has started.")
assertFormError(response, 'form', 'access_at',
"Regardless of what some clients might think, access time cannot be after the event has started.")
def test_requires_valid_hmac(client, admin_user):
event = setup_event()
auth_data, hmac, url = setup_mail(event, admin_user)
@@ -138,7 +108,7 @@ def test_duplicate_warning(client, admin_user):
assertContains(response, 'amount has changed')
@pytest.mark.django_db(transaction=True)
@pytest.mark.django_db
def test_email_sent(admin_client, admin_user, mailoutbox):
event = setup_event()
auth_data, hmac, url = setup_mail(event, admin_user)
@@ -152,36 +122,36 @@ def test_email_sent(admin_client, admin_user, mailoutbox):
assert mailoutbox[1].to == [settings.AUTHORISATION_NOTIFICATION_ADDRESS]
class TECEventAuthorisationTest(BaseCase):
def setUp(self):
super().setUp()
self.url = reverse('event_authorise_request', kwargs={'pk': self.event.pk})
def test_email_check(admin_client, admin_user):
event = setup_event()
url = reverse('event_authorise_request', kwargs={'pk': event.pk})
admin_user.email = 'teccie@someotherdomain.com'
admin_user.save()
def test_email_check(self):
self.profile.email = 'teccie@someotherdomain.com'
self.profile.save()
response = admin_client.post(url)
response = self.client.post(self.url)
assertContains(response, 'must have an @nottinghamtec.co.uk email address')
self.assertContains(response, 'must have an @nottinghamtec.co.uk email address')
def test_request_send(self):
self.profile.email = 'teccie@nottinghamtec.co.uk'
self.profile.save()
response = self.client.post(self.url)
self.assertContains(response, 'This field is required.')
def test_request_send(admin_client, admin_user):
event = setup_event()
url = reverse('event_authorise_request', kwargs={'pk': event.pk})
admin_user.email = 'teccie@nottinghamtec.co.uk'
admin_user.save()
response = admin_client.post(url)
assertContains(response, 'This field is required.')
mail.outbox = []
mail.outbox = []
response = self.client.post(self.url, {'email': 'client@functional.test'})
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
self.assertIn('client@functional.test', email.to)
self.assertIn('/event/%d/' % (self.event.pk), email.body)
response = admin_client.post(url, {'email': 'client@functional.test'})
assert response.status_code == 302
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert 'client@functional.test' in email.to
assert '/event/%d/' % event.pk in email.body
# Check sent by details are populated
self.event.refresh_from_db()
self.assertEqual(self.event.auth_request_by, self.profile)
self.assertEqual(self.event.auth_request_to, 'client@functional.test')
self.assertIsNotNone(self.event.auth_request_at)
# Check sent by details are populated
event.refresh_from_db()
assert event.auth_request_by == admin_user
assert event.auth_request_to == 'client@functional.test'
assert event.auth_request_at is not None

View File

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

View File

@@ -2,6 +2,7 @@ from datetime import date, timedelta, datetime, time
from decimal import *
import pytz
import pytest
from django.conf import settings
from django.test import TestCase
from reversion import revisions as reversion
@@ -9,110 +10,56 @@ from reversion import revisions as reversion
from RIGS import models
class ProfileTestCase(TestCase):
def test_str(self):
profile = models.Profile(first_name='Test', last_name='Case')
self.assertEqual(str(profile), 'Test Case')
profile.initials = 'TC'
self.assertEqual(str(profile), 'Test Case "TC"')
def assert_decimal_equality(d1, d2):
assert float(d1) == pytest.approx(float(d2))
class VatRateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.rates = {
0: models.VatRate.objects.create(start_at='2014-03-01', rate=0.20, comment='test1'),
1: models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2'),
}
def test_find_correct(self):
r = models.VatRate.objects.find_rate('2015-03-01')
self.assertEqual(r, self.rates[0])
r = models.VatRate.objects.find_rate('2016-03-01')
self.assertEqual(r, self.rates[1])
def test_percent_correct(self):
self.assertEqual(self.rates[0].as_percent, 20)
def test_str():
profile = models.Profile(first_name='Test', last_name='Case')
assert str(profile) == 'Test Case'
profile.initials = 'TC'
assert str(profile) == 'Test Case "TC"'
class EventTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.all_events = set(range(1, 18))
cls.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
cls.not_current_events = set(cls.all_events) - set(cls.current_events)
@pytest.mark.django_db
def test_find_correct(vat_rate):
new_rate = models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2')
r = models.VatRate.objects.find_rate('2015-03-01')
assert_decimal_equality(r.rate, vat_rate.rate)
r = models.VatRate.objects.find_rate('2016-03-01')
assert_decimal_equality(r.rate, new_rate.rate)
cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com")
cls.events = {
# produce 7 normal events - 5 current
1: models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6),
description="start future no end"),
2: models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end"),
3: models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(),
description="start today with end today"),
4: models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end"),
5: models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21',
description="start past with end past"),
6: models.Event.objects.create(name="TE E6", start_date=date.today() - timedelta(days=2),
end_date=date.today() + timedelta(days=2),
description="start past, end future"),
7: models.Event.objects.create(name="TE E7", start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2),
description="start + end in future"),
def test_percent_correct(vat_rate):
assert vat_rate.as_percent == 20
# 2 cancelled - 1 current
8: models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled in future"),
9: models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled and started"),
# 5 dry hire - 3 current
10: models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True,
description="dryhire today"),
11: models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True,
checked_in_by=cls.profile,
description="dryhire today, checked in"),
12: models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True,
status=models.Event.BOOKED, description="dryhire past"),
13: models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
checked_in_by=cls.profile, description="dryhire past checked in"),
14: models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True,
status=models.Event.CANCELLED, description="dryhire today cancelled"),
def test_related_vatrate(basic_event, vat_rate):
assert_decimal_equality(vat_rate.rate, basic_event.vat_rate.rate)
# 4 non rig - 3 current
15: models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False,
description="non rig today"),
16: models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False,
description="non rig tomorrow"),
17: models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False,
description="non rig yesterday"),
18: models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False,
status=models.Event.CANCELLED,
description="non rig today cancelled"),
}
def test_count(self):
# Santiy check we have the expected events created
self.assertEqual(models.Event.objects.count(), 18, "Incorrect number of events, check setup")
class EventTest():
def test_count(many_events):
# Sanity check we have the expected events created
assert models.Event.objects.count() == 18
def test_rig_count(self):
def test_rig_count(many_events):
# Changed to not include unreturned dry hires in rig count
self.assertEqual(models.Event.objects.rig_count(), 7)
assert models.Event.objects.rig_count() == 7
def test_current_events(self):
def test_current_events(many_events):
all_events = set(range(1, 18))
current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
not_current_events = set(cls.all_events) - set(cls.current_events)
current_events = models.Event.objects.current_events()
self.assertEqual(len(current_events), len(self.current_events))
for eid in self.current_events:
self.assertIn(models.Event.objects.get(name="TE E%d" % eid), current_events)
assert len(current_events) == len(self.current_events)
for eid in current_events:
assert models.Event.objects.get(name="TE E%d" % eid) in current_events
for eid in self.not_current_events:
self.assertNotIn(models.Event.objects.get(name="TE E%d" % eid), current_events)
for eid in not_current_events:
assert models.Event.objects.get(name="TE E%d" % eid) not in current_events
def test_related_venue(self):
def test_related(many_events):
v1 = models.Venue.objects.create(name="TE V1")
v2 = models.Venue.objects.create(name="TE V2")
@@ -127,16 +74,13 @@ class EventTestCase(TestCase):
e2.append(event)
event.save()
self.assertCountEqual(e1, v1.latest_events)
self.assertCountEqual(e2, v2.latest_events)
assert set(e1) == set(v1.latest_events)
assert set(e2) == set(v2.latest_events)
# Cleanup
v1.delete()
v2.delete()
for (key, event) in self.events.items():
event.venue = None
def test_related_vatrate(self):
self.assertEqual(self.vatrate, models.Event.objects.all()[0].vat_rate)
def test_related_person(self):
def test_related_person(many_events):
p1 = models.Person.objects.create(name="TE P1")
p2 = models.Person.objects.create(name="TE P2")
@@ -151,13 +95,13 @@ class EventTestCase(TestCase):
e2.append(event)
event.save()
self.assertCountEqual(e1, p1.latest_events)
self.assertCountEqual(e2, p2.latest_events)
assert set(e1) == set(p1.latest_events)
assert set(e2) == set(p2.latest_events)
for (key, event) in self.events.items():
event.person = None
p1.delete()
p2.delete()
def test_related_organisation(self):
def test_related_organisation(many_events):
o1 = models.Organisation.objects.create(name="TE O1")
o2 = models.Organisation.objects.create(name="TE O2")
@@ -172,13 +116,13 @@ class EventTestCase(TestCase):
e2.append(event)
event.save()
self.assertCountEqual(e1, o1.latest_events)
self.assertCountEqual(e2, o2.latest_events)
assert set(e1) == set(o1.latest_events)
assert set(e1) == set(o2.latest_events)
for (key, event) in self.events.items():
event.organisation = None
def test_organisation_person_join(self):
def test_organisation_person_join(many_events):
p1 = models.Person.objects.create(name="TE P1")
p2 = models.Person.objects.create(name="TE P2")
o1 = models.Organisation.objects.create(name="TE O1")
@@ -202,105 +146,109 @@ class EventTestCase(TestCase):
events = models.Event.objects.all()
# Check person's organisations
self.assertIn((o1, 2), p1.organisations)
self.assertIn((o2, 1), p1.organisations)
self.assertIn((o1, 2), p2.organisations)
self.assertEqual(len(p2.organisations), 1)
assert (o1, 2) in p1.organisations
assert (o2, 1) in p1.organisations
assert (o1, 2) in p2.organisations
assert len(p2.organisations) == 1
# Check organisation's persons
self.assertIn((p1, 2), o1.persons)
self.assertIn((p2, 2), o1.persons)
self.assertIn((p1, 1), o2.persons)
self.assertEqual(len(o2.persons), 1)
assert (p1, 2) in o1.persons
assert (p2, 2) in o1.persons
assert (p1, 1) in o2.persons
assert len(o2.persons) == 1
def test_cancelled_property(self):
edit = self.events[1]
def test_cancelled_property(many_events):
edit = many_events[1]
edit.status = models.Event.CANCELLED
edit.save()
event = models.Event.objects.get(pk=edit.pk)
self.assertEqual(event.status, models.Event.CANCELLED)
self.assertTrue(event.cancelled)
assert event.status == models.Event.CANCELLED
assert event.cancelled
event.status = models.Event.PROVISIONAL
event.save()
def test_confirmed_property(self):
edit = self.events[1]
def test_confirmed_property(many_events):
edit = many_events[1]
edit.status = models.Event.CONFIRMED
edit.save()
event = models.Event.objects.get(pk=edit.pk)
self.assertEqual(event.status, models.Event.CONFIRMED)
self.assertTrue(event.confirmed)
assert event.status == models.Event.CONFIRMED
assert event.confirmed
event.status = models.Event.PROVISIONAL
event.save()
def test_earliest_time(self):
event = models.Event(name="TE ET", start_date=date(2016, 0o1, 0o1))
# Just a start date
self.assertEqual(event.earliest_time, date(2016, 0o1, 0o1))
def test_earliest_time():
event = models.Event(name="TE ET", start_date=date(2016, 0o1, 0o1))
# With start time
event.start_time = time(9, 00)
self.assertEqual(event.earliest_time, self.create_datetime(2016, 1, 1, 9, 00))
# Just a start date
assert event.earliest_time == date(2016, 0o1, 0o1)
# With access time
event.access_at = self.create_datetime(2015, 12, 0o3, 9, 57)
self.assertEqual(event.earliest_time, event.access_at)
# With start time
event.start_time = time(9, 00)
assert event.earliest_time == create_datetime(2016, 1, 1, 9, 00)
# With meet time
event.meet_at = self.create_datetime(2015, 12, 0o3, 9, 55)
self.assertEqual(event.earliest_time, event.meet_at)
# With access time
event.access_at = create_datetime(2015, 12, 0o3, 9, 57)
assert event.earliest_time == event.access_at
# Check order isn't important
event.start_date = date(2015, 12, 0o3)
self.assertEqual(event.earliest_time, self.create_datetime(2015, 12, 0o3, 9, 00))
# With meet time
event.meet_at = create_datetime(2015, 12, 0o3, 9, 55)
assert event.earliest_time == event.meet_at
def test_latest_time(self):
event = models.Event(name="TE LT", start_date=date(2016, 0o1, 0o1))
# Check order isn't important
event.start_date = date(2015, 12, 0o3)
assert event.earliest_time == create_datetime(2015, 12, 0o3, 9, 00)
# Just start date
self.assertEqual(event.latest_time, event.start_date)
# Just end date
event.end_date = date(2016, 1, 2)
self.assertEqual(event.latest_time, event.end_date)
def test_latest_time():
event = models.Event(name="TE LT", start_date=date(2016, 0o1, 0o1))
# With end time
event.end_time = time(23, 00)
self.assertEqual(event.latest_time, self.create_datetime(2016, 1, 2, 23, 00))
# Just start date
assert event.latest_time == event.start_date
def test_in_bounds(self):
manager = models.Event.objects
events = [
manager.create(name="TE IB0", start_date='2016-01-02'), # yes no
manager.create(name="TE IB1", start_date='2015-12-31', end_date='2016-01-04'),
# Just end date
event.end_date = date(2016, 1, 2)
assert event.latest_time == event.end_date
# basic checks
manager.create(name='TE IB2', start_date='2016-01-02', end_date='2016-01-04'),
manager.create(name='TE IB3', start_date='2015-12-31', end_date='2016-01-03'),
manager.create(name='TE IB4', start_date='2016-01-04',
access_at=self.create_datetime(2016, 0o1, 0o3, 00, 00)),
manager.create(name='TE IB5', start_date='2016-01-04',
meet_at=self.create_datetime(2016, 0o1, 0o2, 00, 00)),
# With end time
event.end_time = time(23, 00)
assert event.latest_time == create_datetime(2016, 1, 2, 23, 00)
# negative check
manager.create(name='TE IB6', start_date='2015-12-31', end_date='2016-01-01'),
]
in_bounds = manager.events_in_bounds(self.create_datetime(2016, 1, 2, 0, 0),
self.create_datetime(2016, 1, 3, 0, 0))
self.assertIn(events[0], in_bounds)
self.assertIn(events[1], in_bounds)
self.assertIn(events[2], in_bounds)
self.assertIn(events[3], in_bounds)
self.assertIn(events[4], in_bounds)
self.assertIn(events[5], in_bounds)
def test_in_bounds():
manager = models.Event.objects
events = [
manager.create(name="TE IB0", start_date='2016-01-02'), # yes no
manager.create(name="TE IB1", start_date='2015-12-31', end_date='2016-01-04'),
self.assertNotIn(events[6], in_bounds)
# basic checks
manager.create(name='TE IB2', start_date='2016-01-02', end_date='2016-01-04'),
manager.create(name='TE IB3', start_date='2015-12-31', end_date='2016-01-03'),
manager.create(name='TE IB4', start_date='2016-01-04',
access_at=create_datetime(2016, 0o1, 0o3, 00, 00)),
manager.create(name='TE IB5', start_date='2016-01-04',
meet_at=create_datetime(2016, 0o1, 0o2, 00, 00)),
def create_datetime(self, year, month, day, hour, min):
tz = pytz.timezone(settings.TIME_ZONE)
return tz.localize(datetime(year, month, day, hour, min))
# negative check
manager.create(name='TE IB6', start_date='2015-12-31', end_date='2016-01-01'),
]
in_bounds = manager.events_in_bounds(create_datetime(2016, 1, 2, 0, 0),
create_datetime(2016, 1, 3, 0, 0))
assert events[0] in in_bounds
assert events[1], in_bounds
assert events[2], in_bounds
assert events[3], in_bounds
assert events[4], in_bounds
assert events[5], in_bounds
assert events[6] not in in_bounds
def create_datetime(year, month, day, hour, minute):
tz = pytz.timezone(settings.TIME_ZONE)
return tz.localize(datetime(year, month, day, hour, minute))
class EventItemTestCase(TestCase):
@@ -331,7 +279,6 @@ class EventItemTestCase(TestCase):
class EventPricingTestCase(TestCase):
def setUp(self):
models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01')
models.VatRate.objects.create(rate=0.10, comment="TP V2", start_at=date.today() - timedelta(days=1))
self.e1 = models.Event.objects.create(name="TP E1", start_date=date.today() - timedelta(days=2))
self.e2 = models.Event.objects.create(name="TP E2", start_date=date.today())
@@ -364,7 +311,6 @@ class EventPricingTestCase(TestCase):
class EventAuthorisationTestCase(TestCase):
def setUp(self):
models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01')
self.profile = models.Profile.objects.get_or_create(
first_name='Test',
last_name='TEC User',

View File

@@ -1,23 +1,25 @@
from datetime import date
from django.core.exceptions import ObjectDoesNotExist
from django.core.management import call_command
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from pytest_django.asserts import assertRedirects, assertNotContains, assertContains
from PyRIGS.tests.base import assert_times_equal
from PyRIGS.tests.base import assert_times_almost_equal, assert_oembed, login
from RIGS import models
import pytest
pytestmark = pytest.mark.django_db
class TestAdminMergeObjects(TestCase):
@classmethod
def setUpTestData(cls):
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
is_active=True, is_staff=True)
cls.persons = {
1: models.Person.objects.create(name="Person 1"),
2: models.Person.objects.create(name="Person 2"),
@@ -168,9 +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())
@@ -201,7 +200,7 @@ class TestInvoiceDelete(TestCase):
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[2].pk))
# Actually delete it
response = self.client.post(request_url, follow=True)
self.client.post(request_url, follow=True)
# Check the invoice is deleted
self.assertRaises(ObjectDoesNotExist, models.Invoice.objects.get, pk=self.invoices[2].pk)
@@ -216,7 +215,7 @@ class TestInvoiceDelete(TestCase):
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
# Try to actually delete it
response = self.client.post(request_url, follow=True)
self.client.post(request_url, follow=True)
# Check this didn't work
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
@@ -227,9 +226,6 @@ class TestPrintPaperwork(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(),
description="This is an event description\nthat for a very specific reason spans two lines."),
@@ -257,102 +253,50 @@ class TestPrintPaperwork(TestCase):
self.assertEqual(response.status_code, 200)
class TestEmbeddedViews(TestCase):
@classmethod
def setUpTestData(cls):
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
is_active=True, is_staff=True)
def test_login_redirect(client, django_user_model):
request_url = reverse('event_embed', kwargs={'pk': 1})
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
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())
}
# Request the page and check it redirects
response = client.get(request_url, follow=True)
assertRedirects(response, expected_url, status_code=302, target_status_code=200)
cls.invoices = {
1: models.Invoice.objects.create(event=cls.events[1]),
2: models.Invoice.objects.create(event=cls.events[2])
}
# Now login
login(client, django_user_model)
cls.payments = {
1: models.Payment.objects.create(invoice=cls.invoices[1], date=date.today(), amount=12.34,
method=models.Payment.CASH)
}
def setUp(self):
self.profile.set_password('testuser')
self.profile.save()
def testLoginRedirect(self):
request_url = reverse('event_embed', kwargs={'pk': 1})
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
# Request the page and check it redirects
response = self.client.get(request_url, follow=True)
self.assertRedirects(response, expected_url, status_code=302, target_status_code=200)
# Now login
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
# And check that it no longer redirects
response = self.client.get(request_url, follow=True)
self.assertEqual(len(response.redirect_chain), 0)
def testLoginCookieWarning(self):
login_url = reverse('login_embed')
response = self.client.post(login_url, follow=True)
self.assertContains(response, "Cookies do not seem to be enabled")
def testXFrameHeaders(self):
event_url = reverse('event_embed', kwargs={'pk': 1})
login_url = reverse('login_embed')
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
response = self.client.get(event_url, follow=True)
with self.assertRaises(KeyError):
response._headers["X-Frame-Options"]
response = self.client.get(login_url, follow=True)
with self.assertRaises(KeyError):
response._headers["X-Frame-Options"]
def testOEmbed(self):
event_url = reverse('event_detail', kwargs={'pk': 1})
event_embed_url = reverse('event_embed', kwargs={'pk': 1})
oembed_url = reverse('event_oembed', kwargs={'pk': 1})
alt_oembed_url = reverse('event_oembed', kwargs={'pk': 999})
alt_event_embed_url = reverse('event_embed', kwargs={'pk': 999})
# Test the meta tag is in place
response = self.client.get(event_url, follow=True, HTTP_HOST='example.com')
self.assertContains(response, '<link rel="alternate" type="application/json+oembed"')
self.assertContains(response, oembed_url)
# Test that the JSON exists
response = self.client.get(oembed_url, follow=True, HTTP_HOST='example.com')
self.assertEqual(response.status_code, 200)
self.assertContains(response, event_embed_url)
# Should also work for non-existant events
response = self.client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
self.assertEqual(response.status_code, 200)
self.assertContains(response, alt_event_embed_url)
# And check that it no longer redirects
response = client.get(request_url, follow=True)
assert len(response.redirect_chain) == 0
class TestSampleDataGenerator(TestCase):
@override_settings(DEBUG=True)
def test_generate_sample_data(self):
# Run the management command and check there are no exceptions
call_command('generateSampleRIGSData')
def test_login_cookie_warning(client):
login_url = reverse('login_embed')
response = client.post(login_url, follow=True)
assertContains(response, "Cookies do not seem to be enabled")
# Check there are lots of events
self.assertTrue(models.Event.objects.all().count() > 100)
def test_production_exception(self):
from django.core.management.base import CommandError
def test_xframe_headers(admin_client, basic_event):
event_url = reverse('event_embed', kwargs={'pk': basic_event.pk})
login_url = reverse('login_embed')
self.assertRaisesRegex(CommandError, ".*production", call_command, 'generateSampleRIGSData')
response = admin_client.get(event_url, follow=True)
with pytest.raises(KeyError):
response._headers["X-Frame-Options"]
response = admin_client.get(login_url, follow=True)
with pytest.raises(KeyError):
response._headers["X-Frame-Options"]
def test_oembed(client, basic_event):
event_url = reverse('event_detail', kwargs={'pk': basic_event.pk})
event_embed_url = reverse('event_embed', kwargs={'pk': basic_event.pk})
oembed_url = reverse('event_oembed', kwargs={'pk': basic_event.pk})
alt_oembed_url = reverse('event_oembed', kwargs={'pk': 999})
alt_event_embed_url = reverse('event_embed', kwargs={'pk': 999})
assert_oembed(alt_event_embed_url, alt_oembed_url, client, event_embed_url, event_url, oembed_url)
def search(client, url, found, notfound, arguments):
@@ -391,45 +335,11 @@ def test_search(admin_client):
['name', 'id', 'address'])
def setup_for_hs():
models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
venue = models.Venue.objects.create(name="Venue 1")
return venue, {
1: models.Event.objects.create(name="TE E1", start_date=date.today(),
description="This is an event description\nthat for a very specific reason spans two lines.",
venue=venue),
2: models.Event.objects.create(name="TE E2", start_date=date.today()),
}
def create_ra(usr):
venue, events = setup_for_hs()
return models.RiskAssessment.objects.create(event=events[1], nonstandard_equipment=False, nonstandard_use=False,
contractors=False, other_companies=False, crew_fatigue=False,
big_power=False, power_mic=usr, generators=False,
other_companies_power=False, nonstandard_equipment_power=False,
multiple_electrical_environments=False, noise_monitoring=False,
known_venue=True, safe_loading=True, safe_storage=True,
area_outside_of_control=True, barrier_required=True,
nonstandard_emergency_procedure=True, special_structures=False,
suspended_structures=False, outside=False)
def create_checklist(usr):
venue, events = setup_for_hs()
return models.EventChecklist.objects.create(event=events[1], power_mic=usr, safe_parking=False,
safe_packing=False, exits=False, trip_hazard=False, warning_signs=False,
ear_plugs=False, hs_location="Locked away safely",
extinguishers_location="Somewhere, I forgot", earthing=False, pat=False,
date=timezone.now(), venue=venue)
def test_list(admin_client):
venue, events = setup_for_hs()
def test_hs_list(admin_client, basic_event):
request_url = reverse('hs_list')
response = admin_client.get(request_url, follow=True)
assertContains(response, events[1].name)
assertContains(response, events[2].name)
assertContains(response, basic_event.name)
# assertContains(response, events[2].name)
assertContains(response, 'Create')
@@ -439,19 +349,18 @@ def review(client, profile, obj, request_url):
obj.refresh_from_db()
assertContains(response, 'Reviewed by')
assertContains(response, profile.name)
assert_times_equal(time, obj.reviewed_at)
assert_times_almost_equal(time, obj.reviewed_at)
def test_ra_review(admin_client, admin_user):
review(admin_client, admin_user, create_ra(admin_user), 'ra_review')
def test_ra_review(admin_client, admin_user, ra):
review(admin_client, admin_user, ra, 'ra_review')
def test_checklist_review(admin_client, admin_user):
review(admin_client, admin_user, create_checklist(admin_user), 'ec_review')
def test_checklist_review(admin_client, admin_user, checklist):
review(admin_client, admin_user, checklist, 'ec_review')
def test_ra_redirect(admin_client, admin_user):
ra = create_ra(admin_user)
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})

View File

@@ -1,5 +1,4 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import login_required
from django.urls import path, re_path
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import RedirectView
@@ -62,7 +61,7 @@ urlpatterns = [
path('event/<int:pk>/embed/',
xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())),
name='event_embed'),
path('event/<int:pk>/oembed_json/', rigboard.EventOembed.as_view(),
path('event/<int:pk>/oembed_json/', rigboard.EventOEmbed.as_view(),
name='event_oembed'),
path('event/<int:pk>/print/', permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()),
name='event_print'),
@@ -133,6 +132,8 @@ urlpatterns = [
name='event_authorise_preview'),
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
name='event_authorise'),
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/preview/$', rigboard.EventAuthorise.as_view(preview=True),
name='event_authorise_form_preview'),
# ICS Calendar - API key authentication
re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),