diff --git a/.gitignore b/.gitignore
index b17c3115..041dcbd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,7 @@ htmlcov/
.tox/
.coverage
.cache
+.pytest_cache
nosetests.xml
coverage.xml
@@ -107,3 +108,4 @@ atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
+.vscode/
\ No newline at end of file
diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py
index a41ad2c9..5877ad74 100644
--- a/PyRIGS/settings.py
+++ b/PyRIGS/settings.py
@@ -58,6 +58,7 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'RIGS',
+ 'assets',
'debug_toolbar',
'registration',
diff --git a/PyRIGS/urls.py b/PyRIGS/urls.py
index 5cc85548..cb78130c 100644
--- a/PyRIGS/urls.py
+++ b/PyRIGS/urls.py
@@ -12,6 +12,7 @@ urlpatterns = [
# url(r'^blog/', include('blog.urls')),
url(r'^', include('RIGS.urls')),
+ url('^assets/', include('assets.urls')),
url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail),
name="registration_register"),
url('^user/', include('django.contrib.auth.urls')),
diff --git a/RIGS/management/commands/generateSampleData.py b/RIGS/management/commands/generateSampleData.py
index 5263a030..e19c569d 100644
--- a/RIGS/management/commands/generateSampleData.py
+++ b/RIGS/management/commands/generateSampleData.py
@@ -1,252 +1,11 @@
from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group, Permission
-from django.db import transaction
-from reversion import revisions as reversion
-
-import datetime
-import random
-
-from RIGS import models
+from django.core.management import call_command
class Command(BaseCommand):
help = 'Adds sample data to use for testing'
can_import_settings = True
- people = []
- organisations = []
- venues = []
- profiles = []
-
- keyholder_group = None
- finance_group = None
-
def handle(self, *args, **options):
- from django.conf import settings
-
- if not (settings.DEBUG or settings.STAGING):
- raise CommandError('You cannot run this command in production')
-
- random.seed('Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests
-
- with transaction.atomic():
- models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
-
- self.setupGenericProfiles()
-
- self.setupPeople()
- self.setupOrganisations()
- self.setupVenues()
-
- self.setupGroups()
-
- self.setupEvents()
-
- self.setupUsefulProfiles()
-
- def setupPeople(self):
- names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe", "Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore", "Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan", "Nicolas Flamel", "Cornelius Fudge", "Goyle", "Gregory Goyle", "Hermione Granger", "Rubeus Hagrid", "Igor Karkaroff", "Viktor Krum", "Bellatrix Lestrange", "Alice Longbottom", "Frank Longbottom", "Neville Longbottom", "Luna Lovegood", "Xenophilius Lovegood", # noqa
- "Remus Lupin", "Draco Malfoy", "Lucius Malfoy", "Narcissa Malfoy", "Olympe Maxime", "Minerva McGonagall", "Mad-Eye Moody", "Peter Pettigrew", "Harry Potter", "James Potter", "Lily Potter", "Quirinus Quirrell", "Tom Riddle", "Mary Riddle", "Lord Voldemort", "Rita Skeeter", "Severus Snape", "Nymphadora Tonks", "Dolores Janes Umbridge", "Arthur Weasley", "Bill Weasley", "Charlie Weasley", "Fred Weasley", "George Weasley", "Ginny Weasley", "Molly Weasley", "Percy Weasley", "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))
-
- newPerson = models.Person.objects.create(name=name)
- if i % 3 == 0:
- newPerson.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"
-
- if i % 7 == 0:
- newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
-
- if i % 9 == 0:
- newPerson.phone = "01234 567894"
-
- newPerson.save()
- self.people.append(newPerson)
-
- def setupOrganisations(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", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa
- "Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
- for i, name in enumerate(names):
- with reversion.create_revision():
- reversion.set_user(random.choice(self.profiles))
- newOrganisation = models.Organisation.objects.create(name=name)
- if i % 2 == 0:
- newOrganisation.has_su_account = True
-
- if i % 3 == 0:
- newOrganisation.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"
-
- if i % 7 == 0:
- newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
-
- if i % 9 == 0:
- newOrganisation.phone = "01234 567894"
-
- newOrganisation.save()
- self.organisations.append(newOrganisation)
-
- def setupVenues(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", "The Vale of Arryn", "The Whispering Wood", "White Harbor", "Winterfell", "The Arbor", "Ashemark", "Brightwater Keep", "Casterly Rock", "Clegane's Keep", "Dragonstone", "Dorne", "God's Eye", "The Golden Tooth", # noqa
- "Harrenhal", "Highgarden", "Horn Hill", "Fingers", "King's Landing", "Lannisport", "Oldtown", "Rainswood", "Storm's End", "Summerhall", "Sunspear", "Tarth", "Castle Black", "Craster's Keep", "Fist of the First Men", "The Frostfangs", "The Gift", "The Skirling Pass", "The Wall", "Asshai", "Astapor", "Braavos", "The Dothraki Sea", "Lys", "Meereen", "Myr", "Norvos", "Pentos", "Qarth", "Qohor", "The Red Waste", "Tyrosh", "Vaes Dothrak", "Valyria", "Village of the Lhazareen", "Volantis", "Yunkai"] # noqa
- for i, name in enumerate(names):
- with reversion.create_revision():
- reversion.set_user(random.choice(self.profiles))
- newVenue = models.Venue.objects.create(name=name)
- if i % 2 == 0:
- newVenue.three_phase_available = True
-
- if i % 3 == 0:
- newVenue.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"
-
- if i % 7 == 0:
- newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
-
- if i % 9 == 0:
- newVenue.phone = "01234 567894"
-
- newVenue.save()
- self.venues.append(newVenue)
-
- def setupGroups(self):
- self.keyholder_group = Group.objects.create(name='Keyholders')
- self.finance_group = Group.objects.create(name='Finance')
-
- 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"]
- financePerms = ["change_event", "view_event", "add_eventitem", "change_eventitem", "add_invoice", "change_invoice", "view_invoice", "add_organisation", "change_organisation", "view_organisation", "add_payment", "change_payment", "delete_payment", "add_person", "change_person", "view_person"]
-
- 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))
-
- 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)
- financeUser.groups.add(self.finance_group)
- financeUser.groups.add(self.keyholder_group)
- financeUser.set_password('finance')
- financeUser.save()
-
- keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU",
- email="keyholderuser@example.com", is_active=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)
- basicUser.set_password('basic')
- basicUser.save()
-
- def setupEvents(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", "Awards Ceremony", "Debating Event", "Club Night", "DJ Evening", "Building Projection", "Choir Concert"]
- descriptions = ["A brief desciption of the event", "This event is boring", "Probably wont happen", "Warning: this has lots of kit"]
- 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 = [{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2, 'cost': 200.00},
- {'name': 'Projector', 'description': 'Some kind of video thinamejig, probably with unnecessary processing for free', 'quantity': 1, 'cost': 500.00},
- {'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1, 'cost': 200.52},
- {'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00},
- {'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5, 'cost': 0.50},
- {'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1, 'cost': 100.00},
- {'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00},
- {'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
- {'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
-
- dayDelta = -120 # start adding events from 4 months ago
-
- for i in range(150): # Let's add 100 events
- with reversion.create_revision():
- reversion.set_user(random.choice(self.profiles))
-
- name = names[i % len(names)]
-
- startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
- dayDelta = dayDelta + random.randint(0, 3)
-
- newEvent = models.Event.objects.create(name=name, start_date=startDate)
-
- if random.randint(0, 2) > 1: # 1 in 3 have a start time
- newEvent.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))
- 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))
- 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))
-
- if random.randint(0, 6) > 0: # 5 in 6 have MIC
- newEvent.mic = random.choice(self.profiles)
-
- if random.randint(0, 6) > 0: # 5 in 6 have organisation
- newEvent.organisation = random.choice(self.organisations)
-
- if random.randint(0, 6) > 0: # 5 in 6 have person
- newEvent.person = random.choice(self.people)
-
- if random.randint(0, 6) > 0: # 5 in 6 have venue
- newEvent.venue = random.choice(self.venues)
-
- # Could have any status, equally weighted
- newEvent.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
-
- if random.randint(0, 1) > 0: # 1 in 2 have description
- newEvent.description = random.choice(descriptions)
-
- if random.randint(0, 1) > 0: # 1 in 2 have notes
- newEvent.notes = random.choice(notes)
-
- newEvent.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()
-
- 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()
-
- with reversion.create_revision():
- reversion.set_user(random.choice(self.profiles))
- if newEvent.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
- elif random.randint(0, 2) > 1: # 1 in 3 have been paid
- models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance, date=datetime.date.today())
+ call_command('generateSampleRIGSData')
+ call_command('generateSampleAssetsData')
diff --git a/RIGS/management/commands/generateSampleRIGSData.py b/RIGS/management/commands/generateSampleRIGSData.py
new file mode 100644
index 00000000..6b543971
--- /dev/null
+++ b/RIGS/management/commands/generateSampleRIGSData.py
@@ -0,0 +1,260 @@
+from django.core.management.base import BaseCommand, CommandError
+from django.contrib.auth.models import Group, Permission
+from django.db import transaction
+from reversion import revisions as reversion
+
+import datetime
+import random
+
+from RIGS import models
+
+
+class Command(BaseCommand):
+ help = 'Adds sample data to use for testing'
+ can_import_settings = True
+
+ people = []
+ organisations = []
+ venues = []
+ profiles = []
+
+ keyholder_group = None
+ finance_group = None
+
+ def handle(self, *args, **options):
+ from django.conf import settings
+
+ if not (settings.DEBUG or settings.STAGING):
+ raise CommandError('You cannot run this command in production')
+
+ random.seed('Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests
+
+ with transaction.atomic():
+ models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
+
+ self.setupGenericProfiles()
+
+ self.setupPeople()
+ self.setupOrganisations()
+ self.setupVenues()
+
+ self.setupGroups()
+
+ self.setupEvents()
+
+ self.setupUsefulProfiles()
+
+ def setupPeople(self):
+ names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe", "Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore", "Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan", "Nicolas Flamel", "Cornelius Fudge", "Goyle", "Gregory Goyle", "Hermione Granger", "Rubeus Hagrid", "Igor Karkaroff", "Viktor Krum", "Bellatrix Lestrange", "Alice Longbottom", "Frank Longbottom", "Neville Longbottom", "Luna Lovegood", "Xenophilius Lovegood", # noqa
+ "Remus Lupin", "Draco Malfoy", "Lucius Malfoy", "Narcissa Malfoy", "Olympe Maxime", "Minerva McGonagall", "Mad-Eye Moody", "Peter Pettigrew", "Harry Potter", "James Potter", "Lily Potter", "Quirinus Quirrell", "Tom Riddle", "Mary Riddle", "Lord Voldemort", "Rita Skeeter", "Severus Snape", "Nymphadora Tonks", "Dolores Janes Umbridge", "Arthur Weasley", "Bill Weasley", "Charlie Weasley", "Fred Weasley", "George Weasley", "Ginny Weasley", "Molly Weasley", "Percy Weasley", "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))
+
+ newPerson = models.Person.objects.create(name=name)
+ if i % 3 == 0:
+ newPerson.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"
+
+ if i % 7 == 0:
+ newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567"
+
+ if i % 9 == 0:
+ newPerson.phone = "01234 567894"
+
+ newPerson.save()
+ self.people.append(newPerson)
+
+ def setupOrganisations(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", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa
+ "Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
+ for i, name in enumerate(names):
+ with reversion.create_revision():
+ reversion.set_user(random.choice(self.profiles))
+ newOrganisation = models.Organisation.objects.create(name=name)
+ if i % 2 == 0:
+ newOrganisation.has_su_account = True
+
+ if i % 3 == 0:
+ newOrganisation.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"
+
+ if i % 7 == 0:
+ newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567"
+
+ if i % 9 == 0:
+ newOrganisation.phone = "01234 567894"
+
+ newOrganisation.save()
+ self.organisations.append(newOrganisation)
+
+ def setupVenues(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", "The Vale of Arryn", "The Whispering Wood", "White Harbor", "Winterfell", "The Arbor", "Ashemark", "Brightwater Keep", "Casterly Rock", "Clegane's Keep", "Dragonstone", "Dorne", "God's Eye", "The Golden Tooth", # noqa
+ "Harrenhal", "Highgarden", "Horn Hill", "Fingers", "King's Landing", "Lannisport", "Oldtown", "Rainswood", "Storm's End", "Summerhall", "Sunspear", "Tarth", "Castle Black", "Craster's Keep", "Fist of the First Men", "The Frostfangs", "The Gift", "The Skirling Pass", "The Wall", "Asshai", "Astapor", "Braavos", "The Dothraki Sea", "Lys", "Meereen", "Myr", "Norvos", "Pentos", "Qarth", "Qohor", "The Red Waste", "Tyrosh", "Vaes Dothrak", "Valyria", "Village of the Lhazareen", "Volantis", "Yunkai"] # noqa
+ for i, name in enumerate(names):
+ with reversion.create_revision():
+ reversion.set_user(random.choice(self.profiles))
+ newVenue = models.Venue.objects.create(name=name)
+ if i % 2 == 0:
+ newVenue.three_phase_available = True
+
+ if i % 3 == 0:
+ newVenue.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"
+
+ if i % 7 == 0:
+ newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567"
+
+ if i % 9 == 0:
+ newVenue.phone = "01234 567894"
+
+ newVenue.save()
+ self.venues.append(newVenue)
+
+ def setupGroups(self):
+ self.keyholder_group = Group.objects.create(name='Keyholders')
+ self.finance_group = Group.objects.create(name='Finance')
+
+ 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",
+ "asset_finance", "view_asset", "view_supplier", "asset_finance",
+ "add_supplier"]
+ financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice",
+ "add_payment", "change_payment", "delete_payment"]
+
+ 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))
+
+ 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)
+ financeUser.groups.add(self.finance_group)
+ financeUser.groups.add(self.keyholder_group)
+ financeUser.set_password('finance')
+ financeUser.save()
+
+ keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU",
+ email="keyholderuser@example.com", is_active=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)
+ basicUser.set_password('basic')
+ basicUser.save()
+
+ def setupEvents(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", "Awards Ceremony", "Debating Event", "Club Night", "DJ Evening", "Building Projection", "Choir Concert"]
+ descriptions = ["A brief desciption of the event", "This event is boring", "Probably wont happen", "Warning: this has lots of kit"]
+ 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 = [{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2, 'cost': 200.00},
+ {'name': 'Projector', 'description': 'Some kind of video thinamejig, probably with unnecessary processing for free', 'quantity': 1, 'cost': 500.00},
+ {'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1, 'cost': 200.52},
+ {'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00},
+ {'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5, 'cost': 0.50},
+ {'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1, 'cost': 100.00},
+ {'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00},
+ {'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00},
+ {'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}]
+
+ dayDelta = -120 # start adding events from 4 months ago
+
+ for i in range(150): # Let's add 100 events
+ with reversion.create_revision():
+ reversion.set_user(random.choice(self.profiles))
+
+ name = names[i % len(names)]
+
+ startDate = datetime.date.today() + datetime.timedelta(days=dayDelta)
+ dayDelta = dayDelta + random.randint(0, 3)
+
+ newEvent = models.Event.objects.create(name=name, start_date=startDate)
+
+ if random.randint(0, 2) > 1: # 1 in 3 have a start time
+ newEvent.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))
+ 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))
+ 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))
+
+ if random.randint(0, 6) > 0: # 5 in 6 have MIC
+ newEvent.mic = random.choice(self.profiles)
+
+ if random.randint(0, 6) > 0: # 5 in 6 have organisation
+ newEvent.organisation = random.choice(self.organisations)
+
+ if random.randint(0, 6) > 0: # 5 in 6 have person
+ newEvent.person = random.choice(self.people)
+
+ if random.randint(0, 6) > 0: # 5 in 6 have venue
+ newEvent.venue = random.choice(self.venues)
+
+ # Could have any status, equally weighted
+ newEvent.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
+
+ if random.randint(0, 1) > 0: # 1 in 2 have description
+ newEvent.description = random.choice(descriptions)
+
+ if random.randint(0, 1) > 0: # 1 in 2 have notes
+ newEvent.notes = random.choice(notes)
+
+ newEvent.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()
+
+ 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()
+
+ with reversion.create_revision():
+ reversion.set_user(random.choice(self.profiles))
+ if newEvent.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
+ elif random.randint(0, 2) > 1: # 1 in 3 have been paid
+ models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance, date=datetime.date.today())
diff --git a/RIGS/templates/RIGS/activity_feed_data.html b/RIGS/templates/RIGS/activity_feed_data.html
index 4de8bf7b..cff6c323 100644
--- a/RIGS/templates/RIGS/activity_feed_data.html
+++ b/RIGS/templates/RIGS/activity_feed_data.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax_nomodal.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax_nomodal.html,base_rigs.html" %}
{% load static %}
{% load paginator from filters %}
diff --git a/RIGS/templates/RIGS/activity_table.html b/RIGS/templates/RIGS/activity_table.html
index e12dfd7a..f9addd87 100644
--- a/RIGS/templates/RIGS/activity_table.html
+++ b/RIGS/templates/RIGS/activity_table.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load static %}
{% load paginator from filters %}
{% load to_class_name from filters %}
diff --git a/RIGS/templates/RIGS/calendar.html b/RIGS/templates/RIGS/calendar.html
index f6891d84..51e5e0b7 100644
--- a/RIGS/templates/RIGS/calendar.html
+++ b/RIGS/templates/RIGS/calendar.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load static %}
diff --git a/RIGS/templates/RIGS/event_archive.html b/RIGS/templates/RIGS/event_archive.html
index 00baaad6..4fc5642e 100644
--- a/RIGS/templates/RIGS/event_archive.html
+++ b/RIGS/templates/RIGS/event_archive.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% block title %}Event Archive{% endblock %}
diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html
index 6a18d2f3..176756dc 100644
--- a/RIGS/templates/RIGS/event_detail.html
+++ b/RIGS/templates/RIGS/event_detail.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %}
{% block content %}
diff --git a/RIGS/templates/RIGS/event_form.html b/RIGS/templates/RIGS/event_form.html
index d7ba9591..954ebdbd 100644
--- a/RIGS/templates/RIGS/event_form.html
+++ b/RIGS/templates/RIGS/event_form.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load widget_tweaks %}
{% load static %}
{% load multiply from filters %}
diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html
index ec9755c0..415293e9 100644
--- a/RIGS/templates/RIGS/event_invoice.html
+++ b/RIGS/templates/RIGS/event_invoice.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% load static %}
diff --git a/RIGS/templates/RIGS/eventauthorisation_request.html b/RIGS/templates/RIGS/eventauthorisation_request.html
index 6067b3fe..dbb617c3 100644
--- a/RIGS/templates/RIGS/eventauthorisation_request.html
+++ b/RIGS/templates/RIGS/eventauthorisation_request.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
+{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}Request Authorisation{% endblock %}
diff --git a/RIGS/templates/RIGS/eventauthorisation_request_error.html b/RIGS/templates/RIGS/eventauthorisation_request_error.html
index b366622d..aaebe4c0 100644
--- a/RIGS/templates/RIGS/eventauthorisation_request_error.html
+++ b/RIGS/templates/RIGS/eventauthorisation_request_error.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
+{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}NottinghamTEC Email Address Required{% endblock %}
diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html
index b5c4c511..826839e6 100644
--- a/RIGS/templates/RIGS/index.html
+++ b/RIGS/templates/RIGS/index.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}RIGS{% endblock %}
{% block content %}
@@ -20,6 +20,7 @@
Rigboard
Calendar
{% if perms.RIGS.add_event %} New Event{% endif %}
+ Asset Database
diff --git a/RIGS/templates/RIGS/invoice_confirm_delete.html b/RIGS/templates/RIGS/invoice_confirm_delete.html
index fe295ed6..4b575cb5 100644
--- a/RIGS/templates/RIGS/invoice_confirm_delete.html
+++ b/RIGS/templates/RIGS/invoice_confirm_delete.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %}
diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html
index e1b77b6c..2e0211da 100644
--- a/RIGS/templates/RIGS/invoice_detail.html
+++ b/RIGS/templates/RIGS/invoice_detail.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}Invoice {{ object.pk }}{% endblock %}
diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html
index 63e61e11..fca84c27 100644
--- a/RIGS/templates/RIGS/invoice_list.html
+++ b/RIGS/templates/RIGS/invoice_list.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% block title %}Invoices{% endblock %}
diff --git a/RIGS/templates/RIGS/organisation_detail.html b/RIGS/templates/RIGS/organisation_detail.html
index 7743086c..b12b6391 100644
--- a/RIGS/templates/RIGS/organisation_detail.html
+++ b/RIGS/templates/RIGS/organisation_detail.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load widget_tweaks %}
{% block title %}Organisation | {{ object.name }}{% endblock %}
diff --git a/RIGS/templates/RIGS/organisation_form.html b/RIGS/templates/RIGS/organisation_form.html
index 7ef77e0d..f95e31d7 100644
--- a/RIGS/templates/RIGS/organisation_form.html
+++ b/RIGS/templates/RIGS/organisation_form.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
+{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}{% if object.pk %}Edit {{ object.name }}{% else %}Add Organisation{% endif %}{% endblock %}
diff --git a/RIGS/templates/RIGS/organisation_list.html b/RIGS/templates/RIGS/organisation_list.html
index c8856886..080ca938 100644
--- a/RIGS/templates/RIGS/organisation_list.html
+++ b/RIGS/templates/RIGS/organisation_list.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load widget_tweaks %}
{% load paginator from filters %}
{% load url_replace from filters %}
diff --git a/RIGS/templates/RIGS/payment_confirm_delete.html b/RIGS/templates/RIGS/payment_confirm_delete.html
index daa5d004..ab58e243 100644
--- a/RIGS/templates/RIGS/payment_confirm_delete.html
+++ b/RIGS/templates/RIGS/payment_confirm_delete.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %}
diff --git a/RIGS/templates/RIGS/person_detail.html b/RIGS/templates/RIGS/person_detail.html
index 10c995ae..e8a4f3ad 100644
--- a/RIGS/templates/RIGS/person_detail.html
+++ b/RIGS/templates/RIGS/person_detail.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load widget_tweaks %}
{% block title %}Person | {{ object.name }}{% endblock %}
diff --git a/RIGS/templates/RIGS/person_form.html b/RIGS/templates/RIGS/person_form.html
index a5720b9f..3ebf6409 100644
--- a/RIGS/templates/RIGS/person_form.html
+++ b/RIGS/templates/RIGS/person_form.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
+{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}{% if object.pk %}Edit {{ object.name }}{% else %}Add Person{% endif %}{% endblock %}
diff --git a/RIGS/templates/RIGS/person_list.html b/RIGS/templates/RIGS/person_list.html
index 2cbdff8e..02b9eb2c 100644
--- a/RIGS/templates/RIGS/person_list.html
+++ b/RIGS/templates/RIGS/person_list.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load widget_tweaks %}
{% load paginator from filters %}
{% load url_replace from filters %}
diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html
index 11904b18..d0a86444 100644
--- a/RIGS/templates/RIGS/profile_detail.html
+++ b/RIGS/templates/RIGS/profile_detail.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% block title %}RIGS Profile {{object.pk}}{% endblock %}
diff --git a/RIGS/templates/RIGS/profile_form.html b/RIGS/templates/RIGS/profile_form.html
index baa33424..b8426875 100644
--- a/RIGS/templates/RIGS/profile_form.html
+++ b/RIGS/templates/RIGS/profile_form.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}Update Profile {{object.name}}{% endblock %}
diff --git a/RIGS/templates/RIGS/rigboard.html b/RIGS/templates/RIGS/rigboard.html
index 9292e304..786f9385 100644
--- a/RIGS/templates/RIGS/rigboard.html
+++ b/RIGS/templates/RIGS/rigboard.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}Rigboard{% endblock %}
diff --git a/RIGS/templates/RIGS/venue_detail.html b/RIGS/templates/RIGS/venue_detail.html
index ede9d4ec..25d3daef 100644
--- a/RIGS/templates/RIGS/venue_detail.html
+++ b/RIGS/templates/RIGS/venue_detail.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load widget_tweaks %}
{% block title %}Venue | {{ object.name }}{% endblock %}
diff --git a/RIGS/templates/RIGS/venue_form.html b/RIGS/templates/RIGS/venue_form.html
index 4d99af7e..b410a062 100644
--- a/RIGS/templates/RIGS/venue_form.html
+++ b/RIGS/templates/RIGS/venue_form.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %}
+{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}{{ object.pk|yesno:"Edit,Add" }} Venue{% endblock %}
diff --git a/RIGS/templates/RIGS/venue_list.html b/RIGS/templates/RIGS/venue_list.html
index 88ae61ac..80c9d8ac 100644
--- a/RIGS/templates/RIGS/venue_list.html
+++ b/RIGS/templates/RIGS/venue_list.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load widget_tweaks %}
{% load paginator from filters %}
{% load url_replace from filters %}
diff --git a/RIGS/templates/RIGS/version_history.html b/RIGS/templates/RIGS/version_history.html
index 18ff22d4..676d231e 100644
--- a/RIGS/templates/RIGS/version_history.html
+++ b/RIGS/templates/RIGS/version_history.html
@@ -1,4 +1,4 @@
-{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
+{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load to_class_name from filters %}
{% load paginator from filters %}
{% load static %}
diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py
index 6006e995..aa65158a 100644
--- a/RIGS/test_functional.py
+++ b/RIGS/test_functional.py
@@ -88,7 +88,7 @@ class UserRegistrationTest(LiveServerTestCase):
initials.send_keys('JS')
phone.send_keys('0123456789')
self.browser.execute_script(
- "return jQuery('#g-recaptcha-response').val('PASSED')")
+ "return function() {jQuery('#g-recaptcha-response').val('PASSED'); return 0}()")
# Submit incorrect form
submit = self.browser.find_element_by_xpath("//input[@type='submit']")
@@ -110,8 +110,9 @@ class UserRegistrationTest(LiveServerTestCase):
# Correct error
password1.send_keys('correcthorsebatterystaple')
password2.send_keys('correcthorsebatterystaple')
+ self.browser.execute_script("console.log('Hello, world!')")
self.browser.execute_script(
- "return jQuery('#g-recaptcha-response').val('PASSED')")
+ "return function() {jQuery('#g-recaptcha-response').val('PASSED'); return 0}()")
# Submit again
password2.send_keys(Keys.ENTER)
@@ -150,7 +151,7 @@ class UserRegistrationTest(LiveServerTestCase):
username.send_keys('TestUsername')
password.send_keys('correcthorsebatterystaple')
self.browser.execute_script(
- "return jQuery('#g-recaptcha-response').val('PASSED')")
+ "return function() {jQuery('#g-recaptcha-response').val('PASSED'); return 0}()")
password.send_keys(Keys.ENTER)
# Check we are logged in
diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py
index 4a020292..0a360a10 100644
--- a/RIGS/test_unit.py
+++ b/RIGS/test_unit.py
@@ -414,7 +414,7 @@ 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('generateSampleData')
+ call_command('generateSampleRIGSData')
# Check there are lots of events
self.assertTrue(models.Event.objects.all().count() > 100)
@@ -422,4 +422,4 @@ class TestSampleDataGenerator(TestCase):
def test_production_exception(self):
from django.core.management.base import CommandError
- self.assertRaisesRegex(CommandError, ".*production", call_command, 'generateSampleData')
+ self.assertRaisesRegex(CommandError, ".*production", call_command, 'generateSampleRIGSData')
diff --git a/assets/__init__.py b/assets/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/assets/admin.py b/assets/admin.py
new file mode 100644
index 00000000..7044040f
--- /dev/null
+++ b/assets/admin.py
@@ -0,0 +1,37 @@
+from django.contrib import admin
+from assets import models as assets
+
+
+@admin.register(assets.AssetCategory)
+class AssetCategoryAdmin(admin.ModelAdmin):
+ list_display = ['id', 'name']
+ ordering = ['id']
+
+
+@admin.register(assets.AssetStatus)
+class AssetStatusAdmin(admin.ModelAdmin):
+ list_display = ['id', 'name']
+ ordering = ['id']
+
+
+@admin.register(assets.Supplier)
+class SupplierAdmin(admin.ModelAdmin):
+ list_display = ['id', 'name']
+ ordering = ['id']
+
+
+@admin.register(assets.Asset)
+class AssetAdmin(admin.ModelAdmin):
+ list_display = ['id', 'asset_id', 'description', 'category', 'status']
+ list_filter = ['is_cable', 'category']
+ search_fields = ['id', 'asset_id', 'description']
+
+
+@admin.register(assets.Connector)
+class ConnectorAdmin(admin.ModelAdmin):
+ list_display = ['id', '__str__', 'current_rating', 'voltage_rating', 'num_pins']
+
+
+admin.AdminSite.site_header = 'PyAssets - TEC\'s Asset System'
+admin.AdminSite.site_title = 'PyAssets Admin'
+admin.AdminSite.index_title = 'System Administration'
diff --git a/assets/apps.py b/assets/apps.py
new file mode 100644
index 00000000..5569d303
--- /dev/null
+++ b/assets/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AssetsConfig(AppConfig):
+ name = 'assets'
diff --git a/assets/filters.py b/assets/filters.py
new file mode 100644
index 00000000..c7efcd7f
--- /dev/null
+++ b/assets/filters.py
@@ -0,0 +1,9 @@
+import django_filters
+
+from assets import models
+
+
+class AssetFilter(django_filters.FilterSet):
+ class Meta:
+ model = models.Asset
+ fields = ['asset_id', 'description', 'category', 'status']
diff --git a/assets/forms.py b/assets/forms.py
new file mode 100644
index 00000000..430eef20
--- /dev/null
+++ b/assets/forms.py
@@ -0,0 +1,30 @@
+from django import forms
+
+from assets import models
+
+
+class AssetForm(forms.ModelForm):
+ class Meta:
+ model = models.Asset
+ fields = '__all__'
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields['date_sold'].widget.format = '%Y-%m-%d'
+ self.fields['date_acquired'].widget.format = '%Y-%m-%d'
+
+
+class AssetSearchForm(forms.Form):
+ query = forms.CharField(required=False)
+ category = forms.ModelMultipleChoiceField(models.AssetCategory.objects.all(), required=False)
+ status = forms.ModelMultipleChoiceField(models.AssetStatus.objects.all(), required=False)
+
+
+class SupplierForm(forms.ModelForm):
+ class Meta:
+ model = models.Supplier
+ fields = '__all__'
+
+
+class SupplierSearchForm(forms.Form):
+ query = forms.CharField(required=False)
diff --git a/assets/management/__init__.py b/assets/management/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/assets/management/commands/__init__.py b/assets/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/assets/management/commands/deleteSampleData.py b/assets/management/commands/deleteSampleData.py
new file mode 100644
index 00000000..cdf34ce9
--- /dev/null
+++ b/assets/management/commands/deleteSampleData.py
@@ -0,0 +1,23 @@
+from django.core.management.base import BaseCommand, CommandError
+
+from assets import models
+
+
+class Command(BaseCommand):
+ help = 'Deletes testing sample data'
+
+ def handle(self, *args, **kwargs):
+ from django.conf import settings
+
+ if not (settings.DEBUG):
+ raise CommandError('You cannot run this command in production')
+
+ self.delete_objects(models.AssetCategory)
+ self.delete_objects(models.AssetStatus)
+ self.delete_objects(models.Supplier)
+ self.delete_objects(models.Connector)
+ self.delete_objects(models.Asset)
+
+ def delete_objects(self, model):
+ for object in model.objects.all():
+ object.delete()
diff --git a/assets/management/commands/generateSampleAssetsData.py b/assets/management/commands/generateSampleAssetsData.py
new file mode 100644
index 00000000..15961ab9
--- /dev/null
+++ b/assets/management/commands/generateSampleAssetsData.py
@@ -0,0 +1,114 @@
+from django.core.management.base import BaseCommand, CommandError
+from django.utils import timezone
+
+from assets import models
+
+
+class Command(BaseCommand):
+ help = 'Creates some sample data for testing'
+
+ def handle(self, *args, **kwargs):
+ from django.conf import settings
+
+ if not (settings.DEBUG or settings.STAGING):
+ raise CommandError('You cannot run this command in production')
+
+ random.seed('Some object to see the random number generator')
+
+ self.create_categories()
+ self.create_statuses()
+ self.create_suppliers()
+ self.create_assets()
+ self.create_connectors()
+ self.create_cables()
+
+ def create_categories(self):
+ categories = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging']
+
+ for cat in categories:
+ models.AssetCategory.objects.create(name=cat)
+
+ def create_statuses(self):
+ statuses = [('In Service', True), ('Lost', False), ('Binned', False), ('Sold', False), ('Broken', False)]
+
+ for stat in statuses:
+ models.AssetStatus.objects.create(name=stat[0], should_show=stat[1])
+
+ def create_suppliers(self):
+ suppliers = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa
+ "Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa
+
+ for supplier in suppliers:
+ models.Supplier.objects.create(name=supplier)
+
+ def create_assets(self):
+ asset_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink']
+
+ categories = models.AssetCategory.objects.all()
+ statuses = models.AssetStatus.objects.all()
+ suppliers = models.Supplier.objects.all()
+
+ for i in range(100):
+ asset = models.Asset.objects.create(
+ asset_id='{}'.format(models.Asset.get_available_asset_id()),
+ description=random.choice(asset_description),
+ category=random.choice(categories),
+ status=random.choice(statuses),
+ date_acquired=timezone.now().date()
+ )
+
+ if i % 4 == 0:
+ asset.parent = models.Asset.objects.order_by('?').first()
+
+ if i % 3 == 0:
+ asset.purchased_from = random.choice(suppliers)
+
+ asset.save()
+
+ def create_cables(self):
+ asset_description = ['The worm', 'Harting without a cap', 'Heavy cable', 'Extension lead', 'IEC cable that we should remember to prep']
+
+ csas = [0.75, 1.00, 1.25, 2.5, 4]
+ lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100]
+ cores = [3, 5]
+ circuits = [1, 2, 3, 6]
+ categories = models.AssetCategory.objects.all()
+ statuses = models.AssetStatus.objects.all()
+ suppliers = models.Supplier.objects.all()
+ connectors = models.Connector.objects.all()
+
+ for i in range(100):
+ asset = models.Asset.objects.create(
+ asset_id='{}'.format(models.Asset.get_available_asset_id()),
+ description=random.choice(asset_description),
+ category=random.choice(categories),
+ status=random.choice(statuses),
+ date_acquired=timezone.now().date(),
+
+ is_cable=True,
+ plug=random.choice(connectors),
+ socket=random.choice(connectors),
+ csa=random.choice(csas),
+ length=random.choice(lengths),
+ circuits=random.choice(circuits),
+ cores=random.choice(circuits)
+ )
+
+ if i % 4 == 0:
+ asset.parent = models.Asset.objects.order_by('?').first()
+
+ if i % 3 == 0:
+ asset.purchased_from = random.choice(suppliers)
+
+ asset.save()
+
+ def create_connectors(self):
+ connectors = [
+ {"description": "13A UK", "current_rating": 13, "voltage_rating": 230, "num_pins": 3},
+ {"description": "16A", "current_rating": 16, "voltage_rating": 230, "num_pins": 3},
+ {"description": "32/3", "current_rating": 32, "voltage_rating": 400, "num_pins": 5},
+ {"description": "Socapex", "current_rating": 23, "voltage_rating": 600, "num_pins": 19},
+ ]
+ for connector in connectors:
+ conn = models.Connector.objects.create(** connector)
+ conn.save()
diff --git a/assets/management/commands/import_old_db.py b/assets/management/commands/import_old_db.py
new file mode 100644
index 00000000..0fcff787
--- /dev/null
+++ b/assets/management/commands/import_old_db.py
@@ -0,0 +1,229 @@
+import os
+import datetime
+import xml.etree.ElementTree as ET
+from django.core.management.base import BaseCommand
+from django.conf import settings
+
+from assets import models
+
+
+class Command(BaseCommand):
+ help = 'Imports old db from XML dump'
+
+ epoch = datetime.date(1970, 1, 1)
+
+ def handle(self, *args, **options):
+ self.import_categories()
+ self.import_statuses()
+ self.import_suppliers()
+ self.import_collections()
+ self.import_assets()
+ self.import_cables()
+
+ @staticmethod
+ def xml_path(file):
+ return os.path.join(settings.BASE_DIR, 'data/DB_Dump/{}'.format(file))
+
+ @staticmethod
+ def parse_xml(file):
+ tree = ET.parse(file)
+
+ return tree.getroot()
+
+ def import_categories(self):
+ # 0: updated, 1: created
+ tally = [0, 0]
+ root = self.parse_xml(self.xml_path('TEC_Asset_Categories.xml'))
+
+ for child in root:
+ obj, created = models.AssetCategory.objects.update_or_create(
+ pk=int(child.find('AssetCategoryID').text),
+ name=child.find('AssetCategory').text
+ )
+
+ if created:
+ tally[1] += 1
+ else:
+ tally[0] += 1
+
+ print('Categories - Updated: {}, Created: {}'.format(tally[0], tally[1]))
+
+ def import_statuses(self):
+ # 0: updated, 1: created
+ tally = [0, 0]
+ root = self.parse_xml(self.xml_path('TEC_Asset_Status_new.xml'))
+
+ for child in root:
+ obj, created = models.AssetStatus.objects.update_or_create(
+ pk=int(child.find('StatusID').text),
+ name=child.find('Status').text
+ )
+
+ if created:
+ tally[1] += 1
+ else:
+ tally[0] += 1
+
+ print('Statuses - Updated: {}, Created: {}'.format(tally[0], tally[1]))
+
+ def import_suppliers(self):
+ # 0: updated, 1: created
+ tally = [0, 0]
+ root = self.parse_xml(self.xml_path('TEC_Asset_Suppliers_new.xml'))
+
+ for child in root:
+ obj, created = models.Supplier.objects.update_or_create(
+ pk=int(child.find('Supplier_x0020_Id').text),
+ name=child.find('Supplier_x0020_Name').text
+ )
+
+ if created:
+ tally[1] += 1
+ else:
+ tally[0] += 1
+
+ print('Suppliers - Updated: {}, Created: {}'.format(tally[0], tally[1]))
+
+ def import_assets(self):
+ # 0: updated, 1: created
+ tally = [0, 0]
+ root = self.parse_xml(self.xml_path('TEC_Assets.xml'))
+
+ for child in root:
+ defaults = dict()
+
+ # defaults['pk'] = int(child.find('ID').text)
+ defaults['asset_id'] = child.find('AssetID').text
+
+ try:
+ defaults['description'] = child.find('AssetDescription').text
+ except AttributeError:
+ defaults['description'] = 'None'
+
+ defaults['category'] = models.AssetCategory.objects.get(pk=int(child.find('AssetCategoryID').text))
+ defaults['status'] = models.AssetStatus.objects.get(pk=int(child.find('StatusID').text))
+
+ try:
+ defaults['serial_number'] = child.find('SerialNumber').text
+ except AttributeError:
+ pass
+
+ try:
+ defaults['purchased_from'] = models.Supplier.objects.get(pk=int(child.find('Supplier_x0020_Id').text))
+ except AttributeError:
+ pass
+
+ try:
+ defaults['date_acquired'] = datetime.datetime.strptime(child.find('DateAcquired').text, '%d/%m/%Y').date()
+ except AttributeError:
+ defaults['date_acquired'] = self.epoch
+
+ try:
+ defaults['date_sold'] = datetime.datetime.strptime(child.find('DateSold').text, '%d/%m/%Y').date()
+ except AttributeError:
+ pass
+
+ try:
+ defaults['purchase_price'] = float(child.find('Replacement_x0020_Value').text)
+ except AttributeError:
+ pass
+
+ try:
+ defaults['salvage_value'] = float(child.find('SalvageValue').text)
+ except AttributeError:
+ pass
+
+ try:
+ defaults['comments'] = child.find('Comments').text
+ except AttributeError:
+ pass
+
+ try:
+ date = child.find('NextSchedMaint').text.split('T')[0]
+ defaults['next_sched_maint'] = datetime.datetime.strptime(date, '%Y-%m-%d').date()
+ except AttributeError:
+ pass
+
+ print(defaults)
+
+ obj, created = models.Asset.objects.update_or_create(**defaults)
+
+ if created:
+ tally[1] += 1
+ else:
+ tally[0] += 1
+
+ print('Assets - Updated: {}, Created: {}'.format(tally[0], tally[1]))
+
+ def import_collections(self):
+ tally = [0, 0]
+ root = self.parse_xml(self.xml_path('TEC_Cable_Collections.xml'))
+
+ for child in root:
+ defaults = dict()
+
+ defaults['pk'] = int(child.find('ID').text)
+ defaults['name'] = child.find('Cable_x0020_Trunk').text
+
+ obj, created = models.Collection.objects.update_or_create(**defaults)
+
+ if created:
+ tally[1] += 1
+ else:
+ tally[0] += 1
+
+ print('Collections - Updated: {}, Created: {}'.format(tally[0], tally[1]))
+
+ def import_cables(self):
+ tally = [0, 0]
+ root = self.parse_xml(self.xml_path('TEC_Cables.xml'))
+
+ for child in root:
+ defaults = dict()
+
+ defaults['asset_id'] = child.find('Asset_x0020_Number').text
+
+ try:
+ defaults['description'] = child.find('Type_x0020_of_x0020_Cable').text
+ except AttributeError:
+ defaults['description'] = 'None'
+
+ defaults['is_cable'] = True
+ defaults['category'] = models.AssetCategory.objects.get(pk=9)
+
+ try:
+ defaults['length'] = child.find('Length_x0020__x0028_m_x0029_').text
+ except AttributeError:
+ pass
+
+ defaults['status'] = models.AssetStatus.objects.get(pk=int(child.find('Status').text))
+
+ try:
+ defaults['comments'] = child.find('Comments').text
+ except AttributeError:
+ pass
+
+ try:
+ collection_id = int(child.find('Collection').text)
+ if collection_id != 0:
+ defaults['collection'] = models.Collection.objects.get(pk=collection_id)
+ except AttributeError:
+ pass
+
+ try:
+ defaults['purchase_price'] = float(child.find('Purchase_x0020_Price').text)
+ except AttributeError:
+ pass
+
+ defaults['date_acquired'] = self.epoch
+
+ print(defaults)
+
+ obj, created = models.Asset.objects.update_or_create(**defaults)
+
+ if created:
+ tally[1] += 1
+ else:
+ tally[0] += 1
+
+ print('Collections - Updated: {}, Created: {}'.format(tally[0], tally[1]))
diff --git a/assets/management/commands/update_old_db_file.py b/assets/management/commands/update_old_db_file.py
new file mode 100644
index 00000000..bff0fe22
--- /dev/null
+++ b/assets/management/commands/update_old_db_file.py
@@ -0,0 +1,110 @@
+import os
+import datetime
+import xml.etree.ElementTree as ET
+from django.core.management.base import BaseCommand
+from django.conf import settings
+
+
+class Command(BaseCommand):
+ help = 'Imports old db from XML dump'
+
+ epoch = datetime.date(1970, 1, 1)
+
+ def handle(self, *args, **options):
+ # self.update_statuses()
+ # self.update_suppliers()
+ self.update_cable_statuses()
+
+ @staticmethod
+ def xml_path(file):
+ return os.path.join(settings.BASE_DIR, 'data/DB_Dump/{}'.format(file))
+
+ @staticmethod
+ def parse_xml(file):
+ tree = ET.parse(file)
+
+ return tree.getroot()
+
+ def update_statuses(self):
+ file = self.xml_path('TEC_Assets.xml')
+ tree = ET.parse(file)
+ root = tree.getroot()
+
+ # map old status pk to new status pk
+ status_map = {
+ 2: 2,
+ 3: 4,
+ 4: 3,
+ 5: 5,
+ 6: 1
+ }
+
+ for child in root:
+ status = int(child.find('StatusID').text)
+ child.find('StatusID').text = str(status_map[status])
+
+ tree.write(file)
+
+ def update_suppliers(self):
+ old_file = self.xml_path('TEC_Asset_Suppliers.xml')
+ old_tree = ET.parse(old_file)
+ old_root = old_tree.getroot()
+
+ new_file = self.xml_path('TEC_Asset_Suppliers_new.xml')
+ new_tree = ET.parse(new_file)
+ new_root = new_tree.getroot()
+
+ # map old supplier pk to new supplier pk
+ supplier_map = dict()
+
+ def find_in_old(name, root):
+ for child in root:
+ found_id = child.find('Supplier_x0020_Id').text
+ found_name = child.find('Supplier_x0020_Name').text
+
+ if found_name == name:
+ return found_id
+
+ for new_child in new_root:
+ new_id = new_child.find('Supplier_x0020_Id').text
+ new_name = new_child.find('Supplier_x0020_Name').text
+
+ old_id = find_in_old(new_name, old_root)
+
+ supplier_map[int(old_id)] = int(new_id)
+
+ file = self.xml_path('TEC_Assets.xml')
+ tree = ET.parse(file)
+ root = tree.getroot()
+
+ for child in root:
+ try:
+ supplier = int(child.find('Supplier_x0020_Id').text)
+ child.find('Supplier_x0020_Id').text = str(supplier_map[supplier])
+ except AttributeError:
+ pass
+
+ tree.write(file)
+
+ def update_cable_statuses(self):
+ file = self.xml_path('TEC_Cables.xml')
+ tree = ET.parse(file)
+ root = tree.getroot()
+
+ # map old status pk to new status pk
+ status_map = {
+ 0: 7,
+ 1: 3,
+ 3: 2,
+ 4: 5,
+ 6: 6,
+ 7: 1,
+ 8: 4,
+ 9: 2,
+ }
+
+ for child in root:
+ status = int(child.find('Status').text)
+ child.find('Status').text = str(status_map[status])
+
+ tree.write(file)
diff --git a/assets/migrations/0001_initial.py b/assets/migrations/0001_initial.py
new file mode 100644
index 00000000..c1951b19
--- /dev/null
+++ b/assets/migrations/0001_initial.py
@@ -0,0 +1,86 @@
+# Generated by Django 2.0.2 on 2018-02-28 16:06
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Asset',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('asset_id', models.IntegerField()),
+ ('description', models.CharField(max_length=120)),
+ ('serial_number', models.CharField(blank=True, max_length=150, null=True)),
+ ('date_acquired', models.DateField()),
+ ('date_sold', models.DateField(blank=True, null=True)),
+ ('purchase_price', models.IntegerField()),
+ ('salvage_value', models.IntegerField(blank=True, null=True)),
+ ('comments', models.TextField(blank=True, null=True)),
+ ('next_sched_maint', models.DateField(blank=True, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='AssetCategory',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ],
+ options={
+ 'verbose_name': 'Asset Category',
+ 'verbose_name_plural': 'Asset Categories',
+ },
+ ),
+ migrations.CreateModel(
+ name='AssetStatus',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ],
+ options={
+ 'verbose_name': 'Asset Status',
+ 'verbose_name_plural': 'Asset Statuses',
+ },
+ ),
+ migrations.CreateModel(
+ name='Collection',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Supplier',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='category',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetCategory'),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='collection',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Collection'),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='purchased_from',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Supplier'),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='status',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetStatus'),
+ ),
+ ]
diff --git a/assets/migrations/0002_auto_20180301_1654.py b/assets/migrations/0002_auto_20180301_1654.py
new file mode 100644
index 00000000..915a7151
--- /dev/null
+++ b/assets/migrations/0002_auto_20180301_1654.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.2 on 2018-03-01 16:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='asset',
+ name='asset_id',
+ field=models.IntegerField(blank=True),
+ ),
+ ]
diff --git a/assets/migrations/0003_auto_20180301_1700.py b/assets/migrations/0003_auto_20180301_1700.py
new file mode 100644
index 00000000..feef9a6c
--- /dev/null
+++ b/assets/migrations/0003_auto_20180301_1700.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.2 on 2018-03-01 17:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0002_auto_20180301_1654'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='asset',
+ name='purchase_price',
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ ]
diff --git a/assets/migrations/0004_auto_20180301_1711.py b/assets/migrations/0004_auto_20180301_1711.py
new file mode 100644
index 00000000..4c884e93
--- /dev/null
+++ b/assets/migrations/0004_auto_20180301_1711.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.0.2 on 2018-03-01 17:11
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0003_auto_20180301_1700'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='asset',
+ name='collection',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Collection'),
+ ),
+ ]
diff --git a/assets/migrations/0005_auto_20180301_1725.py b/assets/migrations/0005_auto_20180301_1725.py
new file mode 100644
index 00000000..49fae9c6
--- /dev/null
+++ b/assets/migrations/0005_auto_20180301_1725.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.2 on 2018-03-01 17:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0004_auto_20180301_1711'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='asset',
+ name='asset_id',
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ ]
diff --git a/assets/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py b/assets/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py
new file mode 100644
index 00000000..f15341e1
--- /dev/null
+++ b/assets/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py
@@ -0,0 +1,148 @@
+# Generated by Django 2.1.5 on 2019-01-05 19:54
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ replaces = [('assets', '0006_auto_20180728_1451'), ('assets', '0007_auto_20181215_1447'), ('assets', '0008_auto_20181215_1448'), ('assets', '0009_auto_20181215_1640'), ('assets', '0010_auto_20181215_1640'), ('assets', '0011_auto_20181215_1749'), ('assets', '0012_auto_20181215_1813'), ('assets', '0013_asset_parent'), ('assets', '0014_auto_20190103_1615'), ('assets', '0015_auto_20190103_1617'), ('assets', '0016_remove_asset_collection'), ('assets', '0017_delete_collection'), ('assets', '0018_auto_20190103_1708'), ('assets', '0019_auto_20190103_1723'), ('assets', '0020_auto_20190103_1729'), ('assets', '0021_auto_20190105_1156')]
+
+ dependencies = [
+ ('assets', '0005_auto_20180301_1725'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='asset',
+ name='asset_id',
+ field=models.IntegerField(blank=True, null=True, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='asset',
+ name='purchase_price',
+ field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True),
+ ),
+ migrations.AlterField(
+ model_name='asset',
+ name='salvage_value',
+ field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True),
+ ),
+ migrations.AlterField(
+ model_name='asset',
+ name='asset_id',
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='is_cable',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='length',
+ field=models.DecimalField(blank=True, decimal_places=1, max_digits=10, null=True),
+ ),
+ migrations.AlterField(
+ model_name='asset',
+ name='asset_id',
+ field=models.CharField(blank=True, max_length=10, null=True),
+ ),
+ migrations.AlterField(
+ model_name='asset',
+ name='asset_id',
+ field=models.CharField(default='', max_length=10),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='parent',
+ field=models.ForeignKey(blank=True, null=True, on_delete=None, related_name='asset_parent', to='assets.Asset'),
+ ),
+ migrations.RemoveField(
+ model_name='asset',
+ name='collection',
+ ),
+ migrations.DeleteModel(
+ name='Collection',
+ ),
+ migrations.CreateModel(
+ name='Cable',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('asset_id', models.CharField(max_length=10)),
+ ('description', models.CharField(max_length=120)),
+ ('serial_number', models.CharField(blank=True, max_length=150, null=True)),
+ ('date_acquired', models.DateField()),
+ ('date_sold', models.DateField(blank=True, null=True)),
+ ('purchase_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
+ ('salvage_value', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
+ ('comments', models.TextField(blank=True, null=True)),
+ ('next_sched_maint', models.DateField(blank=True, null=True)),
+ ('is_cable', models.BooleanField(default=False)),
+ ('length', models.DecimalField(blank=True, decimal_places=1, help_text='m', max_digits=10, null=True)),
+ ('csa', models.DecimalField(blank=True, decimal_places=2, help_text='mm^2', max_digits=10, null=True)),
+ ('circuits', models.IntegerField(blank=True, null=True)),
+ ('cores', models.IntegerField(blank=True, null=True)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetCategory')),
+ ('parent', models.ForeignKey(blank=True, null=True, on_delete=None, related_name='asset_parent', to='assets.Cable')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Connector',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('description', models.CharField(max_length=80)),
+ ('current_rating', models.DecimalField(decimal_places=2, help_text='Amps', max_digits=10)),
+ ('voltage_rating', models.IntegerField(default=0, help_text='Volts')),
+ ('num_pins', models.IntegerField(blank=True, null=True)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='cable',
+ name='plug',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plug', to='assets.Connector'),
+ ),
+ migrations.AddField(
+ model_name='cable',
+ name='purchased_from',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Supplier'),
+ ),
+ migrations.AddField(
+ model_name='cable',
+ name='socket',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='socket', to='assets.Connector'),
+ ),
+ migrations.AddField(
+ model_name='cable',
+ name='status',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetStatus'),
+ ),
+ migrations.AlterField(
+ model_name='asset',
+ name='comments',
+ field=models.TextField(blank=True, default=''),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='asset',
+ name='serial_number',
+ field=models.CharField(blank=True, default='', max_length=150),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='cable',
+ name='comments',
+ field=models.TextField(blank=True, default=''),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='cable',
+ name='serial_number',
+ field=models.CharField(blank=True, default='', max_length=150),
+ preserve_default=False,
+ ),
+ ]
diff --git a/assets/migrations/0007_auto_20190108_0202_squashed_0014_auto_20191017_2052.py b/assets/migrations/0007_auto_20190108_0202_squashed_0014_auto_20191017_2052.py
new file mode 100644
index 00000000..116fa1d2
--- /dev/null
+++ b/assets/migrations/0007_auto_20190108_0202_squashed_0014_auto_20191017_2052.py
@@ -0,0 +1,176 @@
+# Generated by Django 2.0.13 on 2019-12-04 17:37
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ replaces = [('assets', '0007_auto_20190108_0202'), ('assets', '0008_auto_20191002_1931'), ('assets', '0009_auto_20191008_2148'), ('assets', '0010_auto_20191013_2123'), ('assets', '0011_auto_20191013_2247'), ('assets', '0012_auto_20191014_0012'), ('assets', '0013_auto_20191016_1446'), ('assets', '0014_auto_20191017_2052')]
+
+ dependencies = [
+ ('contenttypes', '0002_remove_content_type_name'),
+ ('assets', '0006_auto_20180728_1451_squashed_0021_auto_20190105_1156'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='asset',
+ name='parent',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_parent', to='assets.Asset'),
+ ),
+ migrations.AlterField(
+ model_name='cable',
+ name='parent',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_parent', to='assets.Cable'),
+ ),
+ migrations.AlterField(
+ model_name='connector',
+ name='voltage_rating',
+ field=models.IntegerField(help_text='Volts'),
+ ),
+ migrations.AlterModelOptions(
+ name='asset',
+ options={'base_manager_name': 'objects'},
+ ),
+ migrations.AlterModelOptions(
+ name='cable',
+ options={'base_manager_name': 'objects'},
+ ),
+ migrations.RemoveField(
+ model_name='asset',
+ name='length',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='asset_id',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='category',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='comments',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='date_acquired',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='date_sold',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='description',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='id',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='is_cable',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='next_sched_maint',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='parent',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='purchase_price',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='purchased_from',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='salvage_value',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='serial_number',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='status',
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='polymorphic_ctype',
+ field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_assets.asset_set+', to='contenttypes.ContentType'),
+ ),
+ migrations.AlterField(
+ model_name='asset',
+ name='asset_id',
+ field=models.CharField(max_length=10, unique=True),
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='plug',
+ ),
+ migrations.RemoveField(
+ model_name='cable',
+ name='socket',
+ ),
+ migrations.AlterModelOptions(
+ name='asset',
+ options={},
+ ),
+ migrations.RemoveField(
+ model_name='asset',
+ name='polymorphic_ctype',
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='circuits',
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='cores',
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='csa',
+ field=models.DecimalField(blank=True, decimal_places=2, help_text='mm^2', max_digits=10, null=True),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='length',
+ field=models.DecimalField(blank=True, decimal_places=1, help_text='m', max_digits=10, null=True),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='plug',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plug', to='assets.Connector'),
+ ),
+ migrations.AddField(
+ model_name='asset',
+ name='socket',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='socket', to='assets.Connector'),
+ ),
+ migrations.DeleteModel(
+ name='Cable',
+ ),
+ migrations.AlterModelOptions(
+ name='asset',
+ options={'ordering': ['asset_id'], 'permissions': (('asset_finance', 'Can see financial data for assets'), ('view_asset', 'Can view an asset'))},
+ ),
+ migrations.AddField(
+ model_name='assetstatus',
+ name='should_show',
+ field=models.BooleanField(default=True, help_text='Should this be shown by default in the asset list.'),
+ ),
+ migrations.AlterModelOptions(
+ name='supplier',
+ options={'permissions': (('view_supplier', 'Can view a supplier'),)},
+ ),
+ ]
diff --git a/assets/migrations/__init__.py b/assets/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/assets/models.py b/assets/models.py
new file mode 100644
index 00000000..7abddfd2
--- /dev/null
+++ b/assets/models.py
@@ -0,0 +1,141 @@
+import re
+from django.core.exceptions import ValidationError
+from django.db import models, connection
+from django.urls import reverse
+
+
+class AssetCategory(models.Model):
+ class Meta:
+ verbose_name = 'Asset Category'
+ verbose_name_plural = 'Asset Categories'
+
+ name = models.CharField(max_length=80)
+
+ def __str__(self):
+ return self.name
+
+
+class AssetStatus(models.Model):
+ class Meta:
+ verbose_name = 'Asset Status'
+ verbose_name_plural = 'Asset Statuses'
+
+ name = models.CharField(max_length=80)
+ should_show = models.BooleanField(default=True, help_text="Should this be shown by default in the asset list.")
+
+ def __str__(self):
+ return self.name
+
+
+class Supplier(models.Model):
+ name = models.CharField(max_length=80)
+
+ class Meta:
+ permissions = (
+ ('view_supplier', 'Can view a supplier'),
+ )
+
+ def get_absolute_url(self):
+ return reverse('supplier_list')
+
+ def __str__(self):
+ return self.name
+
+
+class Connector(models.Model):
+ description = models.CharField(max_length=80)
+ current_rating = models.DecimalField(decimal_places=2, max_digits=10, help_text='Amps')
+ voltage_rating = models.IntegerField(help_text='Volts')
+ num_pins = models.IntegerField(blank=True, null=True)
+
+ def __str__(self):
+ return self.description
+
+
+class Asset(models.Model):
+ class Meta:
+ ordering = ['asset_id']
+ permissions = (
+ ('asset_finance', 'Can see financial data for assets'),
+ ('view_asset', 'Can view an asset')
+ )
+
+ parent = models.ForeignKey(to='self', related_name='asset_parent', blank=True, null=True, on_delete=models.SET_NULL)
+ asset_id = models.CharField(max_length=10, unique=True)
+ description = models.CharField(max_length=120)
+ category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE)
+ status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE)
+ serial_number = models.CharField(max_length=150, blank=True)
+ purchased_from = models.ForeignKey(to=Supplier, on_delete=models.CASCADE, blank=True, null=True)
+ date_acquired = models.DateField()
+ date_sold = models.DateField(blank=True, null=True)
+ purchase_price = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10)
+ salvage_value = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10)
+ comments = models.TextField(blank=True)
+ next_sched_maint = models.DateField(blank=True, null=True)
+
+ # Cable assets
+ is_cable = models.BooleanField(default=False)
+ plug = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='plug', blank=True, null=True)
+ socket = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='socket', blank=True, null=True)
+ length = models.DecimalField(decimal_places=1, max_digits=10, blank=True, null=True, help_text='m')
+ csa = models.DecimalField(decimal_places=2, max_digits=10, blank=True, null=True, help_text='mm^2')
+ circuits = models.IntegerField(blank=True, null=True)
+ cores = models.IntegerField(blank=True, null=True)
+
+ def get_available_asset_id():
+ sql = """
+ SELECT MIN(CAST(a.asset_id AS int))+1
+ FROM assets_asset a
+ LEFT OUTER JOIN assets_asset b ON
+ (CAST(a.asset_id AS int) + 1 = CAST(b.asset_id AS int))
+ WHERE b.asset_id IS NULL AND CAST(a.asset_id AS int) >= %s;
+ """
+ with connection.cursor() as cursor:
+ cursor.execute(sql, [9000])
+ row = cursor.fetchone()
+ if row[0] is None:
+ return 9000
+ else:
+ return row[0]
+
+ def get_absolute_url(self):
+ return reverse('asset_detail', kwargs={'pk': self.asset_id})
+
+ def __str__(self):
+ out = str(self.asset_id) + ' - ' + self.description
+ if self.is_cable:
+ out += '{} - {}m - {}'.format(self.plug, self.length, self.socket)
+ return out
+
+ def clean(self):
+ errdict = {}
+ if self.date_sold and self.date_acquired > self.date_sold:
+ errdict["date_sold"] = ["Cannot sell an item before it is acquired"]
+
+ self.asset_id = self.asset_id.upper()
+ if re.search("^[a-zA-Z0-9]+$", self.asset_id) is None:
+ errdict["asset_id"] = ["An Asset ID can only consist of letters and numbers"]
+
+ if self.purchase_price and self.purchase_price < 0:
+ errdict["purchase_price"] = ["A price cannot be negative"]
+
+ if self.salvage_value and self.salvage_value < 0:
+ errdict["salvage_value"] = ["A price cannot be negative"]
+
+ if self.is_cable:
+ if not self.length or self.length <= 0:
+ errdict["length"] = ["The length of a cable must be more than 0"]
+ if not self.csa or self.csa <= 0:
+ errdict["csa"] = ["The CSA of a cable must be more than 0"]
+ if not self.circuits or self.circuits <= 0:
+ errdict["circuits"] = ["There must be at least one circuit in a cable"]
+ if not self.cores or self.cores <= 0:
+ errdict["cores"] = ["There must be at least one core in a cable"]
+ if self.socket is None:
+ errdict["socket"] = ["A cable must have a socket"]
+ if self.plug is None:
+ errdict["plug"] = ["A cable must have a plug"]
+
+ if errdict != {}: # If there was an error when validation
+ raise ValidationError(errdict)
diff --git a/assets/static/js/csrf.js b/assets/static/js/csrf.js
new file mode 100644
index 00000000..895ce31e
--- /dev/null
+++ b/assets/static/js/csrf.js
@@ -0,0 +1,23 @@
+$.ajaxSetup({
+ beforeSend: function(xhr, settings) {
+ function getCookie(name) {
+ var cookieValue = null;
+ if (document.cookie && document.cookie != '') {
+ var cookies = document.cookie.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ var cookie = jQuery.trim(cookies[i]);
+ // Does this cookie string begin with the name we want?
+ if (cookie.substring(0, name.length + 1) == (name + '=')) {
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+ break;
+ }
+ }
+ }
+ return cookieValue;
+ }
+ if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
+ // Only send the token to relative URLs i.e. locally.
+ xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
+ }
+ }
+});
\ No newline at end of file
diff --git a/assets/templates/asset_create.html b/assets/templates/asset_create.html
new file mode 100644
index 00000000..b0bb8297
--- /dev/null
+++ b/assets/templates/asset_create.html
@@ -0,0 +1,62 @@
+{% extends 'base_assets.html' %}
+{% load widget_tweaks %}
+{% load asset_templatetags %}
+{% block title %}Asset {{ object.asset_id }}{% endblock %}
+
+
+{% block content %}
+
+
+{% if duplicate %}
+
+
+ {% endblock %}
+
+ {% block js%}
+
+ {%endblock%}
diff --git a/assets/templates/asset_list.html b/assets/templates/asset_list.html
new file mode 100644
index 00000000..4c86d58d
--- /dev/null
+++ b/assets/templates/asset_list.html
@@ -0,0 +1,65 @@
+{% extends 'base_assets.html' %}
+{% block title %}Asset List{% endblock %}
+{% load paginator from filters %}
+{% load widget_tweaks %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+ | Asset ID |
+ Description |
+ Category |
+ Status |
+ Quick Links |
+
+
+
+ {% include 'partials/asset_list_table_body.html' %}
+
+
+
+{% if is_paginated %}
+
+ {% paginator %}
+
+{% endif %}
+
+{% endblock %}
+
+{% load static %}
+{% block css %}
+
+
+{% endblock %}
+
+{% block preload_js %}
+
+
+{% endblock %}
diff --git a/assets/templates/asset_update.html b/assets/templates/asset_update.html
new file mode 100644
index 00000000..5a7eec87
--- /dev/null
+++ b/assets/templates/asset_update.html
@@ -0,0 +1,63 @@
+{% extends 'base_assets.html' %}
+{% load widget_tweaks %}
+{% load asset_templatetags %}
+{% block title %}Asset {{ object.asset_id }}{% endblock %}
+
+
+{% block content %}
+
+
+
+
+{% endblock %}
+
+{% block js%}
+{% if edit %}
+
+{% endif %}
+{% endblock %}
diff --git a/assets/templates/asset_update_search_results.html b/assets/templates/asset_update_search_results.html
new file mode 100644
index 00000000..edd156f1
--- /dev/null
+++ b/assets/templates/asset_update_search_results.html
@@ -0,0 +1,16 @@
+{% for asset in object_list %}
+
+ {{ asset.asset_id }} - {{ asset.description }}
+
+
+ {% empty %}
+ No assets match given ID
+{% endfor %}
+
+
\ No newline at end of file
diff --git a/assets/templates/partials/asset_buttons.html b/assets/templates/partials/asset_buttons.html
new file mode 100644
index 00000000..39402cbe
--- /dev/null
+++ b/assets/templates/partials/asset_buttons.html
@@ -0,0 +1,25 @@
+{% if edit and object %}
+
+
+ Duplicate
+{% elif duplicate %}
+
+
+{% elif create %}
+
+
+{% else %}
+
+
+{% endif %}
+{% if create or edit or duplicate %}
+
+
+{% endif %}
diff --git a/assets/templates/partials/asset_form.html b/assets/templates/partials/asset_form.html
new file mode 100644
index 00000000..08f82d18
--- /dev/null
+++ b/assets/templates/partials/asset_form.html
@@ -0,0 +1,61 @@
+{% load widget_tweaks %}
+{% load asset_templatetags %}
+
+
+ Asset Details
+
+
+ {% if create or edit or duplicate %}
+
+
+ {% if duplicate %}
+ {% render_field form.asset_id|add_class:'form-control' value=object.asset_id %}
+ {% elif object.asset_id %}
+ {% render_field form.asset_id|attr:'readonly'|add_class:'disabled_input form-control' value=object.asset_id %}
+ {% else %}
+ {% render_field form.asset_id|add_class:'form-control' %}
+ {% endif %}
+
+
+
+ {% render_field form.description|add_class:'form-control' value=object.description %}
+
+
+
+ {% render_field form.category|add_class:'form-control'%}
+
+ {% render_field form.is_cable|attr:'onchange=checkIfCableHidden()' %}
+
+
+ {% render_field form.status|add_class:'form-control'%}
+
+
+
+ {% render_field form.serial_number|add_class:'form-control' value=object.serial_number %}
+
+
+
+
+ {% render_field form.comments|add_class:'form-control' %}
+
+ {% else %}
+
Asset ID
+
{{ object.asset_id }}
+
+
Description
+
{{ object.description }}
+
+
Category
+
{{ object.category }}
+
+
Status
+
{{ object.status }}
+
+
Serial Number
+
{{ object.serial_number|default:'-' }}
+
+
Comments
+
{{ object.comments|default:'-'|linebreaksbr }}
+ {% endif %}
+
+
diff --git a/assets/templates/partials/asset_list_table_body.html b/assets/templates/partials/asset_list_table_body.html
new file mode 100644
index 00000000..11b9650b
--- /dev/null
+++ b/assets/templates/partials/asset_list_table_body.html
@@ -0,0 +1,33 @@
+{% for item in object_list %}
+ {# {{ item.asset_id }} - {{ item.description }}#}
+
+ | {{ item.asset_id }} |
+ {{ item.description }} |
+ {{ item.category }} |
+ {{ item.status }} |
+
+
+
+ |
+
+{% endfor %}
diff --git a/assets/templates/partials/asset_picker.html b/assets/templates/partials/asset_picker.html
new file mode 100644
index 00000000..fd63fe86
--- /dev/null
+++ b/assets/templates/partials/asset_picker.html
@@ -0,0 +1,65 @@
+
+
+{% load static %}
+{% block css %}
+
+
+{% endblock %}
+
+{% block preload_js %}
+
+
+{% endblock %}
+
+{% block js %}
+{{ js.super }}
+
+{% endblock js %}
\ No newline at end of file
diff --git a/assets/templates/partials/cable_form.html b/assets/templates/partials/cable_form.html
new file mode 100644
index 00000000..9390a73c
--- /dev/null
+++ b/assets/templates/partials/cable_form.html
@@ -0,0 +1,61 @@
+{% load widget_tweaks %}
+{% load asset_templatetags %}
+
+
+ Cable Details
+
+
+ {% if create or edit or duplicate %}
+
+
+ {% render_field form.plug|add_class:'form-control'%}
+
+
+
+ {% render_field form.socket|add_class:'form-control'%}
+
+
+
+
+
+ {% render_field form.circuits|add_class:'form-control' value=object.circuits %}
+
+
+
+ {% render_field form.cores|add_class:'form-control' value=object.cores %}
+
+ {% else %}
+
+ - Socket
+ - {{ object.socket|default_if_none:'-' }}
+
+ - Plug
+ - {{ object.plug|default_if_none:'-' }}
+
+ - Length
+ - {{ object.length|default_if_none:'-' }}m
+
+ - Cross Sectional Area
+ - {{ object.csa|default_if_none:'-' }}m^2
+
+ - Circuits
+ - {{ object.circuits|default_if_none:'-' }}
+
+ - Cores
+ - {{ object.cores|default_if_none:'-' }}
+
+ {% endif %}
+
+
diff --git a/assets/templates/partials/parent_form.html b/assets/templates/partials/parent_form.html
new file mode 100644
index 00000000..5f29240d
--- /dev/null
+++ b/assets/templates/partials/parent_form.html
@@ -0,0 +1,41 @@
+{% load widget_tweaks %}
+{% load asset_templatetags %}
+
diff --git a/assets/templates/partials/purchasedetails_form.html b/assets/templates/partials/purchasedetails_form.html
new file mode 100644
index 00000000..d5979439
--- /dev/null
+++ b/assets/templates/partials/purchasedetails_form.html
@@ -0,0 +1,68 @@
+{% load widget_tweaks %}
+{% load asset_templatetags %}
+
+
+ Purchase Details
+
+
+ {% if create or edit or duplicate %}
+
+
+ {% include 'partials/supplier_picker.html' %}
+
+
+
+
+
+
+
+
+ {% if object.date_acquired%}
+ {% with date_acq=object.date_acquired|date:"Y-m-d" %}
+ {% render_field form.date_acquired|add_class:'form-control'|attr:'type="date"' value=date_acq %}
+ {% endwith %}
+ {% else %}
+
+ {% endif %}
+
+
+
+
+ {% with date_sol=object.form.date_sold|date:"Y-m-d" %}
+ {% render_field form.date_sold|add_class:'form-control'|attr:'type="date"' value=date_sol %}
+ {% endwith %}
+
+ {% else %}
+
+ - Purchased From
+ - {{ object.purchased_from|default_if_none:'-' }}
+
+ - Purchase Price
+ - £{{ object.purchase_price|default_if_none:'-' }}
+
+ - Salvage Value
+ - £{{ object.salvage_value|default_if_none:'-' }}
+
+ - Date Acquired
+ - {{ object.date_acquired|default_if_none:'-' }}
+ {% if object.date_sold %}
+ - Date Sold
+ - {{ object.date_sold|default_if_none:'-' }}
+ {% endif %}
+
+ {% endif %}
+
+
diff --git a/assets/templates/partials/render_field.html b/assets/templates/partials/render_field.html
new file mode 100644
index 00000000..58744530
--- /dev/null
+++ b/assets/templates/partials/render_field.html
@@ -0,0 +1,16 @@
+{% load widget_tweaks %}
+
+
+
+
+ {% if css %}
+ {% render_field field|add_class:css %}
+ {% elif disable_if_filled and field.value %}
+ {% render_field field|attr:'disabled' %}
+ {% elif css and disable_if_filled %}
+ {% render_field field|add_class:css|attr:'disabled' %}
+ {% else %}
+ {{ field }}
+ {% endif %}
+
+
diff --git a/assets/templates/partials/supplier_picker.html b/assets/templates/partials/supplier_picker.html
new file mode 100644
index 00000000..842290b3
--- /dev/null
+++ b/assets/templates/partials/supplier_picker.html
@@ -0,0 +1,64 @@
+
+
+{% load static %}
+{% block css %}
+
+
+{% endblock %}
+
+{% block preload_js %}
+
+
+{% endblock %}
+
+{% block js %}
+{{ js.super }}
+
+{% endblock js %}
diff --git a/assets/templates/supplier_detail.html b/assets/templates/supplier_detail.html
new file mode 100644
index 00000000..7639cf6b
--- /dev/null
+++ b/assets/templates/supplier_detail.html
@@ -0,0 +1,6 @@
+{% extends 'base_assets.html' %}
+{% block title %}Detail{% endblock %}
+
+{% block content %}
+{{ object }}
+{% endblock %}
\ No newline at end of file
diff --git a/assets/templates/supplier_list.html b/assets/templates/supplier_list.html
new file mode 100644
index 00000000..71ceb78c
--- /dev/null
+++ b/assets/templates/supplier_list.html
@@ -0,0 +1,46 @@
+{% extends 'base_assets.html' %}
+{% block title %}Supplier List{% endblock %}
+{% load paginator from filters %}
+{% load widget_tweaks %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+ | Supplier |
+ Quick Links |
+
+
+
+ {% for item in object_list %}
+
+ | {{ item.name }} |
+
+ Edit
+ |
+
+ {% endfor %}
+
+
+
+{% if is_paginated %}
+
+ {% paginator %}
+
+{% endif %}
+
+{% endblock %}
diff --git a/assets/templates/supplier_update.html b/assets/templates/supplier_update.html
new file mode 100644
index 00000000..dfc8cc54
--- /dev/null
+++ b/assets/templates/supplier_update.html
@@ -0,0 +1,20 @@
+{% extends 'base_assets.html' %}
+{% block title %}Edit{% endblock %}
+
+{% block content %}
+
+
+
+{% endblock %}
diff --git a/assets/templatetags/__init__.py b/assets/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/assets/templatetags/asset_templatetags.py b/assets/templatetags/asset_templatetags.py
new file mode 100644
index 00000000..905f9ce2
--- /dev/null
+++ b/assets/templatetags/asset_templatetags.py
@@ -0,0 +1,21 @@
+from django import template
+from django.template.defaultfilters import stringfilter
+from django.utils.safestring import SafeData, mark_safe
+from django.utils.text import normalize_newlines
+from django.utils.html import escape
+
+register = template.Library()
+
+
+@register.filter(is_safe=True, needs_autoescape=True)
+@stringfilter
+def linebreaksn(value, autoescape=True):
+ """
+ Convert all newlines in a piece of plain text to jQuery line breaks
+ (`\n`).
+ """
+ autoescape = autoescape and not isinstance(value, SafeData)
+ value = normalize_newlines(value)
+ if autoescape:
+ value = escape(value)
+ return mark_safe(value.replace('\n', '\\n'))
diff --git a/assets/urls.py b/assets/urls.py
new file mode 100644
index 00000000..fa5facd0
--- /dev/null
+++ b/assets/urls.py
@@ -0,0 +1,22 @@
+from django.urls import path
+from assets import views
+
+from PyRIGS.decorators import permission_required_with_403
+
+urlpatterns = [
+ path('', views.AssetList.as_view(), name='asset_index'),
+ path('asset/list/', views.AssetList.as_view(), name='asset_list'),
+ path('asset/id//', views.AssetDetail.as_view(), name='asset_detail'),
+ path('asset/create/', permission_required_with_403('assets.create_asset')(views.AssetCreate.as_view()), name='asset_create'),
+ path('asset/id//edit/', permission_required_with_403('assets.change_asset')(views.AssetEdit.as_view()), name='asset_update'),
+ path('asset/id//duplicate/', permission_required_with_403('assets.create_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'),
+
+ path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'),
+
+ path('supplier/list', views.SupplierList.as_view(), name='supplier_list'),
+ path('supplier/', views.SupplierDetail.as_view(), name='supplier_detail'),
+ path('supplier/create', permission_required_with_403('assets.create_supplier')(views.SupplierCreate.as_view()), name='supplier_create'),
+ path('supplier//edit', permission_required_with_403('assets.edit_supplier')(views.SupplierUpdate.as_view()), name='supplier_update'),
+
+ path('supplier/search/', views.SupplierSearch.as_view(), name='supplier_search_json'),
+]
diff --git a/assets/views.py b/assets/views.py
new file mode 100644
index 00000000..06449cdb
--- /dev/null
+++ b/assets/views.py
@@ -0,0 +1,205 @@
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.http import JsonResponse
+from django.views import generic
+from django.views.decorators.csrf import csrf_exempt
+from django.utils.decorators import method_decorator
+from django.urls import reverse
+from django.db.models import Q
+from assets import models, forms
+
+
+@method_decorator(csrf_exempt, name='dispatch')
+class AssetList(LoginRequiredMixin, generic.ListView):
+ model = models.Asset
+ template_name = 'asset_list.html'
+ paginate_by = 40
+ ordering = ['-pk']
+ hide_hidden_status = True
+
+ def get_initial(self):
+ initial = {'status': models.AssetStatus.objects.filter(should_show=True)}
+ return initial
+
+ def get_queryset(self):
+ if self.request.method == 'POST':
+ self.form = forms.AssetSearchForm(data=self.request.POST)
+ elif self.request.method == 'GET' and len(self.request.GET) > 0:
+ self.form = forms.AssetSearchForm(data=self.request.GET)
+ else:
+ self.form = forms.AssetSearchForm(data=self.get_initial())
+ form = self.form
+ if not form.is_valid():
+ return self.model.objects.none()
+
+ # TODO Feedback to user when search fails
+ query_string = form.cleaned_data['query'] or ""
+ if len(query_string) == 0:
+ queryset = self.model.objects.all()
+ elif len(query_string) >= 3:
+ queryset = self.model.objects.filter(Q(asset_id__exact=query_string) | Q(description__icontains=query_string))
+ else:
+ queryset = self.model.objects.filter(Q(asset_id__exact=query_string))
+
+ if form.cleaned_data['category']:
+ queryset = queryset.filter(category__in=form.cleaned_data['category'])
+
+ if len(form.cleaned_data['status']) > 0:
+ queryset = queryset.filter(status__in=form.cleaned_data['status'])
+ elif self.hide_hidden_status:
+ queryset = queryset.filter(status__in=models.AssetStatus.objects.filter(should_show=True))
+
+ return queryset
+
+ def get_context_data(self, **kwargs):
+ context = super(AssetList, self).get_context_data(**kwargs)
+ context["form"] = self.form
+
+ context["categories"] = models.AssetCategory.objects.all()
+
+ context["statuses"] = models.AssetStatus.objects.all()
+ return context
+
+
+class AssetSearch(AssetList):
+ hide_hidden_status = False
+
+ def render_to_response(self, context, **response_kwargs):
+ result = []
+
+ for asset in context["object_list"]:
+ result.append({"id": asset.pk, "label": (asset.asset_id + " | " + asset.description)})
+
+ return JsonResponse(result, safe=False)
+
+
+class AssetIDUrlMixin:
+ def get_object(self, queryset=None):
+ pk = self.kwargs.get(self.pk_url_kwarg)
+ queryset = models.Asset.objects.filter(asset_id=pk)
+ try:
+ # Get the single item from the filtered queryset
+ obj = queryset.get()
+ except queryset.model.DoesNotExist:
+ raise Http404(_("No %(verbose_name)s found matching the query") %
+ {'verbose_name': queryset.model._meta.verbose_name})
+ return obj
+
+
+class AssetDetail(LoginRequiredMixin, AssetIDUrlMixin, generic.DetailView):
+ model = models.Asset
+ template_name = 'asset_update.html'
+
+
+class AssetEdit(LoginRequiredMixin, AssetIDUrlMixin, generic.UpdateView):
+ template_name = 'asset_update.html'
+ model = models.Asset
+ form_class = forms.AssetForm
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['edit'] = True
+ context["connectors"] = models.Connector.objects.all()
+
+ return context
+
+ def get_success_url(self):
+ return reverse("asset_detail", kwargs={"pk": self.object.asset_id})
+
+
+class AssetCreate(LoginRequiredMixin, generic.CreateView):
+ template_name = 'asset_create.html'
+ model = models.Asset
+ form_class = forms.AssetForm
+
+ def get_context_data(self, **kwargs):
+ context = super(AssetCreate, self).get_context_data(**kwargs)
+ context["create"] = True
+ context["connectors"] = models.Connector.objects.all()
+
+ return context
+
+ def get_initial(self, *args, **kwargs):
+ initial = super().get_initial(*args, **kwargs)
+ initial["asset_id"] = models.Asset.get_available_asset_id()
+ return initial
+
+ def get_success_url(self):
+ return reverse("asset_detail", kwargs={"pk": self.object.asset_id})
+
+
+class DuplicateMixin:
+ def get(self, request, *args, **kwargs):
+ self.object = self.get_object()
+ self.object.pk = None
+ return self.render_to_response(self.get_context_data())
+
+
+class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate):
+ model = models.Asset
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["create"] = None
+ context["duplicate"] = True
+ context['previous_asset_id'] = self.get_object().asset_id
+ return context
+
+
+class SupplierList(generic.ListView):
+ model = models.Supplier
+ template_name = 'supplier_list.html'
+ paginate_by = 40
+ ordering = ['name']
+
+ def get_queryset(self):
+ if self.request.method == 'POST':
+ self.form = forms.SupplierSearchForm(data=self.request.POST)
+ elif self.request.method == 'GET':
+ self.form = forms.SupplierSearchForm(data=self.request.GET)
+ else:
+ self.form = forms.SupplierSearchForm(data={})
+ form = self.form
+ if not form.is_valid():
+ return self.model.objects.none()
+
+ query_string = form.cleaned_data['query'] or ""
+ if len(query_string) == 0:
+ queryset = self.model.objects.all()
+ else:
+ queryset = self.model.objects.filter(Q(name__icontains=query_string))
+
+ return queryset
+
+ def get_context_data(self, **kwargs):
+ context = super(SupplierList, self).get_context_data(**kwargs)
+ context["form"] = self.form
+ return context
+
+
+class SupplierSearch(SupplierList):
+ hide_hidden_status = False
+
+ def render_to_response(self, context, **response_kwargs):
+ result = []
+
+ for supplier in context["object_list"]:
+ result.append({"id": supplier.pk, "name": supplier.name})
+
+ return JsonResponse(result, safe=False)
+
+
+class SupplierDetail(generic.DetailView):
+ model = models.Supplier
+ template_name = 'supplier_detail.html'
+
+
+class SupplierCreate(generic.CreateView):
+ model = models.Supplier
+ form_class = forms.SupplierForm
+ template_name = 'supplier_update.html'
+
+
+class SupplierUpdate(generic.UpdateView):
+ model = models.Supplier
+ form_class = forms.SupplierForm
+ template_name = 'supplier_update.html'
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 00000000..95b8798b
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+DJANGO_SETTINGS_MODULE = myproject.settings
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index ebe42496..96e43b97 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,6 +4,8 @@ diff-match-patch==20121119
dj-database-url==0.5.0
dj-static==0.0.6
Django==2.0.13
+django-filter==2.0.0
+django-widget-tweaks==1.4.3
django-debug-toolbar==1.9.1
django-ical==1.4
django-recaptcha==1.4.0
@@ -11,7 +13,6 @@ django-registration-redux==2.4
django-reversion==2.0.13
django-toolbelt==0.0.1
premailer==3.2.0
-#django-widget-tweaks==1.4.1
git+git://github.com/jazzband/django-widget-tweaks.git@1.4.2
gunicorn==19.8.1
icalendar==4.0.1
@@ -32,7 +33,8 @@ sqlparse==0.2.4
static3==0.7.0
svg2rlg==0.3
yolk==0.4.3
+whitenoise==4.1.2
z3c.rml==3.5.0
zope.event==4.3.0
zope.interface==4.5.0
-zope.schema==4.5.0
\ No newline at end of file
+zope.schema==4.5.0
diff --git a/templates/400.html b/templates/400.html
index 5dd0c30b..dda8d561 100644
--- a/templates/400.html
+++ b/templates/400.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Bad Request{% endblock %}
diff --git a/templates/401.html b/templates/401.html
index 9b9b019f..7fc7dfe0 100644
--- a/templates/401.html
+++ b/templates/401.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Unauthorized{% endblock %}
diff --git a/templates/403.html b/templates/403.html
index 5f59c351..7fdb8f24 100644
--- a/templates/403.html
+++ b/templates/403.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Forbidden{% endblock %}
diff --git a/templates/404.html b/templates/404.html
index 5b347c1b..5a4ef0bf 100644
--- a/templates/404.html
+++ b/templates/404.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Page Not Found{% endblock %}
diff --git a/templates/500.html b/templates/500.html
index 2fc2db83..26fd115d 100644
--- a/templates/500.html
+++ b/templates/500.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Server error{% endblock %}
diff --git a/templates/base.html b/templates/base.html
index 24cc30bc..53238def 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -43,58 +43,13 @@
- RIGS
+ {% block titleheader %}
+ {% endblock %}
- {% if user.is_authenticated %}
- - Home
- -
- Rigboard
-
-
- {% endif %}
- {% if perms.RIGS.view_invoice %}
- -
- Invoices
-
-
- {% endif %}
- {% if perms.RIGS.view_person %}
- - People
- {% endif %}
- {% if perms.RIGS.view_organisation %}
- - Organisations
- {% endif %}
- {% if perms.RIGS.view_venue %}
- - Venues
- {% endif %}
-
+ {% block titleelements %}
+ {% endblock %}
-
diff --git a/templates/base_assets.html b/templates/base_assets.html
new file mode 100644
index 00000000..7dd53034
--- /dev/null
+++ b/templates/base_assets.html
@@ -0,0 +1,31 @@
+{% extends 'base.html' %}
+{% block titleheader %}
+ RIGS
+ Assets
+{% endblock %}
+
+{% block titleelements %}
+ {% if perms.assets.view_asset%}
+
-
+ Assets
+
+
+ {% endif %}
+ {% if perms.assets.view_supplier%}
+ -
+ Suppliers
+
+
+ {% endif %}
+{% endblock %}
diff --git a/templates/base_rigs.html b/templates/base_rigs.html
new file mode 100644
index 00000000..febaba98
--- /dev/null
+++ b/templates/base_rigs.html
@@ -0,0 +1,54 @@
+{% extends 'base.html' %}
+{% block titleheader %}
+ RIGS
+{% endblock %}
+
+{% block titleelements %}
+ {% if user.is_authenticated %}
+ - Home
+ -
+ Rigboard
+
+
+ {% endif %}
+ {% if perms.RIGS.view_invoice %}
+ -
+ Invoices
+
+
+ {% endif %}
+ {% if perms.RIGS.view_person %}
+ - People
+ {% endif %}
+ {% if perms.RIGS.view_organisation %}
+ - Organisations
+ {% endif %}
+ {% if perms.RIGS.view_venue %}
+ - Venues
+ {% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/login_redirect.html b/templates/login_redirect.html
index d2dcb0b2..6b69cdfd 100644
--- a/templates/login_redirect.html
+++ b/templates/login_redirect.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Login Required{% endblock %}
diff --git a/templates/registration/activation_complete.html b/templates/registration/activation_complete.html
index 4797ef69..5aed33e9 100644
--- a/templates/registration/activation_complete.html
+++ b/templates/registration/activation_complete.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}Activation Complete{% endblock %}
diff --git a/templates/registration/logged_out.html b/templates/registration/logged_out.html
index 47a0abc6..058e2127 100644
--- a/templates/registration/logged_out.html
+++ b/templates/registration/logged_out.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}Logout Successful{% endblock %}
diff --git a/templates/registration/login.html b/templates/registration/login.html
index 5dd85fe7..be02db72 100644
--- a/templates/registration/login.html
+++ b/templates/registration/login.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}Login{% endblock %}
diff --git a/templates/registration/password_change_done.html b/templates/registration/password_change_done.html
index 8db9ea9a..e306a5fa 100644
--- a/templates/registration/password_change_done.html
+++ b/templates/registration/password_change_done.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base_rigs.html" %}
{% load i18n %}
diff --git a/templates/registration/password_change_form.html b/templates/registration/password_change_form.html
index fbe95177..bae74452 100644
--- a/templates/registration/password_change_form.html
+++ b/templates/registration/password_change_form.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base_rigs.html" %}
{% load widget_tweaks %}
diff --git a/templates/registration/password_reset_complete.html b/templates/registration/password_reset_complete.html
index 7543d38f..88bc4d77 100644
--- a/templates/registration/password_reset_complete.html
+++ b/templates/registration/password_reset_complete.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base_rigs.html" %}
{% load i18n %}
diff --git a/templates/registration/password_reset_confirm.html b/templates/registration/password_reset_confirm.html
index 71451fcf..16b43cb0 100644
--- a/templates/registration/password_reset_confirm.html
+++ b/templates/registration/password_reset_confirm.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base_rigs.html" %}
{% load i18n %}
{% load widget_tweaks %}
diff --git a/templates/registration/password_reset_done.html b/templates/registration/password_reset_done.html
index cb0dbf02..062cba6f 100644
--- a/templates/registration/password_reset_done.html
+++ b/templates/registration/password_reset_done.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "base_rigs.html" %}
{% load i18n %}
diff --git a/templates/registration/password_reset_form.html b/templates/registration/password_reset_form.html
index b9358488..6fb3fb8a 100644
--- a/templates/registration/password_reset_form.html
+++ b/templates/registration/password_reset_form.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load i18n %}
{% load widget_tweaks %}
diff --git a/templates/registration/registration_complete.html b/templates/registration/registration_complete.html
index c7b02238..f065ff21 100644
--- a/templates/registration/registration_complete.html
+++ b/templates/registration/registration_complete.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% block title %}Registration complete{% endblock %}
diff --git a/templates/registration/registration_form.html b/templates/registration/registration_form.html
index a73bfaed..5022a161 100644
--- a/templates/registration/registration_form.html
+++ b/templates/registration/registration_form.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}Registration{% endblock %}