Compare commits

...

122 Commits

Author SHA1 Message Date
Matthew Smith
cd73b6cafe Fixed #370 , asset categories that are default selected are now shown 2019-12-04 23:04:31 +00:00
68e977d749 CHANGE: Further tweak colouration
Red is for broken/lost - things we should have but don't
Yellow is for sold/scrapped - things we had but don't have on purpose
Blue is not built yet because... I don't have a reason for that one.
2019-12-04 21:28:03 +00:00
4bf399090b FIX: Update table row colouration for production statuses
TODO: Make the colour defined by the status as opposed to hardcoding it...
2019-12-04 21:11:33 +00:00
c249cf7c60 CHORE: Fix RIGS Sample Data test
I blame @mattysmith22 again ...
2019-12-04 20:47:26 +00:00
da384fbae1 CHORE: Delete duplicate risk assessment migration
Surprisingly, not my fault, it was @mattysmith22
2019-12-04 20:39:00 +00:00
43b7d5178d Merge branch 'master' into assets 2019-12-04 20:28:45 +00:00
e22b288984 CHORE: Fix pep8
Again. Again again.
2019-12-04 20:25:18 +00:00
Matthew Smith
15acc02f74 Switched URL to use asset_id rather than the database ID 2019-12-04 20:13:09 +00:00
Matthew Smith
c4cad76653 Removed unused imports 2019-12-04 19:18:43 +00:00
Matthew Smith
bb44b0c88b Made changes following the review from David Taylor 2019-12-04 19:07:21 +00:00
Matthew Smith
d335ab3a80 Fixed asset picker backend 2019-11-25 13:40:16 +00:00
127b22fe71 Attempt at fancy supplier search
#374
2019-11-24 14:36:53 +00:00
22f0041eaf Add ability to search supplier list. Partial #374 2019-11-24 14:13:47 +00:00
Matthew Smith
0a724e645f Improved colouring of list items so that Lost & Binned are actually visible to the human eye 2019-10-31 19:36:57 +00:00
Matthew Smith
d7e5e61b49 Fixed outdated link to old duplicate 2019-10-31 19:33:37 +00:00
a5da5ad7a8 Remove top right form buttons from create/update
Works on RIGS coz it's just one button. Makes a mess here imo.
2019-10-23 13:11:46 +01:00
7bc47f708c Hide asset list quicklinks on mobile. Partial #336.
Bit of a sledgehammer approach I know...
2019-10-23 13:09:08 +01:00
3d148a7196 Add RIGS hyperlink to assets header. Closes #367.
I'd rather have it further over, but that breaks out of the navbar, and I don't want to move the assets links more right...
2019-10-22 17:56:56 +01:00
b2916c53b5 pep8 compliance, again 2019-10-22 17:34:14 +01:00
Matthew Smith
aa177712b6 Improved validation check on models 2019-10-20 01:07:59 +01:00
Matthew Smith
c8e522d400 Fixed selenium "Object reference chain is too long" exception.
It seems like it couldn't translate the value returned by the script
2019-10-18 02:18:27 +01:00
Matthew Smith
52f13a53a0 Added asset database link to the rigs main page. 2019-10-18 02:18:27 +01:00
Matthew Smith
7864b06ddd Fixed statuses other rhan "In service" not being hidden by default. 2019-10-18 02:18:27 +01:00
Matthew Smith
b39f835a38 Fixed asset permissions 2019-10-18 02:18:27 +01:00
9f9ccd3b0c Fix sample asset data command to pass pep8 2019-10-17 11:44:48 +01:00
Matthew Smith
1b12953803 Improved asset DB's sample data generation
* Now  gets IDs from the model's `get_available_asset_id()` function.
* `parent` field is also now generated for most (25%) assets and cables
* Cables are now generated
2019-10-17 11:10:19 +01:00
ed956f975f Allow asset sample data command in staging 2019-10-16 23:41:14 +01:00
Matthew Smith
9495fa2fdb Merged all sample data generation. 2019-10-16 21:47:45 +01:00
Matthew Smith
9de6c1046c Fixed date persistence, Fixes #362 2019-10-16 20:31:23 +01:00
Matthew Smith
3079ee0814 Select now store selections between forms.Partial: #362 2019-10-16 20:06:20 +01:00
Matthew Smith
3de58740c6 Added multi-option search along.
Added auto hiding of categories in search unless selected.
Added field allowing to choose whether category auto-hidden

