From df63af3ee044614bed1da9317496e11afa393a8a Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 5 Jul 2024 15:32:07 +0200 Subject: [PATCH] add device details --- dashboard/mixins.py | 55 ++++ dashboard/templates/base.html | 247 +++++++++++++---- dashboard/templates/dashboard.html | 213 -------------- dashboard/templates/unassigned_devices.html | 49 ++++ dashboard/urls.py | 4 +- dashboard/views.py | 47 +--- device/forms.py | 42 +++ ...rand_alter_device_devicehub_id_and_more.py | 33 +++ ...nent_type_remove_computer_type_and_more.py | 73 +++++ device/models.py | 71 ++--- device/templates/details.html | 262 ++++++++++++++++++ device/templates/new_device.html | 32 +++ device/templates/physical_properties.html | 246 ++++++++++++++++ device/urls.py | 10 + device/views.py | 82 +++++- dhub/urls.py | 1 + 16 files changed, 1123 insertions(+), 344 deletions(-) create mode 100644 dashboard/mixins.py delete mode 100644 dashboard/templates/dashboard.html create mode 100644 dashboard/templates/unassigned_devices.html create mode 100644 device/forms.py create mode 100644 device/migrations/0002_alter_device_brand_alter_device_devicehub_id_and_more.py create mode 100644 device/migrations/0003_remove_component_type_remove_computer_type_and_more.py create mode 100644 device/templates/details.html create mode 100644 device/templates/new_device.html create mode 100644 device/templates/physical_properties.html create mode 100644 device/urls.py diff --git a/dashboard/mixins.py b/dashboard/mixins.py new file mode 100644 index 0000000..14ac0f7 --- /dev/null +++ b/dashboard/mixins.py @@ -0,0 +1,55 @@ +from django.urls import resolve +from django.shortcuts import get_object_or_404 +from django.utils.translation import gettext_lazy as _ +from django.core.exceptions import PermissionDenied +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views.generic.base import TemplateView + + +class Http403(PermissionDenied): + status_code = 403 + default_detail = _('Permission denied. User is not authenticated') + default_code = 'forbidden' + + def __init__(self, details=None, code=None): + if details is not None: + self.detail = details or self.default_details + if code is not None: + self.code = code or self.default_code + + +class DashboardView(LoginRequiredMixin): + login_url = "/login/" + template_name = "dashboard.html" + breadcrumb = "" + title = "" + subtitle = "" + section = "" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'title': self.title, + 'subtitle': self.subtitle, + 'breadcrumb': self.breadcrumb, + # 'icon': self.icon, + 'section': self.section, + 'path': resolve(self.request.path).url_name, + 'user': self.request.user, + }) + return context + + +class DetailsMixin(DashboardView, TemplateView): + + def get(self, request, *args, **kwargs): + self.pk = kwargs['pk'] + self.object = get_object_or_404(self.model, pk=self.pk) + return super().get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'object': self.object, + }) + return context diff --git a/dashboard/templates/base.html b/dashboard/templates/base.html index d99dc43..dbf2e8d 100644 --- a/dashboard/templates/base.html +++ b/dashboard/templates/base.html @@ -1,72 +1,205 @@ - +{% load i18n static %} + + + + {% block head %} + {% block meta %} + + + + + + {% endblock %} + {% block title %}{% if title %}{{ title }} – {% endif %}DeviceHub{% endblock %} - - - + + {% block style %} + + + - {% block page_title %}{% endblock %} - Usody - - + + + + + {% endblock %} + {% endblock %} + + + - - - - +
+
+ +
+ {% block messages %} + {% for message in messages %} + + {% endfor %} + {% endblock messages %} +
+

{{ title }}

