+
+
{% trans 'Details' %}
+
-
- {% endif %}
- {% endfor %}
- {% endfor %}
-
-
Components last evidence
-
- {% for c in object.components %}
-
-
-
{{ c.type }}
-
{{ evidence.created }}
+ {% if object.is_eraseserver %}
+
+
+ {% trans 'Is a erase server' %}
+
+
-
- {% for k, v in c.items %}
- {% if k not in "actions,type" %}
- {{ k }}: {{ v }}
- {% endif %}
- {% endfor %}
-
-
-
-
-
- {% endfor %}
-
-
+ {% endif %}
-
-
List of evidences
-
- {% for snap in object.evidences %}
-
-
-
- {{ snap.created }}
-
-
- {{ snap.uuid }}
-
-
-
+
+
Type
+
{{ object.type }}
+
+ {% if object.is_websnapshot %}
+ {% for k, v in object.last_user_evidence %}
+
+
{{ k }}
+
{{ v|default:'' }}
+
+ {% endfor %}
+ {% else %}
+
+
+ {% trans 'Manufacturer' %}
+
+
{{ object.manufacturer|default:'' }}
+
+
+
+
+ {% trans 'Model' %}
+
+
{{ object.model|default:'' }}
+
+
+
+
+ {% trans 'Serial Number' %}
+
+
{{ object.last_evidence.doc.device.serialNumber|default:'' }}
+
+ {% endif %}
+
+
+
+ {% trans 'Identifiers' %}
+
+
+ {% for chid in object.hids %}
+
+
{{ chid|default:'' }}
+
{% endfor %}
+
+
+
+
+
{% trans 'Annotations' %}
+
+
+
+
+ {% trans 'Key' %}
+ |
+
+ {% trans 'Value' %}
+ |
+
+ {% trans 'Created on' %}
+ |
+ |
+ |
+
+
+
+ {% for a in object.get_user_annotations %}
+
+ {{ a.key }} |
+ {{ a.value }} |
+ {{ a.created }} |
+ |
+ |
+
+ {% endfor %}
+
+
+
+
+
+
+
+
{% trans 'Documents' %}
+
+
+
+
+ {% trans 'Key' %}
+ |
+
+ {% trans 'Value' %}
+ |
+
+ {% trans 'Created on' %}
+ |
+ |
+ |
+
+
+
+ {% for a in object.get_user_documents %}
+
+ {{ a.key }} |
+ {{ a.value }} |
+ {{ a.created }} |
+ |
+ |
+
+ {% endfor %}
+
+
+
+
+
+ {% for tag in lot_tags %}
+
{{ tag }}
+ {% for lot in object.lots %}
+ {% if lot.type == tag %}
+
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+
+
+
+
{% trans 'Components last evidence' %}
+
+ {% for c in object.components %}
+
+
+
{{ c.type }}
+ {{ evidence.created }}
+
+
+ {% for k, v in c.items %}
+ {% if k not in 'actions,type' %}
+ {{ k }}: {{ v }}
+ {% endif %}
+ {% endfor %}
+
+
+ {% endfor %}
+
+
+
+
+
{% trans 'List of evidences' %}
+
+ {% for snap in object.evidences %}
+
+ {% endfor %}
+
+
-
{% endblock %}
{% block extrascript %}
-
+ })
+
{% endblock %}
-
diff --git a/device/templates/device_web.html b/device/templates/device_web.html
new file mode 100644
index 0000000..21ca570
--- /dev/null
+++ b/device/templates/device_web.html
@@ -0,0 +1,169 @@
+
+
+
+
+
+
{{ object.type }}
+
+
+
+
+
+
+
{{ object.manufacturer }} {{ object.type }} {{ object.model }}
+
+
+
+
Details
+
+
+
Type
+
{{ object.type }}
+
+
+ {% if object.is_websnapshot %}
+ {% for snapshot_key, snapshot_value in object.last_user_evidence %}
+
+
{{ snapshot_key }}
+
{{ snapshot_value|default:'' }}
+
+ {% endfor %}
+ {% else %}
+
+
Manufacturer
+
{{ object.manufacturer|default:'' }}
+
+
+
Model
+
{{ object.model|default:'' }}
+
+
+
Serial Number
+
{{ object.last_evidence.doc.device.serialNumber|default:'' }}
+
+ {% endif %}
+
+
+
+
Identifiers
+ {% for chid in object.hids %}
+
+
{{ chid|default:'' }}
+
+ {% endfor %}
+
+
+
+
Components
+
+ {% for component in object.components %}
+
+
+
+
{{ component.type }}
+
+ {% for component_key, component_value in component.items %}
+ {% if component_key not in 'actions,type' %}
+ {{ component_key }}: {{ component_value }}
+ {% endif %}
+ {% endfor %}
+
+
+
+
+ {% endfor %}
+
+
+
+
+
+
+
+
diff --git a/device/tests.py b/device/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/device/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/device/tests/__init__.py b/device/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/device/tests/test_mock_device.py b/device/tests/test_mock_device.py
new file mode 100644
index 0000000..a3fe019
--- /dev/null
+++ b/device/tests/test_mock_device.py
@@ -0,0 +1,76 @@
+from device.models import Device
+from unittest.mock import MagicMock
+
+
+class TestDevice(Device):
+ """A test subclass of Device that overrides the database-dependent methods"""
+ # TODO Leaving commented bc not used, but might be useful at some point
+ # def get_annotations(self):
+ # """Return empty list instead of querying database"""
+ # return []
+
+ # def get_uuids(self):
+ # """Set uuids directly instead of querying"""
+ # self.uuids = ['uuid1', 'uuid2']
+
+ # def get_hids(self):
+ # """Set hids directly instead of querying"""
+ # self.hids = ['hid1', 'hid2']
+
+ # def get_evidences(self):
+ # """Set evidences directly instead of querying"""
+ # self.evidences = []
+
+ # def get_lots(self):
+ # """Set lots directly instead of querying"""
+ # self.lots = []
+
+ def get_last_evidence(self):
+ if not hasattr(self, '_evidence'):
+ self._evidence = MagicMock()
+ self._evidence.doc = {
+ 'type': 'Computer',
+ 'manufacturer': 'Test Manufacturer',
+ 'model': 'Test Model',
+ 'device': {
+ 'serialNumber': 'SN123456',
+ 'type': 'Computer'
+ }
+ }
+ self._evidence.get_manufacturer = lambda: 'Test Manufacturer'
+ self._evidence.get_model = lambda: 'Test Model'
+ self._evidence.get_chassis = lambda: 'Computer'
+ self._evidence.get_components = lambda: [
+ {
+ 'type': 'CPU',
+ 'model': 'Intel i7',
+ 'manufacturer': 'Intel'
+ },
+ {
+ 'type': 'RAM',
+ 'size': '8GB',
+ 'manufacturer': 'Kingston'
+ }
+ ]
+ self.last_evidence = self._evidence
+
+
+class TestWebSnapshotDevice(TestDevice):
+ """A test subclass of Device that simulates a WebSnapshot device"""
+
+ def get_last_evidence(self):
+ if not hasattr(self, '_evidence'):
+ self._evidence = MagicMock()
+ self._evidence.doc = {
+ 'type': 'WebSnapshot',
+ 'kv': {
+ 'URL': 'http://example.com',
+ 'Title': 'Test Page',
+ 'Timestamp': '2024-01-01'
+ },
+ 'device': {
+ 'type': 'Laptop'
+ }
+ }
+ self.last_evidence = self._evidence
+ return self._evidence
diff --git a/device/tests/test_public_device_web.py b/device/tests/test_public_device_web.py
new file mode 100644
index 0000000..33efe05
--- /dev/null
+++ b/device/tests/test_public_device_web.py
@@ -0,0 +1,67 @@
+from django.test import TestCase, Client
+from django.urls import reverse
+from unittest.mock import patch
+from device.views import PublicDeviceWebView
+from device.tests.test_mock_device import TestDevice, TestWebSnapshotDevice
+
+
+class PublicDeviceWebViewTests(TestCase):
+ def setUp(self):
+ self.client = Client()
+ self.test_id = "test123"
+ self.test_url = reverse('device:device_web',
+ kwargs={'pk': self.test_id})
+
+ def test_url_resolves_correctly(self):
+ """Test that the URL is constructed correctly"""
+ url = reverse('device:device_web', kwargs={'pk': self.test_id})
+ self.assertEqual(url, f'/device/{self.test_id}/public/')
+
+ @patch('device.views.Device')
+ def test_html_response(self, MockDevice):
+ test_device = TestDevice(id=self.test_id)
+ MockDevice.return_value = test_device
+ response = self.client.get(self.test_url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'device_web.html')
+
+ self.assertContains(response, 'Test Manufacturer')
+ self.assertContains(response, 'Test Model')
+ self.assertContains(response, 'Computer')
+ self.assertContains(response, self.test_id)
+
+ self.assertContains(response, 'CPU')
+ self.assertContains(response, 'Intel')
+ self.assertContains(response, 'RAM')
+ self.assertContains(response, 'Kingston')
+
+ @patch('device.views.Device')
+ def test_json_response(self, MockDevice):
+ test_device = TestDevice(id=self.test_id)
+ MockDevice.return_value = test_device
+
+ response = self.client.get(
+ self.test_url,
+ HTTP_ACCEPT='application/json'
+ )
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response['Content-Type'], 'application/json')
+
+ json_data = response.json()
+ self.assertEqual(json_data['id'], self.test_id)
+ self.assertEqual(json_data['shortid'], self.test_id[:6].upper())
+ self.assertEqual(json_data['components'], test_device.components)
+
+ @patch('device.views.Device')
+ def test_websnapshot_device(self, MockDevice):
+ test_device = TestWebSnapshotDevice(id=self.test_id)
+ MockDevice.return_value = test_device
+ response = self.client.get(self.test_url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'device_web.html')
+
+ self.assertContains(response, 'http://example.com')
+ self.assertContains(response, 'Test Page')
diff --git a/device/urls.py b/device/urls.py
index 3d7c12b..a56aa29 100644
--- a/device/urls.py
+++ b/device/urls.py
@@ -9,4 +9,6 @@ urlpatterns = [
path("
/", views.DetailsView.as_view(), name="details"),
path("/annotation/add", views.AddAnnotationView.as_view(), name="add_annotation"),
path("/document/add", views.AddDocumentView.as_view(), name="add_document"),
+ path("/public/", views.PublicDeviceWebView.as_view(), name="device_web"),
+
]
diff --git a/device/views.py b/device/views.py
index 72e56b1..90b43e8 100644
--- a/device/views.py
+++ b/device/views.py
@@ -1,4 +1,5 @@
import json
+from django.http import JsonResponse
from django.http import Http404
from django.urls import reverse_lazy
@@ -95,7 +96,7 @@ class DetailsView(DashboardView, TemplateView):
raise Http404
if self.object.owner != self.request.user.institution:
raise Http403
-
+
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
@@ -110,6 +111,39 @@ class DetailsView(DashboardView, TemplateView):
return context
+class PublicDeviceWebView(TemplateView):
+ template_name = "device_web.html"
+
+ def get(self, request, *args, **kwargs):
+ self.pk = kwargs['pk']
+ self.object = Device(id=self.pk)
+
+ if not self.object.last_evidence:
+ raise Http404
+
+ if self.request.headers.get('Accept') == 'application/json':
+ return self.get_json_response()
+ return super().get(request, *args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ self.object.initial()
+ context.update({
+ 'object': self.object
+ })
+ return context
+
+ def get_json_response(self):
+ data = {
+ 'id': self.object.id,
+ 'shortid': self.object.shortid,
+ 'uuids': self.object.uuids,
+ 'hids': self.object.hids,
+ 'components': self.object.components
+ }
+ return JsonResponse(data)
+
+
class AddAnnotationView(DashboardView, CreateView):
template_name = "new_annotation.html"
title = _("New annotation")
@@ -134,7 +168,7 @@ class AddAnnotationView(DashboardView, CreateView):
value=pk,
type=Annotation.Type.SYSTEM
).first()
-
+
if not self.annotation:
raise Http404