diff --git a/RIGS/forms.py b/RIGS/forms.py index 9caf3016..18a81e14 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -39,6 +39,7 @@ class EmbeddedAuthenticationForm(AuthenticationForm): super().__init__(*args, **kwargs) self.fields['username'].widget.attrs.pop('autofocus', None) + class PasswordReset(PasswordResetForm): captcha = ReCaptchaField(label='Captcha') diff --git a/RIGS/management/commands/generateSampleData.py b/RIGS/management/commands/generateSampleData.py index 5263a030..3c82fe57 100644 --- a/RIGS/management/commands/generateSampleData.py +++ b/RIGS/management/commands/generateSampleData.py @@ -121,8 +121,19 @@ class Command(BaseCommand): self.keyholder_group = Group.objects.create(name='Keyholders') self.finance_group = Group.objects.create(name='Finance') - keyholderPerms = ["add_event", "change_event", "view_event", "add_eventitem", "change_eventitem", "delete_eventitem", "add_organisation", "change_organisation", "view_organisation", "add_person", "change_person", "view_person", "view_profile", "add_venue", "change_venue", "view_venue"] - financePerms = ["change_event", "view_event", "add_eventitem", "change_eventitem", "add_invoice", "change_invoice", "view_invoice", "add_organisation", "change_organisation", "view_organisation", "add_payment", "change_payment", "delete_payment", "add_person", "change_person", "view_person"] + keyholderPerms = ["add_event", "change_event", "view_event", + "add_eventitem", "change_eventitem", "delete_eventitem", + "add_organisation", "change_organisation", "view_organisation", + "add_person", "change_person", "view_person", "view_profile", + "add_venue", "change_venue", "view_venue", + "add_asset", "change_asset", "delete_asset", + "asset_finance"] + financePerms = ["change_event", "view_event", "add_eventitem", + "change_eventitem", "add_invoice", "change_invoice", "view_invoice", + "add_organisation", "change_organisation", "view_organisation", + "add_payment", "change_payment", "delete_payment", + "add_person", "change_person", "view_person", + "asset_finance", "change_asset"] for permId in keyholderPerms: self.keyholder_group.permissions.add(Permission.objects.get(codename=permId)) diff --git a/RIGS/migrations/0035_auto_20191008_2148.py b/RIGS/migrations/0035_auto_20191008_2148.py new file mode 100644 index 00000000..756ceac5 --- /dev/null +++ b/RIGS/migrations/0035_auto_20191008_2148.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.13 on 2019-10-08 20:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0034_event_risk_assessment_edit_url'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='risk_assessment_edit_url', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='risk assessment'), + ), + ] diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index e6112b31..99c80601 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -83,6 +83,7 @@ class EventEmbed(EventDetail): class EventRA(generic.base.RedirectView): permanent = False + def get_redirect_url(self, *args, **kwargs): event = get_object_or_404(models.Event, pk=kwargs['pk']) @@ -406,6 +407,7 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView): context['to_name'] = self.request.GET.get('to_name', None) return context + @method_decorator(csrf_exempt, name='dispatch') class LogRiskAssessment(generic.View): http_method_names = ["post"] diff --git a/RIGS/views.py b/RIGS/views.py index aaaac7da..b197334c 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -62,7 +62,7 @@ def login_embed(request, **kwargs): messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.') request.method = 'GET' # Render the page without trying to login - return login(request, template_name="registration/login_embed.html", authentication_form=forms.EmbeddedAuthenticationForm) + return login(request, template_name="registration/login_embed.html", authentication_form=forms.EmbeddedAuthenticationForm) """ diff --git a/assets/admin.py b/assets/admin.py index bbd7b2d0..7044040f 100644 --- a/assets/admin.py +++ b/assets/admin.py @@ -32,11 +32,6 @@ class ConnectorAdmin(admin.ModelAdmin): list_display = ['id', '__str__', 'current_rating', 'voltage_rating', 'num_pins'] -@admin.register(assets.Cable) -class CableAdmin(admin.ModelAdmin): - pass - - admin.AdminSite.site_header = 'PyAssets - TEC\'s Asset System' admin.AdminSite.site_title = 'PyAssets Admin' admin.AdminSite.index_title = 'System Administration' diff --git a/assets/forms.py b/assets/forms.py index c727a9d1..19e00b11 100644 --- a/assets/forms.py +++ b/assets/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.core.exceptions import ValidationError from assets import models @@ -9,7 +10,7 @@ class AssetForm(forms.ModelForm): fields = '__all__' -class SupplierForm(forms.ModelForm): +class SupplierForm(forms.Form): class Meta: model = models.Supplier fields = '__all__' diff --git a/assets/management/commands/createSampleData.py b/assets/management/commands/createSampleData.py index 0d5beeb0..616cff7c 100644 --- a/assets/management/commands/createSampleData.py +++ b/assets/management/commands/createSampleData.py @@ -21,6 +21,7 @@ class Command(BaseCommand): self.create_statuses() self.create_suppliers() self.create_assets() + self.create_connectors() def create_categories(self): categories = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging'] @@ -35,7 +36,8 @@ class Command(BaseCommand): models.AssetStatus.objects.create(name=stat) 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","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"] + 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", + "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"] for supplier in suppliers: models.Supplier.objects.create(name=supplier) @@ -60,3 +62,14 @@ class Command(BaseCommand): asset.purchased_from = random.choice(suppliers) asset.save() + + def create_connectors(self): + connectors = [ + {"description": "13A UK", "current_rating": 13, "voltage_rating": 230, "num_pins": 3}, + {"description": "16A", "current_rating": 16, "voltage_rating": 230, "num_pins": 3}, + {"description": "32/3", "current_rating": 32, "voltage_rating": 400, "num_pins": 5}, + {"description": "Socapex", "current_rating": 23, "voltage_rating": 600, "num_pins": 19}, + ] + for connector in connectors: + conn = models.Connector.objects.create(**connector) + conn.save() diff --git a/assets/management/commands/deleteSampleData.py b/assets/management/commands/deleteSampleData.py index d58bee5f..f0338faa 100644 --- a/assets/management/commands/deleteSampleData.py +++ b/assets/management/commands/deleteSampleData.py @@ -21,7 +21,7 @@ class Command(BaseCommand): self.delete_objects(models.AssetCategory) self.delete_objects(models.AssetStatus) self.delete_objects(models.Supplier) - self.delete_objects(models.Collection) + self.delete_objects(models.Connector) self.delete_objects(models.Asset) def delete_objects(self, model): diff --git a/assets/migrations/0009_auto_20191008_2148.py b/assets/migrations/0009_auto_20191008_2148.py new file mode 100644 index 00000000..cbb474cc --- /dev/null +++ b/assets/migrations/0009_auto_20191008_2148.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.13 on 2019-10-08 20:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0008_auto_20191002_1931'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.CharField(max_length=10, unique=True), + ), + ] diff --git a/assets/migrations/0010_auto_20191013_2123.py b/assets/migrations/0010_auto_20191013_2123.py new file mode 100644 index 00000000..b56c40c4 --- /dev/null +++ b/assets/migrations/0010_auto_20191013_2123.py @@ -0,0 +1,67 @@ +# Generated by Django 2.0.13 on 2019-10-13 20:23 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0009_auto_20191008_2148'), + ] + + operations = [ + migrations.RemoveField( + model_name='cable', + name='asset_ptr', + ), + migrations.RemoveField( + model_name='cable', + name='plug', + ), + migrations.RemoveField( + model_name='cable', + name='socket', + ), + migrations.AlterModelOptions( + name='asset', + options={}, + ), + migrations.RemoveField( + model_name='asset', + name='polymorphic_ctype', + ), + migrations.AddField( + model_name='asset', + name='circuits', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='asset', + name='cores', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='asset', + name='csa', + field=models.DecimalField(blank=True, decimal_places=2, help_text='mm^2', max_digits=10, null=True), + ), + migrations.AddField( + model_name='asset', + name='length', + field=models.DecimalField(blank=True, decimal_places=1, help_text='m', max_digits=10, null=True), + ), + migrations.AddField( + model_name='asset', + name='plug', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plug', to='assets.Connector'), + ), + migrations.AddField( + model_name='asset', + name='socket', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='socket', to='assets.Connector'), + ), + migrations.DeleteModel( + name='Cable', + ), + ] diff --git a/assets/migrations/0011_auto_20191013_2247.py b/assets/migrations/0011_auto_20191013_2247.py new file mode 100644 index 00000000..af49b124 --- /dev/null +++ b/assets/migrations/0011_auto_20191013_2247.py @@ -0,0 +1,24 @@ +# Generated by Django 2.0.13 on 2019-10-13 21:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0010_auto_20191013_2123'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='plug', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plug', to='assets.Connector'), + ), + migrations.AlterField( + model_name='asset', + name='socket', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='socket', to='assets.Connector'), + ), + ] diff --git a/assets/migrations/0012_auto_20191014_0012.py b/assets/migrations/0012_auto_20191014_0012.py new file mode 100644 index 00000000..fdff40bb --- /dev/null +++ b/assets/migrations/0012_auto_20191014_0012.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.13 on 2019-10-13 23:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0011_auto_20191013_2247'), + ] + + operations = [ + migrations.AlterModelOptions( + name='asset', + options={'permissions': (('asset_finance', 'Can see financial data for assets'),)}, + ), + ] diff --git a/assets/models.py b/assets/models.py index 37a57628..005beadd 100644 --- a/assets/models.py +++ b/assets/models.py @@ -1,8 +1,10 @@ +from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from polymorphic.models import PolymorphicModel import datetime +import re class AssetCategory(models.Model): @@ -37,10 +39,25 @@ class Supplier(models.Model): return self.name -class Asset(PolymorphicModel): +class Connector(models.Model): + description = models.CharField(max_length=80) + current_rating = models.DecimalField(decimal_places=2, max_digits=10, help_text='Amps') + voltage_rating = models.IntegerField(help_text='Volts') + num_pins = models.IntegerField(blank=True, null=True) + + def __str__(self): + return self.description + + +class Asset(models.Model): + class Meta: + ordering = ['asset_id'] + permissions = ( + ('asset_finance', 'Can see financial data for assets'), + ) 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) + asset_id = models.CharField(max_length=10, unique=True) description = models.CharField(max_length=120) category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) @@ -55,34 +72,55 @@ class Asset(PolymorphicModel): # Cable assets is_cable = models.BooleanField(default=False) - - def get_absolute_url(self): - return reverse('asset_detail', kwargs={'pk': self.pk}) - - def __str__(self): - return str(self.asset_id) + ' - ' + self.description - -class Connector(models.Model): - description = models.CharField(max_length=80) - current_rating = models.DecimalField(decimal_places=2, max_digits=10, help_text='Amps') - voltage_rating = models.IntegerField(help_text='Volts') - num_pins = models.IntegerField(blank=True, null=True) - - def __str__(self): - return self.description - - -class Cable(Asset): - plug = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='plug', null=True) - socket = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='socket', null=True) + plug = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='plug', blank=True, null=True) + socket = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='socket', blank=True, null=True) length = models.DecimalField(decimal_places=1, max_digits=10, blank=True, null=True, help_text='m') csa = models.DecimalField(decimal_places=2, max_digits=10, blank=True, null=True, help_text='mm^2') circuits = models.IntegerField(blank=True, null=True) cores = models.IntegerField(blank=True, null=True) - def cable_resistance(self): - rho = 0.0000000168 - return (rho * self.length) / (self.csa * 1000000) + def get_absolute_url(self): + return reverse('asset_detail', kwargs={'pk': self.pk}) def __str__(self): - return '{} - {}m - {}'.format(self.plug, self.length, self.socket) + out = str(self.asset_id) + ' - ' + self.description + if self.is_cable: + out += '{} - {}m - {}'.format(self.plug, self.length, self.socket) + return out + + def clean(self): + if self.date_sold and self.date_acquired > self.date_sold: + raise ValidationError({"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: + raise ValidationError({"asset_id": "An Asset ID can only consist of letters and numbers"}) + + if self.purchase_price and self.purchase_price < 0: + raise ValidationError({"purchase_price": "A price cannot be negative"}) + + if self.salvage_value and self.salvage_value < 0: + raise ValidationError({"purchase_price": "A price cannot be negative"}) + + if self.is_cable: + if self.length is None: + raise ValidationError({"length": "The length of a cable must be a number"}) + elif self.csa is None: + raise ValidationError({"csa": "The csa of a cable must be a number"}) + elif self.circuits is None: + raise ValidationError({"circuits": "The number of circuits in a cable must be a number"}) + elif self.cores is None: + raise ValidationError({"cores": "The number of cores in a cable must be a number"}) + elif self.socket is None: + raise ValidationError({"plug": "A cable must have a plug"}) + elif self.plug is None: + raise ValidationError({"socket": "A cable must have a socket"}) + + if self.length <= 0: + raise ValidationError({"length": "The length of a cable must be more than 0"}) + elif self.csa <= 0: + raise ValidationError({"csa": "The CSA of a cable must be more than 0"}) + elif self.circuits <= 0: + raise ValidationError({"circuits": "There must be at least one circuit in a cable"}) + elif self.cores <= 0: + raise ValidationError({"cores": "There must be at least one core in a cable"}) diff --git a/assets/templates/asset_create.html b/assets/templates/asset_create.html new file mode 100644 index 00000000..1e58c720 --- /dev/null +++ b/assets/templates/asset_create.html @@ -0,0 +1,71 @@ +{% extends 'base_assets.html' %} +{% load widget_tweaks %} +{% load asset_templatetags %} +{% block title %}Asset {{ object.asset_id }}{% endblock %} + + +{% block content %} + +