Compare commits

..

21 Commits

Author SHA1 Message Date
David Taylor
1b3dca04a2 Remove broken migration 2019-12-10 22:44:49 +00:00
David Taylor
3731c5bba0 Merge branch 'master' into asset_fixes 2019-12-10 22:42:14 +00:00
Matthew Smith
61a46fa1c2 Removed bad migrator, made data addition atomic in migration 0008 2019-12-06 21:29:20 +00:00
ddc23ce4e5 FIX: Prefix field still too limited for legacy data
Fingers crossed this works I don't have the actual data locally... I know I'm making a mess but needs must.

I genuinely hate whoever decided prefixes were a good idea now.
2019-12-06 00:58:39 +00:00
602ccc15ea FIX: Fix missing import
Presumably caused by Matt's IDE being overzealous again. I know I shouldn't be pushing to master but...one line fix...
2019-12-06 00:40:56 +00:00
Matthew Smith
b77615b9b9 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
2019-12-06 00:28:54 +00:00
dea628e7a2 Merge branch 'master' into asset_fixes 2019-12-06 00:25:08 +00:00
Matthew Smith
171d5d633e CMULTI is a thing, and therefore a max prefix length of 5 cannot be a thing 2019-12-05 20:43:47 +00:00
Matthew Smith
294c839bc3 Ensured hidden asset fields are completed on every database write 2019-12-05 20:05:15 +00:00
David Taylor
228d72b7b2 Automatically run migrations on deploy
Because running them via Heroku CLI is easy to forget
2019-12-05 17:26:02 +00:00
Matthew Smith
73e8bc3326 Fixed missed migration 2019-12-05 17:01:37 +00:00
Matthew Smith
5a081a97c4 Fixed pep8 2019-12-05 16:50:23 +00:00
Matthew Smith
1f0dc9f1ae Changed generateSampleAssetsData.py to include prefices on some cables. 2019-12-05 15:28:07 +00:00
Matthew Smith
8ad0bdf5f3 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. 2019-12-05 15:07:17 +00:00
faa86dbe8d FIX: Remove misleading admin site title 2019-12-05 13:26:35 +00:00
62541194ee CHORE: Fix pep8
mutter mutter mutter, grumble
2019-12-05 13:10:08 +00:00
0d8fd99d92 FIX: Permission errors
This fixes keyholders being unable to see financials information or create assets. (Well, the latter needs anyone to be able to create assets before it is fully fixed)
2019-12-05 13:00:47 +00:00
9d51a82f31 FIX: Fix asset sample data generation 2019-12-05 12:56:22 +00:00
c059227d5d Revert "CHANGE: Restrict viewing asset DB to keyholders."
This reverts commit 2c334196d5.
2019-12-05 12:42:05 +00:00
2c334196d5 CHANGE: Restrict viewing asset DB to keyholders.
This is in line with what it was when it was on the Shared Drive.
2019-12-04 23:59:39 +00:00
4f036af85a Create the Asset Database (#363) 2019-12-04 23:14:27 +00:00
9 changed files with 100 additions and 26 deletions

View File

@@ -1 +1,2 @@
release: python manage.py migrate
web: gunicorn PyRIGS.wsgi --log-file -

View File

@@ -30,8 +30,3 @@ class AssetAdmin(admin.ModelAdmin):
@admin.register(assets.Connector)
class ConnectorAdmin(admin.ModelAdmin):
list_display = ['id', '__str__', 'current_rating', 'voltage_rating', 'num_pins']
admin.AdminSite.site_header = 'PyAssets - TEC\'s Asset System'
admin.AdminSite.site_title = 'PyAssets Admin'
admin.AdminSite.index_title = 'System Administration'

View File

@@ -7,6 +7,7 @@ class AssetForm(forms.ModelForm):
class Meta:
model = models.Asset
fields = '__all__'
exclude = ['asset_id_prefix', 'asset_id_number']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@@ -1,6 +1,6 @@
import random
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from assets import models
@@ -49,7 +49,7 @@ class Command(BaseCommand):
suppliers = models.Supplier.objects.all()
for i in range(100):
asset = models.Asset.objects.create(
asset = models.Asset(
asset_id='{}'.format(models.Asset.get_available_asset_id()),
description=random.choice(asset_description),
category=random.choice(categories),
@@ -62,11 +62,12 @@ class Command(BaseCommand):
if i % 3 == 0:
asset.purchased_from = random.choice(suppliers)
asset.clean()
asset.save()
def create_cables(self):
asset_description = ['The worm', 'Harting without a cap', 'Heavy cable', 'Extension lead', 'IEC cable that we should remember to prep']
asset_prefixes = ["C", "C4P", "CBNC", "CDMX", "CDV", "CRCD", "CSOCA", "CXLR"]
csas = [0.75, 1.00, 1.25, 2.5, 4]
lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100]
@@ -78,7 +79,7 @@ class Command(BaseCommand):
connectors = models.Connector.objects.all()
for i in range(100):
asset = models.Asset.objects.create(
asset = models.Asset(
asset_id='{}'.format(models.Asset.get_available_asset_id()),
description=random.choice(asset_description),
category=random.choice(categories),
@@ -94,12 +95,17 @@ class Command(BaseCommand):
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:
asset.parent = models.Asset.objects.order_by('?').first()
if i % 3 == 0:
asset.purchased_from = random.choice(suppliers)
asset.clean()
asset.save()
def create_connectors(self):

View File

@@ -0,0 +1,51 @@
# Generated by Django 2.0.13 on 2019-12-06 21:24
from django.db import migrations, models, transaction
import re
def forwards(apps, schema_editor):
AssetModel = apps.get_model('assets', 'Asset')
with transaction.atomic():
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'])
class Migration(migrations.Migration):
dependencies = [
('assets', '0007_auto_20190108_0202_squashed_0014_auto_20191017_2052'),
]
operations = [
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.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=8),
),
migrations.AlterField(
model_name='asset',
name='asset_id',
field=models.CharField(max_length=15, unique=True),
),
migrations.RunPython(
code=forwards,
reverse_code=migrations.operations.special.RunPython.noop,
),
]

View File

@@ -3,6 +3,9 @@ from django.core.exceptions import ValidationError
from django.db import models, connection
from django.urls import reverse
from django.db.models.signals import pre_save
from django.dispatch.dispatcher import receiver
class AssetCategory(models.Model):
class Meta:
@@ -54,14 +57,14 @@ class Connector(models.Model):
class Asset(models.Model):
class Meta:
ordering = ['asset_id']
ordering = ['asset_id_prefix', 'asset_id_number']
permissions = (
('asset_finance', 'Can see financial data for assets'),
('view_asset', 'Can view an asset')
)
parent = models.ForeignKey(to='self', related_name='asset_parent', blank=True, null=True, on_delete=models.SET_NULL)
asset_id = models.CharField(max_length=10, unique=True)
asset_id = models.CharField(max_length=15, unique=True)
description = models.CharField(max_length=120)
category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE)
status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE)
@@ -83,18 +86,24 @@ class Asset(models.Model):
circuits = 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 = """
SELECT MIN(CAST(a.asset_id AS int))+1
SELECT a.asset_id_number+1
FROM assets_asset a
LEFT OUTER JOIN assets_asset b ON
(CAST(a.asset_id AS int) + 1 = CAST(b.asset_id AS int))
WHERE b.asset_id IS NULL AND CAST(a.asset_id AS int) >= %s;
(a.asset_id_number + 1 = b.asset_id_number AND
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:
cursor.execute(sql, [9000])
cursor.execute(sql, [9000, wanted_prefix])
row = cursor.fetchone()
if row[0] is None:
if row is None or row[0] is None:
return 9000
else:
return row[0]
@@ -114,8 +123,9 @@ class Asset(models.Model):
errdict["date_sold"] = ["Cannot sell an item before it is acquired"]
self.asset_id = self.asset_id.upper()
if re.search("^[a-zA-Z0-9]+$", self.asset_id) is None:
errdict["asset_id"] = ["An Asset ID can only consist of letters and numbers"]
asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", self.asset_id)
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:
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
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))

View File

@@ -25,12 +25,12 @@
</div>
</div>
<div class="row">
{% if perms.asset.asset_financial %}
{% if perms.assets.asset_finance %}
<div class="col-md-6">
{% include 'partials/purchasedetails_form.html' %}
</div>
{%endif%}
<div class="col-md-6"
<div class="col-md-6"
{% if not object.is_cable %} hidden="true" {% endif %} id="cable-table">
{% include 'partials/cable_form.html' %}
</div>

View File

@@ -20,7 +20,6 @@
<td style="vertical-align: middle;">{{ item.category }}</td>
<td style="vertical-align: middle;">{{ item.status }}</td>
<td class="hidden-xs">
<div class="btn-group" role="group">
<a type="button" class="btn btn-default btn-sm" href="{% url 'asset_detail' item.asset_id %}"><i class="glyphicon glyphicon-eye-open"></i> View</a>
{% if perms.assets.change_asset %}

View File

@@ -7,16 +7,16 @@ urlpatterns = [
path('', views.AssetList.as_view(), name='asset_index'),
path('asset/list/', views.AssetList.as_view(), name='asset_list'),
path('asset/id/<str:pk>/', views.AssetDetail.as_view(), name='asset_detail'),
path('asset/create/', permission_required_with_403('assets.create_asset')(views.AssetCreate.as_view()), name='asset_create'),
path('asset/create/', permission_required_with_403('assets.add_asset')(views.AssetCreate.as_view()), name='asset_create'),
path('asset/id/<str:pk>/edit/', permission_required_with_403('assets.change_asset')(views.AssetEdit.as_view()), name='asset_update'),
path('asset/id/<str:pk>/duplicate/', permission_required_with_403('assets.create_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'),
path('asset/id/<str:pk>/duplicate/', permission_required_with_403('assets.add_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'),
path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'),
path('supplier/list', views.SupplierList.as_view(), name='supplier_list'),
path('supplier/<int:pk>', views.SupplierDetail.as_view(), name='supplier_detail'),
path('supplier/create', permission_required_with_403('assets.create_supplier')(views.SupplierCreate.as_view()), name='supplier_create'),
path('supplier/<int:pk>/edit', permission_required_with_403('assets.edit_supplier')(views.SupplierUpdate.as_view()), name='supplier_update'),
path('supplier/create', permission_required_with_403('assets.add_supplier')(views.SupplierCreate.as_view()), name='supplier_create'),
path('supplier/<int:pk>/edit', permission_required_with_403('assets.change_supplier')(views.SupplierUpdate.as_view()), name='supplier_update'),
path('supplier/search/', views.SupplierSearch.as_view(), name='supplier_search_json'),
]