Fixes #353
2019-10-16 18:23:16 +01:00
Matthew Smith
2d84a598f7 Fixes #357, removes the ability to delete assets 2019-10-16 14:11:20 +01:00
Matthew Smith
8ec27eb075 Added asset_id autofill for asset creation and duplication 2019-10-15 01:13:14 +01:00
64439ff53b Merge assetforms -> main assets branch 2019-10-14 14:16:24 +01:00
Matthew Smith
85a20dac26 Assets now conforms to pep8 2019-10-14 14:07:06 +01:00
Matthew Smith
743d290405 Assets are now ordered by asset_id. Fixes #355 2019-10-14 00:19:38 +01:00
Matthew Smith
d2ba39bc60 Merge of cable additions 2019-10-14 00:13:00 +01:00
Matthew Smith
aac900318e Cables are now a thing, unfortunately by making a monolithic Asset model
Co-authored-by: Arona Jones <aj@aronajones.com>
2019-10-14 00:05:18 +01:00
f5fc41ce30 Initial work on asset DB perms
Partial #352.
2019-10-11 17:41:48 +01:00
Matthew Smith
b7e14b7dc3 Added form validation
* Asset IDs must be unique
* Asset IDs that are legacy and unchange have no validation applied
* Asset IDs must be alphanumeric
* Values must be >=0
* Lengths, CSAs, Circuits and Cores of cables must all be >0
2019-10-08 22:34:03 +01:00
1d6208414f Fix old duplicate link 2019-10-08 18:32:49 +01:00
5c9d7466c2 Change asset buttons - added cancel button, moved stuff 2019-10-08 18:01:15 +01:00
dc68a1f808 Improve handling of really long strings
Partially resolves #350, my table fix/hack doesn't work on mobile.
2019-10-08 17:51:33 +01:00
1811cc98ba Oops. Create is an edit state... 2019-10-08 17:04:10 +01:00
1b124a9da9 Partialize alllllll the things
Split each form section into its own partial which handles both the form and display view of that section.
2019-10-08 16:49:10 +01:00
bfd4a380f3 Wrap cableform input groups 2019-10-08 13:56:56 +01:00
f9c851b960 Sod it. Cable form display now depends only on checkbox state 2019-10-08 13:53:24 +01:00
Matthew Smith
f8a9062b8e Merge branch 'asset-assetforms' of https://github.com/nottinghamtec/PyRIGS into asset-assetforms 2019-10-08 01:34:01 +01:00
Matthew Smith
fe1541acbf Cables can now be created. 2019-10-08 01:33:38 +01:00
d30bd9850d Align text against buttons (middle) in asset list 2019-10-07 16:46:15 +01:00
b8b82c0970 Prettify search/filter a bit 2019-10-07 16:41:52 +01:00
Matthew Smith
98b9697366 Temporary fix to cable_form tag. No cable functionality has actually been added, that can be done once the frontend is in place to handle it. 2019-10-07 10:45:23 +01:00
Matthew Smith
85db26bdb1 Changed functionality of asset search - now IDs are always searched even if the string is under 3 characters in length 2019-10-06 01:13:26 +01:00
Matthew Smith
7b795ac332 Fixed some bugs with search 2019-10-06 01:03:41 +01:00
Matthew Smith
d944f99e1f Fixed cable form fields not appearing, and made the 'cable details' group appearing more reliable 2019-10-05 23:19:22 +01:00
a22956cc62 Initial work on cable template things 2019-10-05 22:42:21 +01:00
Matthew Smith
3d5272d722 Fixed incorrecty data being sent to the date_sold field in the asset_update template. Fixes last of dates not being passed around correctly 2019-10-05 21:58:49 +01:00
Matthew Smith
1c06bf3ba4 Merge branch 'asset-assetforms' of https://github.com/nottinghamtec/PyRIGS into asset-assetforms 2019-10-05 21:16:58 +01:00
Matthew Smith
d5f08a8bff Started adding functionality for assets and cables forms to be dynamically swapped 2019-10-05 21:16:42 +01:00
Matthew Smith
7359e05427 Started adding functionality for assets and cables forms to be dynamically swapped 2019-10-05 21:16:03 +01:00
366d9c4130 Label the collection 'set parent' field as such 2019-10-05 20:33:03 +01:00
Matthew Smith
e3cbcbd151 Fixed search and implemented an asset search api
Added asset picker for parent attribute

Co-authored-by: Panagiotis Petridis <lPanagiotisPetridisl@gmail.com>
2019-10-05 20:18:07 +01:00
Matthew Smith
1d253aa452 Fixed edit always being invalid due to always failing form validation 2019-10-05 15:08:40 +01:00
Matthew Smith
3aa3498d3a Split out create and duplicate templates, got both working 2019-10-05 14:59:15 +01:00
Matthew Smith
a4adc8bc7b Now asset_ids are going to be editable, removed that limitation from form 2019-10-05 14:54:19 +01:00
Matthew Smith
769137fabd Started work on improving asset form update and validation - requires de-spaghettifying of asset_update.html before continuing 2019-10-05 01:44:54 +01:00
5bfa737f6b First pass at filters
They're currently exclusive with each other and search, which ain't quite right!