+
+
+
+ {{ breadcrumb }} +
+
+
+
- - - + {% block content %} + {% endblock content %} - - +
+
+
- - - {% block body %}{% endblock %} - - - - - - - - - - - - - - + + + {% block script %} + + + + {% block extrascript %}{% endblock %} + {% endblock %} + diff --git a/dashboard/templates/dashboard.html b/dashboard/templates/dashboard.html deleted file mode 100644 index 47289f0..0000000 --- a/dashboard/templates/dashboard.html +++ /dev/null @@ -1,213 +0,0 @@ -{% load i18n static %} - - - - - {% block head %} - {% block meta %} - - - - - - {% endblock %} - {% block title %}{% if title %}{{ title }} – {% endif %}DeviceHub{% endblock %} - - - {% block style %} - - - - - - - - - - {% endblock %} - {% endblock %} - - - - -
- -
- - - - - {% block script %} - - - - {% block extrascript %}{% endblock %} - {% endblock %} - - diff --git a/dashboard/templates/unassigned_devices.html b/dashboard/templates/unassigned_devices.html new file mode 100644 index 0000000..26a2503 --- /dev/null +++ b/dashboard/templates/unassigned_devices.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+
+

{{ subtitle }}

+
+ +
+ +
+ + + + + + + {% for dev in devices %} + + + + + + {% endfor %} +
+ Title +
+ {{ dev.type }} {{ dev.manufacturer }} {{ dev.model }} +
+
+{% endblock %} diff --git a/dashboard/urls.py b/dashboard/urls.py index 0215580..f799360 100644 --- a/dashboard/urls.py +++ b/dashboard/urls.py @@ -1,8 +1,8 @@ from django.urls import path -from dashboard.views import DashboardView +from dashboard import views app_name = 'dashboard' urlpatterns = [ - path("", DashboardView.as_view(), name="dashboard"), + path("", views.UnassignedDevicesView.as_view(), name="unassigned_devices"), ] diff --git a/dashboard/views.py b/dashboard/views.py index 86ae47c..99475c8 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -1,46 +1,19 @@ -from django.views import View -from django.template.loader import get_template -from django.http import HttpResponse -from django.urls import resolve from django.utils.translation import gettext_lazy as _ -from django.core.exceptions import PermissionDenied -from django.contrib.auth.mixins import LoginRequiredMixin +from django.views.generic.base import TemplateView +from dashboard.mixins import DashboardView +from device.models import Device -class Http403(PermissionDenied): - status_code = 403 - default_detail = _('Permission denied. User is not authenticated') - default_code = 'forbidden' +class UnassignedDevicesView(DashboardView, TemplateView): + template_name = "unassigned_devices.html" + section = "Unassigned" + title = _("Unassigned Devices") + breadcrumb = "Devices / Unassigned Devices" - def __init__(self, details=None, code=None): - if details is not None: - self.detail = details or self.default_details - if code is not None: - self.code = code or self.default_code - - -class DashboardView(LoginRequiredMixin, View): - login_url = "/login/" - template_name = "dashboard.html" - - def get(self, request, *args, **kwargs): - - template = get_template( - self.template_name, - ).render() - return HttpResponse(template) - - # response = super().get(request, *args, **kwargs) - # return response - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + devices = Device.objects.filter(owner=self.request.user) context.update({ - # 'title': self.title, - # 'subtitle': self.subtitle, - # 'icon': self.icon, - # 'section': self.section, - 'path': resolve(self.request.path).url_name, - # 'user': self.request.user, + 'devices': devices, }) return context diff --git a/device/forms.py b/device/forms.py new file mode 100644 index 0000000..a364709 --- /dev/null +++ b/device/forms.py @@ -0,0 +1,42 @@ +from django import forms +# from django.utils.translation import gettext_lazy as _ +# from django.core.exceptions import ValidationError +# from user.models import User +from device.models import ( + Device, + PhysicalProperties +) + + +class DeviceForm(forms.ModelForm): + + class Meta: + model = Device + fields = [ + 'type', + "model", + "manufacturer", + "serial_number", + "part_number", + "brand", + "generation", + "version", + "production_date", + "variant", + "family", + ] + + +class PhysicalPropsForm(forms.Form): + + class Meta: + model = PhysicalProperties + fields = [ + "device", + "weight", + "width", + "height", + "depth", + "color", + "image", + ] diff --git a/device/migrations/0002_alter_device_brand_alter_device_devicehub_id_and_more.py b/device/migrations/0002_alter_device_brand_alter_device_devicehub_id_and_more.py new file mode 100644 index 0000000..1d02fb7 --- /dev/null +++ b/device/migrations/0002_alter_device_brand_alter_device_devicehub_id_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.6 on 2024-07-03 11:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("device", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="device", + name="brand", + field=models.CharField(blank=True, max_length=64, null=True), + ), + migrations.AlterField( + model_name="device", + name="devicehub_id", + field=models.CharField(blank=True, max_length=64, null=True, unique=True), + ), + migrations.AlterField( + model_name="device", + name="variant", + field=models.CharField(blank=True, max_length=64, null=True), + ), + migrations.AlterField( + model_name="device", + name="version", + field=models.CharField(blank=True, max_length=64, null=True), + ), + ] diff --git a/device/migrations/0003_remove_component_type_remove_computer_type_and_more.py b/device/migrations/0003_remove_component_type_remove_computer_type_and_more.py new file mode 100644 index 0000000..d57d6bc --- /dev/null +++ b/device/migrations/0003_remove_component_type_remove_computer_type_and_more.py @@ -0,0 +1,73 @@ +# Generated by Django 5.0.6 on 2024-07-03 12:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("device", "0002_alter_device_brand_alter_device_devicehub_id_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="component", + name="type", + ), + migrations.RemoveField( + model_name="computer", + name="type", + ), + migrations.RemoveField( + model_name="datastorage", + name="type", + ), + migrations.AlterField( + model_name="computer", + name="chassis", + field=models.CharField( + blank=True, + choices=[ + ("Tower", "Tower"), + ("All in one", "Allinone"), + ("Microtower", "Microtower"), + ("Netbook", "Netbook"), + ("Laptop", "Laptop"), + ("Tablet", "Tabler"), + ("Server", "Server"), + ("Non-physical device", "Virtual"), + ], + max_length=32, + null=True, + ), + ), + migrations.AlterField( + model_name="computer", + name="sku", + field=models.CharField(blank=True, max_length=32, null=True), + ), + migrations.AlterField( + model_name="device", + name="type", + field=models.CharField( + choices=[ + ("Desktop", "Desktop"), + ("Laptop", "Laptop"), + ("Server", "Server"), + ("GraphicCard", "Graphiccard"), + ("HardDrive", "Harddrive"), + ("SolidStateDrive", "Solidstatedrive"), + ("Motherboard", "Motherboard"), + ("NetworkAdapter", "Networkadapter"), + ("Processor", "Processor"), + ("RamModule", "Rammodule"), + ("SoundCard", "Soundcard"), + ("Display", "Display"), + ("Battery", "Battery"), + ("Camera", "Camera"), + ], + default="Laptop", + max_length=32, + ), + ), + ] diff --git a/device/models.py b/device/models.py index 852ed73..2215a3c 100644 --- a/device/models.py +++ b/device/models.py @@ -3,23 +3,36 @@ from user.models import User from utils.constants import STR_SM_SIZE, STR_SIZE -# Create your models here. - - class Device(models.Model): + class Types(models.TextChoices): + DESKTOP = "Desktop" + LAPTOP = "Laptop" + SERVER = "Server" + GRAPHICCARD = "GraphicCard" + HARDDRIVE = "HardDrive" + SOLIDSTATEDRIVE = "SolidStateDrive" + MOTHERBOARD = "Motherboard" + NETWORKADAPTER = "NetworkAdapter" + PROCESSOR = "Processor" + RAMMODULE = "RamModule" + SOUNDCARD = "SoundCard" + DISPLAY = "Display" + BATTERY = "Battery" + CAMERA = "Camera" + created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) - type = models.CharField(max_length=STR_SM_SIZE) + type = models.CharField(max_length=STR_SM_SIZE, choices=Types, default=Types.LAPTOP) model = models.CharField(max_length=STR_SIZE, blank=True, null=True) manufacturer = models.CharField(max_length=STR_SIZE, blank=True, null=True) serial_number = models.CharField(max_length=STR_SIZE, blank=True, null=True) part_number = models.CharField(max_length=STR_SIZE, blank=True, null=True) - brand = models.TextField(blank=True, null=True) + brand = models.CharField(max_length=STR_SIZE, blank=True, null=True) generation = models.SmallIntegerField(blank=True, null=True) - version = models.TextField(blank=True, null=True) + version = models.CharField(max_length=STR_SIZE, blank=True, null=True) production_date = models.DateTimeField(blank=True, null=True) - variant = models.TextField(blank=True, null=True) - devicehub_id = models.TextField(unique=True, blank=True, null=True) + variant = models.CharField(max_length=STR_SIZE, blank=True, null=True) + devicehub_id = models.CharField(max_length=STR_SIZE, unique=True, blank=True, null=True) dhid_bk = models.CharField(max_length=STR_SIZE, blank=True, null=True) phid_bk = models.CharField(max_length=STR_SIZE, blank=True, null=True) family = models.CharField(max_length=STR_SIZE, blank=True, null=True) @@ -40,33 +53,30 @@ class PhysicalProperties(models.Model): class Computer(models.Model): - class Types(models.TextChoices): - DESKTOP = "Desktop" - LAPTOP = "Laptop" + class Chassis(models.TextChoices): + TOWER = 'Tower' + ALLINONE = 'All in one' + MICROTOWER = 'Microtower' + NETBOOK = 'Netbook' + LAPTOP = 'Laptop' + TABLER = 'Tablet' SERVER = "Server" + VIRTUAL = 'Non-physical device' + device = models.OneToOneField(Device, models.CASCADE, primary_key=True) - chassis = models.TextField(blank=True, null=True) + chassis = models.CharField( + blank=True, + null=True, + max_length=STR_SM_SIZE, + choices=Chassis + ) system_uuid = models.UUIDField() - sku = models.TextField(blank=True, null=True) - type = models.CharField(max_length=STR_SM_SIZE, choices=Types, default=Types.LAPTOP) + sku = models.CharField(max_length=STR_SM_SIZE, blank=True, null=True) class Component(models.Model): - class Types(models.TextChoices): - GRAPHICCARD = "GraphicCard" - DATASTORAGE = "DataStorage" - MOTHERBOARD = "Motherboard" - NETWORKADAPTER = "NetworkAdapter" - PROCESSOR = "Processor" - RAMMODULE = "RamModule" - SOUNDCARD = "SoundCard" - DISPLAY = "Display" - BATTERY = "Battery" - CAMERA = "Camera" - device = models.OneToOneField(Device, models.CASCADE, primary_key=True) - type = models.CharField(max_length=STR_SM_SIZE, choices=Types) computer = models.OneToOneField(Computer, models.CASCADE, null=True) @@ -82,14 +92,9 @@ class DataStorage(models.Model): PCI = 'PCI' NVME = 'NVME' - class Type(models.TextChoices): - HARDDRIVE = "HardDrive" - SOLIDSTATEDRIVE = "SolidStateDrive" - - component = models.OneToOneField(Component, models.CASCADE) size = models.IntegerField(blank=True, null=True) interface = models.CharField(max_length=STR_SM_SIZE, choices=Interface) - type = models.CharField(max_length=STR_SM_SIZE, choices=Type) + component = models.OneToOneField(Component, models.CASCADE) class Motherboard(models.Model): diff --git a/device/templates/details.html b/device/templates/details.html new file mode 100644 index 0000000..65fffbf --- /dev/null +++ b/device/templates/details.html @@ -0,0 +1,262 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+
+

