From 60bcf5c266f3527b4ff27352f8421f3185c3fdf9 Mon Sep 17 00:00:00 2001 From: Ben Burwood <77846694+benbur98@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:10:08 +0100 Subject: [PATCH] Rack Management (#10) * Add Rack and RackItem Models and Forms * Add Views and URLs * Add Template Pages * MakeMigrations for Rack and RackItem * Migrate to Abstract Device Model for Client/Network Devices * Link RackItem and Network Device * Bugfixes * More Bugfixes * Generalise Device Create and Index Pages for Client and Network Devices * Delete Admin/Test Files * Rack Measure Conversions and Index * Add Networked vs Infrastructure Devices * Forms Folder for Network App * Make Migrations for Network Devices --- common/templates/base/crud.html | 4 + common/templates/base/sidebar.html | 1 + common/utils/units.py | 8 ++ electric/admin.py | 3 - .../0002_alter_circuit_breaker_type.py | 27 ++++++ .../0003_alter_circuit_breaker_type.py | 27 ++++++ electric/templates/electric/index.html | 4 + electric/tests.py | 3 - home/admin.py | 3 - home/tests.py | 3 - mqtt/admin.py | 3 - mqtt/tests.py | 3 - network/admin.py | 3 - network/forms.py | 58 ------------ network/forms/__init__.py | 0 network/forms/devices.py | 63 +++++++++++++ network/forms/networking.py | 27 ++++++ network/forms/rack.py | 23 +++++ network/migrations/0002_rack_rackitem.py | 60 +++++++++++++ ...r_clientdevice_connection_type_and_more.py | 90 +++++++++++++++++++ network/migrations/0004_rackitem_device.py | 25 ++++++ ...edevice_alter_networkdevice_device_type.py | 57 ++++++++++++ network/models/__init__.py | 3 +- network/models/client_device.py | 35 -------- network/models/device.py | 55 ++++++++++++ network/models/rack.py | 68 ++++++++++++++ network/objects.py | 36 ++++++++ .../device/{edit.html => client_edit.html} | 2 +- network/templates/network/device/create.html | 48 ++++++++-- network/templates/network/device/index.html | 26 +++++- .../network/device/network_edit.html | 17 ++++ network/templates/network/rack/create.html | 17 ++++ network/templates/network/rack/edit.html | 17 ++++ network/templates/network/rack/index.html | 47 ++++++++++ .../templates/network/rack_item/create.html | 17 ++++ network/templates/network/rack_item/edit.html | 17 ++++ .../templates/network/rack_item/index.html | 35 ++++++++ network/tests.py | 3 - network/urls.py | 85 +++++++++++++++--- network/views/__init__.py | 2 +- network/views/device.py | 63 ++++++++++--- network/views/rack.py | 84 +++++++++++++++++ network/views/vlan.py | 3 +- network/views/wifi.py | 3 +- 44 files changed, 1019 insertions(+), 159 deletions(-) create mode 100644 common/utils/units.py delete mode 100644 electric/admin.py create mode 100644 electric/migrations/0002_alter_circuit_breaker_type.py create mode 100644 electric/migrations/0003_alter_circuit_breaker_type.py delete mode 100644 electric/tests.py delete mode 100644 home/admin.py delete mode 100644 home/tests.py delete mode 100644 mqtt/admin.py delete mode 100644 mqtt/tests.py delete mode 100644 network/admin.py delete mode 100644 network/forms.py create mode 100644 network/forms/__init__.py create mode 100644 network/forms/devices.py create mode 100644 network/forms/networking.py create mode 100644 network/forms/rack.py create mode 100644 network/migrations/0002_rack_rackitem.py create mode 100644 network/migrations/0003_alter_clientdevice_connection_type_and_more.py create mode 100644 network/migrations/0004_rackitem_device.py create mode 100644 network/migrations/0005_infrastructuredevice_alter_networkdevice_device_type.py delete mode 100644 network/models/client_device.py create mode 100644 network/models/device.py create mode 100644 network/models/rack.py create mode 100644 network/objects.py rename network/templates/network/device/{edit.html => client_edit.html} (86%) create mode 100644 network/templates/network/device/network_edit.html create mode 100644 network/templates/network/rack/create.html create mode 100644 network/templates/network/rack/edit.html create mode 100644 network/templates/network/rack/index.html create mode 100644 network/templates/network/rack_item/create.html create mode 100644 network/templates/network/rack_item/edit.html create mode 100644 network/templates/network/rack_item/index.html delete mode 100644 network/tests.py create mode 100644 network/views/rack.py diff --git a/common/templates/base/crud.html b/common/templates/base/crud.html index 13d8c9b..2718590 100644 --- a/common/templates/base/crud.html +++ b/common/templates/base/crud.html @@ -15,11 +15,15 @@

{% block header_content %}{% endblock %}

Back + {% if show_form|default:"True" %}
{% csrf_token %} {{ form|crispy }}
+ {% endif %} + + {% block additional_content %}{% endblock %} diff --git a/common/templates/base/sidebar.html b/common/templates/base/sidebar.html index 1376ae3..3bb911b 100644 --- a/common/templates/base/sidebar.html +++ b/common/templates/base/sidebar.html @@ -8,6 +8,7 @@
  • VLAN
  • WiFi
  • Devices
  • +
  • Racks
  • MQTT
  • diff --git a/common/utils/units.py b/common/utils/units.py new file mode 100644 index 0000000..9c024a7 --- /dev/null +++ b/common/utils/units.py @@ -0,0 +1,8 @@ +def mm_to_inch(mm: float) -> float: + """Convert mm to Inches""" + return mm / 25.4 + + +def inch_to_mm(inch: float) -> float: + """Convert Inches to mm""" + return inch * 25.4 diff --git a/electric/admin.py b/electric/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/electric/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/electric/migrations/0002_alter_circuit_breaker_type.py b/electric/migrations/0002_alter_circuit_breaker_type.py new file mode 100644 index 0000000..4555b4d --- /dev/null +++ b/electric/migrations/0002_alter_circuit_breaker_type.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.2 on 2024-06-06 18:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("electric", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="circuit", + name="breaker_type", + field=models.CharField( + choices=[ + ("fuse", "Fuse"), + ("mcb", "MCB"), + ("rcd", "RCD"), + ("RCBO", "rcbo"), + ("gfci", "GFCI"), + ], + max_length=10, + ), + ), + ] diff --git a/electric/migrations/0003_alter_circuit_breaker_type.py b/electric/migrations/0003_alter_circuit_breaker_type.py new file mode 100644 index 0000000..bfc3c4c --- /dev/null +++ b/electric/migrations/0003_alter_circuit_breaker_type.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.2 on 2024-07-08 18:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("electric", "0002_alter_circuit_breaker_type"), + ] + + operations = [ + migrations.AlterField( + model_name="circuit", + name="breaker_type", + field=models.CharField( + choices=[ + ("fuse", "Fuse"), + ("mcb", "MCB"), + ("rcd", "RCD"), + ("rcbo", "RCBO"), + ("gfci", "GFCI"), + ], + max_length=10, + ), + ), + ] diff --git a/electric/templates/electric/index.html b/electric/templates/electric/index.html index 048657c..f76634c 100644 --- a/electric/templates/electric/index.html +++ b/electric/templates/electric/index.html @@ -20,6 +20,10 @@

    Voltage

    + +

    + +

    Circuits

    diff --git a/electric/tests.py b/electric/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/electric/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/home/admin.py b/home/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/home/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/home/tests.py b/home/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/home/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/mqtt/admin.py b/mqtt/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/mqtt/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/mqtt/tests.py b/mqtt/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/mqtt/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/network/admin.py b/network/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/network/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/network/forms.py b/network/forms.py deleted file mode 100644 index 8fa3447..0000000 --- a/network/forms.py +++ /dev/null @@ -1,58 +0,0 @@ -from django import forms - -from .models import ClientDevice, VLAN, WifiNetwork -from .models.ip import IpRange - -class IpRangeForm(forms.Form): - - description = forms.CharField(widget=forms.Textarea(attrs={"style": "height:50px;"}), required=False) - - class Meta: - model = IpRange - fields = ["start_address", "end_address", "num_addresses", "description"] - - -class VLANForm(forms.ModelForm): - - description = forms.CharField(widget=forms.Textarea(attrs={"style": "height:50px;"}), required=False) - - class Meta: - model = VLAN - fields = ["vlan_id", "name", "description"] - - -class WifiForm(forms.ModelForm): - class Meta: - model = WifiNetwork - fields = ["ssid", "password"] - - -class ClientDeviceForm(forms.ModelForm): - - connection_type = forms.ChoiceField(choices=ClientDevice.CONNECTION_TYPES, widget=forms.Select(attrs={"class": "form-control"})) - - class Meta: - model = ClientDevice - fields = ["name", "mac_address", "vlan", "ip_address", "connection_type", "wifi"] - - def __init__(self, *args, vlan_options=None, wifi_options=None, **kwargs): - super(ClientDeviceForm, self).__init__(*args, **kwargs) - - self.fields["vlan"].widget = forms.Select(choices=vlan_options, attrs={"class": "form-control"}) - - empty_choice = [("", "None")] - - self.fields["wifi"].widget = forms.Select(choices=empty_choice + wifi_options, attrs={"class": "form-control"}) - self.fields["wifi"].required = False - - def clean(self): - cleaned_data = super().clean() - - connection_type = cleaned_data.get("connection_type") - wifi = cleaned_data.get("wifi") - if connection_type == "ethernet" and wifi: - raise forms.ValidationError("WiFi must be null for Ethernet Connections", code="invalid") - if connection_type == "wifi" and not wifi: - raise forms.ValidationError("WiFi must be set for WiFi Connections", code="invalid") - - return cleaned_data diff --git a/network/forms/__init__.py b/network/forms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/network/forms/devices.py b/network/forms/devices.py new file mode 100644 index 0000000..348da2c --- /dev/null +++ b/network/forms/devices.py @@ -0,0 +1,63 @@ +from django import forms + +from network.models import ClientDevice, NetworkDevice +from network.models.device import InfrastructureDevice, NetworkedDevice +from network.objects import ConnectionType + +class NetworkedDeviceForm(forms.ModelForm): + + connection_type = forms.ChoiceField( + choices=ConnectionType.choices(), + widget=forms.Select(attrs={"class": "form-control"}), + ) + + class Meta: + model = NetworkedDevice + abstract = True + fields = [ + "name", + "mac_address", + "vlan", + "ip_address", + "connection_type", + "wifi", + ] + + def __init__(self, *args, vlan_options=None, wifi_options=None, **kwargs): + super(NetworkedDeviceForm, self).__init__(*args, **kwargs) + + self.fields["vlan"].widget = forms.Select(choices=vlan_options, attrs={"class": "form-control"}) + + empty_choice = [("", "None")] + + self.fields["wifi"].widget = forms.Select(choices=empty_choice + wifi_options, attrs={"class": "form-control"}) + self.fields["wifi"].required = False + + def clean(self): + cleaned_data = super().clean() + + connection_type = cleaned_data.get("connection_type") + wifi = cleaned_data.get("wifi") + if connection_type == "ethernet" and wifi: + raise forms.ValidationError("WiFi must be null for Ethernet Connections", code="invalid") + if connection_type == "wifi" and not wifi: + raise forms.ValidationError("WiFi must be set for WiFi Connections", code="invalid") + + return cleaned_data + + +class ClientDeviceForm(NetworkedDeviceForm): + class Meta(NetworkedDeviceForm.Meta): + model = ClientDevice + + +class NetworkDeviceForm(NetworkedDeviceForm): + class Meta(NetworkedDeviceForm.Meta): + model = NetworkDevice + fields = NetworkedDeviceForm.Meta.fields + ["device_type"] + + +class InfrastructureDeviceForm(forms.ModelForm): + class Meta: + model = InfrastructureDevice + fields = ["name", "device_type"] diff --git a/network/forms/networking.py b/network/forms/networking.py new file mode 100644 index 0000000..da40971 --- /dev/null +++ b/network/forms/networking.py @@ -0,0 +1,27 @@ +from django import forms + +from network.models import VLAN, WifiNetwork +from network.models.ip import IpRange + +class IpRangeForm(forms.Form): + + description = forms.CharField(widget=forms.Textarea(attrs={"style": "height:50px;"}), required=False) + + class Meta: + model = IpRange + fields = ["start_address", "end_address", "num_addresses", "description"] + + +class VLANForm(forms.ModelForm): + + description = forms.CharField(widget=forms.Textarea(attrs={"style": "height:50px;"}), required=False) + + class Meta: + model = VLAN + fields = ["vlan_id", "name", "description"] + + +class WifiForm(forms.ModelForm): + class Meta: + model = WifiNetwork + fields = ["ssid", "password"] diff --git a/network/forms/rack.py b/network/forms/rack.py new file mode 100644 index 0000000..380e457 --- /dev/null +++ b/network/forms/rack.py @@ -0,0 +1,23 @@ +from django import forms + +from network.models import Rack, RackItem + +class RackForm(forms.ModelForm): + + class Meta: + model = Rack + fields = ["name", "width", "rack_units"] + + +class RackItemForm(forms.ModelForm): + + class Meta: + model = RackItem + fields = ["name", "rack_units", "rack", "device"] + + def __init__(self, *args, rack_options=None, device_options=None, **kwargs): + super(RackItemForm, self).__init__(*args, **kwargs) + + self.fields["rack"].widget = forms.Select(choices=rack_options, attrs={"class": "form-control"}) + + self.fields["device"].widget = forms.Select(choices=device_options, attrs={"class": "form-control"}) diff --git a/network/migrations/0002_rack_rackitem.py b/network/migrations/0002_rack_rackitem.py new file mode 100644 index 0000000..9bfd22e --- /dev/null +++ b/network/migrations/0002_rack_rackitem.py @@ -0,0 +1,60 @@ +# Generated by Django 5.0.2 on 2024-06-06 18:05 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("network", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Rack", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=50)), + ( + "width", + models.PositiveIntegerField( + choices=[(10, "WIDTH_10"), (19, "WIDTH_19"), (23, "WIDTH_23")] + ), + ), + ("rack_units", models.FloatField()), + ], + ), + migrations.CreateModel( + name="RackItem", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=50)), + ("rack_units", models.FloatField()), + ( + "rack", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="rack_items", + to="network.rack", + ), + ), + ], + ), + ] diff --git a/network/migrations/0003_alter_clientdevice_connection_type_and_more.py b/network/migrations/0003_alter_clientdevice_connection_type_and_more.py new file mode 100644 index 0000000..f807f7e --- /dev/null +++ b/network/migrations/0003_alter_clientdevice_connection_type_and_more.py @@ -0,0 +1,90 @@ +# Generated by Django 5.0.2 on 2024-06-06 18:18 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("network", "0002_rack_rackitem"), + ] + + operations = [ + migrations.AlterField( + model_name="clientdevice", + name="connection_type", + field=models.CharField( + choices=[("ETHERNET", "Ethernet"), ("WIFI", "Wifi")], max_length=10 + ), + ), + migrations.AlterField( + model_name="clientdevice", + name="vlan", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="network.vlan", + ), + ), + migrations.CreateModel( + name="NetworkDevice", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ("mac_address", models.CharField(max_length=17, unique=True)), + ( + "ip_address", + models.GenericIPAddressField(blank=True, null=True, unique=True), + ), + ( + "connection_type", + models.CharField( + choices=[("ETHERNET", "Ethernet"), ("WIFI", "Wifi")], + max_length=10, + ), + ), + ( + "device_type", + models.CharField( + choices=[ + ("ROUTER", "Router"), + ("SWITCH", "Switch"), + ("ACCESS_POINT", "Access_point"), + ], + max_length=20, + ), + ), + ( + "vlan", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="network.vlan", + ), + ), + ( + "wifi", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="network.wifinetwork", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/network/migrations/0004_rackitem_device.py b/network/migrations/0004_rackitem_device.py new file mode 100644 index 0000000..d4a347c --- /dev/null +++ b/network/migrations/0004_rackitem_device.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.2 on 2024-06-06 18:20 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("network", "0003_alter_clientdevice_connection_type_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="rackitem", + name="device", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="rack_item", + to="network.networkdevice", + ), + ), + ] diff --git a/network/migrations/0005_infrastructuredevice_alter_networkdevice_device_type.py b/network/migrations/0005_infrastructuredevice_alter_networkdevice_device_type.py new file mode 100644 index 0000000..41a8850 --- /dev/null +++ b/network/migrations/0005_infrastructuredevice_alter_networkdevice_device_type.py @@ -0,0 +1,57 @@ +# Generated by Django 5.0.2 on 2024-07-08 18:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("network", "0004_rackitem_device"), + ] + + operations = [ + migrations.CreateModel( + name="InfrastructureDevice", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ( + "device_type", + models.CharField( + choices=[ + ("POWER", "Power"), + ("PATCH_PANEL", "Patch_panel"), + ("BLANK", "Blank"), + ("CABLE_MANAGEMENT", "Cable_management"), + ], + max_length=20, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AlterField( + model_name="networkdevice", + name="device_type", + field=models.CharField( + choices=[ + ("ROUTER", "Router"), + ("FIREWALL", "Firewall"), + ("SWITCH", "Switch"), + ("ACCESS_POINT", "Access_point"), + ("STORAGE", "Storage"), + ], + max_length=20, + ), + ), + ] diff --git a/network/models/__init__.py b/network/models/__init__.py index bf01afd..430ce82 100644 --- a/network/models/__init__.py +++ b/network/models/__init__.py @@ -1,3 +1,4 @@ -from .client_device import ClientDevice +from .device import ClientDevice, NetworkDevice +from .rack import Rack, RackItem from .vlan import VLAN from .wifi import WifiNetwork diff --git a/network/models/client_device.py b/network/models/client_device.py deleted file mode 100644 index 74964c8..0000000 --- a/network/models/client_device.py +++ /dev/null @@ -1,35 +0,0 @@ -import re - -from django.core.exceptions import ValidationError -from django.db import models - -from .vlan import VLAN -from .wifi import WifiNetwork - - -class ClientDevice(models.Model): - CONNECTION_TYPES = ( - ("ethernet", "Ethernet"), - ("wifi", "WiFi"), - ) - - name = models.CharField(max_length=100) - mac_address = models.CharField(max_length=17, unique=True) - ip_address = models.GenericIPAddressField(blank=True, null=True, unique=True) - vlan = models.ForeignKey(VLAN, on_delete=models.CASCADE) - wifi = models.ForeignKey(WifiNetwork, on_delete=models.CASCADE, blank=True, null=True) - connection_type = models.CharField(max_length=10, choices=CONNECTION_TYPES) - - def __str__(self): - return self.name - - def clean(self): - super().clean() - - if not self.mac_address_validation(self.mac_address): - raise ValidationError("Invalid MAC Address format") - - @staticmethod - def mac_address_validation(mac_address: str) -> bool: - MAC_ADDRESS_PATTERN = r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4})$" - return re.match(MAC_ADDRESS_PATTERN, mac_address) is not None diff --git a/network/models/device.py b/network/models/device.py new file mode 100644 index 0000000..ccfb79f --- /dev/null +++ b/network/models/device.py @@ -0,0 +1,55 @@ +import re + +from django.core.exceptions import ValidationError +from django.db import models + +from network.models.vlan import VLAN +from network.models.wifi import WifiNetwork +from network.objects import ConnectionType, InfrastructureDeviceType, NetworkDeviceType + +class Device(models.Model): + """Generic Device with a Name""" + + name = models.CharField(max_length=100) + + def __str__(self): + return self.name + + class Meta: + abstract = True + + +class NetworkedDevice(Device): + """Networked Device with MAC Address, IP Address, VLAN, and Connection Type (and/or WiFi Network)""" + + mac_address = models.CharField(max_length=17, unique=True) + ip_address = models.GenericIPAddressField(blank=True, null=True, unique=True) + vlan = models.ForeignKey(VLAN, on_delete=models.CASCADE, blank=True, null=True) + wifi = models.ForeignKey(WifiNetwork, on_delete=models.CASCADE, blank=True, null=True) + connection_type = models.CharField(max_length=10, choices=ConnectionType.choices()) + + def clean(self): + super().clean() + + if not self.mac_address_validation(self.mac_address): + raise ValidationError("Invalid MAC Address format") + + @staticmethod + def mac_address_validation(mac_address: str) -> bool: + MAC_ADDRESS_PATTERN = r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4})$" + return re.match(MAC_ADDRESS_PATTERN, mac_address) is not None + + class Meta: + abstract = True + + +class ClientDevice(NetworkedDevice): + pass + + +class NetworkDevice(NetworkedDevice): + device_type = models.CharField(max_length=20, choices=NetworkDeviceType.choices()) + + +class InfrastructureDevice(Device): + device_type = models.CharField(max_length=20, choices=InfrastructureDeviceType.choices()) diff --git a/network/models/rack.py b/network/models/rack.py new file mode 100644 index 0000000..4b04baa --- /dev/null +++ b/network/models/rack.py @@ -0,0 +1,68 @@ +from enum import Enum + +from django.db import models + +from common.utils.units import mm_to_inch +from network.models import NetworkDevice + +RACK_UNIT_HEIGHT = 44.45 # 1U = 44.45mm + + +class RackWidth(int, Enum): + WIDTH_10 = 10 + WIDTH_19 = 19 + WIDTH_23 = 23 + + @property + def width(self) -> float: + """Width of the Rack in Inches""" + return self.value + + @classmethod + def choices(cls) -> list[tuple[int, str]]: + """Choices for the Rack Width""" + return [(width.value, width.name) for width in cls] + + +class Rack(models.Model): + name = models.CharField(max_length=50) + width = models.PositiveIntegerField(choices=RackWidth.choices()) + rack_units = models.FloatField() + + @property + def height(self) -> float: + """Total Unit height of the Rack in mm""" + return round(self.rack_units * RACK_UNIT_HEIGHT) + + @property + def height_inches(self) -> float: + """Height of the Rack Item in Inches""" + return round(mm_to_inch(self.height), 2) + + @property + def remaining_units(self) -> int: + """Remaining Rack Units in the Rack""" + used_units = sum(item.rack_units for item in self.rack_items.all()) + return self.rack_units - used_units + + +class RackItem(models.Model): + name = models.CharField(max_length=50) + rack_units = models.FloatField() + rack = models.ForeignKey(Rack, on_delete=models.CASCADE, related_name="rack_items") + device = models.OneToOneField(NetworkDevice, on_delete=models.CASCADE, blank=True, null=True, related_name="rack_item") + + @property + def height(self) -> float: + """Height of the Rack Item in mm""" + return round(self.height_units * RACK_UNIT_HEIGHT) + + @property + def height_inches(self) -> float: + """Height of the Rack Item in Inches""" + return round(mm_to_inch(self.height), 2) + + def save(self, *args, **kwargs): + if self.rack_units > self.rack.remaining_units: + raise ValueError(f"Not enough space in Rack {self.rack.name} for this RackItem {self.name}") + super().save(*args, **kwargs) diff --git a/network/objects.py b/network/objects.py new file mode 100644 index 0000000..0ca78ad --- /dev/null +++ b/network/objects.py @@ -0,0 +1,36 @@ +from enum import Enum + + +class ConnectionType(Enum): + ETHERNET = "ethernet" + WIFI = "wifi" + + @classmethod + def choices(cls) -> list[tuple[str, str]]: + """Choices for the Connection Types""" + return [(connection.name, connection.value.capitalize()) for connection in cls] + + +class NetworkDeviceType(Enum): + ROUTER = "router" + FIREWALL = "firewall" + SWITCH = "switch" + ACCESS_POINT = "access_point" + STORAGE = "storage" + + @classmethod + def choices(cls) -> list[tuple[str, str]]: + """Choices for the Network Device Types""" + return [(device.name, device.value.capitalize()) for device in cls] + + +class InfrastructureDeviceType(Enum): + POWER = "power" + PATCH_PANEL = "patch_panel" + BLANK = "blank" + CABLE_MANAGEMENT = "cable_management" + + @classmethod + def choices(cls) -> list[tuple[str, str]]: + """Choices for the Infrastructure Device Types""" + return [(device.name, device.value.capitalize()) for device in cls] diff --git a/network/templates/network/device/edit.html b/network/templates/network/device/client_edit.html similarity index 86% rename from network/templates/network/device/edit.html rename to network/templates/network/device/client_edit.html index 4e7b424..f082521 100644 --- a/network/templates/network/device/edit.html +++ b/network/templates/network/device/client_edit.html @@ -9,7 +9,7 @@ {% endblock %} {% block form_action %} -{% url 'device.edit' device.id %} +{% url 'client_device.edit' device.id %} {% endblock %} {% block back_link %} diff --git a/network/templates/network/device/create.html b/network/templates/network/device/create.html index db263ad..de4ae08 100644 --- a/network/templates/network/device/create.html +++ b/network/templates/network/device/create.html @@ -1,5 +1,7 @@ {% extends "base/crud.html" %} +{% load crispy_forms_tags %} + {% block title_content %} Create Device {% endblock %} @@ -8,10 +10,46 @@ Create Device {% endblock %} -{% block form_action %} -{% url 'device.create' %} -{% endblock %} - {% block back_link %} {% url 'device.index' %} -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block additional_content %} +
    +Device Type: + + + + +
    + +
    + {% csrf_token %} + {{ client_form|crispy }} + + +
    + + + + +{% endblock %} diff --git a/network/templates/network/device/index.html b/network/templates/network/device/index.html index 7f797fb..d8da001 100644 --- a/network/templates/network/device/index.html +++ b/network/templates/network/device/index.html @@ -20,11 +20,12 @@ VLAN Wifi Network Connection + Device Type {% endblock %} {% block table_body %} -{% for device in devices %} +{% for device in client_devices %} {{ device.name }} {{ device.mac_address }} @@ -32,11 +33,30 @@ {{ device.vlan }} {{ device.wifi.ssid }} {{ device.connection_type }} + Client - Edit + Edit - Delete + Delete + + +{% endfor %} +{% for device in network_devices %} + + {{ device.name }} + {{ device.mac_address }} + {{ device.ip_address }} + {{ device.vlan }} + {{ device.wifi.ssid }} + {{ device.connection_type }} + Network + {{ device.device_type }} + + Edit + + + Delete {% endfor %} diff --git a/network/templates/network/device/network_edit.html b/network/templates/network/device/network_edit.html new file mode 100644 index 0000000..0c68d94 --- /dev/null +++ b/network/templates/network/device/network_edit.html @@ -0,0 +1,17 @@ +{% extends "base/crud.html" %} + +{% block title_content %} +Edit Device +{% endblock %} + +{% block header_content %} +Edit Device - {{ device.name }} +{% endblock %} + +{% block form_action %} +{% url 'network_device.edit' device.id %} +{% endblock %} + +{% block back_link %} +{% url 'device.index' %} +{% endblock %} \ No newline at end of file diff --git a/network/templates/network/rack/create.html b/network/templates/network/rack/create.html new file mode 100644 index 0000000..7d2b1cc --- /dev/null +++ b/network/templates/network/rack/create.html @@ -0,0 +1,17 @@ +{% extends "base/crud.html" %} + +{% block title_content %} +Create Rack +{% endblock %} + +{% block header_content %} +Create Rack +{% endblock %} + +{% block form_action %} +{% url 'rack.create' %} +{% endblock %} + +{% block back_link %} +{% url 'rack.index' %} +{% endblock %} \ No newline at end of file diff --git a/network/templates/network/rack/edit.html b/network/templates/network/rack/edit.html new file mode 100644 index 0000000..c856501 --- /dev/null +++ b/network/templates/network/rack/edit.html @@ -0,0 +1,17 @@ +{% extends "base/crud.html" %} + +{% block title_content %} +Edit Rack +{% endblock %} + +{% block header_content %} +Edit Rack - {{ rack.name }} +{% endblock %} + +{% block form_action %} +{% url 'rack.edit' rack.id %} +{% endblock %} + +{% block back_link %} +{% url 'rack.index' %} +{% endblock %} \ No newline at end of file diff --git a/network/templates/network/rack/index.html b/network/templates/network/rack/index.html new file mode 100644 index 0000000..26e64eb --- /dev/null +++ b/network/templates/network/rack/index.html @@ -0,0 +1,47 @@ +{% extends "base/index.html" %} + +{% block title_content %} +Racks +{% endblock %} + +{% block header_content %} +Racks +{% endblock %} + +{% block create_link %} +{% url 'rack.create' %} +{% endblock %} + + +{% block table_head %} + + Name + Width (Inch) + Height (Inch) + Total Units + Available Units + +{% endblock %} + +{% block table_body %} +{% for rack in racks %} + + {{ rack.name }} + {{ rack.width }} + {{ rack.height_inches }} + {{ rack.rack_units }} + {{ rack.remaining_units }} + + + + + + + Edit + + + Delete + + +{% endfor %} +{% endblock %} \ No newline at end of file diff --git a/network/templates/network/rack_item/create.html b/network/templates/network/rack_item/create.html new file mode 100644 index 0000000..25be4c2 --- /dev/null +++ b/network/templates/network/rack_item/create.html @@ -0,0 +1,17 @@ +{% extends "base/crud.html" %} + +{% block title_content %} +Create Rack Item +{% endblock %} + +{% block header_content %} +Create Rack Item +{% endblock %} + +{% block form_action %} +{% url 'rack_item.create' %} +{% endblock %} + +{% block back_link %} +{% url 'rack.index' %} +{% endblock %} \ No newline at end of file diff --git a/network/templates/network/rack_item/edit.html b/network/templates/network/rack_item/edit.html new file mode 100644 index 0000000..af736e9 --- /dev/null +++ b/network/templates/network/rack_item/edit.html @@ -0,0 +1,17 @@ +{% extends "base/crud.html" %} + +{% block title_content %} +Edit Rack Item +{% endblock %} + +{% block header_content %} +Edit Rack Item - {{ rack_item.name }} +{% endblock %} + +{% block form_action %} +{% url 'rack_item.edit' rack_item.id %} +{% endblock %} + +{% block back_link %} +{% url 'rack.index' %} +{% endblock %} \ No newline at end of file diff --git a/network/templates/network/rack_item/index.html b/network/templates/network/rack_item/index.html new file mode 100644 index 0000000..d842dfa --- /dev/null +++ b/network/templates/network/rack_item/index.html @@ -0,0 +1,35 @@ +{% extends "base/index.html" %} + +{% block title_content %} +Racks Items +{% endblock %} + +{% block header_content %} +Rack Items : {{ rack.name }} +{% endblock %} + +{% block create_link %} +{% url 'rack_item.create' %} +{% endblock %} + +{% block table_head %} + + Name + Rack Units + +{% endblock %} + +{% block table_body %} +{% for rack_item in rack_items %} + + {{ rack_item.name }} + {{ rack_item.rack_units }} + + Edit + + + Delete + + +{% endfor %} +{% endblock %} diff --git a/network/tests.py b/network/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/network/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/network/urls.py b/network/urls.py index 28f8886..d27c9aa 100644 --- a/network/urls.py +++ b/network/urls.py @@ -1,19 +1,78 @@ -from django.urls import path +from django.urls import include, path from . import views urlpatterns = [ path("", views.index.index, name="network_index"), - path("vlan", views.vlan.index, name="vlan.index"), - path("vlan/create", views.vlan.create, name="vlan.create"), - path("vlan//edit", views.vlan.edit, name="vlan.edit"), - path("vlan//delete", views.vlan.delete, name="vlan.delete"), - path("wifi", views.wifi.index, name="wifi.index"), - path("wifi/create", views.wifi.create, name="wifi.create"), - path("wifi//edit", views.wifi.edit, name="wifi.edit"), - path("wifi//delete", views.wifi.delete, name="wifi.delete"), - path("device", views.device.index, name="device.index"), - path("device/create", views.device.create, name="device.create"), - path("device//edit", views.device.edit, name="device.edit"), - path("device//delete", views.device.delete, name="device.delete"), + path( + "vlan/", + include( + [ + path("", views.vlan.index, name="vlan.index"), + path("create", views.vlan.create, name="vlan.create"), + path("/edit", views.vlan.edit, name="vlan.edit"), + path("/delete", views.vlan.delete, name="vlan.delete"), + ] + ), + ), + path( + "wifi/", + include( + [ + path("", views.wifi.index, name="wifi.index"), + path("create", views.wifi.create, name="wifi.create"), + path("/edit", views.wifi.edit, name="wifi.edit"), + path("/delete", views.wifi.delete, name="wifi.delete"), + ] + ), + ), + path( + "device/", + include( + [ + path("", views.device.index, name="device.index"), + path("create", views.device.create, name="device.create"), + path( + "client/", + include( + [ + path("/edit", views.device.edit_client, name="client_device.edit"), + path("/delete", views.device.delete_client, name="client_device.delete"), + ] + ), + ), + path( + "network/", + include( + [ + path("/edit", views.device.edit_network, name="network_device.edit"), + path("/delete", views.device.delete_network, name="network_device.delete"), + ] + ), + ), + ] + ), + ), + path( + "rack/", + include( + [ + path("", views.rack.index, name="rack.index"), + path("create", views.rack.create_rack, name="rack.create"), + path("/edit", views.rack.edit_rack, name="rack.edit"), + path("/delete", views.rack.delete_rack, name="rack.delete"), + path("/", views.rack.rack_index, name="rack.view"), + ] + ), + ), + path( + "rack-item/", + include( + [ + path("create", views.rack.create_rack_item, name="rack_item.create"), + path("/edit", views.rack.edit_rack_item, name="rack_item.edit"), + path("/delete", views.rack.delete_rack_item, name="rack_item.delete"), + ] + ), + ), ] diff --git a/network/views/__init__.py b/network/views/__init__.py index 2c4b302..f98a531 100644 --- a/network/views/__init__.py +++ b/network/views/__init__.py @@ -1 +1 @@ -from . import device, index, vlan, wifi +from . import device, index, rack, vlan, wifi diff --git a/network/views/device.py b/network/views/device.py index 6d28309..0e62b2a 100644 --- a/network/views/device.py +++ b/network/views/device.py @@ -1,12 +1,12 @@ from django.shortcuts import get_object_or_404, redirect, render -from ..forms import ClientDeviceForm -from ..models import ClientDevice, VLAN, WifiNetwork - +from ..forms.devices import ClientDeviceForm, NetworkDeviceForm +from ..models import ClientDevice, NetworkDevice, VLAN, WifiNetwork def index(request): - devices = ClientDevice.objects.all() - return render(request, "network/device/index.html", {"devices": devices}) + client_devices = ClientDevice.objects.all() + network_devices = NetworkDevice.objects.all() + return render(request, "network/device/index.html", {"client_devices": client_devices, "network_devices": network_devices}) def create(request): @@ -14,17 +14,29 @@ def create(request): wifi_options = [(wifi.id, wifi.ssid) for wifi in WifiNetwork.objects.all()] if request.method == "POST": - form = ClientDeviceForm(request.POST, vlan_options=vlan_options, wifi_options=wifi_options) - if form.is_valid(): - form.save() - return redirect("device.index") + if "client" in request.POST: + client_form = ClientDeviceForm(request.POST, vlan_options=vlan_options, wifi_options=wifi_options) + if client_form.is_valid(): + client_form.save() + return redirect("device.index") + elif "network" in request.POST: + network_form = NetworkDeviceForm(request.POST, vlan_options=vlan_options, wifi_options=wifi_options) + if network_form.is_valid(): + network_form.save() + return redirect("device.index") else: - form = ClientDeviceForm(vlan_options=vlan_options, wifi_options=wifi_options) + client_form = ClientDeviceForm(vlan_options=vlan_options, wifi_options=wifi_options) + network_form = NetworkDeviceForm(vlan_options=vlan_options, wifi_options=wifi_options) + + if not client_form: + client_form = ClientDeviceForm(vlan_options=vlan_options, wifi_options=wifi_options) + if not network_form: + network_form = NetworkDeviceForm(vlan_options=vlan_options, wifi_options=wifi_options) - return render(request, "network/device/create.html", {"form": form}) + return render(request, "network/device/create.html", {"client_form": client_form, "network_form": network_form, "show_form": False}) -def edit(request, id: int): +def edit_client(request, id: int): device = get_object_or_404(ClientDevice, id=id) vlan_options = [(vlan.vlan_id, f"{vlan.vlan_id} - {vlan.name}") for vlan in VLAN.objects.all()] @@ -38,10 +50,33 @@ def edit(request, id: int): else: form = ClientDeviceForm(instance=device, vlan_options=vlan_options, wifi_options=wifi_options) - return render(request, "network/device/edit.html", {"form": form, "device": device}) + return render(request, "network/device/client_edit.html", {"form": form, "device": device}) + +def edit_network(request, id: int): + device = get_object_or_404(NetworkDevice, id=id) -def delete(request, id: int): + vlan_options = [(vlan.vlan_id, f"{vlan.vlan_id} - {vlan.name}") for vlan in VLAN.objects.all()] + wifi_options = [(wifi.id, wifi.ssid) for wifi in WifiNetwork.objects.all()] + + if request.method == "POST": + form = NetworkDeviceForm(request.POST, instance=device, vlan_options=vlan_options, wifi_options=wifi_options) + if form.is_valid(): + form.save() + return redirect("device.index") + else: + form = NetworkDeviceForm(instance=device, vlan_options=vlan_options, wifi_options=wifi_options) + + return render(request, "network/device/network_edit.html", {"form": form, "device": device}) + + +def delete_client(request, id: int): device = get_object_or_404(ClientDevice, id=id) device.delete() return redirect("device.index") + + +def delete_network(request, id: int): + device = get_object_or_404(NetworkDevice, id=id) + device.delete() + return redirect("device.index") diff --git a/network/views/rack.py b/network/views/rack.py new file mode 100644 index 0000000..83babb3 --- /dev/null +++ b/network/views/rack.py @@ -0,0 +1,84 @@ +from django.shortcuts import get_object_or_404, redirect, render + +from ..forms.rack import RackForm, RackItemForm +from ..models import NetworkDevice, Rack, RackItem + +def index(request): + racks = Rack.objects.all() + return render(request, "network/rack/index.html", {"racks": racks}) + + +def rack_index(request, rack_id): + rack = get_object_or_404(Rack, id=rack_id) + rack_items = RackItem.objects.filter(rack=rack) + return render(request, "network/rack_item/index.html", {"rack": rack, "rack_items": rack_items}) + + +def create_rack(request): + if request.method == "POST": + form = RackForm(request.POST) + if form.is_valid(): + form.save() + return redirect("rack.index") + else: + form = RackForm() + + return render(request, "network/rack/create.html", {"form": form}) + + +def create_rack_item(request): + rack_options = [(rack.id, rack.name) for rack in Rack.objects.all()] + device_options = [(device.id, device.name) for device in NetworkDevice.objects.all()] + + if request.method == "POST": + form = RackItemForm(request.POST, rack_options=rack_options, device_options=device_options) + if form.is_valid(): + form.save() + return redirect("rack_item.index") + else: + form = RackItemForm(rack_options=rack_options, device_options=device_options) + + return render(request, "network/rack_item/create.html", {"form": form}) + + +def edit_rack(request, rack_id: int): + rack = get_object_or_404(Rack, id=rack_id) + + if request.method == "POST": + form = RackForm(request.POST, instance=rack) + if form.is_valid(): + form.save() + return redirect("rack.index") + else: + form = RackForm(instance=rack) + + return render(request, "network/rack/edit.html", {"form": form, "rack": rack}) + + +def edit_rack_item(request, rack_item_id: int): + rack_options = [(rack.id, rack.name) for rack in Rack.objects.all()] + device_options = [(device.id, device.name) for device in NetworkDevice.objects.all()] + + rack_item = get_object_or_404(RackItem, id=rack_item_id) + + if request.method == "POST": + form = RackItemForm(request.POST, instance=rack_item, rack_options=rack_options, device_options=device_options) + if form.is_valid(): + form.save() + return redirect("rack_item.index") + else: + form = RackItemForm(instance=rack_item, rack_options=rack_options, device_options=device_options) + + return render(request, "network/rack_item/edit.html", {"form": form, "rack_item": rack_item}) + + +def delete_rack(request, rack_id: int): + rack = get_object_or_404(Rack, id=rack_id) + rack.delete() + return redirect("rack.index") + + +def delete_rack_item(request, rack_item_id: int): + rack_item = get_object_or_404(RackItem, id=rack_item_id) + rack_item.delete() + return redirect("rack_item.index") diff --git a/network/views/vlan.py b/network/views/vlan.py index b4e9222..24928ad 100644 --- a/network/views/vlan.py +++ b/network/views/vlan.py @@ -1,9 +1,8 @@ from django.shortcuts import get_object_or_404, redirect, render -from ..forms import VLANForm +from ..forms.networking import VLANForm from ..models import VLAN - def index(request): vlans = VLAN.objects.all() return render(request, "network/vlan/index.html", {"vlans": vlans}) diff --git a/network/views/wifi.py b/network/views/wifi.py index 1cfea37..679eaa9 100644 --- a/network/views/wifi.py +++ b/network/views/wifi.py @@ -1,9 +1,8 @@ from django.shortcuts import get_object_or_404, redirect, render -from ..forms import WifiForm +from ..forms.networking import WifiForm from ..models import WifiNetwork - def index(request): wifis = WifiNetwork.objects.all() return render(request, "network/wifi/index.html", {"wifis": wifis})