Also stopped search from searching comments.
2019-10-02 22:49:54 +01:00
Matthew Smith
8599a2ae5b Improved handling of asset ID when searching 2019-10-02 21:54:39 +01:00
Matthew Smith
63eb0fc3ce Merge branch 'assets' of https://github.com/nottinghamtec/PyRIGS into assets 2019-10-02 21:14:03 +01:00
Matthew Smith
207bf7bd7a Added dependency django-polymorphic
Model change to take advantage of said polymorphism
Implemented asset search
2019-10-02 21:12:24 +01:00
e591ebe05a Show purchase and collection details side by side
Not sure if I like this or not. Maybe collection should start collapsed, too.
2019-10-02 19:38:05 +01:00
3d7fc18fb7 Fix unclosed tags in asset_update 2019-10-02 18:58:52 +01:00
6c68343653 Add status colors to asset list 2019-10-02 18:34:34 +01:00
5cb668fd7a Fix delete confirmation modal styling 2019-10-02 17:40:21 +01:00
Matthew Smith
7aba164a46 Added .pytest_cache to gitignore 2019-10-02 17:32:57 +01:00
42a63f035c Asset detail view button improvements 2019-10-02 17:17:35 +01:00
15113c4fbc Fix asset list quicklinks 2019-10-02 16:51:45 +01:00
721599d35e Delete Materialize 2019-10-02 12:49:43 +01:00
warlordjones
9bcff7cd5f Supplier bootstrap port 2019-10-02 12:40:55 +01:00
warlordjones
f3c7f89f31 Initial bootstrap port of asset_update 2019-10-02 12:08:52 +01:00
Matthew Smith
8c60f6cb1d created separate base for RIGS and assets.
Fixed create asset form
2019-10-01 22:46:48 +01:00
warlordjones
a9a05b06fa Begin bootstrap port 2019-10-01 19:59:02 +01:00
warlordjones
d89d449ab1 Fix sample data generator 2019-10-01 19:22:15 +01:00
warlordjones
36ac122f7a Add requirements from assets
Only added new requirements rather than version matching dependencies RIGS already required
2019-10-01 18:45:59 +01:00
Matthew Smith
6cff7c090c Modified configs so that templates are rendered. Functionality and styling are NOT complete (need to change css framework) 2019-10-01 18:10:34 +01:00
Matthew Smith
1df971bbba Updated RIGS configs to allow for extra assets info 2019-10-01 13:29:52 +01:00
Matthew Smith
39e3898c7d Merge branch 'master' of PyAssets into assets 2019-10-01 13:24:47 +01:00
Matthew Smith
1e1f3037f8 Preparing files for transfer to PyRIGS 2019-10-01 13:22:16 +01:00
Harry Bridge
5aafa36d2a Tidy up logic for which links to show, made comments duplicate 2019-01-31 20:03:49 +00:00
Harry Bridge
1ef60943ea Put duplication into one view 2019-01-31 20:03:08 +00:00
Johnathan Graydon
9480f697ff Merge branch 'master' into duplicate_asset 2019-01-13 16:49:03 +00:00
Johnathan Graydon
7478068a14 Fix defaults, will close #3
Moved to template now model as it both fixed the bug and consolidated edit points
2019-01-13 16:30:45 +00:00
Johnathan Graydon
c0a7e962b7 Add asset duplication 2019-01-13 15:28:37 +00:00
Johnathan Graydon
2717133d1c Style adjustments 2019-01-12 16:25:59 +00:00
Johnathan Graydon
c03902d76b Add minor customisation to backend 2019-01-12 15:46:48 +00:00
Johnathan Graydon
932739b505 Add default for acquired and status to base model
Will close #3
2019-01-12 15:27:28 +00:00
Harry Bridge
5e15622cdf Migration for some changes that happened in the past 2019-01-11 22:52:35 +00:00
Harry Bridge
393d3a1f49 Made asset list title less massive 2019-01-08 00:23:29 +00:00
Harry Bridge
905eea7792 Updated sample data creation 2019-01-08 00:22:46 +00:00
Harry Bridge
f24a510a8e Added crud for suppliers 2019-01-08 00:21:58 +00:00
Harry Bridge
51aec6f597 Added basic api 2019-01-05 22:52:39 +00:00
Harry Bridge
d8440ad9e5 Updated admin and settings 2019-01-05 20:05:41 +00:00
Harry Bridge
ef8ddf3642 Removed old template 2019-01-05 20:05:14 +00:00
Harry Bridge
779fc42f26 Added views 2019-01-05 20:04:58 +00:00
Harry Bridge
e8fcfd3a50 Sass updates 2019-01-05 20:01:19 +00:00
Harry Bridge
b72bc63560 Templatetags for better field rendering 2019-01-05 20:01:02 +00:00
Harry Bridge
fbfd8f272b Working asset edit/create template 2019-01-05 20:00:00 +00:00
Harry Bridge
9f3c1f7f3c Updated asset list 2019-01-05 19:59:05 +00:00
Harry Bridge
502a2c4e0e Importer scripts for old asset DB 2019-01-05 19:58:17 +00:00
Harry Bridge
a4cfe17f74 Migrations for model updates 2019-01-05 19:57:31 +00:00
Harry Bridge
5ec0a179eb Started on PAT models 2019-01-05 19:56:43 +00:00
Harry Bridge
c418678fe2 Updated asset model to remove collections 2019-01-05 19:56:25 +00:00
Harry Bridge
4c11371f0c Remove unnecessary JS files 2018-07-16 14:40:51 +01:00
Harry Bridge
47c46f4cc4 Updated new template locations 2018-07-16 00:30:39 +01:00
Harry Bridge
44d84f10c9 Moved some templates around and added styling 2018-07-16 00:28:42 +01:00
Harry Bridge
116b228de3 Added materialize sass 2018-07-16 00:27:47 +01:00
Harry Bridge
0e8a23733f Working CRUD views 2018-03-06 14:02:24 +00:00
Harry Bridge
9fb74ecc9e Updated models and added post-save hook 2018-03-06 14:01:22 +00:00
Harry Bridge
e2a37beb83 Added sample data commands 2018-03-06 14:00:11 +00:00
Harry Bridge
680570696c User creation management commands 2018-02-28 16:10:18 +00:00
Harry Bridge
37d5ecf89a Initial Models 2018-02-28 16:10:02 +00:00
Harry Bridge
6072072b61 Initial commit 2018-02-28 16:09:34 +00:00
97 changed files with 2577 additions and 344 deletions

2
.gitignore vendored
View File

@@ -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/

View File

@@ -58,6 +58,7 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'RIGS',
'assets',
'debug_toolbar',
'registration',

View File

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

View File

@@ -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')

View File

@@ -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())

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load static %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% block title %}Event Archive{% endblock %}

View File