{{ object.pk }}

+
+
+ +
+
+ +
+
+
+ +
+
Details
+
+
+ (Edit Device) +
+
+ {%if object.hid %}Snapshot{% else %}Placeholder{% endif %} +
+
+ +
+
Phid
+
{{ object.id }}
+
+ +
+
Id device internal
+
+
+ +
+
Type
+
{{ object.type }}
+
+ +
+
Manufacturer
+
{{ object.manufacturer|default:"" }}
+
+ +
+
Model
+
{{ object.model|default:"" }}
+
+ +
+
Part Number
+
{{ object.part_number|default:"" }}
+
+ +
+
Serial Number
+
{{ object.serial_number|default:"" }}
+
+
+ +
+
Physical Properties
+ + +
+
+ Weight: +
+
+
+
+ +
+
width:
+
+
+ +
+
height:
+
+
+ +
+
depth:
+
+
+ +
+
color:
+
+
+ +
+
image:
+
+
+
+ + +
+
Incoming Lots
+ +
+ +
+ +
Outgoing Lots
+ +
+ +
+ +
Temporary Lots
+ +
+ +
+
+ +
+ + +
Documents
+ + + + + + + + + + + + + + +
FileTypeDescriptionUploaded on
+
+ +
+
Status Details
+
+
Physical State
+
+ +
+
+
+
Lifecycle State
+
+ +
+
+
+
Allocated State
+
+ +
+
+
+ +
+
Traceability log Details
+
+ +
+ Snapshot ✓ + 14:07 23-06-2024 +
+ +
+ EraseCrypto ✓ + 14:07 23-06-2024 +
+ +
+ EraseCrypto ✓ + 14:07 23-06-2024 +
+ +
+
+ +
+
Components Snapshot
+
+ +
+
+
Motherboard
+ 14:07 23-06-2024 +
+

