Merge pull request '96-add-serial-number-in-public-web' (#24) from 96-add-serial-number-in-public-web into main
Reviewed-on: https://gitea.pangea.org/ereuse/devicehub-django/pulls/24
This commit is contained in:
commit
dd6c58267f
|
@ -129,7 +129,8 @@ class Device:
|
||||||
return self.uuids[0]
|
return self.uuids[0]
|
||||||
|
|
||||||
def get_lots(self):
|
def get_lots(self):
|
||||||
self.lots = [x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
|
self.lots = [
|
||||||
|
x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_unassigned(cls, institution, offset=0, limit=None):
|
def get_unassigned(cls, institution, offset=0, limit=None):
|
||||||
|
@ -179,7 +180,6 @@ class Device:
|
||||||
count = cls.get_unassigned_count(institution)
|
count = cls.get_unassigned_count(institution)
|
||||||
return devices, count
|
return devices, count
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_unassigned_count(cls, institution):
|
def get_unassigned_count(cls, institution):
|
||||||
|
|
||||||
|
@ -279,6 +279,12 @@ class Device:
|
||||||
self.get_last_evidence()
|
self.get_last_evidence()
|
||||||
return self.last_evidence.get_manufacturer()
|
return self.last_evidence.get_manufacturer()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serial_number(self):
|
||||||
|
if not self.last_evidence:
|
||||||
|
self.get_last_evidence()
|
||||||
|
return self.last_evidence.get_serial_number()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
if self.last_evidence.doc['type'] == "WebSnapshot":
|
if self.last_evidence.doc['type'] == "WebSnapshot":
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
<div class="col-lg-3 col-md-4 label">
|
<div class="col-lg-3 col-md-4 label">
|
||||||
{% trans 'Serial Number' %}
|
{% trans 'Serial Number' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-9 col-md-8">{{ object.last_evidence.doc.device.serialNumber|default:'' }}</div>
|
<div class="col-lg-9 col-md-8">{{ object.serial_number|default:'' }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -120,11 +120,13 @@
|
||||||
<div class="col-md-4 info-label">Model</div>
|
<div class="col-md-4 info-label">Model</div>
|
||||||
<div class="col-md-8 info-value">{{ object.model|default:'' }}</div>
|
<div class="col-md-8 info-value">{{ object.model|default:'' }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
<div class="info-row row">
|
<div class="info-row row">
|
||||||
<div class="col-md-4 info-label">Serial Number</div>
|
<div class="col-md-4 info-label">Serial Number</div>
|
||||||
<div class="col-md-8 info-value">{{ object.last_evidence.doc.device.serialNumber|default:'' }}</div>
|
<div class="col-md-8 info-value">{{ object.serial_number|default:'' }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
|
@ -136,7 +138,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="section-title mt-5">Components</h2>
|
<h2 class="section-title mt-5">Components</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% for component in object.components %}
|
{% for component in object.components %}
|
||||||
|
@ -147,8 +148,10 @@
|
||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
{% for component_key, component_value in component.items %}
|
{% for component_key, component_value in component.items %}
|
||||||
{% if component_key not in 'actions,type' %}
|
{% if component_key not in 'actions,type' %}
|
||||||
|
{% if component_key != 'serialNumber' or user.is_authenticated %}
|
||||||
<strong>{{ component_key }}:</strong> {{ component_value }}<br />
|
<strong>{{ component_key }}:</strong> {{ component_value }}<br />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -157,7 +160,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>
|
<p>
|
||||||
©{% now 'Y' %}eReuse. All rights reserved.
|
©{% now 'Y' %}eReuse. All rights reserved.
|
||||||
|
|
|
@ -3,30 +3,14 @@ from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
|
||||||
class TestDevice(Device):
|
class TestDevice(Device):
|
||||||
"""A test subclass of Device that overrides the database-dependent methods"""
|
def __init__(self, id):
|
||||||
# TODO Leaving commented bc not used, but might be useful at some point
|
super().__init__(id=id)
|
||||||
# def get_annotations(self):
|
self.shortid = id[:6].upper()
|
||||||
# """Return empty list instead of querying database"""
|
self.uuids = []
|
||||||
# return []
|
self.hids = ['hid1', 'hid2']
|
||||||
|
self._setup_evidence()
|
||||||
|
|
||||||
# def get_uuids(self):
|
def _setup_evidence(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 = MagicMock()
|
||||||
self._evidence.doc = {
|
self._evidence.doc = {
|
||||||
'type': 'Computer',
|
'type': 'Computer',
|
||||||
|
@ -44,33 +28,22 @@ class TestDevice(Device):
|
||||||
{
|
{
|
||||||
'type': 'CPU',
|
'type': 'CPU',
|
||||||
'model': 'Intel i7',
|
'model': 'Intel i7',
|
||||||
'manufacturer': 'Intel'
|
'manufacturer': 'Intel',
|
||||||
|
'serialNumber': 'SN12345678'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'RAM',
|
'type': 'RAM',
|
||||||
'size': '8GB',
|
'size': '8GB',
|
||||||
'manufacturer': 'Kingston'
|
'manufacturer': 'Kingston',
|
||||||
|
'serialNumber': 'SN87654321'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
self.last_evidence = self._evidence
|
self.last_evidence = self._evidence
|
||||||
|
|
||||||
|
@property
|
||||||
|
def components(self):
|
||||||
|
return self.last_evidence.get_components()
|
||||||
|
|
||||||
class TestWebSnapshotDevice(TestDevice):
|
@property
|
||||||
"""A test subclass of Device that simulates a WebSnapshot device"""
|
def serial_number(self):
|
||||||
|
return self.last_evidence.doc['device']['serialNumber']
|
||||||
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
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ from django.test import TestCase, Client
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from device.views import PublicDeviceWebView
|
from device.views import PublicDeviceWebView
|
||||||
from device.tests.test_mock_device import TestDevice, TestWebSnapshotDevice
|
from device.tests.test_mock_device import TestDevice
|
||||||
|
from user.models import User, Institution
|
||||||
|
|
||||||
|
|
||||||
class PublicDeviceWebViewTests(TestCase):
|
class PublicDeviceWebViewTests(TestCase):
|
||||||
|
@ -11,57 +12,99 @@ class PublicDeviceWebViewTests(TestCase):
|
||||||
self.test_id = "test123"
|
self.test_id = "test123"
|
||||||
self.test_url = reverse('device:device_web',
|
self.test_url = reverse('device:device_web',
|
||||||
kwargs={'pk': self.test_id})
|
kwargs={'pk': self.test_id})
|
||||||
|
self.institution = Institution.objects.create(
|
||||||
|
name="Test Institution"
|
||||||
|
)
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
email='test@example.com',
|
||||||
|
institution=self.institution,
|
||||||
|
password='testpass123'
|
||||||
|
)
|
||||||
|
|
||||||
def test_url_resolves_correctly(self):
|
def test_url_resolves_correctly(self):
|
||||||
"""Test that the URL is constructed correctly"""
|
|
||||||
url = reverse('device:device_web', kwargs={'pk': self.test_id})
|
url = reverse('device:device_web', kwargs={'pk': self.test_id})
|
||||||
self.assertEqual(url, f'/device/{self.test_id}/public/')
|
self.assertEqual(url, f'/device/{self.test_id}/public/')
|
||||||
|
|
||||||
@patch('device.views.Device')
|
@patch('device.views.Device')
|
||||||
def test_html_response(self, MockDevice):
|
def test_html_response_anonymous(self, MockDevice):
|
||||||
test_device = TestDevice(id=self.test_id)
|
test_device = TestDevice(id=self.test_id)
|
||||||
MockDevice.return_value = test_device
|
MockDevice.return_value = test_device
|
||||||
response = self.client.get(self.test_url)
|
response = self.client.get(self.test_url)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertTemplateUsed(response, 'device_web.html')
|
self.assertTemplateUsed(response, 'device_web.html')
|
||||||
|
|
||||||
self.assertContains(response, 'Test Manufacturer')
|
self.assertContains(response, 'Test Manufacturer')
|
||||||
self.assertContains(response, 'Test Model')
|
self.assertContains(response, 'Test Model')
|
||||||
self.assertContains(response, 'Computer')
|
self.assertContains(response, 'Computer')
|
||||||
self.assertContains(response, self.test_id)
|
self.assertContains(response, self.test_id)
|
||||||
|
self.assertNotContains(response, 'Serial Number')
|
||||||
|
self.assertNotContains(response, 'serialNumber')
|
||||||
|
|
||||||
|
@patch('device.views.Device')
|
||||||
|
def test_html_response_authenticated(self, MockDevice):
|
||||||
|
test_device = TestDevice(id=self.test_id)
|
||||||
|
MockDevice.return_value = test_device
|
||||||
|
self.client.login(username='test@example.com', password='testpass123')
|
||||||
|
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, 'Serial Number')
|
||||||
|
self.assertContains(response, 'Components')
|
||||||
self.assertContains(response, 'CPU')
|
self.assertContains(response, 'CPU')
|
||||||
self.assertContains(response, 'Intel')
|
self.assertContains(response, 'Intel')
|
||||||
self.assertContains(response, 'RAM')
|
self.assertContains(response, 'RAM')
|
||||||
self.assertContains(response, 'Kingston')
|
self.assertContains(response, 'Kingston')
|
||||||
|
|
||||||
@patch('device.views.Device')
|
@patch('device.views.Device')
|
||||||
def test_json_response(self, MockDevice):
|
def test_json_response_anonymous(self, MockDevice):
|
||||||
test_device = TestDevice(id=self.test_id)
|
test_device = TestDevice(id=self.test_id)
|
||||||
MockDevice.return_value = test_device
|
MockDevice.return_value = test_device
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
self.test_url,
|
self.test_url,
|
||||||
HTTP_ACCEPT='application/json'
|
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['uuids'], [])
|
||||||
|
self.assertEqual(json_data['hids'], ['hid1', 'hid2'])
|
||||||
|
self.assertNotIn('serial_number', json_data)
|
||||||
|
self.assertNotIn('serialNumber', json_data)
|
||||||
|
|
||||||
|
@patch('device.views.Device')
|
||||||
|
def test_json_response_authenticated(self, MockDevice):
|
||||||
|
test_device = TestDevice(id=self.test_id)
|
||||||
|
MockDevice.return_value = test_device
|
||||||
|
self.client.login(username='test@example.com', password='testpass123')
|
||||||
|
response = self.client.get(
|
||||||
|
self.test_url,
|
||||||
|
HTTP_ACCEPT='application/json'
|
||||||
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response['Content-Type'], 'application/json')
|
self.assertEqual(response['Content-Type'], 'application/json')
|
||||||
|
|
||||||
json_data = response.json()
|
json_data = response.json()
|
||||||
self.assertEqual(json_data['id'], self.test_id)
|
self.assertEqual(json_data['id'], self.test_id)
|
||||||
self.assertEqual(json_data['shortid'], self.test_id[:6].upper())
|
self.assertEqual(json_data['shortid'], self.test_id[:6].upper())
|
||||||
self.assertEqual(json_data['components'], test_device.components)
|
self.assertEqual(json_data['components'], [
|
||||||
|
{
|
||||||
@patch('device.views.Device')
|
'type': 'CPU',
|
||||||
def test_websnapshot_device(self, MockDevice):
|
'model': 'Intel i7',
|
||||||
test_device = TestWebSnapshotDevice(id=self.test_id)
|
'manufacturer': 'Intel',
|
||||||
MockDevice.return_value = test_device
|
'serialNumber': 'SN12345678'
|
||||||
response = self.client.get(self.test_url)
|
},
|
||||||
|
{
|
||||||
self.assertEqual(response.status_code, 200)
|
'type': 'RAM',
|
||||||
self.assertTemplateUsed(response, 'device_web.html')
|
'size': '8GB',
|
||||||
|
'manufacturer': 'Kingston',
|
||||||
self.assertContains(response, 'http://example.com')
|
'serialNumber': 'SN87654321'
|
||||||
self.assertContains(response, 'Test Page')
|
}
|
||||||
|
])
|
||||||
|
self.assertEqual(json_data['serial_number'], 'SN123456')
|
||||||
|
self.assertEqual(json_data['uuids'], [])
|
||||||
|
self.assertEqual(json_data['hids'], ['hid1', 'hid2'])
|
||||||
|
|
|
@ -133,15 +133,38 @@ class PublicDeviceWebView(TemplateView):
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_json_response(self):
|
@property
|
||||||
data = {
|
def public_fields(self):
|
||||||
|
return {
|
||||||
'id': self.object.id,
|
'id': self.object.id,
|
||||||
'shortid': self.object.shortid,
|
'shortid': self.object.shortid,
|
||||||
'uuids': self.object.uuids,
|
'uuids': self.object.uuids,
|
||||||
'hids': self.object.hids,
|
'hids': self.object.hids,
|
||||||
'components': self.object.components
|
'components': self.remove_serial_number_from(self.object.components),
|
||||||
}
|
}
|
||||||
return JsonResponse(data)
|
|
||||||
|
@property
|
||||||
|
def authenticated_fields(self):
|
||||||
|
return {
|
||||||
|
'serial_number': self.object.serial_number,
|
||||||
|
'components': self.object.components,
|
||||||
|
}
|
||||||
|
|
||||||
|
def remove_serial_number_from(self, components):
|
||||||
|
for component in components:
|
||||||
|
if 'serial_number' in component:
|
||||||
|
del component['SerialNumber']
|
||||||
|
return components
|
||||||
|
|
||||||
|
def get_device_data(self):
|
||||||
|
data = self.public_fields
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
data.update(self.authenticated_fields)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_json_response(self):
|
||||||
|
device_data = self.get_device_data()
|
||||||
|
return JsonResponse(device_data)
|
||||||
|
|
||||||
|
|
||||||
class AddAnnotationView(DashboardView, CreateView):
|
class AddAnnotationView(DashboardView, CreateView):
|
||||||
|
|
|
@ -19,14 +19,16 @@ class Annotation(models.Model):
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
uuid = models.UUIDField()
|
uuid = models.UUIDField()
|
||||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
user = models.ForeignKey(
|
||||||
|
User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
type = models.SmallIntegerField(choices=Type)
|
type = models.SmallIntegerField(choices=Type)
|
||||||
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(fields=["type", "key", "uuid"], name="unique_type_key_uuid")
|
models.UniqueConstraint(
|
||||||
|
fields=["type", "key", "uuid"], name="unique_type_key_uuid")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,7 +89,7 @@ class Evidence:
|
||||||
return self.components
|
return self.components
|
||||||
|
|
||||||
def get_manufacturer(self):
|
def get_manufacturer(self):
|
||||||
if self.doc.get("type") == "WebSnapshot":
|
if self.is_web_snapshot():
|
||||||
kv = self.doc.get('kv', {})
|
kv = self.doc.get('kv', {})
|
||||||
if len(kv) < 1:
|
if len(kv) < 1:
|
||||||
return ""
|
return ""
|
||||||
|
@ -99,7 +101,7 @@ class Evidence:
|
||||||
return self.dmi.manufacturer().strip()
|
return self.dmi.manufacturer().strip()
|
||||||
|
|
||||||
def get_model(self):
|
def get_model(self):
|
||||||
if self.doc.get("type") == "WebSnapshot":
|
if self.is_web_snapshot():
|
||||||
kv = self.doc.get('kv', {})
|
kv = self.doc.get('kv', {})
|
||||||
if len(kv) < 2:
|
if len(kv) < 2:
|
||||||
return ""
|
return ""
|
||||||
|
@ -122,6 +124,11 @@ class Evidence:
|
||||||
return k
|
return k
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def get_serial_number(self):
|
||||||
|
if self.is_legacy():
|
||||||
|
return self.doc['device']['serialNumber']
|
||||||
|
return self.dmi.serial_number().strip()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls, user):
|
def get_all(cls, user):
|
||||||
return Annotation.objects.filter(
|
return Annotation.objects.filter(
|
||||||
|
@ -136,3 +143,6 @@ class Evidence:
|
||||||
|
|
||||||
def is_legacy(self):
|
def is_legacy(self):
|
||||||
return self.doc.get("software") != "workbench-script"
|
return self.doc.get("software") != "workbench-script"
|
||||||
|
|
||||||
|
def is_web_snapshot(self):
|
||||||
|
return self.doc.get("type") == "WebSnapshot"
|
||||||
|
|
Loading…
Reference in New Issue