@@ -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 %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load widget_tweaks %}
{% load static %}
{% load multiply from filters %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% load static %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}RIGS{% endblock %}
{% block content %}
@@ -20,6 +20,7 @@
<a class="list-group-item" href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span> Rigboard</a>
<a class="list-group-item" href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span> Calendar</a>
{% if perms.RIGS.add_event %}<a class="list-group-item" href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span> New Event</a>{% endif %}
<a class="list-group-item" href="{% url 'asset_index' %}"><span class="glyphicon glyphicon-tag"></span> Asset Database </a>
<div class="list-group-item default"></div>

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}Invoice {{ object.pk }}{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% block title %}Invoices{% endblock %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}Update Profile {{object.name}}{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}Rigboard{% endblock %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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

View File

@@ -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')

0
assets/__init__.py Normal file
View File

37
assets/admin.py Normal file
View File

@@ -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'

5
assets/apps.py Normal file
View File

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

9
assets/filters.py Normal file
View File

@@ -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']

30
assets/forms.py Normal file
View File

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

View File

View File

View File

@@ -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()

View File

@@ -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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

141
assets/models.py Normal file
View File

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

23
assets/static/js/csrf.js Normal file
View File

@@ -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'));
}
}
});

View File

@@ -0,0 +1,62 @@
{% extends 'base_assets.html' %}
{% load widget_tweaks %}
{% load asset_templatetags %}
{% block title %}Asset {{ object.asset_id }}{% endblock %}
{% block content %}
<div class="page-header">
<h1>
{% if duplicate %}
Duplication of Asset: {{ previous_asset_id }}
{% else %}
Create Asset
{% endif %}
</h1>
</div>
{% if duplicate %}
<form method="post" id="asset_update_form" action="{% url 'asset_duplicate' pk=previous_asset_id%}">
{% else %}
<form method="post" id="asset_update_form" action="{% url 'asset_create'%}">
{% endif %}
{% include 'form_errors.html' %}
{% csrf_token %}
<input type="hidden" name="id" value="{{ object.id|default:0 }}" hidden=true>
<div class="row">
<div class="col-sm-12">
{% include 'partials/asset_form.html' %}
</div>
</div>
<div class="row">
<div class="col-md-6">
{% include 'partials/purchasedetails_form.html' %}
</div>
<div class="col-md-6" hidden="true" id="cable-table">
{% include 'partials/cable_form.html' %}
</div>
<div class="col-md-4">
{% include 'partials/parent_form.html' %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% include 'partials/asset_buttons.html' %}
</div>
</div>
</form>
{% endblock %}
{% block js%}
<script>
function checkIfCableHidden() {
if (document.getElementById("id_is_cable").checked) {
document.getElementById("cable-table").hidden = false;
} else {
document.getElementById("cable-table").hidden = true;
}
}
checkIfCableHidden();
</script>
{%endblock%}

View File

@@ -0,0 +1,65 @@
{% extends 'base_assets.html' %}
{% block title %}Asset List{% endblock %}
{% load paginator from filters %}
{% load widget_tweaks %}
{% block content %}
<div class="page-header">
<h1 class="text-center">Asset List</h1>
</div>
<form id="asset-search-form" method="get" class="form-inline pull-right">
<div class="input-group pull-right" style="width: auto;">
{% render_field form.query|add_class:'form-control' placeholder='Search by Asset ID/Description' style="width: 250px"%}
<label for="query" class="sr-only">Asset ID/Description:</label>
<span class="input-group-btn"><button type="submit" class="btn btn-default">Search</button></span>
</div>
<br>
<div style="margin-top: 1em;" class="pull-right">
<div class="form-group">
<label for="category" class="sr-only">Category</label>
{% render_field form.category|attr:'multiple'|add_class:'form-control selectpicker' data-none-selected-text="Categories" data-header="Categories" data-actions-box="true" %}
</div>
<div class="form-group">
<label for="status" class="sr-only">Status</label>
{% render_field form.status|attr:'multiple'|add_class:'form-control selectpicker' data-none-selected-text="Statuses" data-header="Statuses" data-actions-box="true" %}
</div>
<!---TODO: Auto filter whenever an option is selected, instead of using a button -->
<button type="submit" class="btn btn-default">Filter</button>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>Asset ID</th>
<th>Description</th>
<th>Category</th>
<th>Status</th>
<th class="hidden-xs">Quick Links</th>
</tr>
</thead>
<tbody id="asset_table_body">
{% include 'partials/asset_list_table_body.html' %}
</tbody>
</table>
{% if is_paginated %}
<div class="text-center">
{% paginator %}
</div>
{% endif %}
{% endblock %}
{% load static %}
{% block css %}
<link rel="stylesheet" href="{% static "css/bootstrap-select.min.css" %}"/>
<link rel="stylesheet" href="{% static "css/ajax-bootstrap-select.css" %}"/>
{% endblock %}
{% block preload_js %}
<script src="{% static "js/bootstrap-select.js" %}"></script>
<script src="{% static "js/ajax-bootstrap-select.js" %}"></script>
{% endblock %}

View File

