Fix handling of prefixed Asset IDs and sorting of the asset list (#382)

* FIX: Remove misleading admin site title

* Moved across assets_id sorting to use proper numeric values. Also mofifies SQL command to find free asset IDs so that it works on postgres.

* Changed generateSampleAssetsData.py to include prefices on some cables.

* Fixed pep8

* Fixed missed migration

* Ensured hidden asset fields are completed on every database write

* CMULTI is a thing, and therefore a max prefix length of 5 cannot be a thing
This commit is contained in:
Matthew Smith
2019-12-06 00:28:54 +00:00
committed by Arona Jones
parent 228d72b7b2
commit b77615b9b9
5 changed files with 106 additions and 20 deletions

View File

@@ -30,8 +30,3 @@ class AssetAdmin(admin.ModelAdmin):
@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']
admin.AdminSite.site_header = 'PyAssets - TEC\'s Asset System'
admin.AdminSite.site_title = 'PyAssets Admin'
admin.AdminSite.index_title = 'System Administration'

View File

@@ -7,6 +7,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']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@@ -1,8 +1,7 @@
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 assets import models from assets import models
import random
class Command(BaseCommand): class Command(BaseCommand):
@@ -50,7 +49,7 @@ class Command(BaseCommand):
suppliers = models.Supplier.objects.all() suppliers = models.Supplier.objects.all()
for i in range(100): for i in range(100):
asset = models.Asset.objects.create( asset = models.Asset(
asset_id='{}'.format(models.Asset.get_available_asset_id()), asset_id='{}'.format(models.Asset.get_available_asset_id()),
description=random.choice(asset_description), description=random.choice(asset_description),
category=random.choice(categories), category=random.choice(categories),
@@ -63,11 +62,12 @@ class Command(BaseCommand):
if i % 3 == 0: if i % 3 == 0:
asset.purchased_from = random.choice(suppliers) asset.purchased_from = random.choice(suppliers)
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']
asset_prefixes = ["C", "C4P", "CBNC", "CDMX", "CDV", "CRCD", "CSOCA", "CXLR"]
csas = [0.75, 1.00, 1.25, 2.5, 4] csas = [0.75, 1.00, 1.25, 2.5, 4]
lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100] lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100]
@@ -79,7 +79,7 @@ class Command(BaseCommand):
connectors = models.Connector.objects.all() connectors = models.Connector.objects.all()
for i in range(100): for i in range(100):
asset = models.Asset.objects.create( asset = models.Asset(
asset_id='{}'.format(models.Asset.get_available_asset_id()), asset_id='{}'.format(models.Asset.get_available_asset_id()),
description=random.choice(asset_description), description=random.choice(asset_description),
category=random.choice(categories), category=random.choice(categories),
@@ -95,12 +95,17 @@ class Command(BaseCommand):
cores=random.choice(circuits) cores=random.choice(circuits)
) )
if i % 5 == 0:
prefix = random.choice(asset_prefixes)
asset.asset_id = prefix + str(models.Asset.get_available_asset_id(wanted_prefix=prefix))
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.save() asset.save()
def create_connectors(self): def create_connectors(self):

View File

@@ -0,0 +1,64 @@
# Generated by Django 2.0.13 on 2019-12-05 20:42
from django.db import migrations, models
import django.db.migrations.operations.special
def forwards(apps, schema_editor):
AssetModel = apps.get_model('assets', 'Asset')
for row in AssetModel.objects.all():
row.asset_id = row.asset_id.upper()
asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id)
if asset_search is None: # If the asset_id doesn't have a number at the end
row.asset_id += "1"
asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id)
row.asset_id_prefix = asset_search.group(1)
row.asset_id_number = int(asset_search.group(2))
row.save(update_fields=['asset_id', 'asset_id_prefix', 'asset_id_number'])
# Functions from the following migrations need manual copying.
# Move them and any dependencies into this file, then update the
# RunPython operations to refer to the local versions:
# assets.migrations.0008_auto_20191205_1937
class Migration(migrations.Migration):
replaces = [('assets', '0008_auto_20191205_1937'), ('assets', '0009_auto_20191205_2041')]
dependencies = [
('assets', '0007_auto_20190108_0202_squashed_0014_auto_20191017_2052'),
]
operations = [
migrations.AddField(
model_name='asset',
name='asset_id_number',
field=models.IntegerField(default=1),
),
migrations.AddField(
model_name='asset',
name='asset_id_prefix',
field=models.CharField(default='', max_length=5),
),
migrations.RunPython(
code=forwards,
reverse_code=django.db.migrations.operations.special.RunPython.noop,
),
migrations.AlterModelOptions(
name='asset',
options={'ordering': ['asset_id_prefix', 'asset_id_number'], 'permissions': (('asset_finance', 'Can see financial data for assets'), ('view_asset', 'Can view an asset'))},
),
migrations.AlterField(
model_name='asset',
name='asset_id',
field=models.CharField(max_length=15, unique=True),
),
migrations.AlterField(
model_name='asset',
name='asset_id_prefix',
field=models.CharField(default='', max_length=8),
),
]

