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
|
@ -29,7 +29,7 @@ class Device:
|
|||
self.shortid = self.pk[:6].upper()
|
||||
self.algorithm = None
|
||||
self.owner = None
|
||||
self.annotations = []
|
||||
self.annotations = []
|
||||
self.hids = []
|
||||
self.uuids = []
|
||||
self.evidences = []
|
||||
|
@ -108,7 +108,7 @@ class Device:
|
|||
return
|
||||
annotation = annotations.first()
|
||||
self.last_evidence = Evidence(annotation.uuid)
|
||||
|
||||
|
||||
def is_eraseserver(self):
|
||||
if not self.uuids:
|
||||
self.get_uuids()
|
||||
|
@ -120,7 +120,7 @@ class Device:
|
|||
owner=self.owner,
|
||||
type=Annotation.Type.ERASE_SERVER
|
||||
).first()
|
||||
|
||||
|
||||
if annotation:
|
||||
return True
|
||||
return False
|
||||
|
@ -129,7 +129,8 @@ class Device:
|
|||
return self.uuids[0]
|
||||
|
||||
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
|
||||
def get_unassigned(cls, institution, offset=0, limit=None):
|
||||
|
@ -179,7 +180,6 @@ class Device:
|
|||
count = cls.get_unassigned_count(institution)
|
||||
return devices, count
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_unassigned_count(cls, institution):
|
||||
|
||||
|
@ -279,6 +279,12 @@ class Device:
|
|||
self.get_last_evidence()
|
||||
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
|
||||
def type(self):
|
||||
if self.last_evidence.doc['type'] == "WebSnapshot":
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
<div class="col-lg-3 col-md-4 label">
|
||||
{% trans 'Serial Number' %}
|
||||
</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>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -120,10 +120,12 @@
|
|||
<div class="col-md-4 info-label">Model</div>
|
||||
<div class="col-md-8 info-value">{{ object.model|default:'' }}</div>
|
||||
</div>
|
||||
<div class="info-row row">
|
||||
<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>
|
||||
{% if user.is_authenticated %}
|
||||
<div class="info-row row">
|
||||
<div class="col-md-4 info-label">Serial Number</div>
|
||||
<div class="col-md-8 info-value">{{ object.serial_number|default:'' }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@ -136,7 +138,6 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title mt-5">Components</h2>
|
||||
<div class="row">
|
||||
{% for component in object.components %}
|
||||
|
@ -147,7 +148,9 @@
|
|||
<p class="card-text">
|
||||
{% for component_key, component_value in component.items %}
|
||||
{% if component_key not in 'actions,type' %}
|
||||
<strong>{{ component_key }}:</strong> {{ component_value }}<br />
|
||||
{% if component_key != 'serialNumber' or user.is_authenticated %}
|
||||
<strong>{{ component_key }}:</strong> {{ component_value }}<br />
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
|
@ -157,10 +160,9 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
©{% now 'Y' %} eReuse. All rights reserved.
|
||||
©{% now 'Y' %}eReuse. All rights reserved.
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
|
|
|
@ -3,74 +3,47 @@ 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 __init__(self, id):
|
||||
super().__init__(id=id)
|
||||
self.shortid = id[:6].upper()
|
||||
self.uuids = []
|
||||
self.hids = ['hid1', 'hid2']
|
||||
self._setup_evidence()
|
||||
|
||||
# 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'
|
||||
}
|
||||
def _setup_evidence(self):
|
||||
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._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',
|
||||
'serialNumber': 'SN12345678'
|
||||
},
|
||||
{
|
||||
'type': 'RAM',
|
||||
'size': '8GB',
|
||||
'manufacturer': 'Kingston',
|
||||
'serialNumber': 'SN87654321'
|
||||
}
|
||||
]
|
||||
self.last_evidence = self._evidence
|
||||
|
||||
@property
|
||||
def components(self):
|
||||
return self.last_evidence.get_components()
|
||||
|
||||
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
|
||||
@property
|
||||
def serial_number(self):
|
||||
return self.last_evidence.doc['device']['serialNumber']
|
||||
|
|
|
@ -2,7 +2,8 @@ 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
|
||||
from device.tests.test_mock_device import TestDevice
|
||||
from user.models import User, Institution
|
||||
|
||||
|
||||
class PublicDeviceWebViewTests(TestCase):
|
||||
|
@ -11,57 +12,99 @@ class PublicDeviceWebViewTests(TestCase):
|
|||
self.test_id = "test123"
|
||||
self.test_url = reverse('device:device_web',
|
||||
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):
|
||||
"""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):
|
||||
def test_html_response_anonymous(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.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, 'Intel')
|
||||
self.assertContains(response, 'RAM')
|
||||
self.assertContains(response, 'Kingston')
|
||||
|
||||
@patch('device.views.Device')
|
||||
def test_json_response(self, MockDevice):
|
||||
def test_json_response_anonymous(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['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['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')
|
||||
self.assertEqual(json_data['components'], [
|
||||
{
|
||||
'type': 'CPU',
|
||||
'model': 'Intel i7',
|
||||
'manufacturer': 'Intel',
|
||||
'serialNumber': 'SN12345678'
|
||||
},
|
||||
{
|
||||
'type': 'RAM',
|
||||
'size': '8GB',
|
||||
'manufacturer': 'Kingston',
|
||||
'serialNumber': 'SN87654321'
|
||||
}
|
||||
])
|
||||
self.assertEqual(json_data['serial_number'], 'SN123456')
|
||||
self.assertEqual(json_data['uuids'], [])
|
||||
self.assertEqual(json_data['hids'], ['hid1', 'hid2'])
|
||||
|
|
|
@ -117,10 +117,10 @@ class PublicDeviceWebView(TemplateView):
|
|||
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)
|
||||
|
@ -133,15 +133,38 @@ class PublicDeviceWebView(TemplateView):
|
|||
})
|
||||
return context
|
||||
|
||||
def get_json_response(self):
|
||||
data = {
|
||||
@property
|
||||
def public_fields(self):
|
||||
return {
|
||||
'id': self.object.id,
|
||||
'shortid': self.object.shortid,
|
||||
'uuids': self.object.uuids,
|
||||
'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):
|
||||
|
|
|
@ -11,7 +11,7 @@ from user.models import User, Institution
|
|||
|
||||
class Annotation(models.Model):
|
||||
class Type(models.IntegerChoices):
|
||||
SYSTEM= 0, "System"
|
||||
SYSTEM = 0, "System"
|
||||
USER = 1, "User"
|
||||
DOCUMENT = 2, "Document"
|
||||
ERASE_SERVER = 3, "EraseServer"
|
||||
|
@ -19,14 +19,16 @@ class Annotation(models.Model):
|
|||
created = models.DateTimeField(auto_now_add=True)
|
||||
uuid = models.UUIDField()
|
||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
type = models.SmallIntegerField(choices=Type)
|
||||
user = models.ForeignKey(
|
||||
User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
type = models.SmallIntegerField(choices=Type)
|
||||
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=["type", "key", "uuid"], name="unique_type_key_uuid")
|
||||
models.UniqueConstraint(
|
||||
fields=["type", "key", "uuid"], name="unique_type_key_uuid")
|
||||
]
|
||||
|
||||
|
||||
|
@ -37,8 +39,8 @@ class Evidence:
|
|||
self.doc = None
|
||||
self.created = None
|
||||
self.dmi = None
|
||||
self.annotations = []
|
||||
self.components = []
|
||||
self.annotations = []
|
||||
self.components = []
|
||||
self.default = "n/a"
|
||||
|
||||
self.get_owner()
|
||||
|
@ -87,7 +89,7 @@ class Evidence:
|
|||
return self.components
|
||||
|
||||
def get_manufacturer(self):
|
||||
if self.doc.get("type") == "WebSnapshot":
|
||||
if self.is_web_snapshot():
|
||||
kv = self.doc.get('kv', {})
|
||||
if len(kv) < 1:
|
||||
return ""
|
||||
|
@ -99,7 +101,7 @@ class Evidence:
|
|||
return self.dmi.manufacturer().strip()
|
||||
|
||||
def get_model(self):
|
||||
if self.doc.get("type") == "WebSnapshot":
|
||||
if self.is_web_snapshot():
|
||||
kv = self.doc.get('kv', {})
|
||||
if len(kv) < 2:
|
||||
return ""
|
||||
|
@ -122,6 +124,11 @@ class Evidence:
|
|||
return k
|
||||
return ""
|
||||
|
||||
def get_serial_number(self):
|
||||
if self.is_legacy():
|
||||
return self.doc['device']['serialNumber']
|
||||
return self.dmi.serial_number().strip()
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, user):
|
||||
return Annotation.objects.filter(
|
||||
|
@ -136,3 +143,6 @@ class Evidence:
|
|||
|
||||
def is_legacy(self):
|
||||
return self.doc.get("software") != "workbench-script"
|
||||
|
||||
def is_web_snapshot(self):
|
||||
return self.doc.get("type") == "WebSnapshot"
|
||||
|
|
Loading…
Reference in a new issue