@@ -0,0 +1,63 @@
{% extends 'base_assets.html' %}
{% load widget_tweaks %}
{% load asset_templatetags %}
{% block title %}Asset {{ object.asset_id }}{% endblock %}
{% block content %}
<div class="page-header">
<h1>
{% if edit and object %}
Edit Asset: {{ object.asset_id }}
{% else %}
Asset: {{ object.asset_id }}
{% endif %}
</h1>
</div>
<form method="post" id="asset_update_form" action="{% url 'asset_update' pk=object.asset_id%}">
{% include 'form_errors.html' %}
{% csrf_token %}
<input type="hidden" name="id" value="{{ object.id|default:0 }}" hidden=true>
<div class="row">
<div class="col-sm-12">
{% include 'partials/asset_form.html' %}
</div>
</div>
<div class="row">
{% if perms.asset.asset_financial %}
<div class="col-md-6">
{% include 'partials/purchasedetails_form.html' %}
</div>
{%endif%}
<div class="col-md-6"
{% if not object.is_cable %} hidden="true" {% endif %} id="cable-table">
{% include 'partials/cable_form.html' %}
</div>
<div class="col-md-4">
{% include 'partials/parent_form.html' %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% include 'partials/asset_buttons.html' %}
</div>
</div>
</form>
{% endblock %}
{% block js%}
{% if edit %}
<script>
function checkIfCableHidden() {
if (document.getElementById("id_is_cable").checked) {
document.getElementById("cable-table").hidden = false;
} else {
document.getElementById("cable-table").hidden = true;
}
}
checkIfCableHidden();
</script>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% for asset in object_list %}
<a href="javascript:void(0);" onclick="insertInParentField({{ asset.pk }}, '{{ asset }}')">
{{ asset.asset_id }} - {{ asset.description }}
</a>
<br>
{% empty %}
No assets match given ID
{% endfor %}
<script>
function insertInParentField(pk, str) {
$('#hidden_parent_id').val(pk);
$('#parent_id').val(str);
M.updateTextFields();
}
</script>

View File

@@ -0,0 +1,25 @@
{% if edit and object %}
<!--edit-->
<button type="submit" class="btn btn-success"><i class="glyphicon glyphicon-floppy-disk"></i> Save</button>
<a class="btn btn-default" href="{% url 'asset_duplicate' object.pk %}"><i class="glyphicon glyphicon-duplicate"></i> Duplicate</a>
{% elif duplicate %}
<!--duplicate-->
<button type="submit" class="btn btn-default"><i class="glyphicon glyphicon-ok-sign"></i> Create Duplicate</button>
{% elif create %}
<!--create-->
<button type="submit" class="btn btn-success"><i class="glyphicon glyphicon-floppy-disk"></i> Save</button>
{% else %}
<!--detail view-->
<div class="btn-group">
<a href="{% url 'asset_update' object.asset_id %}" class="btn btn-default"><i class="glyphicon glyphicon-edit"></i> Edit</a>
<a class="btn btn-default" href="{% url 'asset_duplicate' object.asset_id %}"><i class="glyphicon glyphicon-duplicate"></i> Duplicate</a>
</div>
{% endif %}
{% if create or edit or duplicate %}
<br>
<button type="reset" class="btn btn-link" onclick="
{%if duplicate%}
{% url 'asset_detail' previous_asset_id %}
{%else%}
history.back(){%endif%}">Cancel</button>
{% endif %}

View File