View File

@@ -3,6 +3,9 @@ from django.core.exceptions import ValidationError
from django.db import models, connection from django.db import models, connection
from django.urls import reverse from django.urls import reverse
from django.db.models.signals import pre_save
from django.dispatch.dispatcher import receiver
class AssetCategory(models.Model): class AssetCategory(models.Model):
class Meta: class Meta:
@@ -54,14 +57,14 @@ class Connector(models.Model):
class Asset(models.Model): class Asset(models.Model):
class Meta: class Meta:
ordering = ['asset_id'] ordering = ['asset_id_prefix', 'asset_id_number']
permissions = ( permissions = (
('asset_finance', 'Can see financial data for assets'), ('asset_finance', 'Can see financial data for assets'),
('view_asset', 'Can view an asset') ('view_asset', 'Can view an asset')
) )
parent = models.ForeignKey(to='self', related_name='asset_parent', blank=True, null=True, on_delete=models.SET_NULL) 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) asset_id = models.CharField(max_length=15, unique=True)
description = models.CharField(max_length=120) description = models.CharField(max_length=120)
category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE)
status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE)
@@ -83,18 +86,24 @@ class Asset(models.Model):
circuits = models.IntegerField(blank=True, null=True) circuits = models.IntegerField(blank=True, null=True)
cores = models.IntegerField(blank=True, null=True) cores = models.IntegerField(blank=True, null=True)
def get_available_asset_id(): # Hidden asset_id components
# For example, if asset_id was "C1001" then asset_id_prefix would be "C" and number "1001"
asset_id_prefix = models.CharField(max_length=8, default="")
asset_id_number = models.IntegerField(default=1)
def get_available_asset_id(wanted_prefix=""):
sql = """ sql = """
SELECT MIN(CAST(a.asset_id AS int))+1 SELECT a.asset_id_number+1
FROM assets_asset a FROM assets_asset a
LEFT OUTER JOIN assets_asset b ON LEFT OUTER JOIN assets_asset b ON
(CAST(a.asset_id AS int) + 1 = CAST(b.asset_id AS int)) (a.asset_id_number + 1 = b.asset_id_number AND
WHERE b.asset_id IS NULL AND CAST(a.asset_id AS int) >= %s; a.asset_id_prefix = b.asset_id_prefix)
WHERE b.asset_id IS NULL AND a.asset_id_number >= %s AND a.asset_id_prefix = %s;
""" """
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute(sql, [9000]) cursor.execute(sql, [9000, wanted_prefix])
row = cursor.fetchone() row = cursor.fetchone()
if row[0] is None: if row is None or row[0] is None:
return 9000 return 9000
else: else:
return row[0] return row[0]
@@ -114,8 +123,9 @@ class Asset(models.Model):
errdict["date_sold"] = ["Cannot sell an item before it is acquired"] errdict["date_sold"] = ["Cannot sell an item before it is acquired"]
self.asset_id = self.asset_id.upper() self.asset_id = self.asset_id.upper()
if re.search("^[a-zA-Z0-9]+$", self.asset_id) is None: asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", self.asset_id)
errdict["asset_id"] = ["An Asset ID can only consist of letters and numbers"] if asset_search is None:
errdict["asset_id"] = ["An Asset ID can only consist of letters and numbers, with a final number"]
if self.purchase_price and self.purchase_price < 0: if self.purchase_price and self.purchase_price < 0:
errdict["purchase_price"] = ["A price cannot be negative"] errdict["purchase_price"] = ["A price cannot be negative"]
@@ -139,3 +149,14 @@ class Asset(models.Model):
if errdict != {}: # If there was an error when validation if errdict != {}: # If there was an error when validation
raise ValidationError(errdict) raise ValidationError(errdict)
@receiver(pre_save, sender=Asset)
def pre_save_asset(sender, instance, **kwargs):
"""Automatically fills in hidden members on database access"""
asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", instance.asset_id)
if asset_search is None:
instance.asset_id += "1"
asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", instance.asset_id)
instance.asset_id_prefix = asset_search.group(1)
instance.asset_id_number = int(asset_search.group(2))