+ hp
+ 890e
+

+ + +
+ +
+
+
NetworkAdapter
+ 14:07 23-06-2024 +
+

+ realtek semiconductor co., ltd.
+ rtl8852ae 802.11ax pcie wireless network adapter
+

+ + +
+ +
+
+
+{% endblock %} diff --git a/device/templates/new_device.html b/device/templates/new_device.html new file mode 100644 index 0000000..8f2df37 --- /dev/null +++ b/device/templates/new_device.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+
+

{{ subtitle }}

+
+
+ +{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +{% bootstrap_form form %} + + +
+{% endblock %} diff --git a/device/templates/physical_properties.html b/device/templates/physical_properties.html new file mode 100644 index 0000000..63f11c0 --- /dev/null +++ b/device/templates/physical_properties.html @@ -0,0 +1,246 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+
+

{{ object.pk }}

+
+
+ +
+
+ +
+
+
+ +
+
Details
+
+
+ (Edit Device) +
+
+ {%if object.hid %}Snapshot{% else %}Placeholder{% endif %} +
+
+ +
+
Phid
+
{{ object.id }}
+
+ +
+
Id device internal
+
+
+ +
+
Type
+
{{ object.type }}
+
+ +
+
Manufacturer
+
{{ object.manufacturer|default:"" }}
+
+ +
+
Model
+
{{ object.model|default:"" }}
+
+ +
+
Part Number
+
{{ object.part_number|default:"" }}
+
+ +
+
Serial Number
+
{{ object.serial_number|default:"" }}
+
+
+ +
+
Physical Properties
+ + +
+
+ {{ form }} +
+
+ + +
+
color:
+
+
+ +
+
image:
+
+
+
+ + +
+
Incoming Lots
+ +
+ +
+ +
Outgoing Lots
+ +
+ +
+ +
Temporary Lots
+ +
+ +
+
+ +
+ + +
Documents
+ + + + + + + + + + + + + + +
FileTypeDescriptionUploaded on
+
+ +
+
Status Details
+
+
Physical State
+
+ +
+
+
+
Lifecycle State
+
+ +
+
+
+
Allocated State
+
+ +
+
+
+ +
+
Traceability log Details
+
+ +
+ Snapshot ✓ + 14:07 23-06-2024 +
+ +
+ EraseCrypto ✓ + 14:07 23-06-2024 +
+ +
+ EraseCrypto ✓ + 14:07 23-06-2024 +
+ +
+
+ +
+
Components Snapshot
+
+ +
+
+
Motherboard
+ 14:07 23-06-2024 +
+

+ hp
+ 890e
+

+ + +
+ +
+
+
NetworkAdapter
+ 14:07 23-06-2024 +
+

+ realtek semiconductor co., ltd.
+ rtl8852ae 802.11ax pcie wireless network adapter
+

+ + +
+ +
+
+
+{% endblock %} diff --git a/device/urls.py b/device/urls.py new file mode 100644 index 0000000..5e03af5 --- /dev/null +++ b/device/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from device import views + +app_name = 'device' + +urlpatterns = [ + path("add/", views.NewDeviceView.as_view(), name="add"), + path("/", views.DetailsView.as_view(), name="details"), + path("physical//", views.PhysicalView.as_view(), name="physical_edit"), +] diff --git a/device/views.py b/device/views.py index 91ea44a..1585e40 100644 --- a/device/views.py +++ b/device/views.py @@ -1,3 +1,81 @@ -from django.shortcuts import render +from django.urls import reverse_lazy +from django.shortcuts import get_object_or_404 +from django.utils.translation import gettext_lazy as _ +from django.views.generic.edit import ( + CreateView, + UpdateView, +) +from dashboard.mixins import DashboardView, DetailsMixin +from device.forms import DeviceForm, PhysicalPropsForm +from device.models import Device, PhysicalProperties + + +class NewDeviceView(DashboardView, CreateView): + template_name = "new_device.html" + title = _("New Device") + breadcrumb = "Device / New Device" + form_class = DeviceForm + success_url = reverse_lazy('dashboard:unassigned_devices') + + def form_valid(self, form): + form.instance.owner = self.request.user + response = super().form_valid(form) + PhysicalProperties.objects.create(device=form.instance) + return response + + +class DetailsView(DetailsMixin): + template_name = "details.html" + title = _("Device") + breadcrumb = "Device / Details" + model = Device + + +class PhysicalView(DashboardView, UpdateView): + template_name = "physical_properties.html" + title = _("Physical Properties") + breadcrumb = "Device / Physical properties" + form_class = PhysicalPropsForm + success_url = reverse_lazy('dashboard:unassigned_devices') + model = PhysicalProperties + + def get(self, request, *args, **kwargs): + pk = kwargs['pk'] + self.device = get_object_or_404(Device, pk=pk) + try: + self.object = self.device.physicalproperties + except Exception: + self.object = PhysicalProperties.objects.create(device=self.device) + self.initial.update({'instance': self.object}) + return super().get(request, *args, **kwargs) + + def get_form(self, form_class=None): + """Return an instance of the form to be used in this view.""" + if form_class is None: + form_class = self.get_form_class() + # import pdb; pdb.set_trace() + return form_class(**self.get_form_kwargs()) + + def get_form_kwargs(self): + """Return the keyword arguments for instantiating the form.""" + kwargs = { + "initial": self.get_initial(), + "prefix": self.get_prefix(), + } + + if self.request.method in ("POST", "PUT"): + kwargs.update( + { + "data": self.request.POST, + "files": self.request.FILES, + } + ) + return kwargs + + def form_valid(self, form): + self.success_url = reverse_lazy('device:details', self.device.id) + form.instance.owner = self.request.user + response = super().form_valid(form) + return response + -# Create your views here. diff --git a/dhub/urls.py b/dhub/urls.py index dcf7c2a..9bd6fb4 100644 --- a/dhub/urls.py +++ b/dhub/urls.py @@ -21,4 +21,5 @@ urlpatterns = [ # path('api/', include('snapshot.urls')), path("", include("login.urls")), path("dashboard/", include("dashboard.urls")), + path("device/", include("device.urls")), ]