@@ -0,0 +1,61 @@
{% load widget_tweaks %}
{% load asset_templatetags %}
<div class="panel panel-default">
<div class="panel-heading">
Asset Details
</div>
<div class="panel-body">
{% if create or edit or duplicate %}
<div class="form-group">
<label for="{{ form.asset_id.id_for_label }}">Asset ID</label>
{% 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 %}
</div>
<div class="form-group">
<label for="{{ form.description.id_for_label }}">Description</label>
{% render_field form.description|add_class:'form-control' value=object.description %}
</div>
<div class="form-group">
<label for="{{ form.category.id_for_label }}" >Category</label>
{% render_field form.category|add_class:'form-control'%}
</div>
{% render_field form.is_cable|attr:'onchange=checkIfCableHidden()' %} <label for="{{ form.is_cable.id_for_label }}">Cable?</label>
<div class="form-group">
<label for="{{ form.status.id_for_label }}" >Status</label>
{% render_field form.status|add_class:'form-control'%}
</div>
<div class="form-group">
<label for="{{ form.serial_number.id_for_label }}">Serial Number</label>
{% render_field form.serial_number|add_class:'form-control' value=object.serial_number %}
</div>
<!---TODO: Lower default number of lines in comments box-->
<div class="form-group">
<label for="{{ form.comments.id_for_label }}">Comments</label>
{% render_field form.comments|add_class:'form-control' %}
</div>
{% else %}
<dt>Asset ID</dt>
<dd>{{ object.asset_id }}</dd>
<dt>Description</dt>
<dd style="overflow-wrap: break-word;">{{ object.description }}</dd>
<dt>Category</dt>
<dd>{{ object.category }}</dd>
<dt>Status</dt>
<dd>{{ object.status }}</dd>
<dt>Serial Number</dt>
<dd>{{ object.serial_number|default:'-' }}</dd>
<dt>Comments</dt>
<dd style="overflow-wrap: break-word;">{{ object.comments|default:'-'|linebreaksbr }}</dd>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,33 @@
{% for item in object_list %}
{# <li><a href="{% url 'asset_detail' item.pk %}">{{ item.asset_id }} - {{ item.description }}</a></li>#}
<!---TODO: When the ability to filter the list is added, remove the colours from the filter - specifically, stop greying out sold/binned stuff if it is being searched for--> <tr class="
{% if item.status.name == 'Broken' %}
danger
{% elif item.status.name == 'Scrapped'%}
warning
{% elif item.status.name == 'Sold'%}
warning
{% elif item.status.name == 'Lost'%}
danger
{% elif item.status.name == 'Not Built Yet'%}
info
{% elif item.status.name == 'Active'%}
success
{% endif %}
">
<td style="vertical-align: middle;"><a href="{% url 'asset_detail' item.asset_id %}">{{ item.asset_id }}</a></td>
<td style="vertical-align: middle; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; max-width: 25vw">{{ item.description }}</td>
<td style="vertical-align: middle;">{{ item.category }}</td>
<td style="vertical-align: middle;">{{ item.status }}</td>
<td class="hidden-xs">
<div class="btn-group" role="group">
<a type="button" class="btn btn-default btn-sm" href="{% url 'asset_detail' item.asset_id %}"><i class="glyphicon glyphicon-eye-open"></i> View</a>
{% if perms.assets.change_asset %}
<a type="button" class="btn btn-default btn-sm" href="{% url 'asset_update' item.asset_id %}"><i class="glyphicon glyphicon-edit"></i> Edit</a>
<a type="button" class="btn btn-default btn-sm" href="{% url 'asset_duplicate' item.asset_id %}"><i class="glyphicon glyphicon-duplicate"></i> Duplicate</a>
{% endif %}
</div>
</td>
</tr>
{% endfor %}

View File

@@ -0,0 +1,65 @@
<select name="parent" id="parent_id" class="selectpicker">
{% if object.parent%}
<option value="{{object.parent.pk}}" selected>{{object.parent.description}}</option>
{% endif %}
</select>
{% load static %}
{% block css %}
<link rel="stylesheet" href="{% static "css/bootstrap-select.min.css" %}"/>
<link rel="stylesheet" href="{% static "css/ajax-bootstrap-select.css" %}"/>
{% endblock %}
{% block preload_js %}
<script src="{% static "js/bootstrap-select.js" %}"></script>
<script src="{% static "js/ajax-bootstrap-select.js" %}"></script>
{% endblock %}
{% block js %}
{{ js.super }}
<script>
$('#parent_id')
.selectpicker({
liveSearch: true
})
.ajaxSelectPicker({
ajax: {
url: '{% url 'asset_search_json'%}',
type: "get",
data: function () {
var params = {
{% verbatim %}query: '{{{q}}}'{% endverbatim %}
};
return params;
}
},
locale: {
emptyTitle: 'Search for item...'
},
preprocessData: function(data){
var assets = [];
if(data.length){
var len = data.length;
for(var i = 0; i < len; i++){
var curr = data[i];
assets.push(
{
'value': curr.id,
'text': curr.label,
'disabled': false
}
);
}
assets.push(
{
'value': null,
'text': "No parent"
});
}
return assets;
},
preserveSelected: false
});
</script>
{% endblock js %}

View File

@@ -0,0 +1,61 @@
{% load widget_tweaks %}
{% load asset_templatetags %}
<div class="panel panel-default">
<div class="panel-heading">
Cable Details
</div>
<div class="panel-body">
{% if create or edit or duplicate %}
<div class="form-group">
<label for="{{ form.plug.id_for_label }}">Plug</label>
{% render_field form.plug|add_class:'form-control'%}
</div>
<div class="form-group">
<label for="{{ form.socket.id_for_label }}">Socket</label>
{% render_field form.socket|add_class:'form-control'%}
</div>
<div class="form-group">
<label for="{{ form.length.id_for_label }}">Length</label>
<div class="input-group">
{% render_field form.length|add_class:'form-control' %}
<span class="input-group-addon">{{ form.length.help_text }}</span>
</div>
</div>
<div class="form-group">
<label for="{{ form.csa.id_for_label }}">Cross Sectional Area</label>
<div class="input-group">
{% render_field form.csa|add_class:'form-control' value=object.csa %}
<span class="input-group-addon">{{ form.csa.help_text }}</span>
</div>
</div>
<div class="form-group">
<label for="{{ form.circuits.id_for_label }}">Circuits</label>
{% render_field form.circuits|add_class:'form-control' value=object.circuits %}
</div>
<div class="form-group">
<label for="{{ form.cores.id_for_label }}">Cores</label>
{% render_field form.cores|add_class:'form-control' value=object.cores %}
</div>
{% else %}
<dl>
<dt>Socket</dt>
<dd>{{ object.socket|default_if_none:'-' }}</dd>
<dt>Plug</dt>
<dd>{{ object.plug|default_if_none:'-' }}</dd>
<dt>Length</dt>
<dd>{{ object.length|default_if_none:'-' }}m</dd>
<dt>Cross Sectional Area</dt>
<dd>{{ object.csa|default_if_none:'-' }}m^2</dd>
<dt>Circuits</dt>
<dd>{{ object.circuits|default_if_none:'-' }}</dd>
<dt>Cores</dt>
<dd>{{ object.cores|default_if_none:'-' }}</dd>
</dl>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,41 @@
{% load widget_tweaks %}
{% load asset_templatetags %}
<div class="panel panel-default">
<div class="panel-heading">
Collection Details
</div>
<div class="panel-body">
{% if create or edit or duplicate %}
<div class="form-group">
<label for="selectpicker">Set Parent</label>
{% include 'partials/asset_picker.html' %}
</div>
{% else %}
<dl>
<dt>Parent</dt>
<dd>
{% if object.parent %}
<a href="{% url 'asset_detail' object.parent.asset_id %}">
{{ object.parent.asset_id }} - {{ object.parent.description }}
</a>
{% else %}
<span>-</span>
{% endif %}
</dd>
<dt>Children</dt>
{% if object.asset_parent.all %}
{% for child in object.asset_parent.all %}
<dd>
<a href="{% url 'asset_detail' child.asset_id %}">
{{ child.asset_id }} - {{ child.description }}
</a>
</dd>
{% endfor %}
{% else %}
<dd><span>-</span></dd>
{% endif %}
</dl>
{% endif%}
</div>
</div>

View File

@@ -0,0 +1,68 @@
{% load widget_tweaks %}
{% load asset_templatetags %}
<div class="panel panel-default">
<div class="panel-heading">
Purchase Details
</div>
<div class="panel-body">
{% if create or edit or duplicate %}
<div class="form-group">
<label for="{{ form.purchased_from.id_for_label }}">Purchased From</label>
{% include 'partials/supplier_picker.html' %}
</div>
<div class="form-group">
<label for="{{ form.purchase_price.id_for_label }}">Purchase Price</label>
<div class="input-group">
<span class="input-group-addon">£</span>
{% render_field form.purchase_price|add_class:'form-control' value=object.purchase_price %}
</div>
</div>
<div class="form-group">
<label for="{{ form.salvage_value.id_for_label }}">Salvage Value</label>
<div class="input-group">
<span class="input-group-addon">£</span>
{% render_field form.salvage_value|add_class:'form-control' value=object.salvage_value %}
</div>
</div>
<div class="form-group">
<label for="{{ form.date_acquired.id_for_label }}" >Date Acquired</label>
{% 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 %}
<input type="date" name="date_acquired" value="{% now "Y-m-d" %}"
class="form-control" id="id_date_acquired">
{% endif %}
</div>
<div class="form-group">
<label for="{{ form.date_sold.id_for_label }}">Date Sold</label>
{% 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 %}
</div>
{% else %}
<dl>
<dt>Purchased From</dt>
<dd>{{ object.purchased_from|default_if_none:'-' }}</dd>
<dt>Purchase Price</dt>
<dd>£{{ object.purchase_price|default_if_none:'-' }}</dd>
<dt>Salvage Value</dt>
<dd>£{{ object.salvage_value|default_if_none:'-' }}</dd>
<dt>Date Acquired</dt>
<dd>{{ object.date_acquired|default_if_none:'-' }}</dd>
{% if object.date_sold %}
<dt>Date Sold</dt>
<dd>{{ object.date_sold|default_if_none:'-' }}</dd>
{% endif %}
</dl>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,16 @@
{% load widget_tweaks %}
<!---TODO: Assign form-control class in here--->
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ label|default:field.label }}</label>
{% 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 %}
<span class="helper-text" data-error="{{ field.errors.text }}"></span>
</div>

