mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-24 17:02:18 +00:00
Merge branch 'master' into bs4
# Conflicts: # assets/models.py # assets/templates/asset_update.html # assets/templates/partials/asset_buttons.html # assets/templates/partials/asset_list_table_body.html # assets/views.py # templates/base_assets.html
This commit is contained in:
@@ -2,6 +2,7 @@ from pypom import Page, Region
|
|||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver import Chrome
|
from selenium.webdriver import Chrome
|
||||||
from selenium.common.exceptions import NoSuchElementException
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
|
from PyRIGS.tests import regions
|
||||||
|
|
||||||
|
|
||||||
class BasePage(Page):
|
class BasePage(Page):
|
||||||
@@ -36,37 +37,19 @@ class FormPage(BasePage):
|
|||||||
self.driver.execute_script(
|
self.driver.execute_script(
|
||||||
"Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
|
"Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
|
||||||
|
|
||||||
|
def submit(self):
|
||||||
|
previous_errors = self.errors
|
||||||
|
self.find_element(*self._submit_locator).click()
|
||||||
|
self.wait.until(lambda x: self.errors != previous_errors or self.success)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def errors(self):
|
def errors(self):
|
||||||
try:
|
try:
|
||||||
error_page = self.ErrorPage(self, self.find_element(*self._errors_selector))
|
error_page = regions.ErrorPage(self, self.find_element(*self._errors_selector))
|
||||||
return error_page.errors
|
return error_page.errors
|
||||||
except NoSuchElementException:
|
except NoSuchElementException:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class ErrorPage(Region):
|
|
||||||
_error_item_selector = (By.CSS_SELECTOR, "dl>span")
|
|
||||||
|
|
||||||
class ErrorItem(Region):
|
|
||||||
_field_selector = (By.CSS_SELECTOR, "dt")
|
|
||||||
_error_selector = (By.CSS_SELECTOR, "dd>ul>li")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def field_name(self):
|
|
||||||
return self.find_element(*self._field_selector).text
|
|
||||||
|
|
||||||
@property
|
|
||||||
def errors(self):
|
|
||||||
return [x.text for x in self.find_elements(*self._error_selector)]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def errors(self):
|
|
||||||
error_items = [self.ErrorItem(self, x) for x in self.find_elements(*self._error_item_selector)]
|
|
||||||
errors = {}
|
|
||||||
for error in error_items:
|
|
||||||
errors[error.field_name] = error.errors
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
class LoginPage(BasePage):
|
class LoginPage(BasePage):
|
||||||
URL_TEMPLATE = '/user/login'
|
URL_TEMPLATE = '/user/login'
|
||||||
|
|||||||
@@ -131,3 +131,27 @@ class SingleSelectPicker(Region):
|
|||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
picker = Select(self.root)
|
picker = Select(self.root)
|
||||||
picker.select_by_visible_text(value)
|
picker.select_by_visible_text(value)
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorPage(Region):
|
||||||
|
_error_item_selector = (By.CSS_SELECTOR, "dl>span")
|
||||||
|
|
||||||
|
class ErrorItem(Region):
|
||||||
|
_field_selector = (By.CSS_SELECTOR, "dt")
|
||||||
|
_error_selector = (By.CSS_SELECTOR, "dd>ul>li")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def field_name(self):
|
||||||
|
return self.find_element(*self._field_selector).text
|
||||||
|
|
||||||
|
@property
|
||||||
|
def errors(self):
|
||||||
|
return [x.text for x in self.find_elements(*self._error_selector)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def errors(self):
|
||||||
|
error_items = [self.ErrorItem(self, x) for x in self.find_elements(*self._error_item_selector)]
|
||||||
|
errors = {}
|
||||||
|
for error in error_items:
|
||||||
|
errors[error.field_name] = error.errors
|
||||||
|
return errors
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# TEC PA & Lighting - PyRIGS #
|
# TEC PA & Lighting - PyRIGS #
|
||||||
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
[](https://travis-ci.org/nottinghamtec/PyRIGS)
|
||||||
[](https://coveralls.io/github/nottinghamtec/PyRIGS)
|
[](https://coveralls.io/github/nottinghamtec/PyRIGS)
|
||||||
|
|
||||||
Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.
|
Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails.
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,15 @@ class SupplierAdmin(VersionAdmin):
|
|||||||
@admin.register(assets.Asset)
|
@admin.register(assets.Asset)
|
||||||
class AssetAdmin(VersionAdmin):
|
class AssetAdmin(VersionAdmin):
|
||||||
list_display = ['id', 'asset_id', 'description', 'category', 'status']
|
list_display = ['id', 'asset_id', 'description', 'category', 'status']
|
||||||
list_filter = ['is_cable', 'category']
|
list_filter = ['is_cable', 'category', 'status']
|
||||||
search_fields = ['id', 'asset_id', 'description']
|
search_fields = ['id', 'asset_id', 'description']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(assets.CableType)
|
||||||
|
class CableTypeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['id', '__str__', 'plug', 'socket', 'cores', 'circuits']
|
||||||
|
|
||||||
|
|
||||||
@admin.register(assets.Connector)
|
@admin.register(assets.Connector)
|
||||||
class ConnectorAdmin(admin.ModelAdmin):
|
class ConnectorAdmin(admin.ModelAdmin):
|
||||||
list_display = ['id', '__str__', 'current_rating', 'voltage_rating', 'num_pins']
|
list_display = ['id', '__str__', 'current_rating', 'voltage_rating', 'num_pins']
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from assets import models
|
from assets import models
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
class AssetForm(forms.ModelForm):
|
class AssetForm(forms.ModelForm):
|
||||||
@@ -12,7 +13,7 @@ class AssetForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = models.Asset
|
model = models.Asset
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
exclude = ['asset_id_prefix', 'asset_id_number']
|
exclude = ['asset_id_prefix', 'asset_id_number', 'last_audited_at', 'last_audited_by']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -20,6 +21,13 @@ class AssetForm(forms.ModelForm):
|
|||||||
self.fields['date_acquired'].widget.format = '%Y-%m-%d'
|
self.fields['date_acquired'].widget.format = '%Y-%m-%d'
|
||||||
|
|
||||||
|
|
||||||
|
class AssetAuditForm(AssetForm):
|
||||||
|
class Meta(AssetForm.Meta):
|
||||||
|
# Prevents assets losing existing data that isn't included in the audit form
|
||||||
|
exclude = ['asset_id_prefix', 'asset_id_number', 'last_audited_at', 'last_audited_by',
|
||||||
|
'parent', 'purchased_from', 'purchase_price', 'comments']
|
||||||
|
|
||||||
|
|
||||||
class AssetSearchForm(forms.Form):
|
class AssetSearchForm(forms.Form):
|
||||||
query = forms.CharField(required=False)
|
query = forms.CharField(required=False)
|
||||||
category = forms.ModelMultipleChoiceField(models.AssetCategory.objects.all(), required=False)
|
category = forms.ModelMultipleChoiceField(models.AssetCategory.objects.all(), required=False)
|
||||||
@@ -34,3 +42,17 @@ class SupplierForm(forms.ModelForm):
|
|||||||
|
|
||||||
class SupplierSearchForm(forms.Form):
|
class SupplierSearchForm(forms.Form):
|
||||||
query = forms.CharField(required=False)
|
query = forms.CharField(required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class CableTypeForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = models.CableType
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
form_data = self.cleaned_data
|
||||||
|
queryset = models.CableType.objects.filter(Q(plug=form_data['plug']) & Q(socket=form_data['socket']) & Q(circuits=form_data['circuits']) & Q(cores=form_data['cores']))
|
||||||
|
# Being identical to itself shouldn't count...
|
||||||
|
if queryset.exists() and self.instance.pk != queryset[0].pk:
|
||||||
|
raise forms.ValidationError("A cable type that exactly matches this one already exists, please use that instead.", code="notunique")
|
||||||
|
return form_data
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import random
|
import random
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from reversion import revisions as reversion
|
||||||
from assets import models
|
from assets import models
|
||||||
|
from RIGS import models as rigsmodels
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@@ -15,6 +17,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
random.seed('Some object to see the random number generator')
|
random.seed('Some object to see the random number generator')
|
||||||
|
|
||||||
|
self.create_profile()
|
||||||
self.create_categories()
|
self.create_categories()
|
||||||
self.create_statuses()
|
self.create_statuses()
|
||||||
self.create_suppliers()
|
self.create_suppliers()
|
||||||
@@ -22,6 +25,13 @@ class Command(BaseCommand):
|
|||||||
self.create_connectors()
|
self.create_connectors()
|
||||||
self.create_cables()
|
self.create_cables()
|
||||||
|
|
||||||
|
# Make sure that there's at least one profile if this command is run standalone
|
||||||
|
def create_profile(self):
|
||||||
|
name = "Fred Johnson"
|
||||||
|
models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0], last_name=name.split(" ")[-1],
|
||||||
|
email=name.replace(" ", "") + "@example.com",
|
||||||
|
initials="".join([j[0].upper() for j in name.split()]))
|
||||||
|
|
||||||
def create_categories(self):
|
def create_categories(self):
|
||||||
categories = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging']
|
categories = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging']
|
||||||
|
|
||||||
@@ -29,17 +39,19 @@ class Command(BaseCommand):
|
|||||||
models.AssetCategory.objects.create(name=cat)
|
models.AssetCategory.objects.create(name=cat)
|
||||||
|
|
||||||
def create_statuses(self):
|
def create_statuses(self):
|
||||||
statuses = [('In Service', True), ('Lost', False), ('Binned', False), ('Sold', False), ('Broken', False)]
|
statuses = [('In Service', True, 'success'), ('Lost', False, 'warning'), ('Binned', False, 'danger'), ('Sold', False, 'danger'), ('Broken', False, 'warning')]
|
||||||
|
|
||||||
for stat in statuses:
|
for stat in statuses:
|
||||||
models.AssetStatus.objects.create(name=stat[0], should_show=stat[1])
|
models.AssetStatus.objects.create(name=stat[0], should_show=stat[1], display_class=stat[2])
|
||||||
|
|
||||||
def create_suppliers(self):
|
def create_suppliers(self):
|
||||||
suppliers = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa
|
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
|
"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:
|
with reversion.create_revision():
|
||||||
models.Supplier.objects.create(name=supplier)
|
for supplier in suppliers:
|
||||||
|
reversion.set_user(random.choice(rigsmodels.Profile.objects.all()))
|
||||||
|
models.Supplier.objects.create(name=supplier)
|
||||||
|
|
||||||
def create_assets(self):
|
def create_assets(self):
|
||||||
asset_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink']
|
asset_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink']
|
||||||
@@ -48,22 +60,24 @@ class Command(BaseCommand):
|
|||||||
statuses = models.AssetStatus.objects.all()
|
statuses = models.AssetStatus.objects.all()
|
||||||
suppliers = models.Supplier.objects.all()
|
suppliers = models.Supplier.objects.all()
|
||||||
|
|
||||||
for i in range(100):
|
with reversion.create_revision():
|
||||||
asset = models.Asset(
|
for i in range(100):
|
||||||
asset_id='{}'.format(models.Asset.get_available_asset_id()),
|
reversion.set_user(random.choice(rigsmodels.Profile.objects.all()))
|
||||||
description=random.choice(asset_description),
|
asset = models.Asset(
|
||||||
category=random.choice(categories),
|
asset_id='{}'.format(models.Asset.get_available_asset_id()),
|
||||||
status=random.choice(statuses),
|
description=random.choice(asset_description),
|
||||||
date_acquired=timezone.now().date()
|
category=random.choice(categories),
|
||||||
)
|
status=random.choice(statuses),
|
||||||
|
date_acquired=timezone.now().date()
|
||||||
|
)
|
||||||
|
|
||||||
if i % 4 == 0:
|
if i % 4 == 0:
|
||||||
asset.parent = models.Asset.objects.order_by('?').first()
|
asset.parent = models.Asset.objects.order_by('?').first()
|
||||||
|
|
||||||
if i % 3 == 0:
|
if i % 3 == 0:
|
||||||
asset.purchased_from = random.choice(suppliers)
|
asset.purchased_from = random.choice(suppliers)
|
||||||
asset.clean()
|
asset.clean()
|
||||||
asset.save()
|
asset.save()
|
||||||
|
|
||||||
def create_cables(self):
|
def create_cables(self):
|
||||||
asset_description = ['The worm', 'Harting without a cap', 'Heavy cable', 'Extension lead', 'IEC cable that we should remember to prep']
|
asset_description = ['The worm', 'Harting without a cap', 'Heavy cable', 'Extension lead', 'IEC cable that we should remember to prep']
|
||||||
@@ -78,6 +92,9 @@ class Command(BaseCommand):
|
|||||||
suppliers = models.Supplier.objects.all()
|
suppliers = models.Supplier.objects.all()
|
||||||
connectors = models.Connector.objects.all()
|
connectors = models.Connector.objects.all()
|
||||||
|
|
||||||
|
for i in range(len(connectors)):
|
||||||
|
models.CableType.objects.create(plug=random.choice(connectors), socket=random.choice(connectors), circuits=random.choice(circuits), cores=random.choice(cores))
|
||||||
|
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
asset = models.Asset(
|
asset = models.Asset(
|
||||||
asset_id='{}'.format(models.Asset.get_available_asset_id()),
|
asset_id='{}'.format(models.Asset.get_available_asset_id()),
|
||||||
@@ -87,12 +104,9 @@ class Command(BaseCommand):
|
|||||||
date_acquired=timezone.now().date(),
|
date_acquired=timezone.now().date(),
|
||||||
|
|
||||||
is_cable=True,
|
is_cable=True,
|
||||||
plug=random.choice(connectors),
|
cable_type=random.choice(models.CableType.objects.all()),
|
||||||
socket=random.choice(connectors),
|
|
||||||
csa=random.choice(csas),
|
csa=random.choice(csas),
|
||||||
length=random.choice(lengths),
|
length=random.choice(lengths),
|
||||||
circuits=random.choice(circuits),
|
|
||||||
cores=random.choice(circuits)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if i % 5 == 0:
|
if i % 5 == 0:
|
||||||
|
|||||||
29
assets/migrations/0011_auto_20200218_1617.py
Normal file
29
assets/migrations/0011_auto_20200218_1617.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 2.0.13 on 2020-02-18 16:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0010_auto_20200219_1444'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CableType',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('circuits', models.IntegerField(blank=True, null=True)),
|
||||||
|
('cores', models.IntegerField(blank=True, null=True)),
|
||||||
|
('plug', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plug', to='assets.Connector')),
|
||||||
|
('socket', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='socket', to='assets.Connector')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cable_type',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.CableType'),
|
||||||
|
),
|
||||||
|
]
|
||||||
26
assets/migrations/0012_auto_20200218_1627.py
Normal file
26
assets/migrations/0012_auto_20200218_1627.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 2.0.13 on 2020-02-18 16:27
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
|
def move_cable_type_data(apps, schema_editor):
|
||||||
|
Asset = apps.get_model('assets', 'Asset')
|
||||||
|
CableType = apps.get_model('assets', 'CableType')
|
||||||
|
for asset in Asset.objects.filter(is_cable=True):
|
||||||
|
# Only create one type per...well...type
|
||||||
|
if(not CableType.objects.filter(Q(plug=asset.plug) & Q(socket=asset.socket))):
|
||||||
|
cabletype = CableType.objects.create(plug=asset.plug, socket=asset.socket, circuits=asset.circuits, cores=asset.cores)
|
||||||
|
asset.save()
|
||||||
|
cabletype.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0011_auto_20200218_1617'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(move_cable_type_data)
|
||||||
|
]
|
||||||
29
assets/migrations/0013_auto_20200218_1639.py
Normal file
29
assets/migrations/0013_auto_20200218_1639.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 2.0.13 on 2020-02-18 16:39
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0012_auto_20200218_1627'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='circuits',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='cores',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='plug',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='socket',
|
||||||
|
),
|
||||||
|
]
|
||||||
17
assets/migrations/0014_auto_20200218_1840.py
Normal file
17
assets/migrations/0014_auto_20200218_1840.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 2.0.13 on 2020-02-18 18:40
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0013_auto_20200218_1639'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='cabletype',
|
||||||
|
options={'ordering': ['plug', 'socket', '-circuits']},
|
||||||
|
),
|
||||||
|
]
|
||||||
17
assets/migrations/0015_remove_asset_next_sched_maint.py
Normal file
17
assets/migrations/0015_remove_asset_next_sched_maint.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.0.3 on 2020-04-13 15:13
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0014_auto_20200218_1840'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='asset',
|
||||||
|
name='next_sched_maint',
|
||||||
|
),
|
||||||
|
]
|
||||||
34
assets/migrations/0016_auto_20200413_1632.py
Normal file
34
assets/migrations/0016_auto_20200413_1632.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 3.0.3 on 2020-04-13 15:32
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0015_remove_asset_next_sched_maint'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cabletype',
|
||||||
|
name='circuits',
|
||||||
|
field=models.IntegerField(default=1),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cabletype',
|
||||||
|
name='cores',
|
||||||
|
field=models.IntegerField(default=3),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cabletype',
|
||||||
|
name='plug',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plug', to='assets.Connector'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cabletype',
|
||||||
|
name='socket',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='socket', to='assets.Connector'),
|
||||||
|
),
|
||||||
|
]
|
||||||
31
assets/migrations/0017_add_audit.py
Normal file
31
assets/migrations/0017_add_audit.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 3.0.3 on 2020-04-13 00:06
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('assets', '0016_auto_20200413_1632'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='last_audited_at',
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='last_audited_by',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='audited_by', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='csa',
|
||||||
|
field=models.DecimalField(blank=True, decimal_places=2, help_text='mm²', max_digits=10, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -9,7 +9,7 @@ from django.dispatch.dispatcher import receiver
|
|||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
|
|
||||||
from RIGS.models import RevisionMixin
|
from RIGS.models import RevisionMixin, Profile
|
||||||
|
|
||||||
|
|
||||||
class AssetCategory(models.Model):
|
class AssetCategory(models.Model):
|
||||||
@@ -68,6 +68,25 @@ class Connector(models.Model):
|
|||||||
return self.description
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
|
# Things are nullable that shouldn't be because I didn't properly fix the data structure when moving this to its own model...
|
||||||
|
class CableType(models.Model):
|
||||||
|
class Meta:
|
||||||
|
ordering = ['plug', 'socket', '-circuits']
|
||||||
|
|
||||||
|
circuits = models.IntegerField(default=1)
|
||||||
|
cores = models.IntegerField(default=3)
|
||||||
|
plug = models.ForeignKey(Connector, on_delete=models.CASCADE,
|
||||||
|
related_name='plug', null=True)
|
||||||
|
socket = models.ForeignKey(Connector, on_delete=models.CASCADE,
|
||||||
|
related_name='socket', null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.plug and self.socket:
|
||||||
|
return "%s → %s" % (self.plug.description, self.socket.description)
|
||||||
|
else:
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
class Asset(models.Model, RevisionMixin):
|
class Asset(models.Model, RevisionMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -90,18 +109,17 @@ class Asset(models.Model, RevisionMixin):
|
|||||||
salvage_value = 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)
|
comments = models.TextField(blank=True)
|
||||||
|
|
||||||
|
# Audit
|
||||||
|
last_audited_at = models.DateTimeField(blank=True, null=True)
|
||||||
|
last_audited_by = models.ForeignKey(Profile, on_delete=models.SET_NULL, related_name='audited_by', blank=True, null=True)
|
||||||
|
|
||||||
# Cable assets
|
# Cable assets
|
||||||
is_cable = models.BooleanField(default=False)
|
is_cable = models.BooleanField(default=False)
|
||||||
plug = models.ForeignKey(Connector, on_delete=models.SET_NULL,
|
cable_type = models.ForeignKey(to=CableType, blank=True, null=True, 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,
|
length = models.DecimalField(decimal_places=1, max_digits=10,
|
||||||
blank=True, null=True, help_text='m')
|
blank=True, null=True, help_text='m')
|
||||||
csa = models.DecimalField(decimal_places=2, max_digits=10,
|
csa = models.DecimalField(decimal_places=2, max_digits=10,
|
||||||
blank=True, null=True, help_text='mm^2')
|
blank=True, null=True, help_text='mm²')
|
||||||
circuits = models.IntegerField(blank=True, null=True)
|
|
||||||
cores = models.IntegerField(blank=True, null=True)
|
|
||||||
|
|
||||||
# Hidden asset_id components
|
# Hidden asset_id components
|
||||||
# For example, if asset_id was "C1001" then asset_id_prefix would be "C" and number "1001"
|
# For example, if asset_id was "C1001" then asset_id_prefix would be "C" and number "1001"
|
||||||
@@ -131,7 +149,7 @@ class Asset(models.Model, RevisionMixin):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
out = str(self.asset_id) + ' - ' + self.description
|
out = str(self.asset_id) + ' - ' + self.description
|
||||||
if self.is_cable:
|
if self.is_cable:
|
||||||
out += '{} - {}m - {}'.format(self.plug, self.length, self.socket)
|
out += '{} - {}m - {}'.format(self.cable_type.plug, self.length, self.cable_type.socket)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
@@ -156,14 +174,16 @@ class Asset(models.Model, RevisionMixin):
|
|||||||
errdict["length"] = ["The length of a cable must be more than 0"]
|
errdict["length"] = ["The length of a cable must be more than 0"]
|
||||||
if not self.csa or self.csa <= 0:
|
if not self.csa or self.csa <= 0:
|
||||||
errdict["csa"] = ["The CSA of a cable must be more than 0"]
|
errdict["csa"] = ["The CSA of a cable must be more than 0"]
|
||||||
if not self.circuits or self.circuits <= 0:
|
if not self.cable_type:
|
||||||
errdict["circuits"] = ["There must be at least one circuit in a cable"]
|
errdict["cable_type"] = ["A cable must have a type"]
|
||||||
if not self.cores or self.cores <= 0:
|
# if not self.circuits or self.circuits <= 0:
|
||||||
errdict["cores"] = ["There must be at least one core in a cable"]
|
# errdict["circuits"] = ["There must be at least one circuit in a cable"]
|
||||||
if self.socket is None:
|
# if not self.cores or self.cores <= 0:
|
||||||
errdict["socket"] = ["A cable must have a socket"]
|
# errdict["cores"] = ["There must be at least one core in a cable"]
|
||||||
if self.plug is None:
|
# if self.socket is None:
|
||||||
errdict["plug"] = ["A cable must have a plug"]
|
# 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
|
if errdict != {}: # If there was an error when validation
|
||||||
raise ValidationError(errdict)
|
raise ValidationError(errdict)
|
||||||
|
|||||||
142
assets/templates/asset_audit.html
Normal file
142
assets/templates/asset_audit.html
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
{% extends request.is_ajax|yesno:'base_ajax.html,base_assets.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
{% block title %}Audit Asset {{ object.asset_id }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<script>
|
||||||
|
function setAcquired(today) {
|
||||||
|
var date = new Date(1970, 0, 1);
|
||||||
|
if(today) {
|
||||||
|
date = new Date();
|
||||||
|
}
|
||||||
|
$('#id_date_acquired').val([date.getFullYear(), ('0' + (date.getMonth()+1)).slice(-2), ('0' + date.getDate()).slice(-2)].join('-'));
|
||||||
|
}
|
||||||
|
function setLength(length) {
|
||||||
|
$('#id_length').val(length);
|
||||||
|
}
|
||||||
|
function setCSA(CSA) {
|
||||||
|
$('#id_csa').val(CSA);
|
||||||
|
}
|
||||||
|
function checkIfCableHidden() {
|
||||||
|
if (document.getElementById("id_is_cable").checked) {
|
||||||
|
document.getElementById("cable-table").hidden = false;
|
||||||
|
} else {
|
||||||
|
document.getElementById("cable-table").hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkIfCableHidden();
|
||||||
|
</script>
|
||||||
|
<form class="form-horizontal" method="POST" id="asset_audit_form" action="{{ form.action|default:request.path }}">
|
||||||
|
{% include 'form_errors.html' %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="id" value="{{ object.id|default:0 }}" hidden=true>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.asset_id.id_for_label }}" class="col-sm-2 control-label">Asset ID</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
{% render_field form.asset_id|add_class:'form-control' value=object.asset_idz %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.description.id_for_label }}" class="col-sm-2 control-label">Description</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
{% render_field form.description|add_class:'form-control' value=object.description %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.category.id_for_label }}" class="col-sm-2 control-label">Category</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
{% render_field form.category|add_class:'form-control'%}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.status.id_for_label }}" class="col-sm-2 control-label">Status</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
{% render_field form.status|add_class:'form-control'%}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.serial_number.id_for_label }}" class="col-sm-2 control-label">Serial Number</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
{% render_field form.serial_number|add_class:'form-control' value=object.serial_number %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.date_acquired.id_for_label }}" class="col-sm-2 control-label">Date Acquired</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
{% render_field form.date_acquired|add_class:'form-control' value=object.date_acquired %}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<btn class="btn btn-default" onclick="setAcquired(true);" tabindex="-1">Today</btn>
|
||||||
|
<btn class="btn btn-default" onclick="setAcquired(false);" tabindex="-1">Unknown</btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.date_sold.id_for_label }}" class="col-sm-2 control-label">Date Sold</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
{% render_field form.date_sold|add_class:'form-control' value=object.date_sold %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.salvage_value.id_for_label }}" class="col-sm-2 control-label">Salvage Value</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<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>
|
||||||
|
<hr>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.is_cable.id_for_label }}" class="col-sm-2 control-label">Cable?</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
{% render_field form.is_cable|attr:'onchange=checkIfCableHidden()' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="cable-table">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.cable_type.id_for_label }}" class="col-sm-2 control-label">Cable Type</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
{% render_field form.cable_type|add_class:'form-control' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.length.id_for_label }}" class="col-sm-2 control-label">Length</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<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="col-sm-4">
|
||||||
|
<btn class="btn btn-danger" onclick="setLength('5');" tabindex="-1">5{{ form.length.help_text }}</btn>
|
||||||
|
<btn class="btn btn-success" onclick="setLength('10');" tabindex="-1">10{{ form.length.help_text }}</btn>
|
||||||
|
<btn class="btn btn-info" onclick="setLength('20');" tabindex="-1">20{{ form.length.help_text }}</btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.csa.id_for_label }}" class="col-sm-2 control-label">Cross Sectional Area</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<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="col-sm-4">
|
||||||
|
<btn class="btn btn-default" onclick="setCSA('1.5');" tabindex="-1">1.5{{ form.csa.help_text }}</btn>
|
||||||
|
<btn class="btn btn-default" onclick="setCSA('2.5');" tabindex="-1">2.5{{ form.csa.help_text }}</btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if not request.is_ajax %}
|
||||||
|
<div class="form-group pull-right">
|
||||||
|
<button class="btn btn-success" type="submit" form="asset_audit_form" id="id_mark_audited">Mark Audited</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-success pull-right" type="submit" form="asset_audit_form" onclick="onAuditClick({{form.asset_id.value}});" id="id_mark_audited">Mark Audited</button>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
74
assets/templates/asset_audit_list.html
Normal file
74
assets/templates/asset_audit_list.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{% extends 'base_assets.html' %}
|
||||||
|
{% block title %}Asset Audit List{% endblock %}
|
||||||
|
{% load static %}
|
||||||
|
{% load paginator from filters %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
|
||||||
|
<script src="{% static "js/interaction.js" %}"></script>
|
||||||
|
<script src="{% static "js/modal.js" %}"></script>
|
||||||
|
<script>
|
||||||
|
$('document').ready(function(){
|
||||||
|
$('#asset-search-form').submit(function () {
|
||||||
|
$('#searchButton').focus().click();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$('#searchButton').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var url = "{% url 'asset_audit' None %}";
|
||||||
|
var id = $("#{{form.query.id_for_label}}").val();
|
||||||
|
url = url.replace('None', id);
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
success: function(){
|
||||||
|
$link = $(this);
|
||||||
|
// Anti modal inception
|
||||||
|
if ($link.parents('#modal').length == 0) {
|
||||||
|
modaltarget = $link.data('target');
|
||||||
|
modalobject = "";
|
||||||
|
$('#modal').load(url, function (e) {
|
||||||
|
$('#modal').modal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error:function(){
|
||||||
|
$("#error404").attr("hidden", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
function onAuditClick(assetID) {
|
||||||
|
$('#' + assetID).remove();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1 class="text-center">Asset Audit List</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="error404" class="alert alert-danger alert-dismissable" hidden=true>
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||||
|
<span>Asset with that ID does not exist!</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Audit Asset:</h3>
|
||||||
|
<form id="asset-search-form" class="form-horizontal" method="POST">
|
||||||
|
<div class="input-group input-group-lg" style="width: auto;">
|
||||||
|
{% render_field form.query|add_class:'form-control' placeholder='Enter Asset ID' autofocus="true" %}
|
||||||
|
<label for="query" class="sr-only">Asset ID:</label>
|
||||||
|
<span class="input-group-btn"><a id="searchButton" class="btn btn-default" class="submit" type="submit">Search</a></span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h3>Assets Requiring Audit:</h3>
|
||||||
|
{% include 'partials/asset_list_table.html' with audit="true" %}
|
||||||
|
|
||||||
|
{% if is_paginated %}
|
||||||
|
<div class="text-center">
|
||||||
|
{% paginator %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -25,6 +25,9 @@
|
|||||||
<div class="col-md-4 py-3">
|
<div class="col-md-4 py-3">
|
||||||
{% include 'partials/parent_form.html' %}
|
{% include 'partials/parent_form.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% include 'partials/audit_details.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-end">
|
<div class="row justify-content-end">
|
||||||
{% include 'partials/asset_buttons.html' %}
|
{% include 'partials/asset_buttons.html' %}
|
||||||
|
|||||||
@@ -13,22 +13,28 @@
|
|||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Assets</a>
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Assets</a>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<a class="dropdown-item" href="{% url 'asset_list' %}"><span class="fas fa-list"></span> List Assets</a>
|
<a class="dropdown-item" href="{% url 'asset_list' %}"><i class="fas fa-list"></i> List Assets</a>
|
||||||
{% if perms.assets.add_asset %}
|
{% if perms.assets.add_asset %}
|
||||||
<a class="dropdown-item" href="{% url 'asset_create' %}"><span class="fas fa-plus"></span> Create Asset</a>
|
<a class="dropdown-item" href="{% url 'asset_create' %}"><i class="fas fa-plus"></i> Create Asset</a>
|
||||||
|
{% endif %}
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="{% url 'cable_type_list' %}"><i class="fas fa-list"></i> List Cable Types</a>
|
||||||
|
{% if perms.assets.add_cable_type %}
|
||||||
|
<a class="dropdown-item" href="{% url 'cable_type_create' %}"><i class="fas fa-plus"></i> Create Cable Type</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<div class="nav-item dropdown">
|
<div class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Suppliers</a>
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Suppliers</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<a class="dropdown-item" href="{% url 'supplier_list' %}"><span class="fas fa-list"></span> List Suppliers</a>
|
<a class="dropdown-item" href="{% url 'supplier_list' %}"><i class="fas fa-list"></i> List Suppliers</a>
|
||||||
{% if perms.assets.add_supplier %}
|
{% if perms.assets.add_supplier %}
|
||||||
<a class="dropdown-item" href="{% url 'supplier_create' %}"><span class="fas fa-plus"></span> Create Supplier</a>
|
<a class="dropdown-item" href="{% url 'supplier_create' %}"><i class="fas fa-plus"></i> Create Supplier</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% if perms.assets.view_asset %}
|
{% if perms.assets.view_asset %}
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'asset_activity_table' %}">Recent Changes</a></li>
|
<li class="nav-item"><a class="nav-link" href="{% url 'asset_activity_table' %}">Recent Changes</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link" href="{% url 'asset_audit_list' %}">Audit</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
61
assets/templates/cable_type_form.html
Normal file
61
assets/templates/cable_type_form.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{% extends 'base_assets.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
{% block title %}Cable Type{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>
|
||||||
|
{% if create %}Create{% elif edit %}Edit{% endif %} Cable Type
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{% if create %}
|
||||||
|
<form method="POST" action="{% url 'cable_type_create'%}">
|
||||||
|
{% elif edit %}
|
||||||
|
<form method="POST" action="{% url 'cable_type_update' object.id %}">
|
||||||
|
{% 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">
|
||||||
|
{% if create or edit %}
|
||||||
|
<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.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>
|
||||||
|
<div class="pull-left">
|
||||||
|
<button type="submit" class="btn btn-success"><i class="glyphicon glyphicon-floppy-disk"></i> Save</button>
|
||||||
|
<br>
|
||||||
|
<button type="reset" class="btn btn-link">Cancel</button>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<dl>
|
||||||
|
<dt>Socket</dt>
|
||||||
|
<dd>{{ object.socket|default_if_none:'-' }}</dd>
|
||||||
|
|
||||||
|
<dt>Plug</dt>
|
||||||
|
<dd>{{ object.plug|default_if_none:'-' }}</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>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
41
assets/templates/cable_type_list.html
Normal file
41
assets/templates/cable_type_list.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{% extends 'base_assets.html' %}
|
||||||
|
{% block title %}Supplier List{% endblock %}
|
||||||
|
{% load paginator from filters %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Cable Type List</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Cable Type</th>
|
||||||
|
<th>Circuits</th>
|
||||||
|
<th>Cores</th>
|
||||||
|
<th>Quick Links</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in object_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ item }}</td>
|
||||||
|
<td>{{ item.circuits }}</td>
|
||||||
|
<td>{{ item.cores }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'cable_type_detail' item.pk %}" class="btn btn-default"><i class="glyphicon glyphicon-eye-open"></i> View</a>
|
||||||
|
<a href="{% url 'cable_type_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 %}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a href="{% url 'asset_update' object.asset_id %}" class="btn btn-warning"><i class="fas fa-edit"></i> Edit</a>
|
<a href="{% url 'asset_update' object.asset_id %}" class="btn btn-warning"><i class="fas fa-edit"></i> Edit</a>
|
||||||
<a class="btn btn-info" href="{% url 'asset_duplicate' object.asset_id %}"><i class="fas fa-clone"></i> Duplicate</a>
|
<a class="btn btn-info" href="{% url 'asset_duplicate' object.asset_id %}"><i class="fas fa-clone"></i> Duplicate</a>
|
||||||
|
<a type="button" class="btn btn-info" href="{% url 'asset_audit' object.asset_id %}"><i class="fas fa-certificate"></i> Audit</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if create or edit or duplicate %}
|
{% if create or edit or duplicate %}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
<label for="{{ form.category.id_for_label }}" >Category</label>
|
<label for="{{ form.category.id_for_label }}" >Category</label>
|
||||||
{% render_field form.category|add_class:'form-control'%}
|
{% render_field form.category|add_class:'form-control'%}
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.is_cable|attr:'onchange=checkIfCableHidden()' %} <label for="{{ form.is_cable.id_for_label }}">Cable?</label>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.status.id_for_label }}" >Status</label>
|
<label for="{{ form.status.id_for_label }}" >Status</label>
|
||||||
{% render_field form.status|add_class:'form-control'%}
|
{% render_field form.status|add_class:'form-control'%}
|
||||||
@@ -32,6 +31,10 @@
|
|||||||
<label for="{{ form.serial_number.id_for_label }}">Serial Number</label>
|
<label for="{{ form.serial_number.id_for_label }}">Serial Number</label>
|
||||||
{% render_field form.serial_number|add_class:'form-control' value=object.serial_number %}
|
{% render_field form.serial_number|add_class:'form-control' value=object.serial_number %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.is_cable.id_for_label }}">Cable?</label>
|
||||||
|
{% render_field form.is_cable|attr:'onchange=checkIfCableHidden()' %}
|
||||||
|
</div>
|
||||||
<!---TODO: Lower default number of lines in comments box-->
|
<!---TODO: Lower default number of lines in comments box-->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.comments.id_for_label }}">Comments</label>
|
<label for="{{ form.comments.id_for_label }}">Comments</label>
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
<td class="assetCategory">{{ item.category }}</td>
|
<td class="assetCategory">{{ item.category }}</td>
|
||||||
<td class="assetStatus">{{ item.status }}</td>
|
<td class="assetStatus">{{ item.status }}</td>
|
||||||
<td class="d-none d-sm-table-cell">
|
<td class="d-none d-sm-table-cell">
|
||||||
|
{% if audit %}
|
||||||
|
<a type="button" class="btn btn-info btn-sm modal-href" href="{% url 'asset_audit' item.asset_id %}"><i class="fas fa-certificate"></i> Audit</a>
|
||||||
|
{% else %}
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<a type="button" class="btn btn-primary btn-sm" href="{% url 'asset_detail' item.asset_id %}"><i class="fas fa-eye"></i><span class="d-none d-sm-inline"> View</span></a>
|
<a type="button" class="btn btn-primary btn-sm" href="{% url 'asset_detail' item.asset_id %}"><i class="fas fa-eye"></i><span class="d-none d-sm-inline"> View</span></a>
|
||||||
{% if perms.assets.change_asset %}
|
{% if perms.assets.change_asset %}
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
<a type="button" class="btn btn-info btn-sm" href="{% url 'asset_duplicate' item.asset_id %}"><i class="fas fa-clone"></i><span class="d-none d-sm-inline"> Duplicate</span></a>
|
<a type="button" class="btn btn-info btn-sm" href="{% url 'asset_duplicate' item.asset_id %}"><i class="fas fa-clone"></i><span class="d-none d-sm-inline"> Duplicate</span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
|||||||
8
assets/templates/partials/audit_details.html
Normal file
8
assets/templates/partials/audit_details.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="panel {% if object.last_audited_at is not None %} panel-success {% else %} panel-warning {% endif %}">
|
||||||
|
<div class="panel-heading">
|
||||||
|
Audit Details
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>Audited at <span class="label label-default">{{ object.last_audited_at|default_if_none:'-' }}</span> by <span class="label label-info">{{ object.last_audited_by|default_if_none:'-' }}</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -6,12 +6,10 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if create or edit or duplicate %}
|
{% if create or edit or duplicate %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.plug.id_for_label }}">Plug</label>
|
<label for="{{ form.cable_type.id_for_label }}">Cable Type</label>
|
||||||
{% render_field form.plug|add_class:'form-control'%}
|
<div class="input-group">
|
||||||
</div>
|
{% render_field form.cable_type|add_class:'form-control' %}
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label for="{{ form.socket.id_for_label }}">Socket</label>
|
|
||||||
{% render_field form.socket|add_class:'form-control'%}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.length.id_for_label }}">Length</label>
|
<label for="{{ form.length.id_for_label }}">Length</label>
|
||||||
@@ -27,33 +25,16 @@
|
|||||||
<span class="input-group-addon">{{ form.csa.help_text }}</span>
|
<span class="input-group-addon">{{ form.csa.help_text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% else %}
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Socket</dt>
|
<dt>Cable Type</dt>
|
||||||
<dd>{{ object.socket|default_if_none:'-' }}</dd>
|
<dd>{{ object.cable_type|default_if_none:'-' }}</dd>
|
||||||
|
|
||||||
<dt>Plug</dt>
|
|
||||||
<dd>{{ object.plug|default_if_none:'-' }}</dd>
|
|
||||||
|
|
||||||
<dt>Length</dt>
|
<dt>Length</dt>
|
||||||
<dd>{{ object.length|default_if_none:'-' }}m</dd>
|
<dd>{{ object.length|default_if_none:'-' }}m</dd>
|
||||||
|
|
||||||
<dt>Cross Sectional Area</dt>
|
<dt>Cross Sectional Area</dt>
|
||||||
<dd>{{ object.csa|default_if_none:'-' }}m^2</dd>
|
<dd>{{ object.csa|default_if_none:'-' }}mm²</dd>
|
||||||
|
|
||||||
<dt>Circuits</dt>
|
|
||||||
<dd>{{ object.circuits|default_if_none:'-' }}</dd>
|
|
||||||
|
|
||||||
<dt>Cores</dt>
|
|
||||||
<dd>{{ object.cores|default_if_none:'-' }}</dd>
|
|
||||||
</dl>
|
</dl>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from selenium.webdriver import Chrome
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from PyRIGS.tests import regions
|
from PyRIGS.tests import regions
|
||||||
from PyRIGS.tests.pages import BasePage, FormPage
|
from PyRIGS.tests.pages import BasePage, FormPage
|
||||||
import pdb
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
|
|
||||||
|
|
||||||
class AssetList(BasePage):
|
class AssetList(BasePage):
|
||||||
@@ -82,12 +82,9 @@ class AssetForm(FormPage):
|
|||||||
'category': (regions.SingleSelectPicker, (By.ID, 'id_category')),
|
'category': (regions.SingleSelectPicker, (By.ID, 'id_category')),
|
||||||
'status': (regions.SingleSelectPicker, (By.ID, 'id_status')),
|
'status': (regions.SingleSelectPicker, (By.ID, 'id_status')),
|
||||||
|
|
||||||
'plug': (regions.SingleSelectPicker, (By.ID, 'id_plug')),
|
'cable_type': (regions.SingleSelectPicker, (By.ID, 'id_cable_type')),
|
||||||
'socket': (regions.SingleSelectPicker, (By.ID, 'id_socket')),
|
|
||||||
'length': (regions.TextBox, (By.ID, 'id_length')),
|
'length': (regions.TextBox, (By.ID, 'id_length')),
|
||||||
'csa': (regions.TextBox, (By.ID, 'id_csa')),
|
'csa': (regions.TextBox, (By.ID, 'id_csa')),
|
||||||
'circuits': (regions.TextBox, (By.ID, 'id_circuits')),
|
|
||||||
'cores': (regions.TextBox, (By.ID, 'id_cores'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -98,11 +95,6 @@ class AssetForm(FormPage):
|
|||||||
def parent_selector(self):
|
def parent_selector(self):
|
||||||
return regions.BootstrapSelectElement(self, self.find_element(*self._parent_select_locator))
|
return regions.BootstrapSelectElement(self, self.find_element(*self._parent_select_locator))
|
||||||
|
|
||||||
def submit(self):
|
|
||||||
previous_errors = self.errors
|
|
||||||
self.find_element(*self._submit_locator).click()
|
|
||||||
self.wait.until(lambda x: self.errors != previous_errors or self.success)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetEdit(AssetForm):
|
class AssetEdit(AssetForm):
|
||||||
URL_TEMPLATE = '/assets/asset/id/{asset_id}/edit/'
|
URL_TEMPLATE = '/assets/asset/id/{asset_id}/edit/'
|
||||||
@@ -165,11 +157,6 @@ class SupplierForm(FormPage):
|
|||||||
'name': (regions.TextBox, (By.ID, 'id_name')),
|
'name': (regions.TextBox, (By.ID, 'id_name')),
|
||||||
}
|
}
|
||||||
|
|
||||||
def submit(self):
|
|
||||||
previous_errors = self.errors
|
|
||||||
self.find_element(*self._submit_locator).click()
|
|
||||||
self.wait.until(lambda x: self.errors != previous_errors or self.success)
|
|
||||||
|
|
||||||
|
|
||||||
class SupplierCreate(SupplierForm):
|
class SupplierCreate(SupplierForm):
|
||||||
URL_TEMPLATE = reverse('supplier_create')
|
URL_TEMPLATE = reverse('supplier_create')
|
||||||
@@ -186,3 +173,90 @@ class SupplierEdit(SupplierForm):
|
|||||||
@property
|
@property
|
||||||
def success(self):
|
def success(self):
|
||||||
return '/edit' not in self.driver.current_url
|
return '/edit' not in self.driver.current_url
|
||||||
|
|
||||||
|
|
||||||
|
class AssetAuditList(AssetList):
|
||||||
|
URL_TEMPLATE = reverse('asset_audit_list')
|
||||||
|
|
||||||
|
_search_text_locator = (By.ID, 'id_query')
|
||||||
|
_go_button_locator = (By.ID, 'searchButton')
|
||||||
|
_modal_locator = (By.ID, 'modal')
|
||||||
|
_errors_selector = (By.CLASS_NAME, "alert-danger")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def modal(self):
|
||||||
|
return self.AssetAuditModal(self, self.find_element(*self._modal_locator))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def query(self):
|
||||||
|
return self.find_element(*self._search_text_locator).text
|
||||||
|
|
||||||
|
def set_query(self, queryString):
|
||||||
|
element = self.find_element(*self._search_text_locator)
|
||||||
|
element.clear()
|
||||||
|
element.send_keys(queryString)
|
||||||
|
|
||||||
|
def search(self):
|
||||||
|
self.find_element(*self._go_button_locator).click()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error(self):
|
||||||
|
try:
|
||||||
|
return self.find_element(*self._errors_selector)
|
||||||
|
except NoSuchElementException:
|
||||||
|
return None
|
||||||
|
|
||||||
|
class AssetAuditModal(Region):
|
||||||
|
_errors_selector = (By.CLASS_NAME, "alert-danger")
|
||||||
|
# Don't use the usual success selector - that tries and fails to hit the '10m long cable' helper button...
|
||||||
|
_submit_locator = (By.ID, "id_mark_audited")
|
||||||
|
form_items = {
|
||||||
|
'asset_id': (regions.TextBox, (By.ID, 'id_asset_id')),
|
||||||
|
'description': (regions.TextBox, (By.ID, 'id_description')),
|
||||||
|
'is_cable': (regions.CheckBox, (By.ID, 'id_is_cable')),
|
||||||
|
'serial_number': (regions.TextBox, (By.ID, 'id_serial_number')),
|
||||||
|
'salvage_value': (regions.TextBox, (By.ID, 'id_salvage_value')),
|
||||||
|
'date_acquired': (regions.DatePicker, (By.ID, 'id_date_acquired')),
|
||||||
|
'category': (regions.SingleSelectPicker, (By.ID, 'id_category')),
|
||||||
|
'status': (regions.SingleSelectPicker, (By.ID, 'id_status')),
|
||||||
|
|
||||||
|
'plug': (regions.SingleSelectPicker, (By.ID, 'id_plug')),
|
||||||
|
'socket': (regions.SingleSelectPicker, (By.ID, 'id_socket')),
|
||||||
|
'length': (regions.TextBox, (By.ID, 'id_length')),
|
||||||
|
'csa': (regions.TextBox, (By.ID, 'id_csa')),
|
||||||
|
'circuits': (regions.TextBox, (By.ID, 'id_circuits')),
|
||||||
|
'cores': (regions.TextBox, (By.ID, 'id_cores'))
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def errors(self):
|
||||||
|
try:
|
||||||
|
error_page = regions.ErrorPage(self, self.find_element(*self._errors_selector))
|
||||||
|
return error_page.errors
|
||||||
|
except NoSuchElementException:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def submit(self):
|
||||||
|
previous_errors = self.errors
|
||||||
|
self.root.find_element(*self._submit_locator).click()
|
||||||
|
# self.wait.until(lambda x: not self.is_displayed) TODO
|
||||||
|
|
||||||
|
def remove_all_required(self):
|
||||||
|
self.driver.execute_script("Array.from(document.getElementsByTagName(\"input\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
|
||||||
|
self.driver.execute_script("Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});")
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name in self.form_items:
|
||||||
|
element = self.form_items[name]
|
||||||
|
form_element = element[0](self, self.find_element(*element[1]))
|
||||||
|
return form_element.value
|
||||||
|
else:
|
||||||
|
return super().__getattribute__(name)
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if name in self.form_items:
|
||||||
|
element = self.form_items[name]
|
||||||
|
form_element = element[0](self, self.find_element(*element[1]))
|
||||||
|
form_element.set_value(value)
|
||||||
|
else:
|
||||||
|
self.__dict__[name] = value
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ from RIGS import models as rigsmodels
|
|||||||
from PyRIGS.tests.base import BaseTest, AutoLoginTest
|
from PyRIGS.tests.base import BaseTest, AutoLoginTest
|
||||||
from assets import models, urls
|
from assets import models, urls
|
||||||
from reversion import revisions as reversion
|
from reversion import revisions as reversion
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from RIGS.test_functional import animation_is_finished
|
||||||
import datetime
|
import datetime
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
class TestAssetList(AutoLoginTest):
|
class TestAssetList(AutoLoginTest):
|
||||||
@@ -98,6 +103,7 @@ class TestAssetForm(AutoLoginTest):
|
|||||||
self.supplier = models.Supplier.objects.create(name="Fullmetal Heavy Industry")
|
self.supplier = models.Supplier.objects.create(name="Fullmetal Heavy Industry")
|
||||||
self.parent = models.Asset.objects.create(asset_id="9000", description="Shelf", status=self.status, category=self.category, date_acquired=datetime.date(2000, 1, 1))
|
self.parent = models.Asset.objects.create(asset_id="9000", description="Shelf", status=self.status, category=self.category, date_acquired=datetime.date(2000, 1, 1))
|
||||||
self.connector = models.Connector.objects.create(description="IEC", current_rating=10, voltage_rating=240, num_pins=3)
|
self.connector = models.Connector.objects.create(description="IEC", current_rating=10, voltage_rating=240, num_pins=3)
|
||||||
|
self.cable_type = models.CableType.objects.create(plug=self.connector, socket=self.connector, circuits=1, cores=3)
|
||||||
self.page = pages.AssetCreate(self.driver, self.live_server_url).open()
|
self.page = pages.AssetCreate(self.driver, self.live_server_url).open()
|
||||||
|
|
||||||
def test_asset_create(self):
|
def test_asset_create(self):
|
||||||
@@ -154,12 +160,10 @@ class TestAssetForm(AutoLoginTest):
|
|||||||
self.page.is_cable = True
|
self.page.is_cable = True
|
||||||
|
|
||||||
self.assertTrue(self.driver.find_element_by_id('cable-table').is_displayed())
|
self.assertTrue(self.driver.find_element_by_id('cable-table').is_displayed())
|
||||||
self.page.plug = "IEC"
|
self.page.cable_type = "IEC → IEC"
|
||||||
self.page.socket = "IEC"
|
self.page.socket = "IEC"
|
||||||
self.page.length = 10
|
self.page.length = 10
|
||||||
self.page.csa = "1.5"
|
self.page.csa = "1.5"
|
||||||
self.page.circuits = 1
|
|
||||||
self.page.cores = 3
|
|
||||||
|
|
||||||
self.page.submit()
|
self.page.submit()
|
||||||
self.assertTrue(self.page.success)
|
self.assertTrue(self.page.success)
|
||||||
@@ -256,6 +260,76 @@ class TestSupplierCreateAndEdit(AutoLoginTest):
|
|||||||
self.assertTrue(self.page.success)
|
self.assertTrue(self.page.success)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAssetAudit(AutoLoginTest):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.category = models.AssetCategory.objects.create(name="Haulage")
|
||||||
|
self.status = models.AssetStatus.objects.create(name="Probably Fine", should_show=True)
|
||||||
|
self.supplier = models.Supplier.objects.create(name="The Bazaar")
|
||||||
|
self.connector = models.Connector.objects.create(description="Trailer Socket", current_rating=1, voltage_rating=40, num_pins=13)
|
||||||
|
models.Asset.objects.create(asset_id="1", description="Trailer Cable", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
|
||||||
|
models.Asset.objects.create(asset_id="11", description="Trailerboard", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
|
||||||
|
models.Asset.objects.create(asset_id="111", description="Erms", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
|
||||||
|
models.Asset.objects.create(asset_id="1111", description="A hammer", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
|
||||||
|
self.page = pages.AssetAuditList(self.driver, self.live_server_url).open()
|
||||||
|
self.wait = WebDriverWait(self.driver, 5)
|
||||||
|
|
||||||
|
def test_audit_process(self):
|
||||||
|
asset_id = "1111"
|
||||||
|
self.page.set_query(asset_id)
|
||||||
|
self.page.search()
|
||||||
|
mdl = self.page.modal
|
||||||
|
self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||||
|
# Do it wrong on purpose to check error display
|
||||||
|
mdl.remove_all_required()
|
||||||
|
mdl.description = ""
|
||||||
|
mdl.submit()
|
||||||
|
# self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||||
|
self.wait.until(animation_is_finished())
|
||||||
|
# self.assertTrue(self.driver.find_element_by_id('modal').is_displayed())
|
||||||
|
self.assertIn("This field is required.", mdl.errors["Description"])
|
||||||
|
# Now do it properly
|
||||||
|
new_desc = "A BIG hammer"
|
||||||
|
mdl.description = new_desc
|
||||||
|
mdl.submit()
|
||||||
|
self.wait.until(animation_is_finished())
|
||||||
|
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||||
|
|
||||||
|
# Check data is correct
|
||||||
|
audited = models.Asset.objects.get(asset_id="1111")
|
||||||
|
self.assertEqual(audited.description, new_desc)
|
||||||
|
# Make sure audit 'log' was filled out
|
||||||
|
self.assertEqual(self.profile.initials, audited.last_audited_by.initials)
|
||||||
|
self.assertEqual(timezone.now().date(), audited.last_audited_at.date())
|
||||||
|
self.assertEqual(timezone.now().hour, audited.last_audited_at.hour)
|
||||||
|
self.assertEqual(timezone.now().minute, audited.last_audited_at.minute)
|
||||||
|
# Check we've removed it from the 'needing audit' list
|
||||||
|
self.assertNotIn(asset_id, self.page.assets)
|
||||||
|
|
||||||
|
def test_audit_list(self):
|
||||||
|
self.assertEqual(len(models.Asset.objects.filter(last_audited_at=None)), len(self.page.assets))
|
||||||
|
|
||||||
|
assetRow = self.page.assets[0]
|
||||||
|
assetRow.find_element(By.CSS_SELECTOR, "td:nth-child(5) > div:nth-child(1) > a:nth-child(1)").click()
|
||||||
|
self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||||
|
self.assertEqual(self.page.modal.asset_id, assetRow.id)
|
||||||
|
|
||||||
|
# First close button is for the not found error
|
||||||
|
self.page.find_element(By.XPATH, '(//button[@class="close"])[2]').click()
|
||||||
|
self.wait.until(animation_is_finished())
|
||||||
|
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||||
|
# Make sure audit log was NOT filled out
|
||||||
|
audited = models.Asset.objects.get(asset_id=assetRow.id)
|
||||||
|
self.assertEqual(None, audited.last_audited_by)
|
||||||
|
|
||||||
|
# Check that a failed search works
|
||||||
|
self.page.set_query("NOTFOUND")
|
||||||
|
self.page.search()
|
||||||
|
self.wait.until(animation_is_finished())
|
||||||
|
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||||
|
self.assertIn("Asset with that ID does not exist!", self.page.error.text)
|
||||||
|
|
||||||
|
|
||||||
class TestSupplierValidation(TestCase):
|
class TestSupplierValidation(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@@ -375,7 +449,8 @@ class TestFormValidation(TestCase):
|
|||||||
cls.status = models.AssetStatus.objects.create(name="Broken", should_show=True)
|
cls.status = models.AssetStatus.objects.create(name="Broken", should_show=True)
|
||||||
cls.asset = models.Asset.objects.create(asset_id="9999", description="The Office", status=cls.status, category=cls.category, date_acquired=datetime.date(2018, 6, 15))
|
cls.asset = models.Asset.objects.create(asset_id="9999", description="The Office", status=cls.status, category=cls.category, date_acquired=datetime.date(2018, 6, 15))
|
||||||
cls.connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3)
|
cls.connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3)
|
||||||
cls.cable_asset = models.Asset.objects.create(asset_id="666", description="125A -> Jack", comments="The cable from Hell...", status=cls.status, category=cls.category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, plug=cls.connector, socket=cls.connector, length=10, csa="1.5", circuits=1, cores=3)
|
cls.cable_type = models.CableType.objects.create(circuits=11, cores=3, plug=cls.connector, socket=cls.connector)
|
||||||
|
cls.cable_asset = models.Asset.objects.create(asset_id="666", description="125A -> Jack", comments="The cable from Hell...", status=cls.status, category=cls.category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, cable_type=cls.cable_type, length=10, csa="1.5")
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.profile.set_password('testuser')
|
self.profile.set_password('testuser')
|
||||||
@@ -399,12 +474,9 @@ class TestFormValidation(TestCase):
|
|||||||
response = self.client.post(url, {'asset_id': 'X$%A', 'is_cable': True})
|
response = self.client.post(url, {'asset_id': 'X$%A', 'is_cable': True})
|
||||||
self.assertFormError(response, 'form', 'asset_id', 'An Asset ID can only consist of letters and numbers, with a final number')
|
self.assertFormError(response, 'form', 'asset_id', 'An Asset ID can only consist of letters and numbers, with a final number')
|
||||||
|
|
||||||
self.assertFormError(response, 'form', 'plug', 'A cable must have a plug')
|
self.assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||||
self.assertFormError(response, 'form', 'socket', 'A cable must have a socket')
|
|
||||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||||
self.assertFormError(response, 'form', 'circuits', 'There must be at least one circuit in a cable')
|
|
||||||
self.assertFormError(response, 'form', 'cores', 'There must be at least one core in a cable')
|
|
||||||
|
|
||||||
# Given that validation is done at model level it *shouldn't* need retesting...gonna do it anyway!
|
# Given that validation is done at model level it *shouldn't* need retesting...gonna do it anyway!
|
||||||
def test_asset_edit(self):
|
def test_asset_edit(self):
|
||||||
@@ -422,24 +494,19 @@ class TestFormValidation(TestCase):
|
|||||||
def test_cable_edit(self):
|
def test_cable_edit(self):
|
||||||
url = reverse('asset_update', kwargs={'pk': self.cable_asset.asset_id})
|
url = reverse('asset_update', kwargs={'pk': self.cable_asset.asset_id})
|
||||||
# TODO Why do I have to send is_cable=True here?
|
# TODO Why do I have to send is_cable=True here?
|
||||||
response = self.client.post(url, {'is_cable': True, 'length': -3, 'csa': -3, 'circuits': -4, 'cores': -8})
|
response = self.client.post(url, {'is_cable': True, 'length': -3, 'csa': -3})
|
||||||
|
|
||||||
# Can't figure out how to select the 'none' option...
|
# TODO Can't figure out how to select the 'none' option...
|
||||||
# self.assertFormError(response, 'form', 'plug', 'A cable must have a plug')
|
# self.assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||||
# self.assertFormError(response, 'form', 'socket', 'A cable must have a socket')
|
|
||||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||||
self.assertFormError(response, 'form', 'circuits', 'There must be at least one circuit in a cable')
|
|
||||||
self.assertFormError(response, 'form', 'cores', 'There must be at least one core in a cable')
|
|
||||||
|
|
||||||
def test_asset_duplicate(self):
|
def test_asset_duplicate(self):
|
||||||
url = reverse('asset_duplicate', kwargs={'pk': self.cable_asset.asset_id})
|
url = reverse('asset_duplicate', kwargs={'pk': self.cable_asset.asset_id})
|
||||||
response = self.client.post(url, {'is_cable': True, 'length': 0, 'csa': 0, 'circuits': 0, 'cores': 0})
|
response = self.client.post(url, {'is_cable': True, 'length': 0, 'csa': 0})
|
||||||
|
|
||||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||||
self.assertFormError(response, 'form', 'circuits', 'There must be at least one circuit in a cable')
|
|
||||||
self.assertFormError(response, 'form', 'cores', 'There must be at least one core in a cable')
|
|
||||||
|
|
||||||
|
|
||||||
class TestSampleDataGenerator(TestCase):
|
class TestSampleDataGenerator(TestCase):
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ urlpatterns = [
|
|||||||
path('activity', permission_required_with_403('assets.view_asset')
|
path('activity', permission_required_with_403('assets.view_asset')
|
||||||
(views.ActivityTable.as_view()), name='asset_activity_table'),
|
(views.ActivityTable.as_view()), name='asset_activity_table'),
|
||||||
|
|
||||||
|
path('cabletype/list/', permission_required_with_403('assets.view_cable_type')(views.CableTypeList.as_view()), name='cable_type_list'),
|
||||||
|
path('cabletype/create/', permission_required_with_403('assets.add_cable_type')(views.CableTypeCreate.as_view()), name='cable_type_create'),
|
||||||
|
path('cabletype/<int:pk>/update/', permission_required_with_403('assets.change_cable_type')(views.CableTypeUpdate.as_view()), name='cable_type_update'),
|
||||||
|
path('cabletype/<int:pk>/detail/', permission_required_with_403('assets.view_cable_type')(views.CableTypeDetail.as_view()), name='cable_type_detail'),
|
||||||
|
|
||||||
path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'),
|
path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'),
|
||||||
path('asset/id/<str:pk>/embed/',
|
path('asset/id/<str:pk>/embed/',
|
||||||
xframe_options_exempt(
|
xframe_options_exempt(
|
||||||
@@ -31,6 +36,9 @@ urlpatterns = [
|
|||||||
views.AssetOembed.as_view(),
|
views.AssetOembed.as_view(),
|
||||||
name='asset_oembed'),
|
name='asset_oembed'),
|
||||||
|
|
||||||
|
path('asset/audit/', permission_required_with_403('assets.change_asset')(views.AssetAuditList.as_view()), name='asset_audit_list'),
|
||||||
|
path('asset/id/<str:pk>/audit/', permission_required_with_403('assets.change_asset')(views.AssetAudit.as_view()), name='asset_audit'),
|
||||||
|
|
||||||
path('supplier/list', views.SupplierList.as_view(), name='supplier_list'),
|
path('supplier/list', views.SupplierList.as_view(), name='supplier_list'),
|
||||||
path('supplier/<int:pk>', views.SupplierDetail.as_view(), name='supplier_detail'),
|
path('supplier/<int:pk>', views.SupplierDetail.as_view(), name='supplier_detail'),
|
||||||
path('supplier/create', permission_required_with_403('assets.add_supplier')
|
path('supplier/create', permission_required_with_403('assets.add_supplier')
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
import simplejson
|
import simplejson
|
||||||
from assets import forms, models
|
from assets import forms, models
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.core import serializers
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import Http404, HttpResponse, JsonResponse
|
from django.http import Http404, HttpResponse, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
@@ -107,7 +112,14 @@ class AssetEdit(LoginRequiredMixin, AssetIDUrlMixin, generic.UpdateView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("asset_detail", kwargs={"pk": self.object.asset_id})
|
if self.request.is_ajax():
|
||||||
|
url = reverse_lazy('closemodal')
|
||||||
|
update_url = str(reverse_lazy('asset_update', kwargs={'pk': self.object.pk}))
|
||||||
|
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
|
||||||
|
messages.info(self.request, "modalobject[0]['update_url']='" + update_url + "'")
|
||||||
|
else:
|
||||||
|
url = reverse_lazy('asset_detail', kwargs={'pk': self.object.asset_id, })
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
class AssetCreate(LoginRequiredMixin, generic.CreateView):
|
class AssetCreate(LoginRequiredMixin, generic.CreateView):
|
||||||
@@ -169,6 +181,30 @@ class AssetEmbed(AssetDetail):
|
|||||||
template_name = 'asset_embed.html'
|
template_name = 'asset_embed.html'
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
|
class AssetAuditList(AssetList):
|
||||||
|
template_name = 'asset_audit_list.html'
|
||||||
|
hide_hidden_status = False
|
||||||
|
|
||||||
|
# TODO Refresh this when the modal is submitted
|
||||||
|
def get_queryset(self):
|
||||||
|
self.form = forms.AssetSearchForm(data={})
|
||||||
|
return self.model.objects.filter(Q(last_audited_at__isnull=True))
|
||||||
|
|
||||||
|
|
||||||
|
class AssetAudit(AssetEdit):
|
||||||
|
template_name = 'asset_audit.html'
|
||||||
|
form_class = forms.AssetAuditForm
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
# TODO For some reason this doesn't stick when done in form_valid??
|
||||||
|
asset = self.get_object()
|
||||||
|
asset.last_audited_by = self.request.user
|
||||||
|
asset.last_audited_at = timezone.now()
|
||||||
|
asset.save()
|
||||||
|
return super().get_success_url()
|
||||||
|
|
||||||
|
|
||||||
class SupplierList(generic.ListView):
|
class SupplierList(generic.ListView):
|
||||||
model = models.Supplier
|
model = models.Supplier
|
||||||
template_name = 'supplier_list.html'
|
template_name = 'supplier_list.html'
|
||||||
@@ -263,3 +299,44 @@ class ActivityTable(versioning.ActivityTable):
|
|||||||
context['title'] = 'Asset Database'
|
context['title'] = 'Asset Database'
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
class CableTypeList(generic.ListView):
|
||||||
|
model = models.CableType
|
||||||
|
template_name = 'cable_type_list.html'
|
||||||
|
paginate_by = 40
|
||||||
|
# ordering = ['__str__']
|
||||||
|
|
||||||
|
|
||||||
|
class CableTypeDetail(generic.DetailView):
|
||||||
|
model = models.CableType
|
||||||
|
template_name = 'cable_type_form.html'
|
||||||
|
|
||||||
|
|
||||||
|
class CableTypeCreate(generic.CreateView):
|
||||||
|
model = models.CableType
|
||||||
|
template_name = "cable_type_form.html"
|
||||||
|
form_class = forms.CableTypeForm
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CableTypeCreate, self).get_context_data(**kwargs)
|
||||||
|
context["create"] = True
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse("cable_type_detail", kwargs={"pk": self.object.pk})
|
||||||
|
|
||||||
|
|
||||||
|
class CableTypeUpdate(generic.UpdateView):
|
||||||
|
model = models.CableType
|
||||||
|
template_name = "cable_type_form.html"
|
||||||
|
form_class = forms.CableTypeForm
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CableTypeUpdate, self).get_context_data(**kwargs)
|
||||||
|
context["edit"] = True
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse("cable_type_detail", kwargs={"pk": self.object.pk})
|
||||||
|
|||||||
@@ -21,4 +21,5 @@ requests==2.23.0
|
|||||||
selenium==3.141.0
|
selenium==3.141.0
|
||||||
simplejson==3.17.0
|
simplejson==3.17.0
|
||||||
whitenoise==5.0.1
|
whitenoise==5.0.1
|
||||||
|
reportlab==3.4.0
|
||||||
z3c.rml==3.9.1
|
z3c.rml==3.9.1
|
||||||
|
|||||||
Reference in New Issue
Block a user