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 %}
+
+ {{ message }}
+
+
+ {% 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 messages %}
- {% for message in messages %}
-
- {{ message }}
-
-
- {% endfor %}
- {% endblock messages %}
-
-
- {% block content %}
- {% endblock content %}
-
-
-
-
-
-
-
-
- {% 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 %}
+
+
+
+{% 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 %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Web
+
+
+
+
+
+
+
+
+
Details
+
+
+
+ {%if object.hid %}Snapshot{% else %}Placeholder{% endif %}
+
+
+
+
+
Phid
+
{{ object.id }}
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Incoming Lots
+
+
+
+
+
+
Outgoing Lots
+
+
+
+
+
+
Temporary Lots
+
+
+
+
+
+
+
+
+
+
Documents
+
+
+
+ File |
+ Type |
+ Description |
+ Uploaded on |
+ |
+ |
+
+
+
+
+
+
+
+
+
+
Status Details
+
+
+
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 %}
+
+
+{% load django_bootstrap5 %}
+
+{% 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 %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Web
+
+
+
+
+
+
+
+
+
Details
+
+
+
+ {%if object.hid %}Snapshot{% else %}Placeholder{% endif %}
+
+
+
+
+
Phid
+
{{ object.id }}
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
Incoming Lots
+
+
+
+
+
+
Outgoing Lots
+
+
+
+
+
+
Temporary Lots
+
+
+
+
+
+
+
+
+
+
Documents
+
+
+
+ File |
+ Type |
+ Description |
+ Uploaded on |
+ |
+ |
+
+
+
+
+
+
+
+
+
+
Status Details
+
+
+
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")),
]