View File

@@ -0,0 +1,64 @@
<select name="purchased_from" id="supplier_id" class="selectpicker">
{% if object.parent%}
<option value="{{object.parent.pk}}" selected>{{object.parent.name}}</option>
{% endif %}
</select>
{% load static %}
{% block css %}
<link rel="stylesheet" href="{% static "css/bootstrap-select.min.css" %}"/>
<link rel="stylesheet" href="{% static "css/ajax-bootstrap-select.css" %}"/>
{% endblock %}
{% block preload_js %}
<script src="{% static "js/bootstrap-select.js" %}"></script>
<script src="{% static "js/ajax-bootstrap-select.js" %}"></script>
{% endblock %}
{% block js %}
{{ js.super }}
<script>
$('#supplier_id')
.selectpicker({
liveSearch: true
})
.ajaxSelectPicker({
ajax: {
url: '{% url 'supplier_search_json'%}',
type: "get",
data: function () {
var params = {
{% verbatim %}query: '{{{q}}}'{% endverbatim %}
};
return params;
}
},
locale: {
emptyTitle: 'Search for supplier...'
},
preprocessData: function(data){
var suppliers = [];
if(data.length){
var len = data.length;
for(var i = 0; i < len; i++){
var curr = data[i];
suppliers.push(
{
'value': curr.id,
'text': curr.name,
'disabled': false
}
);
}
suppliers.push(
{
'value': null,
'text': "(no selection)"
});
}
return suppliers;
},
preserveSelected: false
});
</script>
{% endblock js %}

View File

@@ -0,0 +1,6 @@
{% extends 'base_assets.html' %}
{% block title %}Detail{% endblock %}
{% block content %}
{{ object }}
{% endblock %}

View File

@@ -0,0 +1,46 @@
{% extends 'base_assets.html' %}
{% block title %}Supplier List{% endblock %}
{% load paginator from filters %}
{% load widget_tweaks %}
{% block content %}
<div class="page-header">
<h1>Supplier List</h1>
</div>
<form id="supplier-search-form" method="get" class="form-inline pull-right">
{% csrf_token %}
<div class="input-group pull-right" style="width: auto;">
{% render_field form.query|add_class:'form-control' placeholder='Search by Name' style="width: 250px"%}
<label for="query" class="sr-only">Name:</label>
<span class="input-group-btn"><button type="submit" class="btn btn-default">Search</button></span>
</div>
</form>
<table class="table table-striped">
<thead>
<tr>
<th>Supplier</th>
<th>Quick Links</th>
</tr>
</thead>
<tbody id="asset_table_body">
{% for item in object_list %}
<tr>
<td>{{ item.name }}</td>
<td>
<a href="{% url 'supplier_update' item.pk %}" class="btn btn-default"><i class="glyphicon glyphicon-edit"></i> Edit</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<div class="text-center">
{% paginator %}
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,20 @@
{% extends 'base_assets.html' %}
{% block title %}Edit{% endblock %}
{% block content %}
<div class="page-header">
<h1>Supplier
{% if object %}
Edit: {{ object.name }}
{% else %}
Create
{% endif %}</h1>
</div>
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Save" class="btn btn-success">
</form>
{% endblock %}

View File

View File

@@ -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'))

22
assets/urls.py Normal file
View File

@@ -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/<str:pk>/', 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/<str:pk>/edit/', permission_required_with_403('assets.change_asset')(views.AssetEdit.as_view()), name='asset_update'),
path('asset/id/<str:pk>/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/<int:pk>', 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/<int:pk>/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'),
]

205
assets/views.py Normal file
View File

@@ -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'

2
pytest.ini Normal file
View File

@@ -0,0 +1,2 @@
[pytest]
DJANGO_SETTINGS_MODULE = myproject.settings

View File

@@ -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
zope.schema==4.5.0

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Bad Request{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Unauthorized{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Forbidden{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Page Not Found{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Server error{% endblock %}

View File

@@ -43,58 +43,13 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">RIGS</a>
{% block titleheader %}
{% endblock %}
</div>
<div class="navbar-collapse">
<ul class="nav navbar-nav">
{% if user.is_authenticated %}
<li><a href="/">Home</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Rigboard<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span>
Rigboard</a></li>
<li><a href="{% url 'event_archive' %}"><span class="glyphicon glyphicon-book"></span>
Archive</a></li>
<li><a href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span>
Calendar</a></li>
{% if perms.RIGS.view_event %}
<li><a href="{% url 'activity_table' %}"><span
class="glyphicon glyphicon-random"></span> Recent Changes</a></li>
{% endif %}
{% if perms.RIGS.add_event %}
<li><a href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span>
New Event</a></li>
{% endif %}
</ul>
</li>
{% endif %}
{% if perms.RIGS.view_invoice %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
<ul class="dropdown-menu">
{% if perms.RIGS.add_invoice %}
<li><a href="{% url 'invoice_waiting' %}"><span
class="glyphicon glyphicon-briefcase text-danger"></span> Waiting</a></li>
{% endif %}
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp text-warning"></span> Outstanding</a>
</li>
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span>
Archive</a></li>
</ul>
</li>
{% endif %}
{% if perms.RIGS.view_person %}
<li><a href="{% url 'person_list' %}">People</a></li>
{% endif %}
{% if perms.RIGS.view_organisation %}
<li><a href="{% url 'organisation_list' %}">Organisations</a></li>
{% endif %}
{% if perms.RIGS.view_venue %}
<li><a href="{% url 'venue_list' %}">Venues</a></li>
{% endif %}
{% block titleelements %}
{% endblock %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">

View File

@@ -0,0 +1,31 @@
{% extends 'base.html' %}
{% block titleheader %}
<a class="nav navbar-brand navbar-left" href="/"><i class="glyphicon glyphicon-circle-arrow-left" style="vertical-align: middle !important;"></i> RIGS</a>
<a class="nav navbar-brand" href="{% url 'asset_index' %}">Assets</a>
{% endblock %}
{% block titleelements %}
{% if perms.assets.view_asset%}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assets<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{% url 'asset_list' %}"><span class="glyphicon glyphicon-list"></span> List Assets</a></li>
{% if perms.assets.add_asset %}
<li><a href="{% url 'asset_create' %}"><span class="glyphicon glyphicon-plus"></span> Create Asset</a></li>
{% endif %}
</ul>
</li>
{% endif %}
{% if perms.assets.view_supplier%}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> Suppliers<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{% url 'supplier_list' %}"><span class="glyphicon glyphicon-list"></span>
List Suppliers</a></li>
{% if perms.assets.add_asset %}
<li><a href="{% url 'supplier_create' %}"><span class="glyphicon glyphicon-plus"></span> Create Supplier</a></li>
{% endif %}
</ul>
</li>
{% endif %}
{% endblock %}

54
templates/base_rigs.html Normal file
View File

@@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% block titleheader %}
<a class="navbar-brand" href="/">RIGS</a>
{% endblock %}
{% block titleelements %}
{% if user.is_authenticated %}
<li><a href="/">Home</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Rigboard<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{% url 'rigboard' %}"><span class="glyphicon glyphicon-list"></span>
Rigboard</a></li>
<li><a href="{% url 'event_archive' %}"><span class="glyphicon glyphicon-book"></span>
Archive</a></li>
<li><a href="{% url 'web_calendar' %}"><span class="glyphicon glyphicon-calendar"></span>
Calendar</a></li>
{% if perms.RIGS.view_event %}
<li><a href="{% url 'activity_table' %}"><span
class="glyphicon glyphicon-random"></span> Recent Changes</a></li>
{% endif %}
{% if perms.RIGS.add_event %}
<li><a href="{% url 'event_create' %}"><span class="glyphicon glyphicon-plus"></span>
New Event</a></li>
{% endif %}
</ul>
</li>
{% endif %}
{% if perms.RIGS.view_invoice %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
<ul class="dropdown-menu">
{% if perms.RIGS.add_invoice %}
<li><a href="{% url 'invoice_waiting' %}"><span
class="glyphicon glyphicon-briefcase text-danger"></span> Waiting</a></li>
{% endif %}
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp text-warning"></span> Outstanding</a>
</li>
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span>
Archive</a></li>
</ul>
</li>
{% endif %}
{% if perms.RIGS.view_person %}
<li><a href="{% url 'person_list' %}">People</a></li>
{% endif %}
{% if perms.RIGS.view_organisation %}
<li><a href="{% url 'organisation_list' %}">Organisations</a></li>
{% endif %}
{% if perms.RIGS.view_venue %}
<li><a href="{% url 'venue_list' %}">Venues</a></li>
{% endif %}
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load staticfiles %}
{% block title %}Login Required{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}Activation Complete{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}Logout Successful{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}Login{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "base_rigs.html" %}
{% load i18n %}

View File

@@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "base_rigs.html" %}
{% load widget_tweaks %}

View File

@@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "base_rigs.html" %}
{% load i18n %}

View File

@@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "base_rigs.html" %}
{% load i18n %}
{% load widget_tweaks %}

View File

@@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "base_rigs.html" %}
{% load i18n %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load i18n %}
{% load widget_tweaks %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% block title %}Registration complete{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'base_rigs.html' %}
{% load widget_tweaks %}
{% block title %}Registration{% endblock %}