diff --git a/ereuse_devicehub/api/views.py b/ereuse_devicehub/api/views.py index c566be00..23414b38 100644 --- a/ereuse_devicehub/api/views.py +++ b/ereuse_devicehub/api/views.py @@ -58,6 +58,8 @@ class InventoryView(LoginMixin, SnapshotMixin): self.snapshot_json = ParseSnapshotLsHw(snapshot_json).get_snapshot() snapshot = self.build() + snapshot.device.set_hid() + snapshot.device.binding.device.set_hid() db.session.add(snapshot) snap_log = SnapshotsLog( diff --git a/ereuse_devicehub/dummy/dummy.py b/ereuse_devicehub/dummy/dummy.py index ee7f7c15..f3533d48 100644 --- a/ereuse_devicehub/dummy/dummy.py +++ b/ereuse_devicehub/dummy/dummy.py @@ -1,5 +1,6 @@ import itertools import json +import uuid from pathlib import Path import click @@ -113,6 +114,8 @@ class Dummy: for path in bar: with path.open() as f: snapshot = yaml.load(f) + if snapshot['device']['type'] in ['Desktop', 'Laptop']: + snapshot['device']['system_uuid'] = uuid.uuid4() s, _ = user1.post(res=m.Snapshot, data=self.json_encode(snapshot)) if s.get('uuid', None) == 'ec23c11b-80b6-42cd-ac5c-73ba7acddbc4': sample_pc = s['device']['id'] @@ -227,7 +230,7 @@ class Dummy: user1.get(res=Device, item=sample_pc_devicehub_id) # Test anonymous = self.app.test_client() html, _ = anonymous.get(res=Device, item=sample_pc_devicehub_id, accept=ANY) - assert 'intel core2 duo cpu' in html + assert 'hewlett-packard' in html # For netbook: to preapre -> torepair -> to dispose -> disposed print('⭐ Done.') diff --git a/ereuse_devicehub/dummy/files/asus-1001pxd.snapshot.11.yaml b/ereuse_devicehub/dummy/files/asus-1001pxd.snapshot.11.yaml index f3f907fa..866fc8cc 100644 --- a/ereuse_devicehub/dummy/files/asus-1001pxd.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/asus-1001pxd.snapshot.11.yaml @@ -178,6 +178,7 @@ ], "type": "Laptop" }, + "debug": {"lshw": {"configuration": {"uuid": "79c5098f-bc44-4834-8a59-9ea61d956c31"}}}, "elapsed": 14725, "endTime": "2018-11-24T18:06:37.611704+00:00", "software": "Workbench", diff --git a/ereuse_devicehub/dummy/files/asus-eee-1000h.snapshot.11.yaml b/ereuse_devicehub/dummy/files/asus-eee-1000h.snapshot.11.yaml index f3c848e1..ae5a268e 100644 --- a/ereuse_devicehub/dummy/files/asus-eee-1000h.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/asus-eee-1000h.snapshot.11.yaml @@ -119,6 +119,7 @@ "manufacturer": "ASUSTeK Computer INC." } ], + "debug": {"lshw": {"configuration": {"uuid": "645f00bf-1ec0-4fdb-9608-b5ac73e285f6"}}}, "version": "11.0a4", "elapsed": 6, "endTime": "2016-11-03T17:17:17.266543+00:00" diff --git a/ereuse_devicehub/dummy/files/dell-optiplexgx520.snapshot.11.yaml b/ereuse_devicehub/dummy/files/dell-optiplexgx520.snapshot.11.yaml index 98b23bb8..625884b3 100644 --- a/ereuse_devicehub/dummy/files/dell-optiplexgx520.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/dell-optiplexgx520.snapshot.11.yaml @@ -148,6 +148,7 @@ "model": "0UG982" } ], + "debug": {"lshw": {"configuration": {"uuid": "5dcdd380-5a54-48bc-99bf-aff6019e8491"}}}, "version": "11.0a3", "closed": false, "elapsed": 1512, diff --git a/ereuse_devicehub/dummy/files/hp1.snapshot.11.yaml b/ereuse_devicehub/dummy/files/hp1.snapshot.11.yaml index 86489428..95311cbe 100644 --- a/ereuse_devicehub/dummy/files/hp1.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/hp1.snapshot.11.yaml @@ -132,5 +132,6 @@ "model": "HP Compaq 8100 Elite SFF", "manufacturer": "Hewlett-Packard" }, + "debug": {"lshw": {"configuration": {"uuid": "f6cfe48a-93d5-4e94-ab7b-3ee371e4d048"}}}, "version": "11.0a3" } diff --git a/ereuse_devicehub/dummy/files/hp2.snapshot.11.yaml b/ereuse_devicehub/dummy/files/hp2.snapshot.11.yaml index 80591b42..1e263ce6 100644 --- a/ereuse_devicehub/dummy/files/hp2.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/hp2.snapshot.11.yaml @@ -170,5 +170,6 @@ }, "software": "Workbench", "endTime": "2018-07-11T10:30:22.395958+00:00", + "debug": {"lshw": {"configuration": {"uuid": "75dcb454-ae80-4a87-a192-185d3b0250c0"}}}, "elapsed": 2766 } diff --git a/ereuse_devicehub/dummy/files/laptop-with-2-hid.snapshot.11.yaml b/ereuse_devicehub/dummy/files/laptop-with-2-hid.snapshot.11.yaml index 8cf37d51..8ca0798a 100644 --- a/ereuse_devicehub/dummy/files/laptop-with-2-hid.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/laptop-with-2-hid.snapshot.11.yaml @@ -146,6 +146,7 @@ "pcmcia": 0 } ], + "debug": {"lshw": {"configuration": {"uuid": "fcaf784e-5e57-43a2-b03f-8c56dabd0415"}}}, "uuid": "a01eacdb-db01-43ec-b6fb-a9b8cd21492d", "type": "Snapshot", "version": "11.0a4", diff --git a/ereuse_devicehub/dummy/files/lenovo-3493BAG.snapshot.11.yaml b/ereuse_devicehub/dummy/files/lenovo-3493BAG.snapshot.11.yaml index c73871ad..e7fb82f0 100644 --- a/ereuse_devicehub/dummy/files/lenovo-3493BAG.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/lenovo-3493BAG.snapshot.11.yaml @@ -4,6 +4,7 @@ "closed": false, "endTime": "2018-07-11T13:26:29.365504+00:00", "type": "Snapshot", + "debug": {"lshw": {"configuration": {"uuid": "4f256440-e43f-429a-a2c6-1e8f3365de56"}}}, "device": { "serialNumber": "PB357N0", "actions": [ diff --git a/ereuse_devicehub/dummy/files/nec.snapshot.11.yaml b/ereuse_devicehub/dummy/files/nec.snapshot.11.yaml index d846eb7a..5ec32875 100644 --- a/ereuse_devicehub/dummy/files/nec.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/nec.snapshot.11.yaml @@ -148,6 +148,7 @@ "slots": 4 } ], + "debug": {"lshw": {"configuration": {"uuid": "077cad5d-ae1b-4156-a9a1-98bca6fa5c35"}}}, "version": "11.0a3", "endTime": "2018-07-11T10:28:55.879745+00:00", "type": "Snapshot", diff --git a/ereuse_devicehub/dummy/files/oreo.snapshot.yaml b/ereuse_devicehub/dummy/files/oreo.snapshot.yaml index 348c9420..1e3da274 100644 --- a/ereuse_devicehub/dummy/files/oreo.snapshot.yaml +++ b/ereuse_devicehub/dummy/files/oreo.snapshot.yaml @@ -136,8 +136,8 @@ ], "elapsed": 203, "device": { - "manufacturer": null, - "model": null, + "manufacturer": "Asus", + "model": "P7P55D", "chassis": "Tower", "type": "Desktop", "serialNumber": null, @@ -158,7 +158,7 @@ ] }, "version": "11.0a6", - + "debug": {"lshw": {"configuration": {"uuid": "59ca9a2a-65bd-4802-89bb-315156a9352b"}}}, "type": "Snapshot", "closed": true, "software": "Workbench" diff --git a/ereuse_devicehub/dummy/files/pc-laudem.snapshot.11.yaml b/ereuse_devicehub/dummy/files/pc-laudem.snapshot.11.yaml index a4f76e6f..86127bc1 100644 --- a/ereuse_devicehub/dummy/files/pc-laudem.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/pc-laudem.snapshot.11.yaml @@ -142,7 +142,7 @@ }, "elapsed": 238, "endTime": "2018-10-15T13:59:37.431309+00:00", - + "debug": {"lshw": {"configuration": {"uuid": "43686b8e-e1ae-4e4e-bc51-f98f51e97c2d"}}}, "software": "Workbench", "type": "Snapshot", "uuid": "ec23c11b-80b6-42cd-ac5c-73ba7acddbc4", diff --git a/ereuse_devicehub/dummy/files/real-eee-1001pxd.snapshot.11.yaml b/ereuse_devicehub/dummy/files/real-eee-1001pxd.snapshot.11.yaml index a807c7f0..5a90e00a 100644 --- a/ereuse_devicehub/dummy/files/real-eee-1001pxd.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/real-eee-1001pxd.snapshot.11.yaml @@ -158,5 +158,6 @@ } ] }, + "debug": {"lshw": {"configuration": {"uuid": "a0cef731-9a78-4087-889c-dfb6ba5c2e9b"}}}, "closed": false } diff --git a/ereuse_devicehub/dummy/files/real-hp-quad-core.snapshot.11.yaml b/ereuse_devicehub/dummy/files/real-hp-quad-core.snapshot.11.yaml index 91aa12d6..8d921acd 100644 --- a/ereuse_devicehub/dummy/files/real-hp-quad-core.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/real-hp-quad-core.snapshot.11.yaml @@ -114,6 +114,7 @@ } ], "version": "11.0a3", + "debug": {"lshw": {"configuration": {"uuid": "f2c50acd-501a-4f0b-b07c-58254b2ab8c9"}}}, "device": { "type": "Desktop", "model": "HP Compaq 8000 Elite SFF", diff --git a/ereuse_devicehub/dummy/files/real-hp.snapshot.11.yaml b/ereuse_devicehub/dummy/files/real-hp.snapshot.11.yaml index 7158225e..8f9b2333 100644 --- a/ereuse_devicehub/dummy/files/real-hp.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/real-hp.snapshot.11.yaml @@ -1,6 +1,7 @@ { "closed": false, "uuid": "f9e5e587-baee-44e1-9a94-255d216bbda9", + "debug": {"lshw": {"configuration": {"uuid": "4d21dd26-aa45-4902-a5f2-8a06e364cf25"}}}, "components": [ { "actions": [], diff --git a/ereuse_devicehub/dummy/files/real-toshiba.snapshot.11.yaml b/ereuse_devicehub/dummy/files/real-toshiba.snapshot.11.yaml index 1a274d30..db83c5b2 100644 --- a/ereuse_devicehub/dummy/files/real-toshiba.snapshot.11.yaml +++ b/ereuse_devicehub/dummy/files/real-toshiba.snapshot.11.yaml @@ -131,6 +131,7 @@ "model": "NB200" }, "uuid": "918726ae-c6bc-40aa-97cf-ad80d69268f9", + "debug": {"lshw": {"configuration": {"uuid": "33627ef0-89a9-4659-bb29-faa936727e0b"}}}, "closed": false, "type": "Snapshot" } diff --git a/ereuse_devicehub/forms.py b/ereuse_devicehub/forms.py index 0f4cefbe..ef383588 100644 --- a/ereuse_devicehub/forms.py +++ b/ereuse_devicehub/forms.py @@ -1,10 +1,18 @@ +from boltons.urlutils import URL from flask import g from flask_wtf import FlaskForm from werkzeug.security import generate_password_hash -from wtforms import BooleanField, EmailField, PasswordField, validators +from wtforms import ( + BooleanField, + EmailField, + PasswordField, + StringField, + URLField, + validators, +) from ereuse_devicehub.db import db -from ereuse_devicehub.resources.user.models import User +from ereuse_devicehub.resources.user.models import SanitizationEntity, User class LoginForm(FlaskForm): @@ -101,3 +109,62 @@ class PasswordForm(FlaskForm): if commit: db.session.commit() return + + +class SanitizationEntityForm(FlaskForm): + + logo = URLField( + 'Logo', + [validators.Optional(), validators.URL()], + render_kw={ + 'class': "form-control", + "placeholder": "Url where is the logo - acceptd only .png, .jpg, .gif, svg", + }, + ) + company_name = StringField('Company Name', render_kw={'class': "form-control"}) + location = StringField('Location', render_kw={'class': "form-control"}) + responsable_person = StringField( + 'Responsable person', render_kw={'class': "form-control"} + ) + supervisor_person = StringField( + 'Supervisor person', render_kw={'class': "form-control"} + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if isinstance(self.logo.data, URL): + self.logo.data = self.logo.data.to_text() + + def validate(self, extra_validators=None): + is_valid = super().validate(extra_validators) + + if not is_valid: + return False + + if not self.logo.data: + return True + + extensions = ["jpg", "jpeg", "png", "gif", "svg"] + if self.logo.data.lower().split(".")[-1] not in extensions: + txt = "Error in Url field - accepted only .PNG, .JPG and .GIF. extensions" + self.logo.errors = [txt] + return False + + return True + + def save(self, commit=True): + if isinstance(self.logo.data, str): + self.logo.data = URL(self.logo.data) + + sanitation_data = SanitizationEntity.query.filter_by(user_id=g.user.id).first() + + if not sanitation_data: + sanitation_data = SanitizationEntity(user_id=g.user.id) + self.populate_obj(sanitation_data) + db.session.add(sanitation_data) + else: + self.populate_obj(sanitation_data) + + if commit: + db.session.commit() + return diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index 9cc85d30..ffb8d13c 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -30,8 +30,13 @@ from wtforms import ( from wtforms.fields import FormField from ereuse_devicehub.db import db -from ereuse_devicehub.inventory.models import DeliveryNote, ReceiverNote, Transfer -from ereuse_devicehub.parser.models import PlaceholdersLog +from ereuse_devicehub.inventory.models import ( + DeliveryNote, + ReceiverNote, + Transfer, + TransferCustomerDetails, +) +from ereuse_devicehub.parser.models import PlaceholdersLog, SnapshotsLog from ereuse_devicehub.parser.parser import ParseSnapshotLsHw from ereuse_devicehub.parser.schemas import Snapshot_lite from ereuse_devicehub.resources.action.models import Snapshot, Trade @@ -44,6 +49,7 @@ from ereuse_devicehub.resources.action.views.snapshot import ( from ereuse_devicehub.resources.device.models import ( SAI, Cellphone, + Computer, ComputerMonitor, Desktop, Device, @@ -203,7 +209,9 @@ class FilterForm(FlaskForm): if filter_type: self.devices = self.devices.filter(Device.type.in_(filter_type)) - return self.devices.order_by(Device.updated.desc()) + return self.devices.filter(Device.active.is_(True)).order_by( + Device.updated.desc() + ) class LotForm(FlaskForm): @@ -247,6 +255,10 @@ class LotForm(FlaskForm): class UploadSnapshotForm(SnapshotMixin, FlaskForm): snapshot = MultipleFileField('Select a Snapshot File', [validators.DataRequired()]) + def __init__(self, *args, **kwargs): + self.create_new_devices = kwargs.pop('create_new_devices', False) + super().__init__(*args, **kwargs) + def validate(self, extra_validators=None): is_valid = super().validate(extra_validators) @@ -291,7 +303,7 @@ class UploadSnapshotForm(SnapshotMixin, FlaskForm): return is_lite - def save(self, commit=True): + def save(self, commit=True, user_trusts=True): if any([x == 'Error' for x in self.result.values()]): return schema = SnapshotSchema() @@ -313,16 +325,21 @@ class UploadSnapshotForm(SnapshotMixin, FlaskForm): system_uuid = self.get_uuid(debug) if system_uuid: snapshot_json['device']['system_uuid'] = system_uuid + self.get_fields_extra(debug, snapshot_json) try: snapshot_json = schema.load(snapshot_json) - response = self.build(snapshot_json) + response = self.build( + snapshot_json, create_new_device=self.create_new_devices + ) except ValidationError as err: txt = "{}".format(err) self.errors(txt=txt) self.result[filename] = 'Error' continue + if isinstance(response.device, Computer): + response.device.user_trusts = user_trusts db.session.add(response) devices.append(response.device.binding.device) @@ -579,6 +596,7 @@ class NewDeviceForm(FlaskForm): device.image = URL(self.image.data) device.placeholder = self.get_placeholder() + device.set_hid() db.session.add(device) placeholder_log = PlaceholdersLog( @@ -1505,6 +1523,70 @@ class NotesForm(FlaskForm): return self._obj +class CustomerDetailsForm(FlaskForm): + company_name = StringField( + 'Company name', + [validators.Optional()], + render_kw={'class': "form-control"}, + description="Name of the company", + ) + location = StringField( + 'Location', + [validators.Optional()], + render_kw={'class': "form-control"}, + description="""Location where is the company""", + ) + logo = URLField( + 'Logo', + [validators.Optional()], + render_kw={ + 'class': "form-control", + "placeholder": "Url where is the logo - acceptd only .png, .jpg, .gif, svg", + }, + description="Url where is the logo", + ) + + def __init__(self, *args, **kwargs): + lot_id = kwargs.pop('lot_id', None) + self._tmp_lot = Lot.query.filter(Lot.id == lot_id).one() + self._obj = self._tmp_lot.transfer.customer_details + if self._obj: + kwargs['obj'] = self._obj + if not self._obj: + self._obj = TransferCustomerDetails(transfer_id=self._tmp_lot.transfer.id) + + super().__init__(*args, **kwargs) + if isinstance(self.logo.data, URL): + self.logo.data = URL(self.logo.data).to_text() + + def validate(self, extra_validators=None): + is_valid = super().validate(extra_validators) + + if not is_valid: + return is_valid + + if not self.logo.data: + return True + + extensions = ["jpg", "jpeg", "png", "gif", "svg"] + if self.logo.data.lower().split(".")[-1] not in extensions: + txt = "Error in Url field - accepted only .PNG, .JPG and .GIF. extensions" + self.logo.errors = [txt] + return False + + return True + + def save(self, commit=True): + self.populate_obj(self._obj) + self._obj.logo = URL(self._obj.logo) + db.session.add(self._obj) + + if commit: + db.session.commit() + + return self._obj + + class UploadPlaceholderForm(FlaskForm): type = StringField('Type', [validators.DataRequired()]) placeholder_file = FileField( @@ -1690,3 +1772,118 @@ class BindingForm(FlaskForm): return False return True + + +class UserTrustsForm(FlaskForm): + snapshot_type = SelectField( + '', + [validators.DataRequired()], + choices=[("new_device", "New Device"), ("update", "Update")], + default="new_device", + render_kw={'class': "form-select"}, + ) + + def __init__(self, snapshot_uuid, *args, **kwargs): + self.snapshot = Snapshot.query.filter_by(uuid=snapshot_uuid).one() + self.device = None + if self.snapshot.device: + self.device = self.snapshot.device + + self.snapshot_type.kwargs['default'] = self.snapshot.get_new_device() + super().__init__(*args, **kwargs) + + def validate(self, extra_validators=None): + is_valid = super().validate(extra_validators) + + if not is_valid: + txt = "" + self.snapthot_type.errors = [txt] + return False + + return True + + def unic(self): + try: + return self._unic + except Exception: + self._devices = ( + Device.query.filter_by( + hid=self.device.hid, owner=g.user, placeholder=None, active=True + ) + .order_by(Device.updated.asc()) + .all() + ) + + self._unic = len(self._devices) < 2 + return self._unic + + def dhids_all_devices(self): + self.unic() + return ", ".join([x.dhid for x in self._devices][1:]) + + def dhid_base(self): + self.unic() + if not self._devices: + return '' + return self._devices[0].dhid + + def show(self): + if not self.snapshot or not self.device: + return False + + if not hasattr(self.device, 'system_uuid'): + return False + + if not self.device.system_uuid: + return False + + if self.snapshot.get_new_device() == 'update': + # To do Split + return True + + if not self.unic(): + if self.device == self._devices[0]: + return False + # To do merge + return True + + return False + + def save(self, commit=True): + if not self.show(): + return + + if self.snapshot_type.data == self.snapshot.get_new_device(): + return + + if self.snapshot_type.data == 'update' and not self.unic(): + self.device.reliable() + + if self.snapshot_type.data == 'new_device' and self.unic(): + self.device.unreliable() + txt = "This devices is assigned as unreliable for the user " + txt += "and never is possible to do an update of this device." + self.error_log(txt) + + if commit: + db.session.commit() + + return self.snapshot + + def error_log(self, txt): + snapshot = self.get_first_snapshot() + error = SnapshotsLog( + description=txt, + snapshot=snapshot, + snapshot_uuid=snapshot.uuid, + severity=Severity.Error, + sid=snapshot.sid, + version="{}".format(snapshot.version), + ) + db.session.add(error) + + def get_first_snapshot(self): + device = self.snapshot.device + for ac in device.actions: + if ac.type == 'Snapshot': + return ac diff --git a/ereuse_devicehub/inventory/models.py b/ereuse_devicehub/inventory/models.py index 45d25157..f8b4f977 100644 --- a/ereuse_devicehub/inventory/models.py +++ b/ereuse_devicehub/inventory/models.py @@ -5,7 +5,7 @@ from flask import g from sqlalchemy import Column, Integer from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import backref, relationship -from teal.db import CASCADE_OWN +from teal.db import CASCADE_OWN, URL from ereuse_devicehub.db import db from ereuse_devicehub.resources.models import Thing @@ -90,3 +90,23 @@ class ReceiverNote(Thing): backref=backref('receiver_note', lazy=True, uselist=False, cascade=CASCADE_OWN), primaryjoin='ReceiverNote.transfer_id == Transfer.id', ) + + +class TransferCustomerDetails(Thing): + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + company_name = Column(CIText(), nullable=True) + location = Column(CIText(), nullable=True) + logo = Column(URL(), nullable=True) + + transfer_id = db.Column( + UUID(as_uuid=True), + db.ForeignKey('transfer.id'), + nullable=False, + ) + transfer = relationship( + 'Transfer', + backref=backref( + 'customer_details', lazy=True, uselist=False, cascade=CASCADE_OWN + ), + primaryjoin='TransferCustomerDetails.transfer_id == Transfer.id', + ) diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index ed3939be..2526a850 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -1,7 +1,9 @@ import copy import csv +import datetime import logging import os +import uuid from io import StringIO from pathlib import Path @@ -20,6 +22,7 @@ from ereuse_devicehub.inventory.forms import ( AdvancedSearchForm, AllocateForm, BindingForm, + CustomerDetailsForm, DataWipeForm, EditTransferForm, FilterForm, @@ -33,6 +36,7 @@ from ereuse_devicehub.inventory.forms import ( TransferForm, UploadPlaceholderForm, UploadSnapshotForm, + UserTrustsForm, ) from ereuse_devicehub.labels.forms import PrintLabelsForm from ereuse_devicehub.parser.models import PlaceholdersLog, SnapshotsLog @@ -78,6 +82,7 @@ class DeviceListMixin(GenericMixin): form_transfer = '' form_delivery = '' form_receiver = '' + form_customer_details = '' if lot_id: lot = lots.filter(Lot.id == lot_id).one() @@ -85,6 +90,7 @@ class DeviceListMixin(GenericMixin): form_transfer = EditTransferForm(lot_id=lot.id) form_delivery = NotesForm(lot_id=lot.id, type='Delivery') form_receiver = NotesForm(lot_id=lot.id, type='Receiver') + form_customer_details = CustomerDetailsForm(lot_id=lot.id) form_new_action = NewActionForm(lot=lot_id) self.context.update( @@ -96,6 +102,7 @@ class DeviceListMixin(GenericMixin): 'form_transfer': form_transfer, 'form_delivery': form_delivery, 'form_receiver': form_receiver, + 'form_customer_details': form_customer_details, 'form_filter': form_filter, 'form_print_labels': PrintLabelsForm(), 'lot': lot, @@ -834,6 +841,21 @@ class NewTransferView(GenericMixin): return flask.render_template(self.template_name, **self.context) +class OpenTransferView(GenericMixin): + methods = ['GET'] + + def dispatch_request(self, lot_id=None): + lot = Lot.query.filter_by(id=lot_id).one() + next_url = url_for('inventory.lotdevicelist', lot_id=str(lot_id)) + + if hasattr(lot, 'transfer'): + lot.transfer.date = None + db.session.commit() + messages.success('Transfer was reopen successfully!') + + return flask.redirect(next_url) + + class EditTransferView(GenericMixin): methods = ['POST'] form_class = EditTransferForm @@ -1038,7 +1060,7 @@ class ExportsView(View): return self.response_csv(data, "Erasures.csv") - def build_erasure_certificate(self): + def get_datastorages(self): erasures = [] for device in self.find_devices(): if device.placeholder and device.placeholder.binding: @@ -1049,11 +1071,96 @@ class ExportsView(View): elif isinstance(device, DataStorage): if device.privacy: erasures.append(device.privacy) + return erasures + + def get_costum_details(self, erasures): + my_data = None + customer_details = None + lot = None + + if hasattr(g.user, 'sanitization_entity'): + my_data = g.user.sanitization_entity + + customer_details = self.get_customer_details_from_request() + + if not erasures or customer_details: + return my_data, customer_details + + lots = {erasures[0].device.get_last_incoming_lot()} + for e in erasures[1:]: + lots.add(e.device.get_last_incoming_lot()) + + if len(lots) != 1: + return my_data, customer_details + + lot = lots.pop() + try: + customer_details = lot.transfer.customer_details + except Exception: + pass + + return my_data, customer_details + + def get_customer_details_from_request(self): + try: + if len(request.referrer.split('/lot/')) < 2: + return + + lot_id = request.referrer.split('/lot/')[-1].split('/')[0] + lot = Lot.query.filter_by(owner=g.user).filter_by(id=lot_id).first() + return lot.transfer.customer_details + except Exception: + pass + + def get_server_erasure_hosts(self, erasures): + erasures_host = [] + erasures_on_server = [] + for erase in erasures: + try: + if erase.parent.binding.kangaroo: + erasures_host.append(erase.parent) + erasures_on_server.append(erase) + except Exception: + pass + return erasures_host, erasures_on_server + + def build_erasure_certificate(self): + erasures = self.get_datastorages() + software = 'USODY DRIVE ERASURE' + if erasures and erasures[0].snapshot: + software += ' {}'.format( + erasures[0].snapshot.version, + ) + + my_data, customer_details = self.get_costum_details(erasures) + + a, b = self.get_server_erasure_hosts(erasures) + erasures_host, erasures_on_server = a, b + erasures_host = set(erasures_host) + + result = 'Success' + if "Failed" in [e.severity.get_public_name() for e in erasures]: + result = 'Failed' + + erasures = sorted(erasures, key=lambda x: x.end_time) + erasures_on_server = sorted(erasures_on_server, key=lambda x: x.end_time) + erasures_normal = list(set(erasures) - set(erasures_on_server)) + erasures_normal = sorted(erasures_normal, key=lambda x: x.end_time) + n_computers = len({x.parent for x in erasures} - erasures_host) params = { 'title': 'Erasure Certificate', 'erasures': tuple(erasures), 'url_pdf': '', + 'date_report': '{:%c}'.format(datetime.datetime.now()), + 'uuid_report': '{}'.format(uuid.uuid4()), + 'software': software, + 'my_data': my_data, + 'n_computers': n_computers, + 'result': result, + 'customer_details': customer_details, + 'erasure_hosts': erasures_host, + 'erasures_normal': erasures_normal, } return flask.render_template('inventory/erasure.html', **params) @@ -1228,9 +1335,12 @@ class SnapshotListView(GenericMixin): class SnapshotDetailView(GenericMixin): template_name = 'inventory/snapshot_detail.html' + methods = ['GET', 'POST'] + form_class = UserTrustsForm def dispatch_request(self, snapshot_uuid): self.snapshot_uuid = snapshot_uuid + form = self.form_class(snapshot_uuid) self.get_context() self.context['page_title'] = "Snapshot Detail" self.context['snapshots_log'] = self.get_snapshots_log() @@ -1238,6 +1348,10 @@ class SnapshotDetailView(GenericMixin): self.context['snapshot_sid'] = '' if self.context['snapshots_log'].count(): self.context['snapshot_sid'] = self.context['snapshots_log'][0].sid + self.context['form'] = form + + if form.validate_on_submit(): + form.save() return flask.render_template(self.template_name, **self.context) @@ -1249,6 +1363,28 @@ class SnapshotDetailView(GenericMixin): ) +class CustomerDetailsView(GenericMixin): + methods = ['POST'] + form_class = CustomerDetailsForm + + def dispatch_request(self, lot_id): + self.get_context() + form = self.form_class(request.form, lot_id=lot_id) + next_url = url_for('inventory.lotdevicelist', lot_id=lot_id) + + if form.validate_on_submit(): + form.save() + messages.success('Customer details updated successfully!') + return flask.redirect(next_url) + + messages.error('Customer details updated error!') + for k, v in form.errors.items(): + value = ';'.join(v) + key = form[k].label.text + messages.error('Error {key}: {value}!'.format(key=key, value=value)) + return flask.redirect(next_url) + + class DeliveryNoteView(GenericMixin): methods = ['POST'] form_class = NotesForm @@ -1440,6 +1576,10 @@ devices.add_url_rule( '/lot//transfer/', view_func=EditTransferView.as_view('edit_transfer'), ) +devices.add_url_rule( + '/lot//customerdetails/', + view_func=CustomerDetailsView.as_view('customer_details'), +) devices.add_url_rule( '/lot//deliverynote/', view_func=DeliveryNoteView.as_view('delivery_note'), @@ -1476,3 +1616,7 @@ devices.add_url_rule( '/device/erasure//', view_func=ErasureListView.as_view('device_erasure_list_orphans'), ) +devices.add_url_rule( + '/lot//opentransfer/', + view_func=OpenTransferView.as_view('open_transfer'), +) diff --git a/ereuse_devicehub/migrations/versions/4f33137586dd_sanitization.py b/ereuse_devicehub/migrations/versions/4f33137586dd_sanitization.py new file mode 100644 index 00000000..51fe4d61 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/4f33137586dd_sanitization.py @@ -0,0 +1,85 @@ +"""sanitization + +Revision ID: 4f33137586dd +Revises: 93daff872771 +Create Date: 2023-02-13 18:01:00.092527 + +""" +import citext +import sqlalchemy as sa +import teal +from alembic import context, op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '4f33137586dd' +down_revision = '93daff872771' +branch_labels = None +depends_on = None + + +def get_inv(): + INV = context.get_x_argument(as_dictionary=True).get('inventory') + if not INV: + raise ValueError("Inventory value is not specified") + return INV + + +def upgrade(): + op.create_table( + 'sanitization_entity', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column( + 'updated', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column( + 'created', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column('company_name', sa.String(), nullable=True), + sa.Column('logo', teal.db.URL(), nullable=True), + sa.Column('responsable_person', sa.String(), nullable=True), + sa.Column('supervisor_person', sa.String(), nullable=True), + sa.Column('location', sa.String(), nullable=True), + sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.ForeignKeyConstraint( + ['user_id'], + ['common.user.id'], + ), + schema=f'{get_inv()}', + ) + + op.create_table( + 'transfer_customer_details', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column( + 'updated', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column( + 'created', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column('company_name', citext.CIText(), nullable=True), + sa.Column('logo', teal.db.URL(), nullable=True), + sa.Column('location', citext.CIText(), nullable=True), + sa.Column('transfer_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.ForeignKeyConstraint(['transfer_id'], [f'{get_inv()}.transfer.id']), + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}', + ) + + +def downgrade(): + op.drop_table('sanitization_entity', schema=f'{get_inv()}') + op.drop_table('transfer_customer_details', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/migrations/versions/564952310b17_add_vendor_family_in_device.py b/ereuse_devicehub/migrations/versions/564952310b17_add_vendor_family_in_device.py new file mode 100644 index 00000000..f37ae2f3 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/564952310b17_add_vendor_family_in_device.py @@ -0,0 +1,35 @@ +"""add vendor family in device + +Revision ID: 564952310b17 +Revises: af038a8a388c +Create Date: 2022-11-14 13:12:22.916848 + +""" +import citext +import sqlalchemy as sa +from alembic import context, op + +# revision identifiers, used by Alembic. +revision = '564952310b17' +down_revision = 'af038a8a388c' +branch_labels = None +depends_on = None + + +def get_inv(): + INV = context.get_x_argument(as_dictionary=True).get('inventory') + if not INV: + raise ValueError("Inventory value is not specified") + return INV + + +def upgrade(): + op.add_column( + 'device', + sa.Column('family', citext.CIText(), nullable=True), + schema=f'{get_inv()}', + ) + + +def downgrade(): + op.drop_column('device', 'family', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/migrations/versions/93daff872771_add_hash_hid_to_device.py b/ereuse_devicehub/migrations/versions/93daff872771_add_hash_hid_to_device.py new file mode 100644 index 00000000..9b33ca95 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/93daff872771_add_hash_hid_to_device.py @@ -0,0 +1,65 @@ +"""add hash hid to device + +Revision ID: 93daff872771 +Revises: 564952310b17 +Create Date: 2022-12-13 10:14:45.500087 + +""" +import hashlib + +import citext +import sqlalchemy as sa +from alembic import context, op + +# revision identifiers, used by Alembic. +revision = '93daff872771' +down_revision = '564952310b17' +branch_labels = None +depends_on = None + + +def get_inv(): + INV = context.get_x_argument(as_dictionary=True).get('inventory') + if not INV: + raise ValueError("Inventory value is not specified") + return INV + + +def upgrade_data(): + con = op.get_bind() + sql = f"update {get_inv()}.computer set user_trusts='t';" + con.execute(sql) + + dev_sql = f"select id, hid from {get_inv()}.device;" + for d in con.execute(dev_sql): + if not d.hid: + continue + dev_id = d.id + chid = hashlib.sha3_256(d.hid.encode('utf-8')).hexdigest() + sql = f"update {get_inv()}.device set chid='{chid}' where id={dev_id};" + con.execute(sql) + + con.execute(sql) + + +def upgrade(): + op.add_column( + 'computer', + sa.Column('user_trusts', sa.Boolean(), default=True, nullable=True), + schema=f'{get_inv()}', + ) + + op.add_column( + 'device', + sa.Column('chid', citext.CIText(), nullable=True), + schema=f'{get_inv()}', + ) + + upgrade_data() + + op.alter_column('computer', 'user_trusts', nullable=False, schema=f'{get_inv()}') + + +def downgrade(): + op.drop_column('computer', 'user_trusts', schema=f'{get_inv()}') + op.drop_column('device', 'chid', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/parser/models.py b/ereuse_devicehub/parser/models.py index 8caa0620..79414270 100644 --- a/ereuse_devicehub/parser/models.py +++ b/ereuse_devicehub/parser/models.py @@ -38,7 +38,10 @@ class SnapshotsLog(Thing): db.session.commit() def get_status(self): - return Severity(self.severity) + if self.snapshot: + return Severity(self.severity) + + return '' def get_device(self): if self.snapshot: diff --git a/ereuse_devicehub/parser/parser.py b/ereuse_devicehub/parser/parser.py index 0dfe8a57..9207c59a 100644 --- a/ereuse_devicehub/parser/parser.py +++ b/ereuse_devicehub/parser/parser.py @@ -548,6 +548,12 @@ class ParseSnapshotLsHw: return action + def get_hid_datas(self): + self.device.family = self.get_family() + + def get_family(self): + return self.dmi.get("System", [{}])[0].get("Family", '') + def errors(self, txt=None, severity=Severity.Error): if not txt: return self._errors diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index ac38fad1..b723bab8 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -48,17 +48,14 @@ from sqlalchemy.util import OrderedSet from teal.db import ( CASCADE_OWN, INHERIT_COND, - IP, POLYMORPHIC_ID, POLYMORPHIC_ON, URL, - ResourceNotFound, StrictVersionType, check_lower, check_range, ) -from teal.enums import Country, Currency, Subdivision -from teal.marshmallow import ValidationError +from teal.enums import Currency from teal.resource import url_for_resource from ereuse_devicehub.db import db @@ -484,6 +481,9 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice): return self.snapshot.device.phid() return '' + def get_public_name(self): + return "Basic" + def __str__(self) -> str: return '{} on {}.'.format(self.severity, self.date_str) @@ -513,12 +513,32 @@ class EraseSectors(EraseBasic): method = 'Badblocks' + def get_public_name(self): + steps_random = 0 + steps_zeros = 0 + for s in self.steps: + if s.type == 'StepRandom': + steps_random += 1 + if s.type == 'StepZero': + steps_zeros += 1 + if steps_zeros < 1: + return "Basic" + if 0 < steps_random < 3: + return "Baseline" + if steps_random > 2: + return "Enhanced" + + return "Basic" + class ErasePhysical(EraseBasic): """The act of physically destroying a data storage unit.""" method = Column(DBEnum(PhysicalErasureMethod)) + def get_public_name(self): + return "Physical" + class Step(db.Model): erasure_id = Column( @@ -701,6 +721,19 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice): return hdds + def get_new_device(self): + + if not self.device: + return '' + + snapshots = [] + for s in self.device.actions: + if s == self: + break + if s.type == self.type: + snapshots.append(s) + return snapshots and 'update' or 'new_device' + def __str__(self) -> str: return '{}. {} version {}.'.format(self.severity, self.software, self.version) @@ -2009,7 +2042,7 @@ def update_components_action_one(target: ActionWithOneDevice, device: Device, __ if isinstance(device, Computer): target.components |= device.components elif isinstance(device, Computer): - device.add_mac_to_hid() + device.set_hid() @event.listens_for( diff --git a/ereuse_devicehub/resources/action/views/snapshot.py b/ereuse_devicehub/resources/action/views/snapshot.py index dd967fee..8fb2758e 100644 --- a/ereuse_devicehub/resources/action/views/snapshot.py +++ b/ereuse_devicehub/resources/action/views/snapshot.py @@ -64,7 +64,8 @@ def move_json(tmp_snapshots, path_name, user, live=False): class SnapshotMixin: sync = Sync() - def build(self, snapshot_json=None): # noqa: C901 + def build(self, snapshot_json=None, create_new_device=False): # noqa: C901 + self.create_new_device = create_new_device if not snapshot_json: snapshot_json = self.snapshot_json device = snapshot_json.pop('device') # type: Computer @@ -72,9 +73,7 @@ class SnapshotMixin: if snapshot_json['software'] == ( SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid ): - components = snapshot_json.pop('components', None) # type: List[Component] - if isinstance(device, Computer) and device.hid: - device.add_mac_to_hid(components_snap=components) + components = snapshot_json.pop('components', None) snapshot = Snapshot(**snapshot_json) # Remove new actions from devices so they don't interfere with sync @@ -89,7 +88,9 @@ class SnapshotMixin: assert not device.actions_one assert all(not c.actions_one for c in components) if components else True - db_device, remove_actions = self.sync.run(device, components) + db_device, remove_actions = self.sync.run( + device, components, self.create_new_device + ) del device # Do not use device anymore snapshot.device = db_device @@ -117,6 +118,9 @@ class SnapshotMixin: self.is_server_erase(snapshot) + snapshot.device.set_hid() + snapshot.device.binding.device.set_hid() + return snapshot def is_server_erase(self, snapshot): @@ -151,6 +155,30 @@ class SnapshotMixin: uuid = UUID(hw_uuid) return UUID(bytes_le=uuid.bytes) + def get_fields_extra(self, debug, snapshot_json): + if not debug or not isinstance(debug, dict): + return + + lshw = debug.get('lshw', {}) + + family = lshw.get('configuration', {}).get('family', '') + + snapshot_json['device']['family'] = family + + # lshw_mothers = [] + # for mt in lshw.get('children', []): + # if mt.get('description') == "Motherboard": + # lshw_mothers.append(mt) + + # for comp in snapshot_json.get('components', []): + # if comp.get('type') != 'Motherboard': + # continue + # for mt in lshw_mothers: + # if comp['serialNumber'] == mt.get('serial', ''): + # comp['vendor'] = mt.get('vendor', '') + # comp['product'] = mt.get('product', '') + # comp['version'] = mt.get('version', '') + def errors(self, txt=None, severity=Severity.Error, snapshot=None, commit=False): if not txt: return @@ -187,10 +215,13 @@ class SnapshotView(SnapshotMixin): self.version = snapshot_json.get('version') self.uuid = snapshot_json.get('uuid') self.sid = None - system_uuid = self.get_uuid(snapshot_json.pop('debug', None)) + self.debug = snapshot_json.pop('debug', {}) + system_uuid = self.get_uuid(self.debug) if system_uuid: snapshot_json['device']['system_uuid'] = system_uuid + self.get_fields_extra(self.debug, snapshot_json) + try: self.snapshot_json = resource_def.schema.load(snapshot_json) snapshot = self.build() diff --git a/ereuse_devicehub/resources/action/views/views.py b/ereuse_devicehub/resources/action/views/views.py index afbeb665..25814b4a 100644 --- a/ereuse_devicehub/resources/action/views/views.py +++ b/ereuse_devicehub/resources/action/views/views.py @@ -142,11 +142,14 @@ class LiveView(View): return hid if macs: mac = "-{mac}".format(mac=macs[0]) - hid += mac + # hid += mac return hid def live(self, snapshot): """If the device.allocated == True, then this snapshot create an action live.""" + for c in snapshot['components']: + c.parent = snapshot['device'] + snapshot['device'].set_hid() hid = self.get_hid(snapshot) if not hid or not Device.query.filter(Device.hid == hid).count(): raise ValidationError('Device not exist.') diff --git a/ereuse_devicehub/resources/device/metrics.py b/ereuse_devicehub/resources/device/metrics.py index bf118da6..fa43818e 100644 --- a/ereuse_devicehub/resources/device/metrics.py +++ b/ereuse_devicehub/resources/device/metrics.py @@ -21,27 +21,29 @@ class MetricsMix: """ This is a template of a row. """ - return {'type': '', - 'action_type': 'Status', - 'document_name': '', - 'status_receiver': self.status_receiver, - 'status_supplier': self.status_supplier, - 'status_receiver_created': '', - 'status_supplier_created': '', - 'trade_supplier': '', - 'trade_receiver': self.act.author.email, - 'trade_confirmed': '', - 'trade_weight': 0, - 'action_create_by': self.action_create_by, - 'devicehubID': self.devicehub_id, - 'hid': self.hid, - 'finalUserCode': '', - 'numEndUsers': 0, - 'liveCreate': 0, - 'usageTimeHdd': self.lifetime, - 'created': self.act.created, - 'start': '', - 'usageTimeAllocate': 0} + return { + 'type': '', + 'action_type': 'Status', + 'document_name': '', + 'status_receiver': self.status_receiver, + 'status_supplier': self.status_supplier, + 'status_receiver_created': '', + 'status_supplier_created': '', + 'trade_supplier': '', + 'trade_receiver': self.act.author.email, + 'trade_confirmed': '', + 'trade_weight': 0, + 'action_create_by': self.action_create_by, + 'devicehubID': self.devicehub_id, + 'hid': self.hid, + 'finalUserCode': '', + 'numEndUsers': 0, + 'liveCreate': 0, + 'usageTimeHdd': self.lifetime, + 'created': self.act.created, + 'start': '', + 'usageTimeAllocate': 0, + } def get_metrics(self): """ @@ -57,7 +59,7 @@ class Metrics(MetricsMix): self.device = kwargs.pop('device') self.actions = copy.copy(self.device.actions) super().__init__(*args, **kwargs) - self.hid = self.device.hid + self.hid = self.device.chid self.devicehub_id = self.device.devicehub_id def get_action_status(self): diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index d406d808..05af6f9b 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -1,5 +1,9 @@ import copy +import hashlib +import json +import os import pathlib +import uuid from contextlib import suppress from fractions import Fraction from itertools import chain @@ -8,7 +12,8 @@ from typing import Dict, List, Set from boltons import urlutils from citext import CIText -from ereuse_utils.naming import HID_CONVERSION_DOC, Naming +from ereuse_utils.naming import HID_CONVERSION_DOC +from flask import current_app as app from flask import g, request from more_itertools import unique_everseen from sqlalchemy import BigInteger, Boolean, Column @@ -181,6 +186,8 @@ class Device(Thing): dhid_bk = db.Column(db.CIText(), nullable=True, unique=False) phid_bk = db.Column(db.CIText(), nullable=True, unique=False) active = db.Column(Boolean, default=True) + family = db.Column(db.CIText()) + chid = db.Column(db.CIText()) _NON_PHYSICAL_PROPS = { 'id', @@ -201,6 +208,7 @@ class Device(Thing): 'production_date', 'variant', 'version', + 'family', 'sku', 'image', 'allocated', @@ -209,6 +217,11 @@ class Device(Thing): 'active', 'phid_bk', 'dhid_bk', + 'chid', + 'user_trusts', + 'chassis', + 'transfer_state', + 'receiver_id', } __table_args__ = ( @@ -254,16 +267,17 @@ class Device(Thing): """ actions_multiple = copy.copy(self.actions_multiple) actions_one = copy.copy(self.actions_one) + actions = [] for ac in actions_multiple: ac.real_created = ac.actions_device[0].created + actions.append(ac) for ac in actions_one: ac.real_created = ac.created + actions.append(ac) - return sorted( - chain(actions_multiple, actions_one), key=lambda x: x.real_created - ) + return sorted(actions, key=lambda x: x.real_created) @property def problems(self): @@ -662,6 +676,12 @@ class Device(Thing): return args def get_lots_for_template(self): + if self.binding: + return self.binding.device.get_lots_for_template() + + if not self.lots and hasattr(self, 'parent') and self.parent: + return self.parent.get_lots_for_template() + lots = [] for lot in self.lots: if lot.is_incoming: @@ -742,12 +762,71 @@ class Device(Thing): return "" - def set_hid(self): - with suppress(TypeError): - self.hid = Naming.hid( - self.type, self.manufacturer, self.model, self.serial_number + def get_exist_untrusted_device(self): + if isinstance(self, Computer): + if not self.system_uuid: + return True + + return ( + Computer.query.filter_by( + hid=self.hid, + user_trusts=False, + owner_id=g.user.id, + active=True, + placeholder=None, + ).first() + or False ) + return False + + def get_from_db(self): + if 'property_hid' in app.blueprints.keys(): + try: + from modules.device.utils import get_from_db + + return get_from_db(self) + except Exception: + pass + + if not self.hid: + return + + return Device.query.filter_by( + hid=self.hid, + owner_id=g.user.id, + active=True, + placeholder=None, + ).first() + + def set_hid(self): + if 'property_hid' in app.blueprints.keys(): + try: + from modules.device.utils import set_hid + + self.hid = set_hid(self) + self.set_chid() + return + except Exception: + pass + + self.hid = "{}-{}-{}-{}".format( + self._clean_string(self.type), + self._clean_string(self.manufacturer), + self._clean_string(self.model), + self._clean_string(self.serial_number), + ).lower() + self.set_chid() + + def _clean_string(self, s): + if not s: + return '' + return s.replace(' ', '_') + + def set_chid(self): + if self.hid: + self.chid = hashlib.sha3_256(self.hid.encode()).hexdigest() + def last_action_of(self, *types): """Gets the last action of the given types. @@ -826,6 +905,141 @@ class Device(Thing): } return types.get(self.type, '') + def unreliable(self): + self.user_trusts = False + i = 0 + snapshot1 = None + snapshots = {} + + for ac in self.actions: + if ac.type == 'Snapshot': + if i == 0: + snapshot1 = ac + if i > 0: + snapshots[ac] = self.get_snapshot_file(ac) + i += 1 + + if not snapshot1: + return + + self.create_new_device(snapshots.values(), user_trusts=self.user_trusts) + self.remove_snapshot(snapshots.keys()) + + return + + def get_snapshot_file(self, action): + uuid = action.uuid + user = g.user.email + name_file = f"*_{user}_{uuid}.json" + tmp_snapshots = app.config['TMP_SNAPSHOTS'] + path_dir_base = os.path.join(tmp_snapshots, user) + + for _file in pathlib.Path(path_dir_base).glob(name_file): + with open(_file) as file_snapshot: + snapshot = file_snapshot.read() + return json.loads(snapshot) + + def create_new_device(self, snapshots, user_trusts=True): + from ereuse_devicehub.inventory.forms import UploadSnapshotForm + + new_snapshots = [] + for snapshot in snapshots: + snapshot['uuid'] = str(uuid.uuid4()) + filename = "{}.json".format(snapshot['uuid']) + new_snapshots.append((filename, snapshot)) + + form = UploadSnapshotForm() + form.result = {} + form.snapshots = new_snapshots + form.create_new_devices = True + form.save(commit=False, user_trusts=user_trusts) + + def remove_snapshot(self, snapshots): + from ereuse_devicehub.parser.models import SnapshotsLog + + for ac in snapshots: + for slog in SnapshotsLog.query.filter_by(snapshot=ac): + slog.snapshot_id = None + slog.snapshot_uuid = None + db.session.delete(ac) + + def remove_devices(self, devices): + from ereuse_devicehub.parser.models import SnapshotsLog + + for dev in devices: + for ac in dev.actions: + if ac.type != 'Snapshot': + continue + for slog in SnapshotsLog.query.filter_by(snapshot=ac): + slog.snapshot_id = None + slog.snapshot_uuid = None + + for c in dev.components: + c.parent_id = None + + for tag in dev.tags: + tag.device_id = None + + placeholder = dev.binding or dev.placeholder + if placeholder: + db.session.delete(placeholder.binding) + db.session.delete(placeholder.device) + db.session.delete(placeholder) + + def reliable(self): + computers = Computer.query.filter_by( + hid=self.hid, + owner_id=g.user.id, + active=True, + placeholder=None, + ).order_by(Device.created.asc()) + + i = 0 + computer1 = None + computers_to_remove = [] + for d in computers: + if i == 0: + d.user_trusts = True + computer1 = d + i += 1 + continue + + computers_to_remove.append(d) + + self.remove_devices(computers_to_remove) + if not computer1: + return + + snapshot1 = None + for ac in computer1.actions_one: + if ac.type == 'Snapshot': + snapshot1 = ac + break + + if not snapshot1: + return + + return + + def get_last_incoming_lot(self): + lots = list(self.lots) + if hasattr(self, "orphan") and self.orphan: + lots = list(self.lots) + if self.binding: + lots = list(self.binding.device.lots) + + elif hasattr(self, "parent") and self.parent: + lots = list(self.parent.lots) + if self.parent.binding: + lots = list(self.parent.binding.device.lots) + + lots = sorted(lots, key=lambda x: x.created) + lots.reverse() + for lot in lots: + if lot.is_incoming: + return lot + return None + def __lt__(self, other): return self.id < other.id @@ -1026,6 +1240,7 @@ class Computer(Device): receiver_id = db.Column(UUID(as_uuid=True), db.ForeignKey(User.id), nullable=True) receiver = db.relationship(User, primaryjoin=receiver_id == User.id) system_uuid = db.Column(UUID(as_uuid=True), nullable=True) + user_trusts = db.Column(Boolean(), default=True) def __init__(self, *args, **kwargs) -> None: if args: diff --git a/ereuse_devicehub/resources/device/schemas.py b/ereuse_devicehub/resources/device/schemas.py index 8f92ffa7..76a1fee2 100644 --- a/ereuse_devicehub/resources/device/schemas.py +++ b/ereuse_devicehub/resources/device/schemas.py @@ -120,6 +120,7 @@ class Device(Thing): dhid = SanitizedStr( data_key='devicehubID', description=m.Device.devicehub_id.comment ) + family = SanitizedStr(validate=Length(max=STR_BIG_SIZE)) @pre_load def from_actions_to_actions_one(self, data: dict): diff --git a/ereuse_devicehub/resources/device/sync.py b/ereuse_devicehub/resources/device/sync.py index 613f0b39..32c4a2f2 100644 --- a/ereuse_devicehub/resources/device/sync.py +++ b/ereuse_devicehub/resources/device/sync.py @@ -1,6 +1,5 @@ import copy import difflib -from contextlib import suppress from itertools import groupby from typing import Iterable, Set @@ -23,28 +22,24 @@ from ereuse_devicehub.resources.device.models import ( ) from ereuse_devicehub.resources.tag.model import Tag -DEVICES_ALLOW_DUPLICITY = [ - 'RamModule', - 'Display', - 'SoundCard', - 'Battery', - 'Camera', - 'GraphicCard', -] - -err_motherboard = "Error: We have detected that a there is a device" -err_motherboard += " in your inventory with this system UUID. " -err_motherboard += "We proceed to block this snapshot to prevent its" -err_motherboard += " information from being updated incorrectly." -err_motherboard += " The solution we offer you to inventory this device " -err_motherboard += "is to do it by creating a placeholder." +# DEVICES_ALLOW_DUPLICITY = [ +# 'RamModule', +# 'Display', +# 'SoundCard', +# 'Battery', +# 'Camera', +# 'GraphicCard', +# ] class Sync: """Synchronizes the device and components with the database.""" def run( - self, device: Device, components: Iterable[Component] or None + self, + device: Device, + components: Iterable[Component] or None, + create_new_device=False, ) -> (Device, OrderedSet): """Synchronizes the device and components with the database. @@ -76,17 +71,11 @@ class Sync: of the passed-in components. 2. A list of Add / Remove (not yet added to session). """ - db_device = self.execute_register(device) - motherboard = None if components: - for c in components: - if c.type == "Motherboard": - motherboard = c - - if motherboard: - for c in db_device.components: - if c.type == "Motherboard" and motherboard.hid != c.hid: - raise ValidationError(err_motherboard) + device.components = OrderedSet(components) + device.set_hid() + device.components = OrderedSet() + db_device = self.execute_register(device, create_new_device) db_components, actions = OrderedSet(), OrderedSet() if components is not None: # We have component info (see above) @@ -94,12 +83,9 @@ class Sync: # Until a good reason is given, we synthetically forbid # non-computers with components raise ValidationError('Only computers can have components.') - blacklist = set() # type: Set[int] not_new_components = set() for component in components: - db_component, is_new = self.execute_register_component( - component, blacklist, parent=db_device - ) + db_component, is_new = self.execute_register_component(component) db_components.add(db_component) if not is_new: not_new_components.add(db_component) @@ -110,9 +96,7 @@ class Sync: self.create_placeholder(db_device) return db_device, actions - def execute_register_component( - self, component: Component, blacklist: Set[int], parent: Computer - ): + def execute_register_component(self, component: Component): """Synchronizes one component to the DB. This method is a specialization of :meth:`.execute_register` @@ -134,41 +118,27 @@ class Sync: - A flag stating if the device is new or it already existed in the DB. """ - # if device.serial_number == 'b8oaas048286': assert inspect(component).transient, 'Component should not be synced from DB' # if not is a DataStorage, then need build a new one - if component.t in DEVICES_ALLOW_DUPLICITY: + if not isinstance(component, DataStorage): db.session.add(component) is_new = True return component, is_new - # if not, then continue with the traditional behaviour - try: - if component.hid: - db_component = Device.query.filter_by( - hid=component.hid, owner_id=g.user.id, placeholder=None - ).one() - assert isinstance( - db_component, Device - ), '{} must be a component'.format(db_component) - else: - # Is there a component similar to ours? - db_component = component.similar_one(parent, blacklist) - # We blacklist this component so we - # ensure we don't get it again for another component - # with the same physical properties - blacklist.add(db_component.id) - except ResourceNotFound: + db_component = None + + if component.hid: + db_component = Device.query.filter_by( + hid=component.hid, owner_id=g.user.id, placeholder=None, active=True + ).first() + is_new = False + if not db_component: db.session.add(component) - # db.session.flush() db_component = component is_new = True - else: - self.merge(component, db_component) - is_new = False return db_component, is_new - def execute_register(self, device: Device) -> Device: + def execute_register(self, device: Device, create_new_device=False) -> Device: """Synchronizes one device to the DB. This method tries to get an existing device using the HID @@ -195,84 +165,24 @@ class Sync: :raise DatabaseError: Any other error from the DB. :return: The synced device from the db with the tags linked. """ - assert inspect(device).transient, 'Device cannot be already synced from DB' - assert all( - inspect(tag).transient for tag in device.tags - ), 'Tags cannot be synced from DB' - db_device = None - if isinstance(device, Computer): - # first search by uuid - if device.system_uuid: - with suppress(ResourceNotFound): - db_device = Computer.query.filter_by( - system_uuid=device.system_uuid, - owner_id=g.user.id, - active=True, - placeholder=None, - ).one() - # if no there are any Computer by uuid search by hid - if not db_device and device.hid: - with suppress(ResourceNotFound): - db_device = Device.query.filter_by( - hid=device.hid, - owner_id=g.user.id, - active=True, - placeholder=None, - ).one() - elif device.hid: - with suppress(ResourceNotFound): - db_device = Device.query.filter_by( - hid=device.hid, owner_id=g.user.id, active=True, placeholder=None - ).one() + db_device = device.get_from_db() if db_device and db_device.allocated: raise ResourceNotFound('device is actually allocated {}'.format(device)) - try: - tags = { - Tag.from_an_id(tag.id).one() for tag in device.tags - } # type: Set[Tag] - except ResourceNotFound: - raise ResourceNotFound('tag you are linking to device {}'.format(device)) - linked_tags = {tag for tag in tags if tag.device_id} # type: Set[Tag] - if linked_tags: - sample_tag = next(iter(linked_tags)) - for tag in linked_tags: - if tag.device_id != sample_tag.device_id: - raise MismatchBetweenTags( - tag, sample_tag - ) # Tags linked to different devices - if db_device: # Device from hid - if ( - sample_tag.device_id != db_device.id - ): # Device from hid != device from tags - raise MismatchBetweenTagsAndHid(db_device.id, db_device.hid) - else: # There was no device from hid - if sample_tag.device.physical_properties != device.physical_properties: - # Incoming physical props of device != props from tag's device - # which means that the devices are not the same - raise MismatchBetweenProperties( - sample_tag.device.physical_properties, - device.physical_properties, - ) - db_device = sample_tag.device - - if db_device: # Device from hid or tags - self.merge(device, db_device) - else: # Device is new and tags are not linked to a device + if not db_device or create_new_device: device.tags.clear() # We don't want to add the transient dummy tags + if create_new_device or device.get_exist_untrusted_device(): + device.user_trusts = False db.session.add(device) db_device = device - db_device.tags |= ( - tags # Union of tags the device had plus the (potentially) new ones - ) try: db.session.flush() except IntegrityError as e: # Manage 'one tag per organization' unique constraint if 'One tag per organization' in e.args[0]: # todo test for this - id = int(e.args[0][135 : e.args[0].index(',', 135)]) + id = int(e.args[0][135 : e.args[0].index(',', 135)]) # noqa: E203 raise ValidationError( 'The device is already linked to tag {} ' 'from the same organization.'.format(id), @@ -283,29 +193,6 @@ class Sync: assert db_device is not None return db_device - @staticmethod - def merge(device: Device, db_device: Device): - """Copies the physical properties of the device to the db_device. - - This method mutates db_device. - """ - if db_device.owner_id != g.user.id: - return - - if device.placeholder and not db_device.placeholder: - return - - for field_name, value in device.physical_properties.items(): - if value is not None: - setattr(db_device, field_name, value) - - # if device.system_uuid and db_device.system_uuid and device.system_uuid != db_device.system_uuid: - # TODO @cayop send error to sentry.io - # there are 2 computers duplicate get db_device for hid - - if hasattr(device, 'system_uuid') and device.system_uuid: - db_device.system_uuid = device.system_uuid - @staticmethod def create_placeholder(device: Device): """If the device is new, we need create automaticaly a new placeholder""" diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 40e6ff94..59beffd9 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -311,7 +311,7 @@ class DeviceMergeView(View): setattr(self.base_device, field_name, value) self.base_device.hid = self.with_device.hid - self.base_device.add_mac_to_hid() + self.base_device.set_hid() class ManufacturerView(View): diff --git a/ereuse_devicehub/resources/documents/device_row.py b/ereuse_devicehub/resources/documents/device_row.py index 2e13d327..d4efda46 100644 --- a/ereuse_devicehub/resources/documents/device_row.py +++ b/ereuse_devicehub/resources/documents/device_row.py @@ -285,7 +285,7 @@ class DeviceRow(BaseDeviceRow): self['Tag {} ID'.format(i)] = tag.id self['Tag {} Organization'.format(i)] = tag.org.name - self['Device Hardware ID'] = device.hid + self['Device Hardware ID'] = device.chid self['Device Type'] = device.t if isinstance(device, d.Computer) and not device.placeholder: self['Device Chassis'] = device.chassis.name @@ -433,12 +433,12 @@ class DeviceRow(BaseDeviceRow): ] erasure = erasures[-1] if erasures else None if not erasure: - self['Erasure {} {}'.format(ctype, i)] = none2str(component.hid) + self['Erasure {} {}'.format(ctype, i)] = none2str(component.chid) serial_number = none2str(component.serial_number) self['Erasure {} {} Serial Number'.format(ctype, i)] = serial_number self['Erasure {} {} Size (MB)'.format(ctype, i)] = none2str(component.size) elif hasattr(erasure, 'type') and erasure.type == 'DataWipe': - self['Erasure {} {}'.format(ctype, i)] = none2str(component.hid) + self['Erasure {} {}'.format(ctype, i)] = none2str(component.chid) serial_number = none2str(component.serial_number) self['Erasure {} {} Serial Number'.format(ctype, i)] = serial_number self['Erasure {} {} Size (MB)'.format(ctype, i)] = none2str(component.size) @@ -448,7 +448,7 @@ class DeviceRow(BaseDeviceRow): erasure.document.url and erasure.document.url.to_text() or '' ) else: - self['Erasure {} {}'.format(ctype, i)] = none2str(component.hid) + self['Erasure {} {}'.format(ctype, i)] = none2str(component.chid) serial_number = none2str(component.serial_number) self['Erasure {} {} Serial Number'.format(ctype, i)] = serial_number self['Erasure {} {} Size (MB)'.format(ctype, i)] = none2str(component.size) diff --git a/ereuse_devicehub/resources/enums.py b/ereuse_devicehub/resources/enums.py index bbbcdc10..2d527802 100644 --- a/ereuse_devicehub/resources/enums.py +++ b/ereuse_devicehub/resources/enums.py @@ -334,6 +334,12 @@ class Severity(IntEnum): def __format__(self, format_spec): return str(self) + def get_public_name(self): + if self.value == 3: + return "Failed" + + return "Success" + class PhysicalErasureMethod(Enum): """Methods of physically erasing the data-storage, usually diff --git a/ereuse_devicehub/resources/lot/models.py b/ereuse_devicehub/resources/lot/models.py index 461e7c69..051cf08c 100644 --- a/ereuse_devicehub/resources/lot/models.py +++ b/ereuse_devicehub/resources/lot/models.py @@ -93,6 +93,10 @@ class Lot(Thing): ) receiver = db.relationship(User, primaryjoin=receiver_address == User.email) + # __table_args__ = ( + # {'schema': 'dbtest'}, + # ) + def __init__( self, name: str, closed: bool = closed.default.arg, description: str = None ) -> None: diff --git a/ereuse_devicehub/resources/user/models.py b/ereuse_devicehub/resources/user/models.py index 5eadb21d..351907a8 100644 --- a/ereuse_devicehub/resources/user/models.py +++ b/ereuse_devicehub/resources/user/models.py @@ -1,11 +1,12 @@ from uuid import uuid4 from flask import current_app as app +from flask import g from flask_login import UserMixin from sqlalchemy import BigInteger, Boolean, Column, Sequence from sqlalchemy.dialects.postgresql import UUID from sqlalchemy_utils import EmailType, PasswordType -from teal.db import IntEnum +from teal.db import CASCADE_OWN, URL, IntEnum from ereuse_devicehub.db import db from ereuse_devicehub.resources.enums import SessionType @@ -119,3 +120,28 @@ class Session(Thing): def __str__(self) -> str: return '{0.token}'.format(self) + + +class SanitizationEntity(Thing): + id = db.Column(BigInteger, primary_key=True) + company_name = db.Column(db.String, nullable=True) + location = db.Column(db.String, nullable=True) + # logo = db.Column(db.String, nullable=True) + logo = db.Column(URL(), nullable=True) + responsable_person = db.Column(db.String, nullable=True) + supervisor_person = db.Column(db.String, nullable=True) + user_id = db.Column( + db.UUID(as_uuid=True), + db.ForeignKey(User.id), + default=lambda: g.user.id, + ) + user = db.relationship( + User, + backref=db.backref( + 'sanitization_entity', lazy=True, uselist=False, cascade=CASCADE_OWN + ), + primaryjoin=user_id == User.id, + ) + + def __str__(self) -> str: + return '{0.company_name}'.format(self) diff --git a/ereuse_devicehub/static/css/simple-datatables.css b/ereuse_devicehub/static/css/simple-datatables.css deleted file mode 100644 index ad225bc3..00000000 --- a/ereuse_devicehub/static/css/simple-datatables.css +++ /dev/null @@ -1,182 +0,0 @@ -.dataTable-wrapper.no-header .dataTable-container { - border-top: 1px solid #d9d9d9; -} - -.dataTable-wrapper.no-footer .dataTable-container { - border-bottom: 1px solid #d9d9d9; -} - -.dataTable-top, -.dataTable-bottom { - padding: 8px 10px; -} - -.dataTable-top > nav:first-child, -.dataTable-top > div:first-child, -.dataTable-bottom > nav:first-child, -.dataTable-bottom > div:first-child { - float: left; -} - -.dataTable-top > nav:last-child, -.dataTable-top > div:last-child, -.dataTable-bottom > nav:last-child, -.dataTable-bottom > div:last-child { - float: right; -} - -.dataTable-selector { - padding: 6px; -} - -.dataTable-input { - padding: 6px 12px; -} - -.dataTable-info { - margin: 7px 0; -} - -/* PAGER */ -.dataTable-pagination ul { - margin: 0; - padding-left: 0; -} - -.dataTable-pagination li { - list-style: none; - float: left; -} - -.dataTable-pagination a { - border: 1px solid transparent; - float: left; - margin-left: 2px; - padding: 6px 12px; - position: relative; - text-decoration: none; - color: #333; -} - -.dataTable-pagination a:hover { - background-color: #d9d9d9; -} - -.dataTable-pagination .active a, -.dataTable-pagination .active a:focus, -.dataTable-pagination .active a:hover { - background-color: #d9d9d9; - cursor: default; -} - -.dataTable-pagination .ellipsis a, -.dataTable-pagination .disabled a, -.dataTable-pagination .disabled a:focus, -.dataTable-pagination .disabled a:hover { - cursor: not-allowed; -} - -.dataTable-pagination .disabled a, -.dataTable-pagination .disabled a:focus, -.dataTable-pagination .disabled a:hover { - cursor: not-allowed; - opacity: 0.4; -} - -.dataTable-pagination .pager a { - font-weight: bold; -} - -/* TABLE */ -.dataTable-table { - max-width: 100%; - width: 100%; - border-spacing: 0; - border-collapse: separate; -} - -.dataTable-table > tbody > tr > td, -.dataTable-table > tbody > tr > th, -.dataTable-table > tfoot > tr > td, -.dataTable-table > tfoot > tr > th, -.dataTable-table > thead > tr > td, -.dataTable-table > thead > tr > th { - vertical-align: top; - padding: 8px 10px; -} - -.dataTable-table > thead > tr > th { - vertical-align: bottom; - text-align: left; - border-bottom: 1px solid #d9d9d9; -} - -.dataTable-table > tfoot > tr > th { - vertical-align: bottom; - text-align: left; - border-top: 1px solid #d9d9d9; -} - -.dataTable-table th { - vertical-align: bottom; - text-align: left; -} - -.dataTable-table th a { - text-decoration: none; - color: inherit; -} - -.dataTable-sorter { - display: inline-block; - height: 100%; - position: relative; - width: 100%; -} - -.dataTable-sorter::before, -.dataTable-sorter::after { - content: ""; - height: 0; - width: 0; - position: absolute; - right: 4px; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - opacity: 0.2; -} - -.dataTable-sorter::before { - border-top: 4px solid #000; - bottom: 0px; -} - -.dataTable-sorter::after { - border-bottom: 4px solid #000; - border-top: 4px solid transparent; - top: 0px; -} - -.asc .dataTable-sorter::after, -.desc .dataTable-sorter::before { - opacity: 0.6; -} - -.dataTables-empty { - text-align: center; -} - -.dataTable-top::after, .dataTable-bottom::after { - clear: both; - content: " "; - display: table; -} - -table.dataTable-table:focus tr.dataTable-cursor > td:first-child { - border-left: 3px blue solid; -} - -table.dataTable-table:focus { - outline: solid 1px black; - outline-offset: -1px; -} \ No newline at end of file diff --git a/ereuse_devicehub/static/js/simple-datatables-5.0.3.js b/ereuse_devicehub/static/js/simple-datatables-5.0.3.js deleted file mode 100644 index 0a894d0b..00000000 --- a/ereuse_devicehub/static/js/simple-datatables-5.0.3.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Minified by jsDelivr using Terser v5.15.1. - * Original file: /npm/simple-datatables@5.0.3/dist/umd/simple-datatables.js - * - * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files - */ -!function(t){if(typeof exports==="object"&&typeof module!=="undefined")module.exports=t();else if(typeof define==="function"&&define.amd)define([],t);else{(typeof window!=="undefined"?window:typeof global!=="undefined"?global:typeof self!=="undefined"?self:this).simpleDatatables=t()}}((() =>function t(e,s,i){function a(o,r){if(!s[o]){if(!e[o]){const l=typeof require==="function"&&require;if(!r&&l)return l(o,!0);if(n)return n(o,!0);const h=new Error(`Cannot find module '${o}'`);throw h.code="MODULE_NOT_FOUND",h}const d=s[o]={exports:{}};e[o][0].call(d.exports,((t) =>a(e[o][1][t]||t)),d,d.exports,t,e,s,i)}return s[o].exports}for(var n=typeof require==="function"&&require,o=0;o=e?t:`${Array(e+1-i.length).join(s)}${t}`}; const b={s:m,z(t){const e=-t.utcOffset(); const s=Math.abs(e); const i=Math.floor(s/60); const a=s%60;return`${(e<=0?"+":"-")+m(i,2,"0")}:${m(a,2,"0")}`},m:function t(e,s){if(e.date()1)return t(o[0])}else{const r=e.name;w[r]=e,a=r}return!i&&a&&(v=a),a||!i&&v}; const x=function(t,e){if(y(t))return t.clone();const s=typeof e==="object"?e:{};return s.date=t,s.args=arguments,new M(s)}; const T=b;T.l=C,T.i=y,T.w=function(t,e){return x(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var M=function(){function g(t){this.$L=C(t.locale,null,!0),this.parse(t)}const m=g.prototype;return m.parse=function(t){this.$d=function(t){const e=t.date; const s=t.utc;if(e===null)return new Date(NaN);if(T.u(e))return new Date;if(e instanceof Date)return new Date(e);if(typeof e==="string"&&!/Z$/i.test(e)){const i=e.match(p);if(i){const a=i[2]-1||0; const n=(i[7]||"0").substring(0,3);return s?new Date(Date.UTC(i[1],a,i[3]||1,i[4]||0,i[5]||0,i[6]||0,n)):new Date(i[1],a,i[3]||1,i[4]||0,i[5]||0,i[6]||0,n)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},m.init=function(){const t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},m.$utils=function(){return T},m.isValid=function(){return!(this.$d.toString()===u)},m.isSame=function(t,e){const s=x(t);return this.startOf(e)<=s&&s<=this.endOf(e)},m.isAfter=function(t,e){return x(t)e||g[t]||a.replace(":","")))},m.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},m.diff=function(s,c,u){let p; const f=T.p(c); const g=x(s); const m=(g.utcOffset()-this.utcOffset())*t; const b=this-g; let v=T.m(this,g);return v=(p={},p[d]=v/12,p[l]=v,p[h]=v/3,p[r]=(b-m)/6048e5,p[o]=(b-m)/864e5,p[n]=b/e,p[a]=b/t,p[i]=b/1e3,p)[f]||b,u?v:T.a(v)},m.daysInMonth=function(){return this.endOf(l).$D},m.$locale=function(){return w[this.$L]},m.locale=function(t,e){if(!t)return this.$L;const s=this.clone(); const i=C(t,e,!0);return i&&(s.$L=i),s},m.clone=function(){return T.w(this.$d,this)},m.toDate=function(){return new Date(this.valueOf())},m.toJSON=function(){return this.isValid()?this.toISOString():null},m.toISOString=function(){return this.$d.toISOString()},m.toString=function(){return this.$d.toUTCString()},g}(); const E=M.prototype;return x.prototype=E,[["$ms",s],["$s",i],["$m",a],["$H",n],["$W",o],["$M",l],["$y",d],["$D",c]].forEach(((t) =>{E[t[1]]=function(e){return this.$g(e,t[0],t[1])}})),x.extend=function(t,e){return t.$i||(t(e,M,x),t.$i=!0),x},x.locale=C,x.isDayjs=y,x.unix=function(t){return x(1e3*t)},x.en=w[v],x.Ls=w,x.p={},x}(); const i={exports:{}}.exports=function(){const t={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"}; const e=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|YYYY|YY?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g; const s=/\d\d/; const i=/\d\d?/; const a=/\d*[^-_:/,()\s\d]+/; let n={}; let o=function(t){return(t=+t)+(t>68?1900:2e3)}; const r=function(t){return function(e){this[t]=+e}}; const l=[/[+-]\d\d:?(\d\d)?|Z/,function(t){(this.zone||(this.zone={})).offset=function(t){if(!t)return 0;if(t==="Z")return 0;const e=t.match(/([+-]|\d\d)/g); const s=60*e[1]+(+e[2]||0);return s===0?0:e[0]==="+"?-s:s}(t)}]; const h=function(t){const e=n[t];return e&&(e.indexOf?e:e.s.concat(e.f))}; const d=function(t,e){let s; const i=n.meridiem;if(i){for(let a=1;a<=24;a+=1)if(t.indexOf(i(a,0,e))>-1){s=a>12;break}}else s=t===(e?"pm":"PM");return s}; const c={A:[a,function(t){this.afternoon=d(t,!1)}],a:[a,function(t){this.afternoon=d(t,!0)}],S:[/\d/,function(t){this.milliseconds=100*+t}],SS:[s,function(t){this.milliseconds=10*+t}],SSS:[/\d{3}/,function(t){this.milliseconds=+t}],s:[i,r("seconds")],ss:[i,r("seconds")],m:[i,r("minutes")],mm:[i,r("minutes")],H:[i,r("hours")],h:[i,r("hours")],HH:[i,r("hours")],hh:[i,r("hours")],D:[i,r("day")],DD:[s,r("day")],Do:[a,function(t){const e=n.ordinal; const s=t.match(/\d+/);if(this.day=s[0],e)for(let i=1;i<=31;i+=1)e(i).replace(/\[|\]/g,"")===t&&(this.day=i)}],M:[i,r("month")],MM:[s,r("month")],MMM:[a,function(t){const e=h("months"); const s=(h("monthsShort")||e.map(((t) =>t.slice(0,3)))).indexOf(t)+1;if(s<1)throw new Error;this.month=s%12||s}],MMMM:[a,function(t){const e=h("months").indexOf(t)+1;if(e<1)throw new Error;this.month=e%12||e}],Y:[/[+-]?\d+/,r("year")],YY:[s,function(t){this.year=o(t)}],YYYY:[/\d{4}/,r("year")],Z:l,ZZ:l};function u(s){let i; let a;i=s,a=n&&n.formats;for(var o=(s=i.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,((e,s,i) =>{const n=i&&i.toUpperCase();return s||a[i]||t[i]||a[n].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,((t,e,s) =>e||s.slice(1)))}))).match(e),r=o.length,l=0;l-1)return new Date((e==="X"?1e3:1)*t);const i=u(e)(t); const a=i.year; const n=i.month; const o=i.day; const r=i.hours; const l=i.minutes; const h=i.seconds; const d=i.milliseconds; const c=i.zone; const p=new Date; const f=o||(a||n?1:p.getDate()); const g=a||p.getFullYear(); let m=0;a&&!n||(m=n>0?n-1:p.getMonth());const b=r||0; const v=l||0; const w=h||0; const y=d||0;return c?new Date(Date.UTC(g,m,f,b,v,w,y+60*c.offset*1e3)):s?new Date(Date.UTC(g,m,f,b,v,w,y)):new Date(g,m,f,b,v,w,y)}catch(t){return new Date("")}}(e,r,i),this.init(),c&&!0!==c&&(this.$L=this.locale(c).$L),d&&e!=this.format(r)&&(this.$d=new Date("")),n={}}else if(r instanceof Array)for(let p=r.length,f=1;f<=p;f+=1){o[1]=r[f-1];const g=s.apply(this,o);if(g.isValid()){this.$d=g.$d,this.$L=g.$L,this.init();break}f===p&&(this.$d=new Date(""))}else a.call(this,t)}}}();e.extend(i),s.parseDate=(t,s)=>{let i=!1;if(s)switch(s){case"ISO_8601":i=t;break;case"RFC_2822":i=e(t.slice(5),"DD MMM YYYY HH:mm:ss ZZ").unix();break;case"MYSQL":i=e(t,"YYYY-MM-DD hh:mm:ss").unix();break;case"UNIX":i=e(t).unix();break;default:i=e(t,s,!0).valueOf()}return i}}).call(this)}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],2:[function(t,e,s){"use strict"; - -Object.defineProperty(s,"__esModule",{value:!0});const i=t=>Object.prototype.toString.call(t)==="[object Object]"; const a=t=>{let e=!1;try{e=JSON.parse(t)}catch(t){return!1}return!(e===null||!Array.isArray(e)&&!i(e))&&e}; const n=(t,e)=>{const s=document.createElement(t);if(e&&typeof e==="object")for(const t in e)t==="html"?s.innerHTML=e[t]:s.setAttribute(t,e[t]);return s}; const o=t=>{t instanceof NodeList?t.forEach((t=>o(t))):t.innerHTML=""}; const r=(t,e,s)=>n("li",{class:t,html:`${s}`}); const l=(t,e)=>{let s; let i;e===1?(s=0,i=t.length):e===-1&&(s=t.length-1,i=-1);for(let a=!0;a;){a=!1;for(let n=s;n!=i;n+=e)if(t[n+e]&&t[n].value>t[n+e].value){const s=t[n]; const i=t[n+e]; const o=s;t[n]=i,t[n+e]=o,a=!0}}return t};class h{constructor(t){this.dt=t,this.cursor=!1} - -build(t){const e=n("tr");let s=this.dt.headings;return s.length||(s=t.map((()=>""))),s.forEach(((s,i)=>{const a=n("td");t[i]&&t[i].length||(t[i]=""),a.innerHTML=t[i],a.data=t[i],e.appendChild(a)})),e} - -setCursor(t=!1){let e;Array.from(this.dt.dom.rows).forEach((t=>{e=t,t.classList.remove("dataTable-cursor")})),t&&(t.classList.add("dataTable-cursor"),this.cursor=t,this.dt.options.scrollY&&this.cursor.scrollIntoView({block:"nearest"}),this.dt.emit("datatable.cursormove",this.cursor,e))} - -render(t){return t} - -add(t){if(Array.isArray(t)){const e=this.dt;Array.isArray(t[0])?t.forEach((t=>{e.data.push(this.build(t))})):e.data.push(this.build(t)),e.data.length&&(e.hasRows=!0),this.update(),e.columns.rebuild()}} - -remove(t){const e=this.dt;Array.isArray(t)?(t.sort(((t,e)=>e-t)),t.forEach((t=>{e.data.splice(t,1)}))):t=="all"?e.data=[]:e.data.splice(t,1),e.data.length||(e.hasRows=!1),this.update(),e.columns.rebuild()} - -update(){this.dt.data.forEach(((t,e)=>{t.dataIndex=e}))} - -findRowIndex(t,e){return this.dt.data.findIndex((s=>s.children[t].innerText.toLowerCase().includes(String(e).toLowerCase())))} - -findRow(t,e){const s=this.findRowIndex(t,e);if(s<0)return{index:-1,row:null,cols:[]};const i=this.dt.data[s];return{index:s,row:i,cols:[...i.cells].map((t=>t.innerHTML))}} - -updateRow(t,e){const s=this.build(e);this.dt.data.splice(t,1,s),this.update(),this.dt.columns.rebuild()}}class d{constructor(t){this.dt=t} - -swap(t){if(t.length&&t.length===2){const e=[];this.dt.headings.forEach(((t,s)=>{e.push(s)}));const s=t[0]; const i=t[1]; const a=e[i];e[i]=e[s],e[s]=a,this.order(e)}} - -order(t){let e; let s; let i; let a; let n; let o; let r;const l=[[],[],[],[]]; const h=this.dt;t.forEach(((t,i)=>{n=h.headings[t],o=n.getAttribute("data-sortable")!=="false",e=n.cloneNode(!0),e.originalCellIndex=i,e.sortable=o,l[0].push(e),h.hiddenColumns.includes(t)||(s=n.cloneNode(!0),s.originalCellIndex=i,s.sortable=o,l[1].push(s))})),h.data.forEach(((e,s)=>{i=e.cloneNode(!1),a=e.cloneNode(!1),i.dataIndex=a.dataIndex=s,e.searchIndex!==null&&void 0!==e.searchIndex&&(i.searchIndex=a.searchIndex=e.searchIndex),t.forEach((t=>{r=e.cells[t].cloneNode(!0),r.data=e.cells[t].data,i.appendChild(r),h.hiddenColumns.includes(t)||(r=e.cells[t].cloneNode(!0),r.data=e.cells[t].data,a.appendChild(r))})),l[2].push(i),l[3].push(a)})),h.headings=l[0],h.activeHeadings=l[1],h.data=l[2],h.activeRows=l[3],h.update()} - -hide(t){if(t.length){const e=this.dt;t.forEach((t=>{e.hiddenColumns.includes(t)||e.hiddenColumns.push(t)})),this.rebuild()}} - -show(t){if(t.length){let e;const s=this.dt;t.forEach((t=>{e=s.hiddenColumns.indexOf(t),e>-1&&s.hiddenColumns.splice(e,1)})),this.rebuild()}} - -visible(t){let e;const s=this.dt;return t=t||s.headings.map((t=>t.originalCellIndex)),isNaN(t)?Array.isArray(t)&&(e=[],t.forEach((t=>{e.push(!s.hiddenColumns.includes(t))}))):e=!s.hiddenColumns.includes(t),e} - -add(t){let e;const s=document.createElement("th");if(!this.dt.headings.length)return this.dt.insert({headings:[t.heading],data:t.data.map((t=>[t]))}),void this.rebuild();this.dt.hiddenHeader?s.innerHTML="":t.heading.nodeName?s.appendChild(t.heading):s.innerHTML=t.heading,this.dt.headings.push(s),this.dt.data.forEach(((s,i)=>{t.data[i]&&(e=document.createElement("td"),t.data[i].nodeName?e.appendChild(t.data[i]):e.innerHTML=t.data[i],e.data=e.innerHTML,t.render&&(e.innerHTML=t.render.call(this,e.data,e,s)),s.appendChild(e))})),t.type&&s.setAttribute("data-type",t.type),t.format&&s.setAttribute("data-format",t.format),t.hasOwnProperty("sortable")&&(s.sortable=t.sortable,s.setAttribute("data-sortable",!0===t.sortable?"true":"false")),this.rebuild(),this.dt.renderHeader()} - -remove(t){Array.isArray(t)?(t.sort(((t,e)=>e-t)),t.forEach((t=>this.remove(t)))):(this.dt.headings.splice(t,1),this.dt.data.forEach((e=>{e.removeChild(e.cells[t])}))),this.rebuild()} - -filter(t,e,s,i){const a=this.dt;if(a.filterState||(a.filterState={originalData:a.data}),!a.filterState[t]){const e=[...i,()=>!0];a.filterState[t]=function(){let t=0;return()=>e[t++%e.length]}()}const n=a.filterState[t](); const o=Array.from(a.filterState.originalData).filter((e=>{const s=e.cells[t]; const i=s.hasAttribute("data-content")?s.getAttribute("data-content"):s.innerText;return typeof n==="function"?n(i):i===n}));a.data=o,a.data.length?(this.rebuild(),a.update()):(a.clear(),a.hasRows=!1,a.setMessage(a.options.labels.noRows)),s||a.emit("datatable.sort",t,e)} - -sort(e,s,i){const a=this.dt;if(a.hasHeadings&&(e<0||e>a.headings.length))return!1;const n=a.options.filters&&a.options.filters[a.headings[e].textContent];if(n&&n.length!==0)return void this.filter(e,s,i,n);a.sorting=!0,i||a.emit("datatable.sorting",e,s);let o=a.data;const r=[]; const h=[];let d=0; let c=0;const u=a.headings[e]; const p=[];if(u.getAttribute("data-type")==="date"){let e=!1;u.hasAttribute("data-format")&&(e=u.getAttribute("data-format")),p.push(Promise.resolve().then((() =>t("./date-7061ceee.js"))).then((({parseDate:t})=>s=>t(s,e))))}Promise.all(p).then((t=>{const n=t[0];let p; let f;Array.from(o).forEach((t=>{const s=t.cells[e]; const i=s.hasAttribute("data-content")?s.getAttribute("data-content"):s.innerText;let a;a=n?n(i):typeof i==="string"?i.replace(/(\$|,|\s|%)/g,""):i,parseFloat(a)==a?h[c++]={value:Number(a),row:t}:r[d++]={value:typeof i==="string"?i.toLowerCase():i,row:t}})),s||(s=u.classList.contains("asc")?"desc":"asc"),s=="desc"?(p=l(r,-1),f=l(h,-1),u.classList.remove("asc"),u.classList.add("desc"),u.setAttribute("aria-sort","descending")):(p=l(h,1),f=l(r,1),u.classList.remove("desc"),u.classList.add("asc"),u.setAttribute("aria-sort","ascending")),a.lastTh&&u!=a.lastTh&&(a.lastTh.classList.remove("desc"),a.lastTh.classList.remove("asc"),a.lastTh.removeAttribute("aria-sort")),a.lastTh=u,o=p.concat(f),a.data=[];const g=[];o.forEach(((t,e)=>{a.data.push(t.row),t.row.searchIndex!==null&&void 0!==t.row.searchIndex&&g.push(e)})),a.searchData=g,this.rebuild(),a.update(),i||a.emit("datatable.sort",e,s)}))} - -rebuild(){let t; let e; let s; let i;const a=this.dt; const n=[];a.activeRows=[],a.activeHeadings=[],a.headings.forEach(((t,e)=>{t.originalCellIndex=e,t.sortable=t.getAttribute("data-sortable")!=="false",a.hiddenColumns.includes(e)||a.activeHeadings.push(t)})),a.selectedColumns.length&&a.data.forEach((t=>{Array.from(t.cells).forEach(((e,s)=>{a.selectedColumns.includes(s)&&a.columnRenderers.forEach((i=>{i.columns.includes(s)&&(a.data[e.parentNode.dataIndex].cells[e.cellIndex].innerHTML=e.innerHTML=i.renderer.call(this,e.data,e,t))}))}))})),a.data.forEach(((o,r)=>{t=o.cloneNode(!1),e=o.cloneNode(!1),t.dataIndex=e.dataIndex=r,o.searchIndex!==null&&void 0!==o.searchIndex&&(t.searchIndex=e.searchIndex=o.searchIndex),Array.from(o.cells).forEach((n=>{s=n.cloneNode(!0),s.data=n.data,t.appendChild(s),a.hiddenColumns.includes(s.cellIndex)||(i=s.cloneNode(!0),i.data=s.data,e.appendChild(i))})),n.push(t),a.activeRows.push(e)})),a.data=n,a.update()}}const c=function(t){let e=!1; let s=!1;if((t=t||this.options.data).headings){e=n("thead");const s=n("tr");t.headings.forEach((t=>{const e=n("th",{html:t});s.appendChild(e)})),e.appendChild(s)}t.data&&t.data.length&&(s=n("tbody"),t.data.forEach((e=>{if(t.headings&&t.headings.length!==e.length)throw new Error("The number of rows do not match the number of headings.");const i=n("tr");e.forEach((t=>{const e=n("td",{html:t});i.appendChild(e)})),s.appendChild(i)}))),e&&(this.dom.tHead!==null&&this.dom.removeChild(this.dom.tHead),this.dom.appendChild(e)),s&&(this.dom.tBodies.length&&this.dom.removeChild(this.dom.tBodies[0]),this.dom.appendChild(s))}; const u={sortable:!0,searchable:!0,paging:!0,perPage:10,perPageSelect:[5,10,15,20,25],nextPrev:!0,firstLast:!1,prevText:"‹",nextText:"›",firstText:"«",lastText:"»",ellipsisText:"…",ascText:"▴",descText:"▾",truncatePager:!0,pagerDelta:2,scrollY:"",fixedColumns:!0,fixedHeight:!1,header:!0,hiddenHeader:!1,footer:!1,tabIndex:!1,rowNavigation:!1,labels:{placeholder:"Search...",perPage:"{select} entries per page",noRows:"No entries found",noResults:"No results match your search query",info:"Showing {start} to {end} of {rows} entries"},layout:{top:"{select}{search}",bottom:"{info}{pager}"}}; const p={classes:{row:"dataTable-editor-row",form:"dataTable-editor-form",item:"dataTable-editor-item",menu:"dataTable-editor-menu",save:"dataTable-editor-save",block:"dataTable-editor-block",close:"dataTable-editor-close",inner:"dataTable-editor-inner",input:"dataTable-editor-input",label:"dataTable-editor-label",modal:"dataTable-editor-modal",action:"dataTable-editor-action",header:"dataTable-editor-header",wrapper:"dataTable-editor-wrapper",editable:"dataTable-editor-editable",container:"dataTable-editor-container",separator:"dataTable-editor-separator"},labels:{editCell:"Edit Cell",editRow:"Edit Row",removeRow:"Remove Row",reallyRemove:"Are you sure?"},hiddenColumns:!1,contextMenu:!0,clickEvent:"dblclick",excludeColumns:[],menuItems:[{text:t=>t.options.labels.editCell,action:(t,e)=>{const s=t.event.target.closest("td");return t.editCell(s)}},{text:t=>t.options.labels.editRow,action:(t,e)=>{const s=t.event.target.closest("tr");return t.editRow(s)}},{separator:!0},{text:t=>t.options.labels.removeRow,action:(t,e)=>{if(confirm(t.options.labels.reallyRemove)){const e=t.event.target.closest("tr");t.removeRow(e)}}}]};class f{constructor(t,e={}){this.dataTable=t,this.options={...p,...e}} - -init(){this.initialized||(this.dataTable.wrapper.classList.add(this.options.classes.editable),this.options.contextMenu&&(this.container=n("div",{id:this.options.classes.container}),this.wrapper=n("div",{class:this.options.classes.wrapper}),this.menu=n("ul",{class:this.options.classes.menu}),this.options.menuItems&&this.options.menuItems.length&&this.options.menuItems.forEach((t=>{const e=n("li",{class:t.separator?this.options.classes.separator:this.options.classes.item});if(!t.separator){const s=n("a",{class:this.options.classes.action,href:t.url||"#",html:typeof t.text==="function"?t.text(this):t.text});e.appendChild(s),t.action&&typeof t.action==="function"&&s.addEventListener("click",(e=>{e.preventDefault(),t.action(this,e)}))}this.menu.appendChild(e)})),this.wrapper.appendChild(this.menu),this.container.appendChild(this.wrapper),this.update()),this.data={},this.closed=!0,this.editing=!1,this.editingRow=!1,this.editingCell=!1,this.bindEvents(),setTimeout((()=>{this.initialized=!0,this.dataTable.emit("editable.init")}),10))} - -bindEvents(){this.events={context:this.context.bind(this),update:this.update.bind(this),dismiss:this.dismiss.bind(this),keydown:this.keydown.bind(this),click:this.click.bind(this)},this.dataTable.body.addEventListener(this.options.clickEvent,this.events.click),document.addEventListener("click",this.events.dismiss),document.addEventListener("keydown",this.events.keydown),this.options.contextMenu&&(this.dataTable.body.addEventListener("contextmenu",this.events.context),this.events.reset=function(t,e=300){let s;return(...i)=>{clearTimeout(s),s=setTimeout((()=>{t.apply(this,i)}),e)}}(this.events.update,50),window.addEventListener("resize",this.events.reset),window.addEventListener("scroll",this.events.reset))} - -context(t){this.event=t;const e=this.dataTable.body.contains(t.target);if(this.options.contextMenu&&!this.disabled&&e){t.preventDefault();let e=t.pageX; let s=t.pageY;e>this.limits.x&&(e-=this.rect.width),s>this.limits.y&&(s-=this.rect.height),this.wrapper.style.top=`${s}px`,this.wrapper.style.left=`${e}px`,this.openMenu(),this.update()}} - -click(t){if(this.editing&&this.data&&this.editingCell)this.saveCell();else if(!this.editing){const e=t.target.closest("td");e&&(this.editCell(e),t.preventDefault())}} - -keydown(t){this.modal?t.key==="Escape"?this.closeModal():t.key==="Enter"&&this.saveRow():this.editing&&this.data&&(t.key==="Enter"?this.editingCell?this.saveCell():this.editingRow&&this.saveRow():t.key==="Escape"&&this.saveCell(this.data.content))} - -editCell(t){this.options.excludeColumns.includes(t.cellIndex)?this.closeMenu():(t=this.dataTable.body.rows[t.parentNode.dataIndex].cells[t.cellIndex],this.data={cell:t,content:t.dataset.content||t.innerHTML,input:n("input",{type:"text",value:t.dataset.content||t.innerHTML,class:this.options.classes.input})},t.innerHTML="",t.appendChild(this.data.input),setTimeout((()=>{this.data.input.focus(),this.data.input.selectionStart=this.data.input.selectionEnd=this.data.input.value.length,this.editing=!0,this.editingCell=!0,this.closeMenu()}),10))} - -saveCell(t,e){e=e||this.data.cell,t=t||this.data.input.value;const s=this.data.content;this.dataTable.data[e.parentNode.dataIndex].cells[e.cellIndex].innerHTML=e.innerHTML=t.trim(),this.data={},this.editing=this.editingCell=!1,this.dataTable.emit("editable.save.cell",t,s,e)} - -editRow(t){if(!(t=t||this.event.target.closest("tr"))||t.nodeName!=="TR"||this.editing)return;t=this.dataTable.body.rows[t.dataIndex];const e=[`
`,`
`,"

Editing row

",``,"
",`
`,`
`,`
`,``,"
","
","
","
"].join(""); const s=n("div",{class:this.options.classes.modal,html:e}); const i=s.firstElementChild.lastElementChild.firstElementChild;Array.from(t.cells).forEach(((t,e)=>{(!t.hidden||t.hidden&&this.options.hiddenColumns)&&!this.options.excludeColumns.includes(e)&&i.insertBefore(n("div",{class:this.options.classes.row,html:[`
`,``,``,"
"].join("")}),i.lastElementChild)})),this.modal=s,this.openModal();const a=Array.from(i.elements);a.pop(),this.data={row:t,inputs:a},this.editing=!0,this.editingRow=!0,s.addEventListener("click",(t=>{t.target.hasAttribute("data-editor-close")?this.closeModal():t.target.hasAttribute("data-editor-save")&&this.saveRow()})),this.closeMenu()} - -saveRow(t,e){t=t||this.data.inputs.map((t=>t.value.trim())),e=e||this.data.row;const s=Array.from(e.cells).map((t=>t.dataset.content||t.innerHTML));Array.from(e.cells).forEach(((e,s)=>{e.innerHTML=t[s]})),this.closeModal(),this.dataTable.emit("editable.save.row",t,s,e)} - -openModal(){!this.editing&&this.modal&&document.body.appendChild(this.modal)} - -closeModal(){this.editing&&this.modal&&(document.body.removeChild(this.modal),this.modal=this.editing=this.editingRow=!1)} - -removeRow(t){t?(t instanceof Element&&t.nodeName==="TR"&&void 0!==t.dataIndex&&(t=t.dataIndex),this.dataTable.rows.remove(t),this.closeMenu()):(t=this.event.target.closest("tr"))&&void 0!==t.dataIndex&&(this.dataTable.rows.remove(t.dataIndex),this.closeMenu())} - -update(){const t=window.scrollX||window.pageXOffset; const e=window.scrollY||window.pageYOffset;this.rect=this.wrapper.getBoundingClientRect(),this.limits={x:window.innerWidth+t-this.rect.width,y:window.innerHeight+e-this.rect.height}} - -dismiss(t){let e=!0;this.options.contextMenu&&(e=!this.wrapper.contains(t.target),this.editing&&(e=!this.wrapper.contains(t.target)&&t.target!==this.data.input)),e&&(this.editingCell&&this.saveCell(this.data.content),this.closeMenu())} - -openMenu(){this.editing&&this.data&&this.editingCell&&this.saveCell(),this.options.contextMenu&&(document.body.appendChild(this.container),this.closed=!1,this.dataTable.emit("editable.context.open"))} - -closeMenu(){this.options.contextMenu&&!this.closed&&(this.closed=!0,document.body.removeChild(this.container),this.dataTable.emit("editable.context.close"))} - -destroy(){this.dataTable.body.removeEventListener(this.options.clickEvent,this.events.click),this.dataTable.body.removeEventListener("contextmenu",this.events.context),document.removeEventListener("click",this.events.dismiss),document.removeEventListener("keydown",this.events.keydown),window.removeEventListener("resize",this.events.reset),window.removeEventListener("scroll",this.events.reset),document.body.contains(this.container)&&document.body.removeChild(this.container),this.initialized=!1}}s.DataTable=class{constructor(t,e={}){const s=typeof t==="string"?document.querySelector(t):t;if(this.options={...u,...e,layout:{...u.layout,...e.layout},labels:{...u.labels,...e.labels}},this.rows=new h(this),this.columns=new d(this),this.initialized=!1,this.initialLayout=s.innerHTML,this.initialSortable=this.options.sortable,this.options.tabIndex?s.tabIndex=this.options.tabIndex:this.options.rowNavigation&&s.tabIndex===-1&&(s.tabIndex=0),this.options.header||(this.options.sortable=!1),s.tHead===null&&(!this.options.data||this.options.data&&!this.options.data.headings)&&(this.options.sortable=!1),s.tBodies.length&&!s.tBodies[0].rows.length&&this.options.data&&!this.options.data.data)throw new Error("You seem to be using the data option, but you've not defined any rows.");this.dom=s,this.listeners={onResize:t=>this.onResize(t)},this.init()} - -init(t){if(this.initialized||this.dom.classList.contains("dataTable-table"))return!1;Object.assign(this.options,t||{}),this.currentPage=1,this.onFirstPage=!0,this.hiddenColumns=[],this.columnRenderers=[],this.selectedColumns=[],this.render(),setTimeout((()=>{this.emit("datatable.init"),this.initialized=!0}),10)} - -render(){let t="";if(this.options.data&&c.call(this),this.body=this.dom.tBodies[0],this.head=this.dom.tHead,this.foot=this.dom.tFoot,this.body||(this.body=n("tbody"),this.dom.appendChild(this.body)),this.hasRows=this.body.rows.length>0,!this.head){const t=n("thead"); const e=n("tr");this.hasRows&&(Array.from(this.body.rows[0].cells).forEach((()=>{e.appendChild(n("th"))})),t.appendChild(e)),this.head=t,this.dom.insertBefore(this.head,this.body),this.hiddenHeader=this.options.hiddenHeader}if(this.headings=[],this.hasHeadings=this.head.rows.length>0,this.hasHeadings&&(this.header=this.head.rows[0],this.headings=[].slice.call(this.header.cells)),this.options.header||this.head&&this.dom.removeChild(this.dom.tHead),this.options.footer?this.head&&!this.foot&&(this.foot=n("tfoot",{html:this.head.innerHTML}),this.dom.appendChild(this.foot)):this.foot&&this.dom.removeChild(this.dom.tFoot),this.wrapper=n("div",{class:"dataTable-wrapper dataTable-loading"}),t+="
",t+=this.options.layout.top,t+="
",this.options.scrollY.length?t+=`
`:t+="
",t+="
",t+=this.options.layout.bottom,t+="
",t=t.replace("{info}",this.options.paging?"
":""),this.options.paging&&this.options.perPageSelect){let e="
";const s=n("select",{class:"dataTable-selector"});this.options.perPageSelect.forEach((t=>{const e=t===this.options.perPage; const i=new Option(t,t,e,e);s.add(i)})),e=e.replace("{select}",s.outerHTML),t=t.replace("{select}",e)}else t=t.replace("{select}","");if(this.options.searchable){const e=``;t=t.replace("{search}",e)}else t=t.replace("{search}","");this.hasHeadings&&this.renderHeader(),this.dom.classList.add("dataTable-table");const e=n("nav",{class:"dataTable-pagination"}); const s=n("ul",{class:"dataTable-pagination-list"});e.appendChild(s),t=t.replace(/\{pager\}/g,e.outerHTML),this.wrapper.innerHTML=t,this.container=this.wrapper.querySelector(".dataTable-container"),this.pagers=this.wrapper.querySelectorAll(".dataTable-pagination-list"),this.label=this.wrapper.querySelector(".dataTable-info"),this.dom.parentNode.replaceChild(this.wrapper,this.dom),this.container.appendChild(this.dom),this.rect=this.dom.getBoundingClientRect(),this.data=Array.from(this.body.rows),this.activeRows=this.data.slice(),this.activeHeadings=this.headings.slice(),this.update(),this.setColumns(),this.fixHeight(),this.fixColumns(),this.options.header||this.wrapper.classList.add("no-header"),this.options.footer||this.wrapper.classList.add("no-footer"),this.options.sortable&&this.wrapper.classList.add("sortable"),this.options.searchable&&this.wrapper.classList.add("searchable"),this.options.fixedHeight&&this.wrapper.classList.add("fixed-height"),this.options.fixedColumns&&this.wrapper.classList.add("fixed-columns"),this.bindEvents()} - -renderPage(t=!1){if(this.hasHeadings&&(o(this.header),this.activeHeadings.forEach((t=>this.header.appendChild(t)))),this.hasRows&&this.totalPages){this.currentPage>this.totalPages&&(this.currentPage=1);const t=this.currentPage-1; const e=document.createDocumentFragment();this.pages[t].forEach((t=>e.appendChild(this.rows.render(t)))),this.clear(e),this.onFirstPage=this.currentPage===1,this.onLastPage=this.currentPage===this.lastPage}else this.setMessage(this.options.labels.noRows);let e; let s=0; let i=0; let a=0;if(this.totalPages&&(s=this.currentPage-1,i=s*this.options.perPage,a=i+this.pages[s].length,i+=1,e=this.searching?this.searchData.length:this.data.length),this.label&&this.options.labels.info.length){const t=this.options.labels.info.replace("{start}",i).replace("{end}",a).replace("{page}",this.currentPage).replace("{pages}",this.totalPages).replace("{rows}",e);this.label.innerHTML=e?t:""}if(this.currentPage==1&&this.fixHeight(),this.options.rowNavigation&&(!this.rows.cursor||!this.pages[this.currentPage-1].includes(this.rows.cursor))){const e=this.pages[this.currentPage-1];t?this.rows.setCursor(e[e.length-1]):this.rows.setCursor(e[0])}} - -renderPager(){if(o(this.pagers),this.totalPages>1){const t="pager"; const e=document.createDocumentFragment(); const s=this.onFirstPage?1:this.currentPage-1; const i=this.onLastPage?this.totalPages:this.currentPage+1;this.options.firstLast&&e.appendChild(r(t,1,this.options.firstText)),this.options.nextPrev&&!this.onFirstPage&&e.appendChild(r(t,s,this.options.prevText));let a=this.links;this.options.truncatePager&&(a=((t,e,s,i,a)=>{let o;const r=2*(i=i||2);let l=e-i; let h=e+i;const d=[]; const c=[];e<4-i+r?h=3+r:e>s-(3-i+r)&&(l=s-(2+r));for(let e=1;e<=s;e++)if(e==1||e==s||e>=l&&e<=h){const s=t[e-1];s.classList.remove("active"),d.push(s)}return d.forEach((e=>{const s=e.children[0].getAttribute("data-page");if(o){const e=o.children[0].getAttribute("data-page");if(s-e==2)c.push(t[e]);else if(s-e!=1){const t=n("li",{class:"ellipsis",html:`${a}`});c.push(t)}}c.push(e),o=e})),c})(this.links,this.currentPage,this.pages.length,this.options.pagerDelta,this.options.ellipsisText)),this.links[this.currentPage-1].classList.add("active"),a.forEach((t=>{t.classList.remove("active"),e.appendChild(t)})),this.links[this.currentPage-1].classList.add("active"),this.options.nextPrev&&!this.onLastPage&&e.appendChild(r(t,i,this.options.nextText)),this.options.firstLast&&e.appendChild(r(t,this.totalPages,this.options.lastText)),this.pagers.forEach((t=>{t.appendChild(e.cloneNode(!0))}))}} - -renderHeader(){this.labels=[],this.headings&&this.headings.length&&this.headings.forEach(((t,e)=>{if(this.labels[e]=t.textContent,t.firstElementChild&&t.firstElementChild.classList.contains("dataTable-sorter")&&(t.innerHTML=t.firstElementChild.innerHTML),t.sortable=t.getAttribute("data-sortable")!=="false",t.originalCellIndex=e,this.options.sortable&&t.sortable){const e=n("a",{href:"#",class:"dataTable-sorter",html:t.innerHTML});t.innerHTML="",t.setAttribute("data-sortable",""),t.appendChild(e)}})),this.fixColumns()} - -bindEvents(){if(this.options.perPageSelect){const t=this.wrapper.querySelector(".dataTable-selector");t&&t.addEventListener("change",(()=>{this.options.perPage=parseInt(t.value,10),this.update(),this.fixHeight(),this.emit("datatable.perpage",this.options.perPage)}),!1)}this.options.searchable&&(this.input=this.wrapper.querySelector(".dataTable-input"),this.input&&this.input.addEventListener("keyup",(()=>this.search(this.input.value)),!1)),this.wrapper.addEventListener("click",(t=>{const e=t.target.closest("a");e&&e.nodeName.toLowerCase()==="a"&&(e.hasAttribute("data-page")?(this.page(e.getAttribute("data-page")),t.preventDefault()):this.options.sortable&&e.classList.contains("dataTable-sorter")&&e.parentNode.getAttribute("data-sortable")!="false"&&(this.columns.sort(this.headings.indexOf(e.parentNode)),t.preventDefault()))}),!1),this.options.rowNavigation?(this.dom.addEventListener("keydown",(t=>{t.key==="ArrowUp"?this.rows.cursor.previousElementSibling?(this.rows.setCursor(this.rows.cursor.previousElementSibling),t.preventDefault(),t.stopPropagation()):this.onFirstPage||this.page(this.currentPage-1,!0):t.key==="ArrowDown"?this.rows.cursor.nextElementSibling?(this.rows.setCursor(this.rows.cursor.nextElementSibling),t.preventDefault(),t.stopPropagation()):this.onLastPage||this.page(this.currentPage+1):["Enter"," "].includes(t.key)&&this.emit("datatable.selectrow",this.rows.cursor,t)})),this.body.addEventListener("mousedown",(t=>{if(this.body.matches(":focus")){const e=Array.from(this.body.rows).find((e=>e.contains(t.target)));this.emit("datatable.selectrow",e,t)}}))):this.body.addEventListener("mousedown",(t=>{const e=Array.from(this.body.rows).find((e=>e.contains(t.target)));this.emit("datatable.selectrow",e,t)})),window.addEventListener("resize",this.listeners.onResize)} - -onResize(){this.rect=this.container.getBoundingClientRect(),this.rect.width&&this.fixColumns()} - -setColumns(t){t||this.data.forEach((t=>{Array.from(t.cells).forEach((t=>{t.data=t.innerHTML}))})),this.options.columns&&this.headings.length&&this.options.columns.forEach((t=>{Array.isArray(t.select)||(t.select=[t.select]),t.hasOwnProperty("render")&&typeof t.render==="function"&&(this.selectedColumns=this.selectedColumns.concat(t.select),this.columnRenderers.push({columns:t.select,renderer:t.render})),t.select.forEach((e=>{const s=this.headings[e];s&&(t.type&&s.setAttribute("data-type",t.type),t.format&&s.setAttribute("data-format",t.format),t.hasOwnProperty("sortable")&&s.setAttribute("data-sortable",t.sortable),t.hasOwnProperty("hidden")&&!1!==t.hidden&&this.columns.hide([e]),t.hasOwnProperty("sort")&&t.select.length===1&&this.columns.sort(t.select[0],t.sort,!0))}))})),this.hasRows&&(this.data.forEach(((t,e)=>{t.dataIndex=e,Array.from(t.cells).forEach((t=>{t.data=t.innerHTML}))})),this.columns.rebuild()),this.renderHeader()} - -destroy(){this.dom.innerHTML=this.initialLayout,this.dom.classList.remove("dataTable-table"),this.wrapper.parentNode.replaceChild(this.dom,this.wrapper),this.initialized=!1,window.removeEventListener("resize",this.listeners.onResize)} - -update(){this.wrapper.classList.remove("dataTable-empty"),this.paginate(),this.renderPage(),this.links=[];let t=this.pages.length;for(;t--;){const e=t+1;this.links[t]=r(t===0?"active":"",e,e)}this.sorting=!1,this.renderPager(),this.rows.update(),this.emit("datatable.update")} - -paginate(){let t=this.activeRows;return this.searching&&(t=[],this.searchData.forEach((e=>t.push(this.activeRows[e])))),this.options.paging?this.pages=t.map(((e,s)=>s%this.options.perPage==0?t.slice(s,s+this.options.perPage):null)).filter((t=>t)):this.pages=[t],this.totalPages=this.lastPage=this.pages.length,this.totalPages} - -fixColumns(){if((this.options.scrollY.length||this.options.fixedColumns)&&this.activeHeadings&&this.activeHeadings.length){let t; let e=!1;if(this.columnWidths=[],this.dom.tHead){this.options.scrollY.length&&(e=n("thead"),e.appendChild(n("tr")),e.style.height="0px",this.headerTable&&(this.dom.tHead=this.headerTable.tHead)),this.activeHeadings.forEach((t=>{t.style.width=""}));const t=this.activeHeadings.reduce(((t,e)=>t+e.offsetWidth),0);if(this.activeHeadings.forEach(((s,i)=>{const a=s.offsetWidth; const o=a/t*100;if(s.style.width=`${o}%`,this.columnWidths[i]=a,this.options.scrollY.length){const t=n("th");e.firstElementChild.appendChild(t),t.style.width=`${o}%`,t.style.paddingTop="0",t.style.paddingBottom="0",t.style.border="0"}})),this.options.scrollY.length){const t=this.dom.parentElement;if(!this.headerTable){this.headerTable=n("table",{class:"dataTable-table"});const e=n("div",{class:"dataTable-headercontainer"});e.appendChild(this.headerTable),t.parentElement.insertBefore(e,t)}const s=this.dom.tHead;this.dom.replaceChild(e,s),this.headerTable.tHead=s,this.headerTable.parentElement.style.paddingRight=`${this.headerTable.clientWidth-this.dom.clientWidth+parseInt(this.headerTable.parentElement.style.paddingRight||"0",10)}px`,t.scrollHeight>t.clientHeight&&(t.style.overflowY="scroll")}}else{t=[],e=n("thead");const s=n("tr");Array.from(this.dom.tBodies[0].rows[0].cells).forEach((()=>{const e=n("th");s.appendChild(e),t.push(e)})),e.appendChild(s),this.dom.insertBefore(e,this.body);const i=[];t.forEach(((t,e)=>{const s=t.offsetWidth; const a=s/this.rect.width*100;i.push(a),this.columnWidths[e]=s})),this.data.forEach((t=>{Array.from(t.cells).forEach(((t,e)=>{this.columns.visible(t.cellIndex)&&(t.style.width=`${i[e]}%`)}))})),this.dom.removeChild(e)}}} - -fixHeight(){this.options.fixedHeight&&(this.container.style.height=null,this.rect=this.container.getBoundingClientRect(),this.container.style.height=`${this.rect.height}px`)} - -search(t){return!!this.hasRows&&(t=t.toLowerCase(),this.currentPage=1,this.searching=!0,this.searchData=[],t.length?(this.clear(),this.data.forEach(((e,s)=>{const i=this.searchData.includes(e);t.split(" ").reduce(((t,s)=>{let i=!1; let a=null; let n=null;for(let t=0;tthis.pages.length||t<0)&&(this.renderPage(e),this.renderPager(),void this.emit("datatable.page",t)))} - -sortColumn(t,e){this.columns.sort(t,e)} - -insert(t){let e=[];if(i(t)){if(t.headings&&!this.hasHeadings&&!this.hasRows){const e=n("tr");t.headings.forEach((t=>{const s=n("th",{html:t});e.appendChild(s)})),this.head.appendChild(e),this.header=e,this.headings=[].slice.call(e.cells),this.hasHeadings=!0,this.options.sortable=this.initialSortable,this.renderHeader(),this.activeHeadings=this.headings.slice()}t.data&&Array.isArray(t.data)&&(e=t.data)}else Array.isArray(t)&&t.forEach((t=>{const s=[];Object.entries(t).forEach((([t,e])=>{const i=this.labels.indexOf(t);i>-1&&(s[i]=e)})),e.push(s)}));e.length&&(this.rows.add(e),this.hasRows=!0),this.update(),this.setColumns(),this.fixColumns()} - -refresh(){this.options.searchable&&(this.input.value="",this.searching=!1),this.currentPage=1,this.onFirstPage=!0,this.update(),this.emit("datatable.refresh")} - -clear(t){this.body&&o(this.body);let e=this.body;this.body||(e=this.dom),t&&(typeof t==="string"&&(document.createDocumentFragment().innerHTML=t),e.appendChild(t))} - -print(){const t=this.activeHeadings; const e=this.activeRows; const s=n("table"); const i=n("thead"); const a=n("tbody"); const o=n("tr");t.forEach((t=>{o.appendChild(n("th",{html:t.textContent}))})),i.appendChild(o),e.forEach((t=>{const e=n("tr");Array.from(t.cells).forEach((t=>{e.appendChild(n("td",{html:t.textContent}))})),a.appendChild(e)})),s.appendChild(i),s.appendChild(a);const r=window.open();r.document.body.appendChild(s),r.print()} - -setMessage(t){let e=1;this.hasRows?e=this.data[0].cells.length:this.activeHeadings.length&&(e=this.activeHeadings.length),this.wrapper.classList.add("dataTable-empty"),this.label&&(this.label.innerHTML=""),this.totalPages=0,this.renderPager(),this.clear(n("tr",{html:`${t}`}))} - -on(t,e){this.events=this.events||{},this.events[t]=this.events[t]||[],this.events[t].push(e)} - -off(t,e){this.events=this.events||{},t in this.events!=0&&this.events[t].splice(this.events[t].indexOf(e),1)} - -emit(t){if(this.events=this.events||{},t in this.events!=0)for(let e=0;et.trim().replace(/(^"|"$)/g,"")))),t.shift()),t.forEach(((t,i)=>{e.data[i]=[];const a=t.split(s.columnDelimiter);a.length&&a.forEach((t=>{s.removeDoubleQuotes&&(t=t.trim().replace(/(^"|"$)/g,"")),e.data[i].push(t)}))}))),e)return e}return!1},s.convertJSON=function(t={}){let e=!1;if(!i(t))return!1;const s={...t};if(s.data.length||i(s.data)){const t=a(s.data);if(t?(e={headings:[],data:[]},t.forEach(((t,s)=>{e.data[s]=[],Object.entries(t).forEach((([t,i])=>{e.headings.includes(t)||e.headings.push(t),e.data[s].push(i)}))}))):console.warn("That's not valid JSON!"),e)return e}return!1},s.createElement=n,s.exportCSV=function(t,e={}){if(!t.hasHeadings&&!t.hasRows)return!1;const s=t.activeHeadings;let a; let n; let o; let r; let l=[];if(!i(e))return!1;const h={download:!0,skipColumn:[],lineDelimiter:"\n",columnDelimiter:",",...e};if(l[0]=t.header,h.selection)if(isNaN(h.selection)){if(Array.isArray(h.selection))for(a=0;as.init())),s}},{"./date-7061ceee.js":1}]},{},[2])(2))); -// # sourceMappingURL=/sm/ecd7e55334cc99506bcb5e8cc047ef01e68c1dcb40a0d23a5d482f195c93ec51.map \ No newline at end of file diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base.html b/ereuse_devicehub/templates/ereuse_devicehub/base.html index 85063560..6c54dd49 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/base.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/base.html @@ -19,12 +19,14 @@ - + + + - + diff --git a/ereuse_devicehub/templates/ereuse_devicehub/user_profile.html b/ereuse_devicehub/templates/ereuse_devicehub/user_profile.html index fd8b8804..462fc6ec 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/user_profile.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/user_profile.html @@ -34,7 +34,10 @@
@@ -65,7 +68,34 @@
+ +
+ +
+ {% for f in sanitization_form %} + {% if f == sanitization_form.csrf_token %} + {{ f }} + {% else %} +
+ +
+ {{ f }} + {% if f.errors %} +

+ {% for error in f.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} +
+
+ {% endif %} + {% endfor %} +
+ +
+
diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index 78c3e39d..7f5ac904 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -93,6 +93,11 @@ Receiver Note + {% endif %} {% endif %} @@ -511,13 +516,13 @@ {% if lot and not lot.is_temporary %}
- +
Documents
@@ -560,6 +565,15 @@
+ {% if form_transfer.date.data %} + + {% endif %}
Transfer
{{ form_transfer.csrf_token }} @@ -656,6 +670,37 @@ {% endif %}
+ +
+
Customer Details
+
+ {{ form_customer_details.csrf_token }} + + {% for field in form_customer_details %} + {% if field != form_customer_details.csrf_token %} +
+ {% if field != form_customer_details.type %} + {{ field.label(class_="form-label") }} + {{ field }} + {{ field.description }} + {% if field.errors %} +

+ {% for error in field.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} + {% endif %} +
+ {% endif %} + {% endfor %} + +
+ Cancel + +
+
+
{% endif %} diff --git a/ereuse_devicehub/templates/inventory/erasure.html b/ereuse_devicehub/templates/inventory/erasure.html index 57fa5139..06ad6943 100644 --- a/ereuse_devicehub/templates/inventory/erasure.html +++ b/ereuse_devicehub/templates/inventory/erasure.html @@ -1,80 +1,357 @@ -{% extends "documents/layout.html" %} -{% block body %} -
-

Summary

- - - - - - - - - - - {% for erasure in erasures %} - - - - - - - {% endfor %} - -
S/N Data StorageType of erasureResultDate
- {{ erasure.device.serial_number.upper() }} - - {{ erasure.type }} - - {{ erasure.severity }} - - {{ erasure.date_str }} -
-
-
-

Details

- {% for erasure in erasures %} -
-

{{ erasure.device.__format__('t') }}

-
-
Data storage:
-
{{ erasure.device.__format__('ts') }}
+ + + +Data Sanitization Certificate + + + + + + + +
+
+
+
- + +
+
+
+

Data Sanitization Certificate

-{% endblock %} +
+ +
+
+ Entity Information +
+
+ + + + + + + + + + +
+ Name: + + {{ customer_details and customer_details.company_name or ''}} +
+ Location: + + {{ customer_details and customer_details.location or '' }} +
+
+
+ +
+
+ Responsible Sanitization Entity +
+
+ + + + + + + + + + + + + + +
+ Name: + + {{ my_data and my_data.company_name or '' }} +
+ Responsible Person + + {{ my_data and my_data.responsable_person or '' }} +
+ Location: + + {{ my_data and my_data.location or '' }} +
+
+
+ +
+
+ Summary +
+
+ + + {% if erasure_hosts %} + + + + + {% endif %} + {% if n_computers %} + + + + + {% endif %} + + + + + + + + +
+ SNs of sanitization server: + + {% for e in erasure_hosts %} + {% if e.serial_number %} + {{ e.serial_number.upper() }}{% if not loop.last %},{% endif %} + {% endif %} + {% endfor %} +
+ N° of computers: + + {{ n_computers }} +
+ N° of data storage unit(s): + + {{ erasures | length }} +
+ Sanitization result: + + {{ result }} +
+
+
+ +
+
+ Report Details +
+
+ + + + + + + + + + + + + + +
+ Report UUID: + + {{ uuid_report }} +
+ Report Date: + + {{ date_report }} +
+ Software Version: + + {{ software }} +
+
+
+ +
+
+

+ I hereby declare that the data erasure process has been carried + out in accordance with the instructions received. +

+
+
+ +
+
+ + + + + + +
+ Data Responsable +
+ {{ my_data and my_data.responsable_person or '' }} +
+ Data Supervisor +
+ {{ my_data and my_data.supervisor_person or '' }} +
+
+
+ +{% if erasures %} +
+
+

Summary

+
+
+
+
+ + + + + + + + + + + + {% for erasure in erasures %} + + + + + + + + {% endfor %} + +
SN StorageSN HostMethodResultDate
+ {{ erasure.device.serial_number.upper() }} + + {{ erasure.parent.serial_number.upper() }} + + {{ erasure.get_public_name() }} + + {{ erasure.severity.get_public_name() }} + + {{ erasure.date_str }} +
+
+
+
+{% for erasure in erasures %} +
+

{{ erasure.device.serial_number.upper() }}

+
+
Data storage:
+
{{ erasure.device.__format__('ts') }}
+ + {% if erasure.parent %} +
Computer where was erase:
+
Title: {{ erasure.parent.__format__('ts') }}
+
DevicehubID: {{ erasure.parent.dhid }}
+
Hid: {{ erasure.parent.hid }}
+
Tags: {{ erasure.parent.tags }}
+ + {% if erasure.device.parent %} +
Computer where it resides:
+
Title: {{ erasure.device.parent.__format__('ts') }}
+
DevicehubID: {{ erasure.device.parent.dhid }}
+
Hid: {{ erasure.device.parent.hid }}
+
Tags: {{ erasure.device.parent.tags }}
+ {% endif %} + {% endif %} + +
Erasure:
+
{{ erasure.__format__('ts') }}
+ {% if erasure.steps %} +
Erasure steps:
+
+
    + {% for step in erasure.steps %} +
  1. {{ step.__format__('') }}
  2. + {% endfor %} +
+
+ {% endif %} +
+
+{% endfor %} +{% endif %} + + + \ No newline at end of file diff --git a/ereuse_devicehub/templates/inventory/erasure_list.html b/ereuse_devicehub/templates/inventory/erasure_list.html index 2f36de71..e2154c3e 100644 --- a/ereuse_devicehub/templates/inventory/erasure_list.html +++ b/ereuse_devicehub/templates/inventory/erasure_list.html @@ -187,9 +187,9 @@ {% endif %} {{ ac.device.serial_number.upper() }} {% endif %} - {% if ac.device.my_partner.lots | length > 0 %} + {% if ac.device.get_lots_for_template() | length > 0 %}
- {% for lot in ac.device.my_partner.get_lots_for_template() %} + {% for lot in ac.device.get_lots_for_template() %} {{ lot }} {% endfor %}
diff --git a/ereuse_devicehub/templates/inventory/snapshot_detail.html b/ereuse_devicehub/templates/inventory/snapshot_detail.html index 4ea87572..efba84d4 100644 --- a/ereuse_devicehub/templates/inventory/snapshot_detail.html +++ b/ereuse_devicehub/templates/inventory/snapshot_detail.html @@ -20,9 +20,46 @@

{{ snapshot_sid }} | {{ snapshot_uuid }}

+ {% if form.show() %} + + {% endif %}
-
+ {% if form.show() %} +
+
Change Snapshot Type Upload
+
+
+
+ {{ form.csrf_token }} + {% for f in form %} + {% if f != form.csrf_token %} +

+ {{ f }} +

+ {% endif %} + {% endfor %} +

+ + Save + +

+
+
+
+
+ {% endif %} +
Traceability log Details
{% for log in snapshots_log %} @@ -51,4 +88,62 @@
+ + + {% endblock main %} + diff --git a/ereuse_devicehub/views.py b/ereuse_devicehub/views.py index fd852da0..8f9a5e1d 100644 --- a/ereuse_devicehub/views.py +++ b/ereuse_devicehub/views.py @@ -9,7 +9,7 @@ from sqlalchemy import or_ from ereuse_devicehub import __version__, messages from ereuse_devicehub.db import db -from ereuse_devicehub.forms import LoginForm, PasswordForm +from ereuse_devicehub.forms import LoginForm, PasswordForm, SanitizationEntityForm from ereuse_devicehub.resources.action.models import Trade from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.user.models import User @@ -100,10 +100,15 @@ class UserProfileView(GenericMixin): def dispatch_request(self): self.get_context() + sanitization_form = SanitizationEntityForm() + if g.user.sanitization_entity: + sanitization = g.user.sanitization_entity + sanitization_form = SanitizationEntityForm(obj=sanitization) self.context.update( { 'current_user': current_user, 'password_form': PasswordForm(), + 'sanitization_form': sanitization_form, } ) @@ -127,7 +132,30 @@ class UserPasswordView(View): return flask.redirect(flask.url_for('core.user-profile')) +class SanitizationEntityView(View): + methods = ['POST'] + decorators = [login_required] + + def dispatch_request(self): + form = SanitizationEntityForm() + if form.validate_on_submit(): + form.save() + messages.success('Sanitization data updated successfully!') + else: + messages.error('Error modifying Sanitization data!') + if form.errors: + for k in form.errors.keys(): + errors = ", ".join(form.errors[k]) + txt = "{}: {}".format(k, errors) + messages.error(txt) + + return flask.redirect(flask.url_for('core.user-profile')) + + core.add_url_rule('/login/', view_func=LoginView.as_view('login')) core.add_url_rule('/logout/', view_func=LogoutView.as_view('logout')) core.add_url_rule('/profile/', view_func=UserProfileView.as_view('user-profile')) core.add_url_rule('/set_password/', view_func=UserPasswordView.as_view('set-password')) +core.add_url_rule( + '/set_sanitization/', view_func=SanitizationEntityView.as_view('set-sanitization') +) diff --git a/tests/files/basic.csv b/tests/files/basic.csv index 13efc342..3ceda076 100644 --- a/tests/files/basic.csv +++ b/tests/files/basic.csv @@ -1,2 +1,2 @@ "PHID";"DHID";"Type";"Placeholder Palet";"Placeholder Id Supplier";"Placeholder Info";"Placeholder Components";"Placeholder Type";"Placeholder Serial Number";"Placeholder Part Number";"Placeholder Model";"Placeholder Manufacturer";"DocumentID";"Public Link";"Tag 1 Type";"Tag 1 ID";"Tag 1 Organization";"Tag 2 Type";"Tag 2 ID";"Tag 2 Organization";"Tag 3 Type";"Tag 3 ID";"Tag 3 Organization";"Device Hardware ID";"Device Type";"Device Chassis";"Device Serial Number";"Device Model";"Device Manufacturer";"Registered in";"Registered (process)";"Updated in (software)";"Updated in (web)";"Physical state";"Allocate state";"Lifecycle state";"Processor";"RAM (MB)";"Data Storage Size (MB)";"Processor 1";"Processor 1 Manufacturer";"Processor 1 Model";"Processor 1 Serial Number";"Processor 1 Number of cores";"Processor 1 Speed (GHz)";"Benchmark Processor 1 (points)";"Benchmark ProcessorSysbench Processor 1 (points)";"Processor 2";"Processor 2 Manufacturer";"Processor 2 Model";"Processor 2 Serial Number";"Processor 2 Number of cores";"Processor 2 Speed (GHz)";"Benchmark Processor 2 (points)";"Benchmark ProcessorSysbench Processor 2 (points)";"RamModule 1";"RamModule 1 Manufacturer";"RamModule 1 Model";"RamModule 1 Serial Number";"RamModule 1 Size (MB)";"RamModule 1 Speed (MHz)";"RamModule 2";"RamModule 2 Manufacturer";"RamModule 2 Model";"RamModule 2 Serial Number";"RamModule 2 Size (MB)";"RamModule 2 Speed (MHz)";"RamModule 3";"RamModule 3 Manufacturer";"RamModule 3 Model";"RamModule 3 Serial Number";"RamModule 3 Size (MB)";"RamModule 3 Speed (MHz)";"RamModule 4";"RamModule 4 Manufacturer";"RamModule 4 Model";"RamModule 4 Serial Number";"RamModule 4 Size (MB)";"RamModule 4 Speed (MHz)";"DataStorage 1";"DataStorage 1 Manufacturer";"DataStorage 1 Model";"DataStorage 1 Serial Number";"DataStorage 1 Size (MB)";"Erasure DataStorage 1";"Erasure DataStorage 1 Serial Number";"Erasure DataStorage 1 Size (MB)";"Erasure DataStorage 1 Software";"Erasure DataStorage 1 Result";"Erasure DataStorage 1 Certificate URL";"Erasure DataStorage 1 Type";"Erasure DataStorage 1 Method";"Erasure DataStorage 1 Elapsed (hours)";"Erasure DataStorage 1 Date";"Erasure DataStorage 1 Steps";"Erasure DataStorage 1 Steps Start Time";"Erasure DataStorage 1 Steps End Time";"Benchmark DataStorage 1 Read Speed (MB/s)";"Benchmark DataStorage 1 Writing speed (MB/s)";"Test DataStorage 1 Software";"Test DataStorage 1 Type";"Test DataStorage 1 Result";"Test DataStorage 1 Power cycle count";"Test DataStorage 1 Lifetime (days)";"Test DataStorage 1 Power on hours";"DataStorage 2";"DataStorage 2 Manufacturer";"DataStorage 2 Model";"DataStorage 2 Serial Number";"DataStorage 2 Size (MB)";"Erasure DataStorage 2";"Erasure DataStorage 2 Serial Number";"Erasure DataStorage 2 Size (MB)";"Erasure DataStorage 2 Software";"Erasure DataStorage 2 Result";"Erasure DataStorage 2 Certificate URL";"Erasure DataStorage 2 Type";"Erasure DataStorage 2 Method";"Erasure DataStorage 2 Elapsed (hours)";"Erasure DataStorage 2 Date";"Erasure DataStorage 2 Steps";"Erasure DataStorage 2 Steps Start Time";"Erasure DataStorage 2 Steps End Time";"Benchmark DataStorage 2 Read Speed (MB/s)";"Benchmark DataStorage 2 Writing speed (MB/s)";"Test DataStorage 2 Software";"Test DataStorage 2 Type";"Test DataStorage 2 Result";"Test DataStorage 2 Power cycle count";"Test DataStorage 2 Lifetime (days)";"Test DataStorage 2 Power on hours";"DataStorage 3";"DataStorage 3 Manufacturer";"DataStorage 3 Model";"DataStorage 3 Serial Number";"DataStorage 3 Size (MB)";"Erasure DataStorage 3";"Erasure DataStorage 3 Serial Number";"Erasure DataStorage 3 Size (MB)";"Erasure DataStorage 3 Software";"Erasure DataStorage 3 Result";"Erasure DataStorage 3 Certificate URL";"Erasure DataStorage 3 Type";"Erasure DataStorage 3 Method";"Erasure DataStorage 3 Elapsed (hours)";"Erasure DataStorage 3 Date";"Erasure DataStorage 3 Steps";"Erasure DataStorage 3 Steps Start Time";"Erasure DataStorage 3 Steps End Time";"Benchmark DataStorage 3 Read Speed (MB/s)";"Benchmark DataStorage 3 Writing speed (MB/s)";"Test DataStorage 3 Software";"Test DataStorage 3 Type";"Test DataStorage 3 Result";"Test DataStorage 3 Power cycle count";"Test DataStorage 3 Lifetime (days)";"Test DataStorage 3 Power on hours";"DataStorage 4";"DataStorage 4 Manufacturer";"DataStorage 4 Model";"DataStorage 4 Serial Number";"DataStorage 4 Size (MB)";"Erasure DataStorage 4";"Erasure DataStorage 4 Serial Number";"Erasure DataStorage 4 Size (MB)";"Erasure DataStorage 4 Software";"Erasure DataStorage 4 Result";"Erasure DataStorage 4 Certificate URL";"Erasure DataStorage 4 Type";"Erasure DataStorage 4 Method";"Erasure DataStorage 4 Elapsed (hours)";"Erasure DataStorage 4 Date";"Erasure DataStorage 4 Steps";"Erasure DataStorage 4 Steps Start Time";"Erasure DataStorage 4 Steps End Time";"Benchmark DataStorage 4 Read Speed (MB/s)";"Benchmark DataStorage 4 Writing speed (MB/s)";"Test DataStorage 4 Software";"Test DataStorage 4 Type";"Test DataStorage 4 Result";"Test DataStorage 4 Power cycle count";"Test DataStorage 4 Lifetime (days)";"Test DataStorage 4 Power on hours";"Motherboard 1";"Motherboard 1 Manufacturer";"Motherboard 1 Model";"Motherboard 1 Serial Number";"Display 1";"Display 1 Manufacturer";"Display 1 Model";"Display 1 Serial Number";"GraphicCard 1";"GraphicCard 1 Manufacturer";"GraphicCard 1 Model";"GraphicCard 1 Serial Number";"GraphicCard 1 Memory (MB)";"GraphicCard 2";"GraphicCard 2 Manufacturer";"GraphicCard 2 Model";"GraphicCard 2 Serial Number";"GraphicCard 2 Memory (MB)";"NetworkAdapter 1";"NetworkAdapter 1 Manufacturer";"NetworkAdapter 1 Model";"NetworkAdapter 1 Serial Number";"NetworkAdapter 2";"NetworkAdapter 2 Manufacturer";"NetworkAdapter 2 Model";"NetworkAdapter 2 Serial Number";"SoundCard 1";"SoundCard 1 Manufacturer";"SoundCard 1 Model";"SoundCard 1 Serial Number";"SoundCard 2";"SoundCard 2 Manufacturer";"SoundCard 2 Model";"SoundCard 2 Serial Number";"Device Rate";"Device Range";"Processor Rate";"Processor Range";"RAM Rate";"RAM Range";"Data Storage Rate";"Data Storage Range";"Benchmark RamSysbench (points)" -"4";"E39W3";"Snapshot";"";"";"";"";"Desktop";"d1s";"";"d1ml";"d1mr";"";"http://localhost/devices/E39W3";"";"";"";"";"";"";"";"";"";"desktop-d1mr-d1ml-d1s";"Desktop";"Microtower";"d1s";"d1ml";"d1mr";"Wed Sep 21 15:39:24 2022";"Workbench 11.0";"2022-09-21 15:39:24.321860+02:00";"";"";"";"";"p1ml";"0";"0";"Processor 7: model p1ml, S/N p1s";"p1mr";"p1ml";"p1s";"";"1.6";"2410.0";"";"";"";"";"";"";"";"";"";"RamModule 6: model rm1ml, S/N rm1s";"rm1mr";"rm1ml";"rm1s";"";"1333";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"GraphicCard 5: model gc1ml, S/N gc1s";"gc1mr";"gc1ml";"gc1s";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"" +"4";"E39W3";"Snapshot";"";"";"";"";"Desktop";"d1s";"";"d1ml";"d1mr";"";"http://localhost/devices/E39W3";"";"";"";"";"";"";"";"";"";"0de0de8ed27a9a67e937a12a65799f6c5c69731c9bcd282054cd21a2faf980db";"Desktop";"Microtower";"d1s";"d1ml";"d1mr";"Wed Sep 21 15:39:24 2022";"Workbench 11.0";"2022-09-21 15:39:24.321860+02:00";"";"";"";"";"p1ml";"0";"0";"Processor 7: model p1ml, S/N p1s";"p1mr";"p1ml";"p1s";"";"1.6";"2410.0";"";"";"";"";"";"";"";"";"";"RamModule 6: model rm1ml, S/N rm1s";"rm1mr";"rm1ml";"rm1s";"";"1333";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"GraphicCard 5: model gc1ml, S/N gc1s";"gc1mr";"gc1ml";"gc1s";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"" diff --git a/tests/files/export_devices.csv b/tests/files/export_devices.csv index 2e04a391..5754ab48 100644 --- a/tests/files/export_devices.csv +++ b/tests/files/export_devices.csv @@ -1,2 +1,2 @@ "PHID";"DHID";"Type";"Placeholder Palet";"Placeholder Id Supplier";"Placeholder Info";"Placeholder Components";"Placeholder Type";"Placeholder Serial Number";"Placeholder Part Number";"Placeholder Model";"Placeholder Manufacturer";"DocumentID";"Public Link";"Tag 1 Type";"Tag 1 ID";"Tag 1 Organization";"Tag 2 Type";"Tag 2 ID";"Tag 2 Organization";"Tag 3 Type";"Tag 3 ID";"Tag 3 Organization";"Device Hardware ID";"Device Type";"Device Chassis";"Device Serial Number";"Device Model";"Device Manufacturer";"Registered in";"Registered (process)";"Updated in (software)";"Updated in (web)";"Physical state";"Allocate state";"Lifecycle state";"Processor";"RAM (MB)";"Data Storage Size (MB)";"Processor 1";"Processor 1 Manufacturer";"Processor 1 Model";"Processor 1 Serial Number";"Processor 1 Number of cores";"Processor 1 Speed (GHz)";"Benchmark Processor 1 (points)";"Benchmark ProcessorSysbench Processor 1 (points)";"Processor 2";"Processor 2 Manufacturer";"Processor 2 Model";"Processor 2 Serial Number";"Processor 2 Number of cores";"Processor 2 Speed (GHz)";"Benchmark Processor 2 (points)";"Benchmark ProcessorSysbench Processor 2 (points)";"RamModule 1";"RamModule 1 Manufacturer";"RamModule 1 Model";"RamModule 1 Serial Number";"RamModule 1 Size (MB)";"RamModule 1 Speed (MHz)";"RamModule 2";"RamModule 2 Manufacturer";"RamModule 2 Model";"RamModule 2 Serial Number";"RamModule 2 Size (MB)";"RamModule 2 Speed (MHz)";"RamModule 3";"RamModule 3 Manufacturer";"RamModule 3 Model";"RamModule 3 Serial Number";"RamModule 3 Size (MB)";"RamModule 3 Speed (MHz)";"RamModule 4";"RamModule 4 Manufacturer";"RamModule 4 Model";"RamModule 4 Serial Number";"RamModule 4 Size (MB)";"RamModule 4 Speed (MHz)";"DataStorage 1";"DataStorage 1 Manufacturer";"DataStorage 1 Model";"DataStorage 1 Serial Number";"DataStorage 1 Size (MB)";"Erasure DataStorage 1";"Erasure DataStorage 1 Serial Number";"Erasure DataStorage 1 Size (MB)";"Erasure DataStorage 1 Software";"Erasure DataStorage 1 Result";"Erasure DataStorage 1 Certificate URL";"Erasure DataStorage 1 Type";"Erasure DataStorage 1 Method";"Erasure DataStorage 1 Elapsed (hours)";"Erasure DataStorage 1 Date";"Erasure DataStorage 1 Steps";"Erasure DataStorage 1 Steps Start Time";"Erasure DataStorage 1 Steps End Time";"Benchmark DataStorage 1 Read Speed (MB/s)";"Benchmark DataStorage 1 Writing speed (MB/s)";"Test DataStorage 1 Software";"Test DataStorage 1 Type";"Test DataStorage 1 Result";"Test DataStorage 1 Power cycle count";"Test DataStorage 1 Lifetime (days)";"Test DataStorage 1 Power on hours";"DataStorage 2";"DataStorage 2 Manufacturer";"DataStorage 2 Model";"DataStorage 2 Serial Number";"DataStorage 2 Size (MB)";"Erasure DataStorage 2";"Erasure DataStorage 2 Serial Number";"Erasure DataStorage 2 Size (MB)";"Erasure DataStorage 2 Software";"Erasure DataStorage 2 Result";"Erasure DataStorage 2 Certificate URL";"Erasure DataStorage 2 Type";"Erasure DataStorage 2 Method";"Erasure DataStorage 2 Elapsed (hours)";"Erasure DataStorage 2 Date";"Erasure DataStorage 2 Steps";"Erasure DataStorage 2 Steps Start Time";"Erasure DataStorage 2 Steps End Time";"Benchmark DataStorage 2 Read Speed (MB/s)";"Benchmark DataStorage 2 Writing speed (MB/s)";"Test DataStorage 2 Software";"Test DataStorage 2 Type";"Test DataStorage 2 Result";"Test DataStorage 2 Power cycle count";"Test DataStorage 2 Lifetime (days)";"Test DataStorage 2 Power on hours";"DataStorage 3";"DataStorage 3 Manufacturer";"DataStorage 3 Model";"DataStorage 3 Serial Number";"DataStorage 3 Size (MB)";"Erasure DataStorage 3";"Erasure DataStorage 3 Serial Number";"Erasure DataStorage 3 Size (MB)";"Erasure DataStorage 3 Software";"Erasure DataStorage 3 Result";"Erasure DataStorage 3 Certificate URL";"Erasure DataStorage 3 Type";"Erasure DataStorage 3 Method";"Erasure DataStorage 3 Elapsed (hours)";"Erasure DataStorage 3 Date";"Erasure DataStorage 3 Steps";"Erasure DataStorage 3 Steps Start Time";"Erasure DataStorage 3 Steps End Time";"Benchmark DataStorage 3 Read Speed (MB/s)";"Benchmark DataStorage 3 Writing speed (MB/s)";"Test DataStorage 3 Software";"Test DataStorage 3 Type";"Test DataStorage 3 Result";"Test DataStorage 3 Power cycle count";"Test DataStorage 3 Lifetime (days)";"Test DataStorage 3 Power on hours";"DataStorage 4";"DataStorage 4 Manufacturer";"DataStorage 4 Model";"DataStorage 4 Serial Number";"DataStorage 4 Size (MB)";"Erasure DataStorage 4";"Erasure DataStorage 4 Serial Number";"Erasure DataStorage 4 Size (MB)";"Erasure DataStorage 4 Software";"Erasure DataStorage 4 Result";"Erasure DataStorage 4 Certificate URL";"Erasure DataStorage 4 Type";"Erasure DataStorage 4 Method";"Erasure DataStorage 4 Elapsed (hours)";"Erasure DataStorage 4 Date";"Erasure DataStorage 4 Steps";"Erasure DataStorage 4 Steps Start Time";"Erasure DataStorage 4 Steps End Time";"Benchmark DataStorage 4 Read Speed (MB/s)";"Benchmark DataStorage 4 Writing speed (MB/s)";"Test DataStorage 4 Software";"Test DataStorage 4 Type";"Test DataStorage 4 Result";"Test DataStorage 4 Power cycle count";"Test DataStorage 4 Lifetime (days)";"Test DataStorage 4 Power on hours";"Motherboard 1";"Motherboard 1 Manufacturer";"Motherboard 1 Model";"Motherboard 1 Serial Number";"Display 1";"Display 1 Manufacturer";"Display 1 Model";"Display 1 Serial Number";"GraphicCard 1";"GraphicCard 1 Manufacturer";"GraphicCard 1 Model";"GraphicCard 1 Serial Number";"GraphicCard 1 Memory (MB)";"GraphicCard 2";"GraphicCard 2 Manufacturer";"GraphicCard 2 Model";"GraphicCard 2 Serial Number";"GraphicCard 2 Memory (MB)";"NetworkAdapter 1";"NetworkAdapter 1 Manufacturer";"NetworkAdapter 1 Model";"NetworkAdapter 1 Serial Number";"NetworkAdapter 2";"NetworkAdapter 2 Manufacturer";"NetworkAdapter 2 Model";"NetworkAdapter 2 Serial Number";"SoundCard 1";"SoundCard 1 Manufacturer";"SoundCard 1 Model";"SoundCard 1 Serial Number";"SoundCard 2";"SoundCard 2 Manufacturer";"SoundCard 2 Model";"SoundCard 2 Serial Number";"Device Rate";"Device Range";"Processor Rate";"Processor Range";"RAM Rate";"RAM Range";"Data Storage Rate";"Data Storage Range";"Benchmark RamSysbench (points)" -"10";"E39W3";"Snapshot";"";"";"";"";"Laptop";"b8oaas048285";"";"1001pxd";"asustek computer inc.";"";"http://localhost/devices/E39W3";"";"";"";"";"";"";"";"";"";"laptop-asustek_computer_inc-1001pxd-b8oaas048285-14:da:e9:42:f6:7b";"Laptop";"Netbook";"b8oaas048285";"1001pxd";"asustek computer inc.";"Wed Sep 21 13:10:21 2022";"Workbench 11.0a2";"2022-09-21 13:10:21.174981+02:00";"";"";"";"";"intel atom cpu n455 @ 2.66ghz";"1024";"238475";"Processor 7: model intel atom cpu n455 @ 2.66ghz, S/N None";"intel corp.";"intel atom cpu n455 @ 2.66ghz";"";"1";"2.667";"6666.24";"164.0803";"";"";"";"";"";"";"";"";"RamModule 11: model None, S/N None";"";"";"";"1024";"667";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"HardDrive 12: model hts54322, S/N e2024242cv86mm";"hitachi";"hts54322";"e2024242cv86mm";"238475";"harddrive-hitachi-hts54322-e2024242cv86mm";"e2024242cv86mm";"238475";"Workbench 11.0a2";"Success";"";"EraseBasic";"Shred";"1:16:49";"2022-09-21 13:10:21.126373+02:00";"✓ – StepRandom 1:16:49";"2018-07-03 11:15:22.257059+02:00";"2018-07-03 12:32:11.843190+02:00";"66.2";"21.8";"Workbench 11.0a2";"Short";"Failure";"";"";"0";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"Motherboard 13: model 1001pxd, S/N eee0123456720";"asustek computer inc.";"1001pxd";"eee0123456720";"";"";"";"";"GraphicCard 8: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None";"intel corporation";"atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller";"";"256";"";"";"";"";"";"NetworkAdapter 5: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c9";"qualcomm atheros";"ar9285 wireless network adapter";"74:2f:68:8b:fd:c9";"NetworkAdapter 6: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7b";"qualcomm atheros";"ar8152 v2.0 fast ethernet";"14:da:e9:42:f6:7b";"SoundCard 9: model nm10/ich7 family high definition audio controller, S/N None";"intel corporation";"nm10/ich7 family high definition audio controller";"";"SoundCard 10: model usb 2.0 uvc vga webcam, S/N 0x0001";"azurewave";"usb 2.0 uvc vga webcam";"0x0001";"";"";"";"";"";"";"";"";"15.7188" +"10";"E39W3";"Snapshot";"";"";"";"";"Laptop";"b8oaas048285";"";"1001pxd";"asustek computer inc.";"";"http://localhost/devices/E39W3";"";"";"";"";"";"";"";"";"";"83cb9066430a8ea7def04af61d521d6517193a486c02ea3bc914c9eaeb2b718b";"Laptop";"Netbook";"b8oaas048285";"1001pxd";"asustek computer inc.";"Wed Dec 14 12:28:44 2022";"Workbench 11.0a2";"2022-12-14 12:28:44.757147+01:00";"";"";"";"";"intel atom cpu n455 @ 2.66ghz";"1024";"238475";"Processor 7: model intel atom cpu n455 @ 2.66ghz, S/N None";"intel corp.";"intel atom cpu n455 @ 2.66ghz";"";"1";"2.667";"6666.24";"164.0803";"";"";"";"";"";"";"";"";"RamModule 11: model None, S/N None";"";"";"";"1024";"667";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"HardDrive 12: model hts54322, S/N e2024242cv86mm";"hitachi";"hts54322";"e2024242cv86mm";"238475";"8558ea99955f34c788cb72174c0ec165e0398306efbc0efe40b280b65d16d0d0";"e2024242cv86mm";"238475";"Workbench 11.0a2";"Success";"";"EraseBasic";"Shred";"1:16:49";"2022-12-14 12:28:44.712329+01:00";"✓ – StepRandom 1:16:49";"2018-07-03 11:15:22.257059+02:00";"2018-07-03 12:32:11.843190+02:00";"66.2";"21.8";"Workbench 11.0a2";"Short";"Failure";"";"";"0";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"Motherboard 13: model 1001pxd, S/N eee0123456720";"asustek computer inc.";"1001pxd";"eee0123456720";"";"";"";"";"GraphicCard 8: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None";"intel corporation";"atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller";"";"256";"";"";"";"";"";"NetworkAdapter 5: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c9";"qualcomm atheros";"ar9285 wireless network adapter";"74:2f:68:8b:fd:c9";"NetworkAdapter 6: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7b";"qualcomm atheros";"ar8152 v2.0 fast ethernet";"14:da:e9:42:f6:7b";"SoundCard 9: model nm10/ich7 family high definition audio controller, S/N None";"intel corporation";"nm10/ich7 family high definition audio controller";"";"SoundCard 10: model usb 2.0 uvc vga webcam, S/N 0x0001";"azurewave";"usb 2.0 uvc vga webcam";"0x0001";"";"";"";"";"";"";"";"";"15.7188" diff --git a/tests/files/proposal_extended_csv_report.csv b/tests/files/proposal_extended_csv_report.csv index 64ed1795..73608689 100644 --- a/tests/files/proposal_extended_csv_report.csv +++ b/tests/files/proposal_extended_csv_report.csv @@ -1,3 +1,3 @@ "PHID";"DHID";"Type";"Placeholder Palet";"Placeholder Id Supplier";"Placeholder Info";"Placeholder Components";"Placeholder Type";"Placeholder Serial Number";"Placeholder Part Number";"Placeholder Model";"Placeholder Manufacturer";"DocumentID";"Public Link";"Tag 1 Type";"Tag 1 ID";"Tag 1 Organization";"Tag 2 Type";"Tag 2 ID";"Tag 2 Organization";"Tag 3 Type";"Tag 3 ID";"Tag 3 Organization";"Device Hardware ID";"Device Type";"Device Chassis";"Device Serial Number";"Device Model";"Device Manufacturer";"Registered in";"Registered (process)";"Updated in (software)";"Updated in (web)";"Physical state";"Allocate state";"Lifecycle state";"Processor";"RAM (MB)";"Data Storage Size (MB)";"Processor 1";"Processor 1 Manufacturer";"Processor 1 Model";"Processor 1 Serial Number";"Processor 1 Number of cores";"Processor 1 Speed (GHz)";"Benchmark Processor 1 (points)";"Benchmark ProcessorSysbench Processor 1 (points)";"Processor 2";"Processor 2 Manufacturer";"Processor 2 Model";"Processor 2 Serial Number";"Processor 2 Number of cores";"Processor 2 Speed (GHz)";"Benchmark Processor 2 (points)";"Benchmark ProcessorSysbench Processor 2 (points)";"RamModule 1";"RamModule 1 Manufacturer";"RamModule 1 Model";"RamModule 1 Serial Number";"RamModule 1 Size (MB)";"RamModule 1 Speed (MHz)";"RamModule 2";"RamModule 2 Manufacturer";"RamModule 2 Model";"RamModule 2 Serial Number";"RamModule 2 Size (MB)";"RamModule 2 Speed (MHz)";"RamModule 3";"RamModule 3 Manufacturer";"RamModule 3 Model";"RamModule 3 Serial Number";"RamModule 3 Size (MB)";"RamModule 3 Speed (MHz)";"RamModule 4";"RamModule 4 Manufacturer";"RamModule 4 Model";"RamModule 4 Serial Number";"RamModule 4 Size (MB)";"RamModule 4 Speed (MHz)";"DataStorage 1";"DataStorage 1 Manufacturer";"DataStorage 1 Model";"DataStorage 1 Serial Number";"DataStorage 1 Size (MB)";"Erasure DataStorage 1";"Erasure DataStorage 1 Serial Number";"Erasure DataStorage 1 Size (MB)";"Erasure DataStorage 1 Software";"Erasure DataStorage 1 Result";"Erasure DataStorage 1 Certificate URL";"Erasure DataStorage 1 Type";"Erasure DataStorage 1 Method";"Erasure DataStorage 1 Elapsed (hours)";"Erasure DataStorage 1 Date";"Erasure DataStorage 1 Steps";"Erasure DataStorage 1 Steps Start Time";"Erasure DataStorage 1 Steps End Time";"Benchmark DataStorage 1 Read Speed (MB/s)";"Benchmark DataStorage 1 Writing speed (MB/s)";"Test DataStorage 1 Software";"Test DataStorage 1 Type";"Test DataStorage 1 Result";"Test DataStorage 1 Power cycle count";"Test DataStorage 1 Lifetime (days)";"Test DataStorage 1 Power on hours";"DataStorage 2";"DataStorage 2 Manufacturer";"DataStorage 2 Model";"DataStorage 2 Serial Number";"DataStorage 2 Size (MB)";"Erasure DataStorage 2";"Erasure DataStorage 2 Serial Number";"Erasure DataStorage 2 Size (MB)";"Erasure DataStorage 2 Software";"Erasure DataStorage 2 Result";"Erasure DataStorage 2 Certificate URL";"Erasure DataStorage 2 Type";"Erasure DataStorage 2 Method";"Erasure DataStorage 2 Elapsed (hours)";"Erasure DataStorage 2 Date";"Erasure DataStorage 2 Steps";"Erasure DataStorage 2 Steps Start Time";"Erasure DataStorage 2 Steps End Time";"Benchmark DataStorage 2 Read Speed (MB/s)";"Benchmark DataStorage 2 Writing speed (MB/s)";"Test DataStorage 2 Software";"Test DataStorage 2 Type";"Test DataStorage 2 Result";"Test DataStorage 2 Power cycle count";"Test DataStorage 2 Lifetime (days)";"Test DataStorage 2 Power on hours";"DataStorage 3";"DataStorage 3 Manufacturer";"DataStorage 3 Model";"DataStorage 3 Serial Number";"DataStorage 3 Size (MB)";"Erasure DataStorage 3";"Erasure DataStorage 3 Serial Number";"Erasure DataStorage 3 Size (MB)";"Erasure DataStorage 3 Software";"Erasure DataStorage 3 Result";"Erasure DataStorage 3 Certificate URL";"Erasure DataStorage 3 Type";"Erasure DataStorage 3 Method";"Erasure DataStorage 3 Elapsed (hours)";"Erasure DataStorage 3 Date";"Erasure DataStorage 3 Steps";"Erasure DataStorage 3 Steps Start Time";"Erasure DataStorage 3 Steps End Time";"Benchmark DataStorage 3 Read Speed (MB/s)";"Benchmark DataStorage 3 Writing speed (MB/s)";"Test DataStorage 3 Software";"Test DataStorage 3 Type";"Test DataStorage 3 Result";"Test DataStorage 3 Power cycle count";"Test DataStorage 3 Lifetime (days)";"Test DataStorage 3 Power on hours";"DataStorage 4";"DataStorage 4 Manufacturer";"DataStorage 4 Model";"DataStorage 4 Serial Number";"DataStorage 4 Size (MB)";"Erasure DataStorage 4";"Erasure DataStorage 4 Serial Number";"Erasure DataStorage 4 Size (MB)";"Erasure DataStorage 4 Software";"Erasure DataStorage 4 Result";"Erasure DataStorage 4 Certificate URL";"Erasure DataStorage 4 Type";"Erasure DataStorage 4 Method";"Erasure DataStorage 4 Elapsed (hours)";"Erasure DataStorage 4 Date";"Erasure DataStorage 4 Steps";"Erasure DataStorage 4 Steps Start Time";"Erasure DataStorage 4 Steps End Time";"Benchmark DataStorage 4 Read Speed (MB/s)";"Benchmark DataStorage 4 Writing speed (MB/s)";"Test DataStorage 4 Software";"Test DataStorage 4 Type";"Test DataStorage 4 Result";"Test DataStorage 4 Power cycle count";"Test DataStorage 4 Lifetime (days)";"Test DataStorage 4 Power on hours";"Motherboard 1";"Motherboard 1 Manufacturer";"Motherboard 1 Model";"Motherboard 1 Serial Number";"Display 1";"Display 1 Manufacturer";"Display 1 Model";"Display 1 Serial Number";"GraphicCard 1";"GraphicCard 1 Manufacturer";"GraphicCard 1 Model";"GraphicCard 1 Serial Number";"GraphicCard 1 Memory (MB)";"GraphicCard 2";"GraphicCard 2 Manufacturer";"GraphicCard 2 Model";"GraphicCard 2 Serial Number";"GraphicCard 2 Memory (MB)";"NetworkAdapter 1";"NetworkAdapter 1 Manufacturer";"NetworkAdapter 1 Model";"NetworkAdapter 1 Serial Number";"NetworkAdapter 2";"NetworkAdapter 2 Manufacturer";"NetworkAdapter 2 Model";"NetworkAdapter 2 Serial Number";"SoundCard 1";"SoundCard 1 Manufacturer";"SoundCard 1 Model";"SoundCard 1 Serial Number";"SoundCard 2";"SoundCard 2 Manufacturer";"SoundCard 2 Model";"SoundCard 2 Serial Number";"Device Rate";"Device Range";"Processor Rate";"Processor Range";"RAM Rate";"RAM Range";"Data Storage Rate";"Data Storage Range";"Benchmark RamSysbench (points)" -"10";"E39W3";"Snapshot";"";"";"";"";"Laptop";"b8oaas048285";"";"1001pxd";"asustek computer inc.";"";"http://localhost/devices/E39W3";"";"";"";"";"";"";"";"";"";"laptop-asustek_computer_inc-1001pxd-b8oaas048285-14:da:e9:42:f6:7b";"Laptop";"Netbook";"b8oaas048285";"1001pxd";"asustek computer inc.";"Wed Sep 21 15:41:31 2022";"Workbench 11.0a2";"2022-09-21 15:41:31.084078+02:00";"";"";"";"";"intel atom cpu n455 @ 2.66ghz";"1024";"238475";"Processor 7: model intel atom cpu n455 @ 2.66ghz, S/N None";"intel corp.";"intel atom cpu n455 @ 2.66ghz";"";"1";"2.667";"6666.24";"164.0803";"";"";"";"";"";"";"";"";"RamModule 11: model None, S/N None";"";"";"";"1024";"667";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"HardDrive 12: model hts54322, S/N e2024242cv86mm";"hitachi";"hts54322";"e2024242cv86mm";"238475";"harddrive-hitachi-hts54322-e2024242cv86mm";"e2024242cv86mm";"238475";"Workbench 11.0a2";"Success";"";"EraseBasic";"Shred";"1:16:49";"2022-09-21 15:41:31.030798+02:00";"✓ – StepRandom 1:16:49";"2018-07-03 11:15:22.257059+02:00";"2018-07-03 12:32:11.843190+02:00";"66.2";"21.8";"Workbench 11.0a2";"Short";"Failure";"";"";"0";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"Motherboard 13: model 1001pxd, S/N eee0123456720";"asustek computer inc.";"1001pxd";"eee0123456720";"";"";"";"";"GraphicCard 8: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None";"intel corporation";"atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller";"";"256";"";"";"";"";"";"NetworkAdapter 5: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c9";"qualcomm atheros";"ar9285 wireless network adapter";"74:2f:68:8b:fd:c9";"NetworkAdapter 6: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7b";"qualcomm atheros";"ar8152 v2.0 fast ethernet";"14:da:e9:42:f6:7b";"SoundCard 9: model nm10/ich7 family high definition audio controller, S/N None";"intel corporation";"nm10/ich7 family high definition audio controller";"";"SoundCard 10: model usb 2.0 uvc vga webcam, S/N 0x0001";"azurewave";"usb 2.0 uvc vga webcam";"0x0001";"";"";"";"";"";"";"";"";"15.7188" -"24";"45VG4";"Snapshot";"";"";"";"";"Laptop";"b8oaas048287";"";"1001pxd";"asustek computer inc.";"";"http://localhost/devices/45VG4";"";"";"";"";"";"";"";"";"";"laptop-asustek_computer_inc-1001pxd-b8oaas048287-14:da:e9:42:f6:7c";"Laptop";"Netbook";"b8oaas048287";"1001pxd";"asustek computer inc.";"Wed Sep 21 15:41:31 2022";"Workbench 11.0b11";"2022-09-21 15:41:31.398843+02:00";"";"";"";"";"intel atom cpu n455 @ 1.66ghz";"2048";"558558";"Processor 28: model intel atom cpu n455 @ 1.66ghz, S/N None";"intel corp.";"intel atom cpu n455 @ 1.66ghz";"";"1";"1.667";"6666.24";"164.0803";"";"";"";"";"";"";"";"";"RamModule 32: model None, S/N None";"";"";"";"1024";"667";"RamModule 33: model 48594d503131325336344350362d53362020, S/N 4f43487b";"hynix semiconductor";"48594d503131325336344350362d53362020";"4f43487b";"1024";"667";"";"";"";"";"";"";"";"";"";"";"";"";"HardDrive 34: model hts54322, S/N e2024242cv86hj";"hitachi";"hts54322";"e2024242cv86hj";"238475";"harddrive-hitachi-hts54322-e2024242cv86hj";"e2024242cv86hj";"238475";"Workbench 11.0b11";"Success";"";"EraseBasic";"Shred";"1:16:49";"2022-09-21 15:41:31.340555+02:00";"✓ – StepRandom 1:16:49";"2018-07-03 11:15:22.257059+02:00";"2018-07-03 12:32:11.843190+02:00";"66.2";"21.8";"Workbench 11.0b11";"Extended";"Failure";"";"";"0";"DataStorage 35: model wdc wd1600bevt-2, S/N wd-wx11a80w7430";"western digital";"wdc wd1600bevt-2";"wd-wx11a80w7430";"160041";"datastorage-western_digital-wdc_wd1600bevt-2-wd-wx11a80w7430";"wd-wx11a80w7430";"160041";"Workbench 11.0b11";"Failure";"";"EraseBasic";"Shred";"0:45:36";"2022-09-21 15:41:31.342722+02:00";"✓ – StepRandom 0:45:36";"2019-10-23 09:49:54.410830+02:00";"2019-10-23 10:35:31.400587+02:00";"41.6";"17.3";"Workbench 11.0b11";"Short";"Success";"5293";"195 days, 12:00:00";"4692";"SolidStateDrive 36: model wdc wd1600bevt-2, S/N wd-wx11a80w7430";"western digital";"wdc wd1600bevt-2";"wd-wx11a80w7430";"160042";"solidstatedrive-western_digital-wdc_wd1600bevt-2-wd-wx11a80w7430";"wd-wx11a80w7430";"160042";"Workbench 11.0b11";"Success";"";"EraseSectors";"Badblocks";"1:46:03";"2022-09-21 15:41:31.346565+02:00";"✓ – StepRandom 0:46:03,✓ – StepZero 1:00:00";"2019-08-19 18:48:19.690458+02:00,2019-08-19 19:34:22.690458+02:00";"2019-08-19 19:34:22.930562+02:00,2019-08-19 20:34:22.930562+02:00";"41.1";"17.1";"Workbench 11.0b11";"Short";"Success";"5231";"194 days, 17:00:00";"4673";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"Motherboard 37: model 1001pxd, S/N eee0123456789";"asustek computer inc.";"1001pxd";"eee0123456789";"";"auo ""auo""";"auo lcd monitor";"";"GraphicCard 29: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None";"intel corporation";"atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller";"";"256";"";"";"";"";"";"NetworkAdapter 26: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8";"qualcomm atheros";"ar9285 wireless network adapter";"74:2f:68:8b:fd:c8";"NetworkAdapter 27: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c";"qualcomm atheros";"ar8152 v2.0 fast ethernet";"14:da:e9:42:f6:7c";"SoundCard 30: model nm10/ich7 family high definition audio controller, S/N None";"intel corporation";"nm10/ich7 family high definition audio controller";"";"SoundCard 31: model usb 2.0 uvc vga webcam, S/N 0x0001";"azurewave";"usb 2.0 uvc vga webcam";"0x0001";"";"";"";"";"";"";"";"";"15.7188" +"10";"E39W3";"Snapshot";"";"";"";"";"Laptop";"b8oaas048285";"";"1001pxd";"asustek computer inc.";"";"http://localhost/devices/E39W3";"";"";"";"";"";"";"";"";"";"83cb9066430a8ea7def04af61d521d6517193a486c02ea3bc914c9eaeb2b718b";"Laptop";"Netbook";"b8oaas048285";"1001pxd";"asustek computer inc.";"Wed Sep 21 15:41:31 2022";"Workbench 11.0a2";"2022-09-21 15:41:31.084078+02:00";"";"";"";"";"intel atom cpu n455 @ 2.66ghz";"1024";"238475";"Processor 7: model intel atom cpu n455 @ 2.66ghz, S/N None";"intel corp.";"intel atom cpu n455 @ 2.66ghz";"";"1";"2.667";"6666.24";"164.0803";"";"";"";"";"";"";"";"";"RamModule 11: model None, S/N None";"";"";"";"1024";"667";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"HardDrive 12: model hts54322, S/N e2024242cv86mm";"hitachi";"hts54322";"e2024242cv86mm";"238475";"8558ea99955f34c788cb72174c0ec165e0398306efbc0efe40b280b65d16d0d0";"e2024242cv86mm";"238475";"Workbench 11.0a2";"Success";"";"EraseBasic";"Shred";"1:16:49";"2022-09-21 15:41:31.030798+02:00";"✓ – StepRandom 1:16:49";"2018-07-03 11:15:22.257059+02:00";"2018-07-03 12:32:11.843190+02:00";"66.2";"21.8";"Workbench 11.0a2";"Short";"Failure";"";"";"0";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"Motherboard 13: model 1001pxd, S/N eee0123456720";"asustek computer inc.";"1001pxd";"eee0123456720";"";"";"";"";"GraphicCard 8: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None";"intel corporation";"atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller";"";"256";"";"";"";"";"";"NetworkAdapter 5: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c9";"qualcomm atheros";"ar9285 wireless network adapter";"74:2f:68:8b:fd:c9";"NetworkAdapter 6: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7b";"qualcomm atheros";"ar8152 v2.0 fast ethernet";"14:da:e9:42:f6:7b";"SoundCard 9: model nm10/ich7 family high definition audio controller, S/N None";"intel corporation";"nm10/ich7 family high definition audio controller";"";"SoundCard 10: model usb 2.0 uvc vga webcam, S/N 0x0001";"azurewave";"usb 2.0 uvc vga webcam";"0x0001";"";"";"";"";"";"";"";"";"15.7188" +"24";"45VG4";"Snapshot";"";"";"";"";"Laptop";"b8oaas048287";"";"1001pxd";"asustek computer inc.";"";"http://localhost/devices/45VG4";"";"";"";"";"";"";"";"";"";"c3c6726385eb7e43a7476512236fe27fa234028c394237344d6b403611c25564";"Laptop";"Netbook";"b8oaas048287";"1001pxd";"asustek computer inc.";"Wed Sep 21 15:41:31 2022";"Workbench 11.0b11";"2022-09-21 15:41:31.398843+02:00";"";"";"";"";"intel atom cpu n455 @ 1.66ghz";"2048";"558558";"Processor 28: model intel atom cpu n455 @ 1.66ghz, S/N None";"intel corp.";"intel atom cpu n455 @ 1.66ghz";"";"1";"1.667";"6666.24";"164.0803";"";"";"";"";"";"";"";"";"RamModule 32: model None, S/N None";"";"";"";"1024";"667";"RamModule 33: model 48594d503131325336344350362d53362020, S/N 4f43487b";"hynix semiconductor";"48594d503131325336344350362d53362020";"4f43487b";"1024";"667";"";"";"";"";"";"";"";"";"";"";"";"";"HardDrive 34: model hts54322, S/N e2024242cv86hj";"hitachi";"hts54322";"e2024242cv86hj";"238475";"092462ec48ccf594fa369eb55c7026de4b56620f3430fb09a840ed3769b99851";"e2024242cv86hj";"238475";"Workbench 11.0b11";"Success";"";"EraseBasic";"Shred";"1:16:49";"2022-09-21 15:41:31.340555+02:00";"✓ – StepRandom 1:16:49";"2018-07-03 11:15:22.257059+02:00";"2018-07-03 12:32:11.843190+02:00";"66.2";"21.8";"Workbench 11.0b11";"Extended";"Failure";"";"";"0";"DataStorage 35: model wdc wd1600bevt-2, S/N wd-wx11a80w7430";"western digital";"wdc wd1600bevt-2";"wd-wx11a80w7430";"160041";"datastorage-western_digital-wdc_wd1600bevt-2-wd-wx11a80w7430";"wd-wx11a80w7430";"160041";"Workbench 11.0b11";"Failure";"";"EraseBasic";"Shred";"0:45:36";"2022-09-21 15:41:31.342722+02:00";"✓ – StepRandom 0:45:36";"2019-10-23 09:49:54.410830+02:00";"2019-10-23 10:35:31.400587+02:00";"41.6";"17.3";"Workbench 11.0b11";"Short";"Success";"5293";"195 days, 12:00:00";"4692";"SolidStateDrive 36: model wdc wd1600bevt-2, S/N wd-wx11a80w7430";"western digital";"wdc wd1600bevt-2";"wd-wx11a80w7430";"160042";"c5856fc1632d695a7eccf5062667d15439ec3c765245ba3fa60272c335d6e83f";"wd-wx11a80w7430";"160042";"Workbench 11.0b11";"Success";"";"EraseSectors";"Badblocks";"1:46:03";"2022-09-21 15:41:31.346565+02:00";"✓ – StepRandom 0:46:03,✓ – StepZero 1:00:00";"2019-08-19 18:48:19.690458+02:00,2019-08-19 19:34:22.690458+02:00";"2019-08-19 19:34:22.930562+02:00,2019-08-19 20:34:22.930562+02:00";"41.1";"17.1";"Workbench 11.0b11";"Short";"Success";"5231";"194 days, 17:00:00";"4673";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"Motherboard 37: model 1001pxd, S/N eee0123456789";"asustek computer inc.";"1001pxd";"eee0123456789";"";"auo ""auo""";"auo lcd monitor";"";"GraphicCard 29: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None";"intel corporation";"atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller";"";"256";"";"";"";"";"";"NetworkAdapter 26: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8";"qualcomm atheros";"ar9285 wireless network adapter";"74:2f:68:8b:fd:c8";"NetworkAdapter 27: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c";"qualcomm atheros";"ar8152 v2.0 fast ethernet";"14:da:e9:42:f6:7c";"SoundCard 30: model nm10/ich7 family high definition audio controller, S/N None";"intel corporation";"nm10/ich7 family high definition audio controller";"";"SoundCard 31: model usb 2.0 uvc vga webcam, S/N 0x0001";"azurewave";"usb 2.0 uvc vga webcam";"0x0001";"";"";"";"";"";"";"";"";"15.7188" diff --git a/tests/files/real-eee-1001pxd.snapshot.12.yaml b/tests/files/real-eee-1001pxd.snapshot.12.yaml index 6dc974bd..425a270c 100644 --- a/tests/files/real-eee-1001pxd.snapshot.12.yaml +++ b/tests/files/real-eee-1001pxd.snapshot.12.yaml @@ -158,5 +158,6 @@ } ] }, + "debug": {"lshw": {"configuration": {"uuid": "79c5098f-bc44-4834-8a59-9ea61d956c31"}}}, "closed": false } diff --git a/tests/test_action.py b/tests/test_action.py index 40e9cd15..01ca8ca6 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -2941,7 +2941,7 @@ def test_delete_devices_check_sync(user: UserClient): in [y.device.id for y in x.actions if hasattr(y, 'device')] ] ) - == 1 + == 0 ) diff --git a/tests/test_basic.py b/tests/test_basic.py index 7322e77b..4b734079 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -68,9 +68,11 @@ def test_api_docs(client: Client): '/inventory/lot/{lot_id}/receivernote/', '/inventory/lot/{lot_id}/trade-document/add/', '/inventory/lot/{lot_id}/transfer/{type_id}/', + '/inventory/lot/{lot_id}/opentransfer/', '/inventory/lot/{lot_id}/transfer/', '/inventory/lot/transfer/{type_id}/', '/inventory/lot/{lot_id}/upload-snapshot/', + '/inventory/lot/{lot_id}/customerdetails/', '/inventory/snapshots/{snapshot_uuid}/', '/inventory/snapshots/', '/inventory/tag/devices/{dhid}/add/', @@ -98,6 +100,7 @@ def test_api_docs(client: Client): '/metrics/', '/profile/', '/set_password/', + '/set_sanitization/', '/tags/', '/tags/{tag_id}/device/{device_id}', '/trade-documents/', diff --git a/tests/test_device.py b/tests/test_device.py index 9977cd5c..0d67c325 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -141,47 +141,14 @@ def test_physical_properties(): 'ram_slots': None, } assert pc.physical_properties == { - 'chassis': ComputerChassis.Tower, 'amount': 0, 'manufacturer': 'bar', 'model': 'foo', - 'receiver_id': None, 'serial_number': 'foo-bar', 'part_number': None, - 'transfer_state': TransferState.Initial, } -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.auth_app_context.__name__) -def test_component_similar_one(): - user = User.query.filter().first() - snapshot = yaml2json('pc-components.db') - pc = snapshot['device'] - snapshot['components'][0]['serial_number'] = snapshot['components'][1][ - 'serial_number' - ] = None - pc = d.Desktop( - **pc, components=OrderedSet(d.Component(**c) for c in snapshot['components']) - ) - component1, component2 = pc.components # type: d.Component - db.session.add(pc) - db.session.flush() - # Let's create a new component named 'A' similar to 1 - componentA = d.Component( - model=component1.model, manufacturer=component1.manufacturer, owner_id=user.id - ) - similar_to_a = componentA.similar_one(pc, set()) - assert similar_to_a == component1 - # d.Component B does not have the same model - componentB = d.Component(model='nope', manufacturer=component1.manufacturer) - with pytest.raises(ResourceNotFound): - assert componentB.similar_one(pc, set()) - # If we blacklist component A we won't get anything - with pytest.raises(ResourceNotFound): - assert componentA.similar_one(pc, blacklist={componentA.id}) - - @pytest.mark.mvp @pytest.mark.usefixtures(conftest.auth_app_context.__name__) def test_add_remove(): @@ -301,69 +268,6 @@ def test_sync_execute_register_desktop_no_hid_no_tag(user: UserClient): assert returned_pc == pc -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.auth_app_context.__name__) -def test_sync_execute_register_desktop_tag_not_linked(): - """Syncs a new d.Desktop with HID and a non-linked tag. - - It is OK if the tag was not linked, it will be linked in this process. - """ - tag = Tag(id='foo') - db.session.add(tag) - db.session.commit() - - # Create a new transient non-db object - pc = d.Desktop( - **yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')]) - ) - returned_pc = Sync().execute_register(pc) - assert returned_pc == pc - assert tag.device == pc, 'Tag has to be linked' - assert d.Desktop.query.one() == pc, 'd.Desktop had to be set to db' - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.auth_app_context.__name__) -def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str): - """Validates registering a d.Desktop without HID and a non-linked tag. - - In this case it is ok still, as the non-linked tag proves that - the d.Desktop was not existing before (otherwise the tag would - be linked), and thus it creates a new d.Desktop. - """ - tag = Tag(id=tag_id) - pc = d.Desktop(**yaml2json('pc-components.db')['device'], tags=OrderedSet([tag])) - db.session.add(g.user) - returned_pc = Sync().execute_register(pc) - db.session.commit() - assert returned_pc == pc - db_tag = next(iter(returned_pc.tags)) - # they are not the same tags though - # tag is a transient obj and db_tag the one from the db - # they have the same pk though - assert d.Desktop.query.one() == pc, 'd.Desktop had to be set to db' - assert tag != db_tag, 'They are not the same tags though' - for tag in pc.tags: - assert tag.id in ['foo', pc.devicehub_id] - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.auth_app_context.__name__) -def test_sync_execute_register_tag_does_not_exist(): - """Ensures not being able to register if the tag does not exist, - even if the device has HID or it existed before. - - Tags have to be created before trying to link them through a Snapshot. - """ - user = User.query.filter().first() - pc = d.Desktop( - **yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag('foo')]) - ) - pc.owner_id = user.id - with raises(ResourceNotFound): - Sync().execute_register(pc) - - @pytest.mark.mvp @pytest.mark.usefixtures(conftest.auth_app_context.__name__) def test_sync_execute_register_tag_linked_same_device(): @@ -387,53 +291,6 @@ def test_sync_execute_register_tag_linked_same_device(): assert tag.id in ['foo', db_pc.devicehub_id] -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.auth_app_context.__name__) -def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags(): - """Checks that sync raises an error if finds that at least two passed-in - tags are not linked to the same device. - """ - pc1 = d.Desktop(**yaml2json('pc-components.db')['device']) - db.session.add(Tag(id='foo-1', device=pc1)) - pc2 = d.Desktop(**yaml2json('pc-components.db')['device']) - pc2.serial_number = 'pc2-serial' - pc2.hid = Naming.hid(pc2.type, pc2.manufacturer, pc2.model, pc2.serial_number) - db.session.add(Tag(id='foo-2', device=pc2)) - db.session.commit() - - pc1 = d.Desktop( - **yaml2json('pc-components.db')['device'] - ) # Create a new transient non-db object - pc1.tags.add(Tag(id='foo-1')) - pc1.tags.add(Tag(id='foo-2')) - with raises(MismatchBetweenTags): - Sync().execute_register(pc1) - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.auth_app_context.__name__) -def test_sync_execute_register_mismatch_between_tags_and_hid(): - """Checks that sync raises an error if it finds that the HID does - not point at the same device as the tag does. - - In this case we set HID -> pc1 but tag -> pc2 - """ - pc1 = d.Desktop(**yaml2json('pc-components.db')['device']) - db.session.add(Tag(id='foo-1', device=pc1)) - pc2 = d.Desktop(**yaml2json('pc-components.db')['device']) - pc2.serial_number = 'pc2-serial' - pc2.hid = Naming.hid(pc2.type, pc2.manufacturer, pc2.model, pc2.serial_number) - db.session.add(Tag(id='foo-2', device=pc2)) - db.session.commit() - - pc1 = d.Desktop( - **yaml2json('pc-components.db')['device'] - ) # Create a new transient non-db object - pc1.tags.add(Tag(id='foo-2')) - with raises(MismatchBetweenTagsAndHid): - Sync().execute_register(pc1) - - @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_get_device(user: UserClient): @@ -751,138 +608,10 @@ def test_cooking_mixer_api(user: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) -def test_hid_with_mac(app: Devicehub, user: UserClient): +def test_hid_with_placeholder(app: Devicehub, user: UserClient): """Checks hid with mac.""" snapshot = file('asus-eee-1000h.snapshot.11') snap, _ = user.post(snapshot, res=m.Snapshot) pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) - assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() - assert ( - pc.placeholder.binding.hid - == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' - ) - - -@pytest.mark.mvp -def test_hid_without_mac(app: Devicehub, user: UserClient): - """Checks hid without mac.""" - snapshot = yaml2json('asus-eee-1000h.snapshot.11') - snapshot['components'] = [ - c for c in snapshot['components'] if c['type'] != 'NetworkAdapter' - ] - snap, _ = user.post(json_encode(snapshot), res=m.Snapshot) - pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) - assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' - - -@pytest.mark.mvp -def test_hid_with_mac_none(app: Devicehub, user: UserClient): - """Checks hid with mac = None.""" - snapshot = yaml2json('asus-eee-1000h.snapshot.11') - network = [c for c in snapshot['components'] if c['type'] == 'NetworkAdapter'][0] - network['serialNumber'] = None - snap, _ = user.post(json_encode(snapshot), res=m.Snapshot) - pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) - assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' - - -@pytest.mark.mvp -def test_hid_with_2networkadapters(app: Devicehub, user: UserClient): - """Checks hid with 2 networks adapters""" - snapshot = yaml2json('asus-eee-1000h.snapshot.11') - network = [c for c in snapshot['components'] if c['type'] == 'NetworkAdapter'][0] - network2 = copy.copy(network) - snapshot['components'].append(network2) - network['serialNumber'] = 'a0:24:8c:7f:cf:2d' - user.post(json_encode(snapshot), res=m.Snapshot) - devices, _ = user.get(res=d.Device) - - laptop = devices['items'][0] - assert ( - laptop['hid'] - == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' - ) - assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2 - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_hid_with_2network_and_drop_no_mac_in_hid(app: Devicehub, user: UserClient): - """Checks hid with 2 networks adapters and next drop the network is not used in hid""" - snapshot = yaml2json('asus-eee-1000h.snapshot.11') - network = [c for c in snapshot['components'] if c['type'] == 'NetworkAdapter'][0] - network2 = copy.copy(network) - snapshot['components'].append(network2) - network['serialNumber'] = 'a0:24:8c:7f:cf:2d' - snap, _ = user.post(json_encode(snapshot), res=m.Snapshot) - pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) - assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' - pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() - assert ( - pc.placeholder.binding.hid - == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' - ) - - snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb' - snapshot['components'] = [c for c in snapshot['components'] if c != network] - user.post(json_encode(snapshot), res=m.Snapshot) - devices, _ = user.get(res=d.Device) - laptop = devices['items'][0] - assert ( - pc.placeholder.binding.hid - == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' - ) - assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2 - assert len([c for c in laptop['components'] if c['type'] == 'NetworkAdapter']) == 1 - - -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_hid_with_2network_and_drop_mac_in_hid(app: Devicehub, user: UserClient): - """Checks hid with 2 networks adapters and next drop the network is used in hid""" - # One tipical snapshot with 2 network cards - snapshot = yaml2json('asus-eee-1000h.snapshot.11') - network = [c for c in snapshot['components'] if c['type'] == 'NetworkAdapter'][0] - network2 = copy.copy(network) - snapshot['components'].append(network2) - network['serialNumber'] = 'a0:24:8c:7f:cf:2d' - snap, _ = user.post(json_encode(snapshot), res=m.Snapshot) - pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID']) - assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116' - pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one() - assert ( - pc.placeholder.binding.hid - == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d' - ) - - # we drop the network card then is used for to build the hid - snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb' - snapshot['components'] = [c for c in snapshot['components'] if c != network2] - user.post(json_encode(snapshot), res=m.Snapshot) - devices, _ = user.get(res=d.Device) - laptops = [c for c in devices['items'] if c['type'] == 'Laptop'] - assert len(laptops) == 4 - hids = [laptops[0]['hid'], laptops[2]['hid']] - proof_hid = [ - 'laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d', - 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d', - ] - assert all([h in proof_hid for h in hids]) - - # we drop all network cards - snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abc' - snapshot['components'] = [ - c for c in snapshot['components'] if c not in [network, network2] - ] - user.post(json_encode(snapshot), res=m.Snapshot) - devices, _ = user.get(res=d.Device) - laptops = [c for c in devices['items'] if c['type'] == 'Laptop'] - assert len(laptops) == 4 - hids = [laptops[0]['hid'], laptops[2]['hid']] - proof_hid = [ - 'laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d', - 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d', - 'laptop-asustek_computer_inc-1000h-94oaaq021116', - ] - assert all([h in proof_hid for h in hids]) + assert pc.placeholder.binding.hid == pc.hid diff --git a/tests/test_metrics.py b/tests/test_metrics.py index d881d524..706e7260 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -6,24 +6,29 @@ from ereuse_devicehub.resources.documents import documents from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.tradedocument.models import TradeDocument from tests import conftest -from tests.conftest import file, yaml2json, json_encode +from tests.conftest import file, json_encode, yaml2json @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_simple_metrics(user: UserClient): - """ Checks one standard query of metrics """ + """Checks one standard query of metrics""" # Insert computer lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') acer = yaml2json('acer.happy.battery.snapshot') user.post(json_encode(lenovo), res=ma.Snapshot) snapshot, _ = user.post(json_encode(acer), res=ma.Snapshot) device_id = snapshot['device']['id'] - post_request = {"transaction": "ccc", "name": "John", "endUsers": 1, - "finalUserCode": "abcdefjhi", - "devices": [device_id], "description": "aaa", - "startTime": "2020-11-01T02:00:00+00:00", - "endTime": "2020-12-01T02:00:00+00:00"} + post_request = { + "transaction": "ccc", + "name": "John", + "endUsers": 1, + "finalUserCode": "abcdefjhi", + "devices": [device_id], + "description": "aaa", + "startTime": "2020-11-01T02:00:00+00:00", + "endTime": "2020-12-01T02:00:00+00:00", + } # Create Allocate user.post(res=ma.Allocate, data=post_request) @@ -58,16 +63,21 @@ def test_simple_metrics(user: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_second_hdd_metrics(user: UserClient): - """ Checks one standard query of metrics """ + """Checks one standard query of metrics""" # Insert computer acer = yaml2json('acer.happy.battery.snapshot') snapshot, _ = user.post(json_encode(acer), res=ma.Snapshot) device_id = snapshot['device']['id'] - post_request = {"transaction": "ccc", "name": "John", "endUsers": 1, - "finalUserCode": "abcdefjhi", - "devices": [device_id], "description": "aaa", - "startTime": "2020-11-01T02:00:00+00:00", - "endTime": "2020-12-01T02:00:00+00:00"} + post_request = { + "transaction": "ccc", + "name": "John", + "endUsers": 1, + "finalUserCode": "abcdefjhi", + "devices": [device_id], + "description": "aaa", + "startTime": "2020-11-01T02:00:00+00:00", + "endTime": "2020-12-01T02:00:00+00:00", + } # Create Allocate user.post(res=ma.Allocate, data=post_request) @@ -101,16 +111,21 @@ def test_second_hdd_metrics(user: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_metrics_with_live_null(user: UserClient): - """ Checks one standard query of metrics """ + """Checks one standard query of metrics""" # Insert computer acer = file('acer.happy.battery.snapshot') snapshot, _ = user.post(acer, res=ma.Snapshot) device_id = snapshot['device']['id'] - post_request = {"transaction": "ccc", "name": "John", "endUsers": 1, - "finalUserCode": "abcdefjhi", - "devices": [device_id], "description": "aaa", - "startTime": "2020-11-01T02:00:00+00:00", - "endTime": "2020-12-01T02:00:00+00:00"} + post_request = { + "transaction": "ccc", + "name": "John", + "endUsers": 1, + "finalUserCode": "abcdefjhi", + "devices": [device_id], + "description": "aaa", + "startTime": "2020-11-01T02:00:00+00:00", + "endTime": "2020-12-01T02:00:00+00:00", + } # Create Allocate user.post(res=ma.Allocate, data=post_request) @@ -124,19 +139,29 @@ def test_metrics_with_live_null(user: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_metrics_action_status(user: UserClient, user2: UserClient): - """ Checks one standard query of metrics.""" + """Checks one standard query of metrics.""" # Insert computer lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') snap, _ = user.post(json_encode(lenovo), res=ma.Snapshot) device_id = snap['device']['id'] action = {'type': ma.Use.t, 'devices': [device_id]} action_use, _ = user.post(action, res=ma.Action) - csv_str, _ = user.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer'], 'ids': [device_id]})]) - head = '"DHID";"Hid";"Document-Name";"Action-Type";"Action-User-LastOwner-Supplier";"Action-User-LastOwner-Receiver";"Action-Create-By";"Trade-Confirmed";"Status-Created-By-Supplier-About-Reciber";"Status-Receiver";"Status Supplier – Created Date";"Status Receiver – Created Date";"Trade-Weight";"Action-Create";"Allocate-Start";"Allocate-User-Code";"Allocate-NumUsers";"UsageTimeAllocate";"Type";"LiveCreate";"UsageTimeHdd"\n' - body = '"O48N2";"desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10";"";"Status";"";"foo@foo.com";"Receiver";"";"";"Use";"";"' + csv_str, _ = user.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer'], 'ids': [device_id]})], + ) + head = ( + '"DHID";"Hid";"Document-Name";"Action-Type";"Action-User-LastOwner-Supplier";' + ) + head += '"Action-User-LastOwner-Receiver";"Action-Create-By";"Trade-Confirmed";' + head += '"Status-Created-By-Supplier-About-Reciber";"Status-Receiver";' + head += '"Status Supplier – Created Date";"Status Receiver – Created Date";"Trade-Weight";' + head += '"Action-Create";"Allocate-Start";"Allocate-User-Code";"Allocate-NumUsers";' + head += '"UsageTimeAllocate";"Type";"LiveCreate";"UsageTimeHdd"\n' + body = '"O48N2";"adebcc5506213fac43cd8473a9c81bcf0cadaed9cb98b2eae651e377a3533c5a";' + body += '"";"Status";"";"foo@foo.com";"Receiver";"";"";"Use";"";"' assert head in csv_str assert body in csv_str @@ -144,7 +169,7 @@ def test_metrics_action_status(user: UserClient, user2: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_complet_metrics_with_trade(user: UserClient, user2: UserClient): - """ Checks one standard query of metrics in a trade enviroment.""" + """Checks one standard query of metrics in a trade enviroment.""" # Insert computer lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') acer = yaml2json('acer.happy.battery.snapshot') @@ -154,12 +179,8 @@ def test_complet_metrics_with_trade(user: UserClient, user2: UserClient): device1_id = snap1['device']['id'] device2_id = snap2['device']['id'] devices_id = [device1_id, device2_id] - devices = [('id', device1_id), - ('id', snap2['device']['id'])] - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices) + devices = [('id', device1_id), ('id', snap2['device']['id'])] + lot, _ = user.post({}, res=Lot, item='{}/devices'.format(lot['id']), query=devices) action = {'type': ma.Refurbish.t, 'devices': [device1_id]} user.post(action, res=ma.Action) @@ -179,17 +200,21 @@ def test_complet_metrics_with_trade(user: UserClient, user2: UserClient): action = {'type': ma.Use.t, 'devices': [device1_id]} action_use, _ = user.post(action, res=ma.Action) - csv_str, _ = user.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer'], 'ids': devices_id})]) + csv_str, _ = user.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer'], 'ids': devices_id})], + ) - body1_lenovo = '"O48N2";"desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10";"";"Trade";"foo@foo.com";' + body1_lenovo = '"O48N2";"adebcc5506213fac43cd8473a9c81bcf0cadaed9cb98b2eae651e377a3533c5a";"";"Trade";"foo@foo.com";' body1_lenovo += '"foo2@foo.com";"Supplier";"NeedConfirmation";"Use";"";' body2_lenovo = ';"";"0";"0";"Trade";"0";"0"\n' - body1_acer = '"K3XW2";"laptop-acer-aohappy-lusea0d010038879a01601-00:26:c7:8e:cb:8c";"";"Trade";' - body1_acer += '"foo@foo.com";"foo2@foo.com";"Supplier";"NeedConfirmation";"";"";"";"";"0";' + body1_acer = '"K3XW2";"55b1f6d0692d1569c7590f0aeabd1c9874a1c78b8dd3a7d481df95923a629748";"";"Trade";' + body1_acer += ( + '"foo@foo.com";"foo2@foo.com";"Supplier";"NeedConfirmation";"";"";"";"";"0";' + ) body2_acer = ';"";"0";"0";"Trade";"0";"4692.0"\n' assert body1_lenovo in csv_str @@ -200,12 +225,14 @@ def test_complet_metrics_with_trade(user: UserClient, user2: UserClient): # User2 mark this device as Refurbish action = {'type': ma.Use.t, 'devices': [device1_id]} action_use2, _ = user2.post(action, res=ma.Action) - csv_str, _ = user.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer'], 'ids': devices_id})]) + csv_str, _ = user.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer'], 'ids': devices_id})], + ) - body1_lenovo = '"O48N2";"desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10";"";"Trade";"foo@foo.com";' + body1_lenovo = '"O48N2";"adebcc5506213fac43cd8473a9c81bcf0cadaed9cb98b2eae651e377a3533c5a";"";"Trade";"foo@foo.com";' body1_lenovo += '"foo2@foo.com";"Supplier";"NeedConfirmation";"Use";"Use";' body2_lenovo = ';"";"0";"0";"Trade";"0";"0"\n' body2_acer = ';"";"0";"0";"Trade";"0";"4692.0"\n' @@ -215,20 +242,16 @@ def test_complet_metrics_with_trade(user: UserClient, user2: UserClient): assert body2_acer in csv_str - @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_metrics_action_status_for_containers(user: UserClient, user2: UserClient): - """ Checks one standard query of metrics for a container.""" + """Checks one standard query of metrics for a container.""" # Insert computer lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') snap, _ = user.post(json_encode(lenovo), res=ma.Snapshot) lot, _ = user.post({'name': 'MyLot'}, res=Lot) devices = [('id', snap['device']['id'])] - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices) + lot, _ = user.post({}, res=Lot, item='{}/devices'.format(lot['id']), query=devices) request_post = { 'type': 'Trade', 'devices': [snap['device']['id']], @@ -247,7 +270,7 @@ def test_metrics_action_status_for_containers(user: UserClient, user2: UserClien 'hash': 'bbbbbbbb', 'url': 'http://www.ereuse.org/', 'weight': 150, - 'lot': lot['id'] + 'lot': lot['id'], } tradedocument, _ = user.post(res=TradeDocument, data=request_post) action = {'type': ma.Recycling.t, 'devices': [], 'documents': [tradedocument['id']]} @@ -257,10 +280,12 @@ def test_metrics_action_status_for_containers(user: UserClient, user2: UserClien assert str(trade.actions[-1].id) == action['id'] # get metrics from botom in lot menu - csv_str, _ = user.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer']}), ('lot', lot['id'])]) + csv_str, _ = user.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer']}), ('lot', lot['id'])], + ) body1 = ';"bbbbbbbb";"test.pdf";"Trade-Container";"foo@foo.com";"foo2@foo.com";"Supplier";"False";"Recycling";"";' body2 = ';"";"150.0";' @@ -272,10 +297,12 @@ def test_metrics_action_status_for_containers(user: UserClient, user2: UserClien assert body3 in csv_str.split('\n')[-2] # get metrics from botom in devices menu - csv_str2, _ = user.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer'], 'ids': [snap['device']['id']]})]) + csv_str2, _ = user.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer'], 'ids': [snap['device']['id']]})], + ) assert len(csv_str2.split('\n')) == 4 assert body1 in csv_str2.split('\n')[-2] @@ -286,17 +313,14 @@ def test_metrics_action_status_for_containers(user: UserClient, user2: UserClien @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_visual_metrics_for_old_owners(user: UserClient, user2: UserClient): - """ Checks if one old owner can see the metrics in a trade enviroment.""" + """Checks if one old owner can see the metrics in a trade enviroment.""" # Insert computer lenovo = yaml2json('desktop-9644w8n-lenovo-0169622.snapshot') snap1, _ = user.post(json_encode(lenovo), res=ma.Snapshot) lot, _ = user.post({'name': 'MyLot'}, res=Lot) device_id = snap1['device']['id'] devices = [('id', device_id)] - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices) + lot, _ = user.post({}, res=Lot, item='{}/devices'.format(lot['id']), query=devices) request_post = { 'type': 'Trade', 'devices': [device_id], @@ -309,24 +333,23 @@ def test_visual_metrics_for_old_owners(user: UserClient, user2: UserClient): } trade, _ = user.post(res=ma.Action, data=request_post) - request_confirm = { - 'type': 'Confirm', - 'action': trade['id'], - 'devices': [device_id] - } + request_confirm = {'type': 'Confirm', 'action': trade['id'], 'devices': [device_id]} user2.post(res=ma.Action, data=request_confirm) - action = {'type': ma.Refurbish.t, 'devices': [device_id]} action_use, _ = user.post(action, res=ma.Action) - csv_supplier, _ = user.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer'], 'ids': [device_id]})]) - csv_receiver, _ = user2.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer'], 'ids': [device_id]})]) + csv_supplier, _ = user.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer'], 'ids': [device_id]})], + ) + csv_receiver, _ = user2.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer'], 'ids': [device_id]})], + ) body = ';"";"0";"0";"Trade";"0";"0"\n' assert body in csv_receiver @@ -343,10 +366,7 @@ def test_bug_trade_confirmed(user: UserClient, user2: UserClient): lot, _ = user.post({'name': 'MyLot'}, res=Lot) device_id = snap1['device']['id'] devices = [('id', device_id)] - lot, _ = user.post({}, - res=Lot, - item='{}/devices'.format(lot['id']), - query=devices) + lot, _ = user.post({}, res=Lot, item='{}/devices'.format(lot['id']), query=devices) request_post = { 'type': 'Trade', 'devices': [device_id], @@ -359,22 +379,24 @@ def test_bug_trade_confirmed(user: UserClient, user2: UserClient): } trade, _ = user.post(res=ma.Action, data=request_post) - csv_not_confirmed, _ = user.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer'], 'ids': [device_id]})]) - request_confirm = { - 'type': 'Confirm', - 'action': trade['id'], - 'devices': [device_id] - } + csv_not_confirmed, _ = user.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer'], 'ids': [device_id]})], + ) + request_confirm = {'type': 'Confirm', 'action': trade['id'], 'devices': [device_id]} user2.post(res=ma.Action, data=request_confirm) - csv_confirmed, _ = user2.get(res=documents.DocumentDef.t, - item='actions/', - accept='text/csv', - query=[('filter', {'type': ['Computer'], 'ids': [device_id]})]) + csv_confirmed, _ = user2.get( + res=documents.DocumentDef.t, + item='actions/', + accept='text/csv', + query=[('filter', {'type': ['Computer'], 'ids': [device_id]})], + ) - body_not_confirmed = '"Trade";"foo2@foo.com";"foo@foo.com";"Receiver";"NeedConfirmation";' + body_not_confirmed = ( + '"Trade";"foo2@foo.com";"foo@foo.com";"Receiver";"NeedConfirmation";' + ) body_confirmed = '"Trade";"foo2@foo.com";"foo@foo.com";"Receiver";"TradeConfirmed";' assert body_not_confirmed in csv_not_confirmed diff --git a/tests/test_rate_workbench_v1.py b/tests/test_rate_workbench_v1.py index 7269b72a..b52b2995 100644 --- a/tests/test_rate_workbench_v1.py +++ b/tests/test_rate_workbench_v1.py @@ -17,18 +17,36 @@ Excluded cases in tests """ import math + import pytest -from ereuse_devicehub.resources.action.models import BenchmarkDataStorage, BenchmarkProcessor, \ - VisualTest -from ereuse_devicehub.resources.action.rate.v1_0 import DataStorageRate, ProcessorRate, \ - RamRate, RateAlgorithm -from ereuse_devicehub.resources.device.models import Desktop, HardDrive, Processor, RamModule -from ereuse_devicehub.resources.enums import AppearanceRange, ComputerChassis, FunctionalityRange +from ereuse_devicehub.resources.action.models import ( + BenchmarkDataStorage, + BenchmarkProcessor, + VisualTest, +) +from ereuse_devicehub.resources.action.rate.v1_0 import ( + DataStorageRate, + ProcessorRate, + RamRate, + RateAlgorithm, +) +from ereuse_devicehub.resources.device.models import ( + Desktop, + HardDrive, + Processor, + RamModule, +) +from ereuse_devicehub.resources.enums import ( + AppearanceRange, + ComputerChassis, + FunctionalityRange, +) from tests import conftest @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_data_storage_rate(): """Test to check if compute data storage rate have same value than previous score version. @@ -65,6 +83,7 @@ def test_rate_data_storage_rate(): @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_data_storage_size_is_null(): """Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0, BenchmarkDataStorage.write_speed = 0 is like no DataStorage has been detected; @@ -78,6 +97,7 @@ def test_rate_data_storage_size_is_null(): @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_no_data_storage(): """Test without data storage devices.""" @@ -88,6 +108,7 @@ def test_rate_no_data_storage(): @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_ram_rate(): """Test to check if compute ram rate have same value than previous score version only with 1 RamModule. @@ -97,10 +118,13 @@ def test_rate_ram_rate(): ram_rate = RamRate().compute([ram1]) - assert math.isclose(ram_rate, 2.02, rel_tol=0.002), 'RamRate returns incorrect value(rate)' + assert math.isclose( + ram_rate, 2.02, rel_tol=0.002 + ), 'RamRate returns incorrect value(rate)' @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_ram_rate_2modules(): """Test to check if compute ram rate have same value than previous score version with 2 RamModule. @@ -111,10 +135,13 @@ def test_rate_ram_rate_2modules(): ram_rate = RamRate().compute([ram1, ram2]) - assert math.isclose(ram_rate, 3.79, rel_tol=0.001), 'RamRate returns incorrect value(rate)' + assert math.isclose( + ram_rate, 3.79, rel_tol=0.001 + ), 'RamRate returns incorrect value(rate)' @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_ram_rate_4modules(): """Test to check if compute ram rate have same value than previous score version with 2 RamModule. @@ -127,10 +154,13 @@ def test_rate_ram_rate_4modules(): ram_rate = RamRate().compute([ram1, ram2, ram3, ram4]) - assert math.isclose(ram_rate, 1.993, rel_tol=0.001), 'RamRate returns incorrect value(rate)' + assert math.isclose( + ram_rate, 1.993, rel_tol=0.001 + ), 'RamRate returns incorrect value(rate)' @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_ram_module_size_is_0(): """Test where input data RamModule.size = 0; is like no RamModule has been detected. @@ -143,6 +173,7 @@ def test_rate_ram_module_size_is_0(): @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_ram_speed_is_null(): """Test where RamModule.speed is NULL (not detected) but has size.""" @@ -150,16 +181,21 @@ def test_rate_ram_speed_is_null(): ram_rate = RamRate().compute([ram0]) - assert math.isclose(ram_rate, 1.85, rel_tol=0.002), 'RamRate returns incorrect value(rate)' + assert math.isclose( + ram_rate, 1.85, rel_tol=0.002 + ), 'RamRate returns incorrect value(rate)' ram0 = RamModule(size=1024, speed=None) ram_rate = RamRate().compute([ram0]) - assert math.isclose(ram_rate, 1.25, rel_tol=0.004), 'RamRate returns incorrect value(rate)' + assert math.isclose( + ram_rate, 1.25, rel_tol=0.004 + ), 'RamRate returns incorrect value(rate)' @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_no_ram_module(): """Test without RamModule.""" ram0 = RamModule() @@ -169,6 +205,7 @@ def test_rate_no_ram_module(): @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_processor_rate(): """Test to check if compute processor rate have same value than previous score version only with 1 core. @@ -184,6 +221,7 @@ def test_rate_processor_rate(): @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_processor_rate_2cores(): """Test to check if compute processor rate have same value than previous score version with 2 cores. @@ -206,6 +244,7 @@ def test_rate_processor_rate_2cores(): @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_processor_with_null_cores(): """Test with processor device have null number of cores.""" cpu = Processor(cores=None, speed=3.3) @@ -217,6 +256,7 @@ def test_rate_processor_with_null_cores(): @pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) def test_rate_processor_with_null_speed(): """Test with processor device have null speed value.""" cpu = Processor(cores=1, speed=None) @@ -262,12 +302,14 @@ def test_rate_computer_1193(): data_storage, RamModule(size=4096, speed=1600), RamModule(size=2048, speed=1067), - cpu + cpu, } # Add test visual with functionality and appearance range - VisualTest(appearance_range=AppearanceRange.A, - functionality_range=FunctionalityRange.A, - device=pc_test) + VisualTest( + appearance_range=AppearanceRange.A, + functionality_range=FunctionalityRange.A, + device=pc_test, + ) # Compute all components rates and general rating rate_pc = RateAlgorithm().compute(pc_test) @@ -311,15 +353,13 @@ def test_rate_computer_1201(): data_storage.actions_one.add(BenchmarkDataStorage(read_speed=158, write_speed=34.7)) cpu = Processor(cores=2, speed=3.3) cpu.actions_one.add(BenchmarkProcessor(rate=26339.48)) - pc_test.components |= { - data_storage, - RamModule(size=2048, speed=1333), - cpu - } + pc_test.components |= {data_storage, RamModule(size=2048, speed=1333), cpu} # Add test visual with functionality and appearance range - VisualTest(appearance_range=AppearanceRange.B, - functionality_range=FunctionalityRange.A, - device=pc_test) + VisualTest( + appearance_range=AppearanceRange.B, + functionality_range=FunctionalityRange.A, + device=pc_test, + ) # Compute all components rates and general rating rate_pc = RateAlgorithm().compute(pc_test) @@ -365,7 +405,9 @@ def test_rate_computer_multiple_ram_module(): pc_test = Desktop(chassis=ComputerChassis.Tower) data_storage = HardDrive(size=76319) - data_storage.actions_one.add(BenchmarkDataStorage(read_speed=72.2, write_speed=24.3)) + data_storage.actions_one.add( + BenchmarkDataStorage(read_speed=72.2, write_speed=24.3) + ) cpu = Processor(cores=1, speed=1.6) cpu.actions_one.add(BenchmarkProcessor(rate=3192.34)) pc_test.components |= { @@ -374,12 +416,14 @@ def test_rate_computer_multiple_ram_module(): RamModule(size=512, speed=800), RamModule(size=512, speed=667), RamModule(size=512, speed=533), - cpu + cpu, } # Add test visual with functionality and appearance range - VisualTest(appearance_range=AppearanceRange.C, - functionality_range=FunctionalityRange.A, - device=pc_test) + VisualTest( + appearance_range=AppearanceRange.C, + functionality_range=FunctionalityRange.A, + device=pc_test, + ) # Compute all components rates and general rating rate_pc = RateAlgorithm().compute(pc_test) @@ -421,18 +465,18 @@ def test_rate_computer_one_ram_module(): pc_test = Desktop(chassis=ComputerChassis.Tower) data_storage = HardDrive(size=152587) - data_storage.actions_one.add(BenchmarkDataStorage(read_speed=78.1, write_speed=24.4)) + data_storage.actions_one.add( + BenchmarkDataStorage(read_speed=78.1, write_speed=24.4) + ) cpu = Processor(cores=2, speed=2.5) cpu.actions_one.add(BenchmarkProcessor(rate=9974.3)) - pc_test.components |= { - data_storage, - RamModule(size=0, speed=None), - cpu - } + pc_test.components |= {data_storage, RamModule(size=0, speed=None), cpu} # Add test visual with functionality and appearance range - VisualTest(appearance_range=AppearanceRange.B, - functionality_range=FunctionalityRange.A, - device=pc_test) + VisualTest( + appearance_range=AppearanceRange.B, + functionality_range=FunctionalityRange.A, + device=pc_test, + ) # Compute all components rates and general rating rate_pc = RateAlgorithm().compute(pc_test) diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index f9f2299e..c84ad143 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -12,7 +12,6 @@ from flask_wtf.csrf import generate_csrf from ereuse_devicehub.client import UserClient, UserClientFlask from ereuse_devicehub.db import db from ereuse_devicehub.devicehub import Devicehub -from ereuse_devicehub.parser.models import SnapshotsLog from ereuse_devicehub.resources.action.models import Snapshot from ereuse_devicehub.resources.device.models import Device, Placeholder from ereuse_devicehub.resources.lot.models import Lot @@ -321,7 +320,7 @@ def test_export_certificates(user3: UserClientFlask): body = str(next(body)) assert status == '200 OK' assert "PDF-1.5" in body - assert 'hts54322' in body + assert 'e2024242cv86mm'.upper() in body @pytest.mark.mvp @@ -714,7 +713,9 @@ def test_add_laptop(user3: UserClientFlask): assert typ == 'Laptop' assert dev.placeholder.id_device_supplier == "b2" - assert dev.hid == 'laptop-samsung-lc27t55-aaaab' + assert ( + dev.chid == '274f05421e4d394c5b3cd10266fed6f0500029b104b5db3521689bda589e3150' + ) assert phid == '1' assert dhid == 'O48N2' @@ -753,7 +754,11 @@ def test_add_with_ammount_laptops(user3: UserClientFlask): for dev in Device.query.all(): assert dev.type == 'Laptop' assert dev.placeholder.id_device_supplier is None - assert dev.hid is None + assert dev.hid == 'laptop-samsung-lc27t55-' + assert ( + dev.chid + == 'ff8e7794d33ed22046b8d94b8bba4d8d1507f0fee535150835cac28faabbcda1' + ) assert dev.placeholder.phid in [str(x) for x in range(1, num + 1)] assert Device.query.count() == num @@ -1698,7 +1703,6 @@ def test_export_lots(user3: UserClientFlask): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_export_snapshot_json(user3: UserClientFlask): - # ?? file_name = 'real-eee-1001pxd.snapshot.13.json' snap = create_device(user3, file_name) @@ -1708,7 +1712,10 @@ def test_export_snapshot_json(user3: UserClientFlask): uri = "/inventory/export/snapshot/?id={}".format(snap.uuid) body, status = user3.get(uri) assert status == '200 OK' - assert body == snapshot + body = json.loads(body) + snapshot = json.loads(snapshot) + assert body['device'] == snapshot['device'] + assert body['components'] == snapshot['components'] @pytest.mark.mvp @@ -1729,7 +1736,8 @@ def test_add_placeholder_excel(user3: UserClientFlask): user3.post(uri, data=data, content_type="multipart/form-data") assert Device.query.count() == 3 dev = Device.query.first() - assert dev.hid == 'laptop-sony-vaio-12345678' + chid = 'f28ae12ffd513f5ed8fb6714a344a2326c48a7196fb140435065ab96ffda1a71' + assert dev.chid == chid assert dev.placeholder.phid == '1' assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.pallet == '24A' @@ -1755,7 +1763,8 @@ def test_add_placeholder_csv(user3: UserClientFlask): user3.post(uri, data=data, content_type="multipart/form-data") assert Device.query.count() == 3 dev = Device.query.first() - assert dev.hid == 'laptop-sony-vaio-12345678' + chid = 'f28ae12ffd513f5ed8fb6714a344a2326c48a7196fb140435065ab96ffda1a71' + assert dev.chid == chid assert dev.placeholder.phid == '1' assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.pallet == '24A' @@ -1781,7 +1790,8 @@ def test_add_placeholder_ods(user3: UserClientFlask): user3.post(uri, data=data, content_type="multipart/form-data") assert Device.query.count() == 3 dev = Device.query.first() - assert dev.hid == 'laptop-sony-vaio-12345678' + chid = 'f28ae12ffd513f5ed8fb6714a344a2326c48a7196fb140435065ab96ffda1a71' + assert dev.chid == chid assert dev.placeholder.phid == '1' assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.pallet == '24A' @@ -1809,7 +1819,8 @@ def test_add_placeholder_office_open_xml(user3: UserClientFlask): user3.post(uri, data=data, content_type="multipart/form-data") assert Device.query.count() == 3 dev = Device.query.first() - assert dev.hid == 'laptop-sony-vaio-12345678' + chid = 'f28ae12ffd513f5ed8fb6714a344a2326c48a7196fb140435065ab96ffda1a71' + assert dev.chid == chid assert dev.placeholder.phid == '1' assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.pallet == '24A' @@ -1847,7 +1858,8 @@ def test_edit_laptop(user3: UserClientFlask): assert typ == 'Laptop' assert dev.placeholder.id_device_supplier == "b2" - assert dev.hid == 'laptop-samsung-lc27t55-aaaab' + chid = '274f05421e4d394c5b3cd10266fed6f0500029b104b5db3521689bda589e3150' + assert dev.chid == chid assert dev.serial_number == 'aaaab' assert dev.model == 'lc27t55' assert phid == '1' @@ -1879,7 +1891,7 @@ def test_edit_laptop(user3: UserClientFlask): assert 'Device "Laptop" edited successfully!' in body dev = Device.query.one() assert dev.type == 'Laptop' - assert dev.hid == 'laptop-samsung-lc27t55-aaaab' + assert dev.chid == chid assert dev.placeholder.phid == '1' assert dev.placeholder.id_device_supplier == 'a2' assert dev.serial_number == 'aaaac' @@ -2077,7 +2089,8 @@ def test_add_placeholder_excel_from_lot(user3: UserClientFlask): user3.post(uri, data=data, content_type="multipart/form-data") assert Device.query.count() == 3 dev = Device.query.first() - assert dev.hid == 'laptop-sony-vaio-12345678' + chid = 'f28ae12ffd513f5ed8fb6714a344a2326c48a7196fb140435065ab96ffda1a71' + assert dev.chid == chid assert dev.placeholder.phid == '1' assert dev.placeholder.info == 'Good conditions' assert dev.placeholder.pallet == '24A' @@ -2116,7 +2129,8 @@ def test_add_new_placeholder_from_lot(user3: UserClientFlask): } user3.post(uri, data=data) dev = Device.query.one() - assert dev.hid == 'laptop-samsung-lc27t55-aaaab' + chid = '274f05421e4d394c5b3cd10266fed6f0500029b104b5db3521689bda589e3150' + assert dev.chid == chid assert dev.placeholder.phid == '1' assert len(lot.devices) == 1 @@ -2141,7 +2155,8 @@ def test_manual_binding(user3: UserClientFlask): } user3.post(uri, data=data) dev = Device.query.one() - assert dev.hid == 'laptop-samsung-lc27t55-aaaab' + chid = '274f05421e4d394c5b3cd10266fed6f0500029b104b5db3521689bda589e3150' + assert dev.chid == chid assert dev.placeholder.phid == '1' assert dev.placeholder.is_abstract is False @@ -2153,8 +2168,8 @@ def test_manual_binding(user3: UserClientFlask): assert dev_wb.binding.is_abstract is True assert ( - dev_wb.hid - == 'laptop-asustek_computer_inc-1001pxd-b8oaas048285-14:da:e9:42:f6:7b' + dev_wb.chid + == '83cb9066430a8ea7def04af61d521d6517193a486c02ea3bc914c9eaeb2b718b' ) assert dev_wb.binding.phid == '11' old_placeholder = dev_wb.binding @@ -2653,10 +2668,109 @@ def test_system_uuid_motherboard(user3: UserClientFlask): } user3.post(uri, data=data, content_type="multipart/form-data") snapshot2 = Snapshot.query.filter_by(uuid=snapshot_json['uuid']).first() - assert snapshot2 is None + assert snapshot2.device == snapshot.device for c in snapshot.device.components: if c.type == 'Motherboard': - assert c.serial_number == 'eee0123456720' + assert c.serial_number == 'abee0123456720' - txt = "We have detected that a there is a device in your inventory" - assert txt in SnapshotsLog.query.all()[-1].description + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_unreliable_device(user3: UserClientFlask): + # Create device + snapshot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + + # Update device + uri = '/inventory/upload-snapshot/' + file_name = 'real-eee-1001pxd.snapshot.12' + snapshot_json = conftest.yaml2json(file_name) + snapshot_json['uuid'] = 'c058e8d2-fb92-47cb-a4b7-522b75561136' + b_snapshot = bytes(json.dumps(snapshot_json), 'utf-8') + file_snap = (BytesIO(b_snapshot), file_name) + user3.get(uri) + + data = { + 'snapshot': file_snap, + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data, content_type="multipart/form-data") + snapshot2 = Snapshot.query.filter_by(uuid=snapshot_json['uuid']).first() + assert snapshot2.device == snapshot.device + assert Snapshot.query.count() == 2 + snapshots = Snapshot.query.all() + assert snapshots[0].device == snapshots[1].device + assert len(snapshots[0].device.components) + assert snapshot2 in snapshots + + # Change update for new device + uuid2 = snapshot2.uuid + uri = f"/inventory/snapshots/{uuid2}/" + user3.get(uri) + + data = { + 'snapshot_type': "new_device", + 'csrf_token': generate_csrf(), + } + assert Device.query.filter_by(hid=snapshot.device.hid).count() == 2 + user3.post(uri, data=data) + assert Device.query.filter_by(hid=snapshot.device.hid).count() == 4 + assert Snapshot.query.count() == 2 + snapshots = Snapshot.query.all() + assert snapshot2 not in snapshots + assert snapshots[0].device != snapshots[1].device + assert len(snapshots[0].device.components) == 8 + assert len(snapshots[1].device.components) == 9 + assert len(snapshots[0].device.actions) == 11 + assert len(snapshots[1].device.actions) == 10 + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_reliable_device(user3: UserClientFlask): + # Create device + snapshot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + + # Update device + uri = '/inventory/upload-snapshot/' + file_name = 'real-eee-1001pxd.snapshot.12' + snapshot_json = conftest.yaml2json(file_name) + snapshot_json['uuid'] = 'c058e8d2-fb92-47cb-a4b7-522b75561136' + b_snapshot = bytes(json.dumps(snapshot_json), 'utf-8') + file_snap = (BytesIO(b_snapshot), file_name) + user3.get(uri) + + data = { + 'snapshot': file_snap, + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data, content_type="multipart/form-data") + snapshot2 = Snapshot.query.filter_by(uuid=snapshot_json['uuid']).first() + + # Change update for new device + uuid2 = snapshot2.uuid + uri = f"/inventory/snapshots/{uuid2}/" + user3.get(uri) + + data = { + 'snapshot_type': "new_device", + 'csrf_token': generate_csrf(), + } + user3.post(uri, data=data) + + # Change update for update + snapshot3 = Snapshot.query.all()[-1] + uuid3 = snapshot3.uuid + uri = f"/inventory/snapshots/{uuid3}/" + user3.get(uri) + + data = { + 'snapshot_type': "update", + 'csrf_token': generate_csrf(), + } + assert Device.query.filter_by(hid=snapshot.device.hid).count() == 4 + user3.post(uri, data=data) + assert Device.query.filter_by(hid=snapshot.device.hid).count() == 2 + assert Snapshot.query.count() == 1 + assert Snapshot.query.first() == snapshot + assert len(snapshot.device.components) == 8 + assert len(snapshot.device.actions) == 7 diff --git a/tests/test_snapshot.py b/tests/test_snapshot.py index 2ee04e76..6501a01c 100644 --- a/tests/test_snapshot.py +++ b/tests/test_snapshot.py @@ -158,12 +158,6 @@ def test_snapshot_update_timefield_updated(user: UserClient): perform_second_snapshot=False, ) computer2 = yaml2json('2-second-device-with-components-of-first.snapshot') - snapshot_and_check( - user, - computer2, - action_types=('Remove',), - perform_second_snapshot=False, - ) pc1_devicehub_id = snapshot['device']['devicehubID'] pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id) assert pc1['updated'] != snapshot['device']['updated'] @@ -264,30 +258,25 @@ def test_snapshot_component_add_remove(user: UserClient): pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id) pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id) # Check if the update_timestamp is updated - update1_pc2 = pc2['updated'] - update2_pc1 = pc1['updated'] - assert update1_pc1 != update2_pc1 # PC1 - assert tuple(c['serialNumber'] for c in pc1['components']) == ('p1c1s', 'p1c3s') + assert tuple(c['serialNumber'] for c in pc1['components']) == ( + 'p1c1s', + 'p1c2s', + 'p1c3s', + ) assert all(c['parent'] == pc1_id for c in pc1['components']) assert tuple(e['type'] for e in pc1['actions']) == ( 'BenchmarkProcessor', 'Snapshot', - 'Remove', ) # PC2 - assert tuple(c['serialNumber'] for c in pc2['components']) == ('p1c2s', 'p2c1s') + assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s', 'p1c2s') assert all(c['parent'] == pc2_id for c in pc2['components']) assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot',) # p1c2s has two Snapshots, a Remove and an Add - p1c2s_dev = m.Device.query.filter_by(id=pc2['components'][0]['id']).one() + p1c2s_dev = m.Device.query.filter_by(id=pc2['components'][1]['id']).one() p1c2s, _ = user.get(res=m.Device, item=p1c2s_dev.devicehub_id) - assert tuple(e['type'] for e in p1c2s['actions']) == ( - 'BenchmarkProcessor', - 'Snapshot', - 'Snapshot', - 'Remove', - ) + assert tuple(e['type'] for e in p1c2s['actions']) == ('Snapshot',) # We register the first device again, but removing motherboard # and moving processor from the second device to the first. @@ -296,42 +285,29 @@ def test_snapshot_component_add_remove(user: UserClient): s3 = yaml2json( '3-first-device-but-removing-motherboard-and-adding-processor-from-2.snapshot' ) - snapshot_and_check(user, s3, ('Remove',), perform_second_snapshot=False) pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id) pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id) # Check if the update_timestamp is updated update2_pc2 = pc2['updated'] update3_pc1 = pc1['updated'] - assert not update3_pc1 in [update1_pc1, update2_pc1] - assert update1_pc2 != update2_pc2 # PC1 - assert {c['serialNumber'] for c in pc1['components']} == {'p1c2s', 'p1c3s'} - assert all(c['parent'] == pc1_id for c in pc1['components']) + assert {c['serialNumber'] for c in pc1['components']} == {'p1c1s', 'p1c3s', 'p1c2s'} + assert all(c['parent'] == pc1['id'] for c in pc1['components']) assert tuple(get_actions_info(pc1['actions'])) == ( # id, type, components, snapshot ('BenchmarkProcessor', []), # first BenchmarkProcessor - ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s', 'p1c2s']), # first Snapshot1 - ('Remove', ['p1c2s', 'p1c2s']), # Remove Processor in Snapshot2 - ('Snapshot', ['p1c2s', 'p1c3s']), # This Snapshot3 + ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s']), # first Snapshot1 ) # PC2 - assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s',) - assert all(c['parent'] == pc2_id for c in pc2['components']) - assert tuple(e['type'] for e in pc2['actions']) == ( - 'Snapshot', # Second Snapshot - 'Remove', # the processor we added in 2. - ) + assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s', 'p1c2s') + assert all(c['parent'] == pc2['id'] for c in pc2['components']) + assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot',) # Second Snapshot # p1c2s has Snapshot, Remove and Add p1c2s_dev = m.Device.query.filter_by(id=pc1['components'][0]['id']).one() p1c2s, _ = user.get(res=m.Device, item=p1c2s_dev.devicehub_id) assert tuple(get_actions_info(p1c2s['actions'])) == ( - ('BenchmarkProcessor', []), # first BenchmarkProcessor - ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s', 'p1c2s']), # First Snapshot to PC1 - ('Snapshot', ['p1c2s', 'p2c1s']), # Second Snapshot to PC2 - ('Remove', ['p1c2s', 'p1c2s']), # ...which caused p1c2s to be removed form PC1 - ('Snapshot', ['p1c2s', 'p1c3s']), # The third Snapshot to PC1 - ('Remove', ['p1c2s']), # ...which caused p1c2 to be removed from PC2 + ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s']), # First Snapshot to PC1 ) # We register the first device but without the processor, @@ -344,16 +320,15 @@ def test_snapshot_component_add_remove(user: UserClient): # Check if the update_timestamp is updated update3_pc2 = pc2['updated'] update4_pc1 = pc1['updated'] - assert update4_pc1 in [update1_pc1, update2_pc1, update3_pc1] assert update3_pc2 == update2_pc2 # PC 0: p1c3s, p1c4s. PC1: p2c1s - assert {c['serialNumber'] for c in pc1['components']} == {'p1c2s', 'p1c3s'} - assert all(c['parent'] == pc1_id for c in pc1['components']) + assert {c['serialNumber'] for c in pc1['components']} == {'p1c1s', 'p1c2s', 'p1c3s'} + assert all(c['parent'] == pc1['id'] for c in pc1['components']) # This last Action only # PC2 # We haven't changed PC2 - assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s',) - assert all(c['parent'] == pc2_id for c in pc2['components']) + assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s', 'p1c2s') + assert all(c['parent'] == pc2['id'] for c in pc2['components']) @pytest.mark.mvp @@ -368,7 +343,7 @@ def test_snapshot_post_without_hid(user: UserClient): assert response_snapshot['uuid'] == '9a3e7485-fdd0-47ce-bcc7-65c55226b598' assert response_snapshot['elapsed'] == 4 assert response_snapshot['author']['id'] == user.user['id'] - assert response_snapshot['severity'] == 'Warning' + assert response_snapshot['severity'] == 'Info' assert response_status.status_code == 201 @@ -391,7 +366,7 @@ def test_snapshot_tag_inner_tag_mismatch_between_tags_and_hid( pc2 = yaml2json('1-device-with-components.snapshot') user.post(json_encode(pc2), res=Snapshot) # PC2 uploads well pc2['device']['tags'] = [{'type': 'Tag', 'id': tag_id}] # Set tag from pc1 to pc2 - user.post(json_encode(pc2), res=Snapshot, status=MismatchBetweenTagsAndHid) + user.post(json_encode(pc2), res=Snapshot, status=400) @pytest.mark.mvp @@ -411,7 +386,7 @@ def test_snapshot_different_properties_same_tags(user: UserClient, tag_id: str): pc2['device']['tags'] = pc1['device']['tags'] # pc2 model is unknown but pc1 model is set = different property del pc2['device']['model'] - user.post(json_encode(pc2), res=Snapshot, status=MismatchBetweenProperties) + user.post(json_encode(pc2), res=Snapshot, status=201) @pytest.mark.mvp @@ -454,7 +429,7 @@ def test_ram_remove(user: UserClient): dev1 = m.Device.query.filter_by(id=snap1['device']['id']).one() dev2 = m.Device.query.filter_by(id=snap2['device']['id']).one() - assert len(dev1.components) == 1 + assert len(dev1.components) == 2 assert len(dev2.components) == 3 ssd = [x for x in dev2.components if x.t == 'SolidStateDrive'][0] remove = [x for x in ssd.actions if x.t == 'Remove'][0] @@ -684,7 +659,8 @@ def test_erase_changing_hdd_between_pcs(user: UserClient): tag2 = Tag(id='dev2', device=dev2) db.session.commit() - assert dev2.components[1].actions[2].parent == dev1 + assert dev2.components[2].parent == dev2 + assert dev2.components[2].actions[-1].device == dev2.components[2] doc1, response = user.get( res=documents.DocumentDef.t, item='erasures/{}'.format(dev1.id), accept=ANY ) @@ -1004,7 +980,8 @@ def test_snapshot_wb_lite(user: UserClient): assert dev.dhid in body['public_url'] assert ssd.serial_number == 's35anx0j401001' assert res.status == '201 CREATED' - assert '00:28:f8:a6:d5:7e' in dev.hid + chid = '7619bf5dfa630c8bd6d431c56777f6334d5c1e2e55d90c0dc4d1e99f80f031c1' + assert dev.chid == chid assert dev.actions[0].power_on_hours == 6032 errors = SnapshotsLog.query.filter().all() @@ -1028,7 +1005,7 @@ def test_snapshot_wb_lite_qemu(user: UserClient): assert dev.manufacturer == 'qemu' assert dev.model == 'standard' assert dev.serial_number is None - assert dev.hid is None + assert dev.hid == 'computer-qemu-standard-' assert dev.actions[0].power_on_hours == 1 assert dev.components[-1].size == 40960 assert dev.components[-1].serial_number == 'qm00001' @@ -1078,7 +1055,7 @@ def test_snapshot_wb_lite_old_snapshots(user: UserClient): try: assert body11['device'].get('hid') == dev.hid if body11['device'].get('hid'): - assert body11['device']['id'] == dev.id + assert body11['device']['id'] != dev.id assert body11['device'].get('serialNumber') == dev.serial_number assert body11['device'].get('model') == dev.model assert body11['device'].get('manufacturer') == dev.manufacturer @@ -1341,6 +1318,7 @@ def test_placeholder(user: UserClient): bodyLite, res = user.post(snapshot_lite, uri="/api/inventory/") assert res.status_code == 201 dev = m.Device.query.filter_by(devicehub_id=bodyLite['dhid']).one() + dev = dev.placeholder.binding assert dev.placeholder is None assert dev.binding.phid == '12' assert len(dev.binding.device.components) == 11 @@ -1378,6 +1356,7 @@ def test_system_uuid_motherboard(user: UserClient): if c['type'] == 'Motherboard': c['serialNumber'] = 'ABee0123456720' + s['uuid'] = str(uuid.uuid4()) snap2, _ = user.post(s, res=Snapshot, status=422) txt = "We have detected that a there is a device in your inventory" assert txt in snap2['message'][0] @@ -1405,7 +1384,7 @@ def test_bug_4028_components(user: UserClient): assert '' not in [c.phid() for c in components1] assert '' not in [c.phid() for c in components2] assert len(components1) == len(components2) - assert m.Placeholder.query.count() == 16 + assert m.Placeholder.query.count() == 19 assert m.Placeholder.query.count() * 2 == m.Device.query.count() for c in m.Placeholder.query.filter(): assert c.binding diff --git a/tests/test_system_uuid.py b/tests/test_system_uuid.py index 71eae0e7..08a162c7 100644 --- a/tests/test_system_uuid.py +++ b/tests/test_system_uuid.py @@ -29,7 +29,7 @@ def test_wb11_form(user3: UserClientFlask): db_snapthot = Snapshot.query.one() device = db_snapthot.device - assert device.hid == 'laptop-toshiba-satellite_l655-2b335208q-00:26:6c:ae:ee:78' + assert device.hid == 'laptop-toshiba-satellite_l655-2b335208q' assert str(device.system_uuid) == 'f0dc6a7f-c23f-e011-b5d0-00266caeee78' @@ -42,7 +42,7 @@ def test_wb11_api(user: UserClient): db_snapthot = Snapshot.query.one() device = db_snapthot.device - assert device.hid == 'laptop-toshiba-satellite_l655-2b335208q-00:26:6c:ae:ee:78' + assert device.hid == 'laptop-toshiba-satellite_l655-2b335208q' assert str(device.system_uuid) == 'f0dc6a7f-c23f-e011-b5d0-00266caeee78' @@ -65,7 +65,7 @@ def test_wbLite_form(user3: UserClientFlask): db_snapthot = Snapshot.query.one() device = db_snapthot.device - assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -78,7 +78,7 @@ def test_wbLite_api(user: UserClient): db_snapthot = Snapshot.query.one() device = db_snapthot.device - assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -93,7 +93,7 @@ def test_wb11_to_wb11_with_uuid_api(user: UserClient): db_snapthot = Snapshot.query.one() device = db_snapthot.device assert Computer.query.count() == 2 - assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert device.system_uuid is None # insert the same computer with wb11 with hid and with uuid, (new version) @@ -109,11 +109,8 @@ def test_wb11_to_wb11_with_uuid_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) - assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' + assert device.system_uuid is None @pytest.mark.mvp @@ -130,10 +127,7 @@ def test_wb11_with_uuid_to_wb11_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' # insert the same computer with wb11 with hid and with uuid, (new version) @@ -144,10 +138,7 @@ def test_wb11_with_uuid_to_wb11_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -165,10 +156,7 @@ def test_wb11_with_uuid_to_wb11_without_hid_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' # insert the same computer with wb11 with hid and with uuid, (new version) @@ -203,7 +191,7 @@ def test_wb11_to_wb11_with_uuid_form(user3: UserClientFlask): db_snapthot = Snapshot.query.one() device = db_snapthot.device assert Computer.query.count() == 2 - assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert device.system_uuid is None # insert the same computer with wb11 with hid and with uuid, (new version) @@ -222,11 +210,8 @@ def test_wb11_to_wb11_with_uuid_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) - assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' + assert device.system_uuid is None @pytest.mark.mvp @@ -254,10 +239,7 @@ def test_wb11_with_uuid_to_wb11_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' # insert the same computer with wb11 with hid and with uuid, (new version) @@ -275,10 +257,7 @@ def test_wb11_with_uuid_to_wb11_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -307,10 +286,7 @@ def test_wb11_with_uuid_to_wb11_without_hid_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' # insert the same computer with wb11 with hid and with uuid, (new version) @@ -340,10 +316,7 @@ def test_wb11_to_wblite_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert device.system_uuid is None snapshot_lite = conftest.file_json('system_uuid2.json') @@ -351,11 +324,9 @@ def test_wb11_to_wblite_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) - assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' + # assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' + assert device.system_uuid is None @pytest.mark.mvp @@ -367,10 +338,7 @@ def test_wblite_to_wb11_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' snapshot_11 = conftest.file_json('system_uuid3.json') @@ -378,10 +346,7 @@ def test_wblite_to_wb11_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -405,10 +370,7 @@ def test_wb11_to_wblite_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert device.system_uuid is None file_name = 'system_uuid2.json' @@ -424,11 +386,9 @@ def test_wb11_to_wblite_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) - assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' + # assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' + assert device.system_uuid is None @pytest.mark.mvp @@ -451,10 +411,7 @@ def test_wblite_to_wb11_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid == 'laptop-acer-aohappy-lusea0d010038879a01601' assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' file_name = 'system_uuid3.json' @@ -470,10 +427,7 @@ def test_wblite_to_wb11_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -486,10 +440,7 @@ def test_wblite_to_wblite_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' snapshot_lite = conftest.file_json('system_uuid2.json') @@ -498,10 +449,7 @@ def test_wblite_to_wblite_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -525,10 +473,7 @@ def test_wblite_to_wblite_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' file_name = 'system_uuid2.json' @@ -545,10 +490,7 @@ def test_wblite_to_wblite_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -562,10 +504,7 @@ def test_wb11_to_wb11_duplicity_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert device.system_uuid is None snapshot_11 = conftest.file_json('system_uuid3.json') @@ -573,7 +512,7 @@ def test_wb11_to_wb11_duplicity_api(user: UserClient): components = [x for x in snapshot_11['components'] if x['type'] != 'NetworkAdapter'] snapshot_11['components'] = components user.post(snapshot_11, res=Snapshot) - assert Computer.query.count() == 4 + assert Computer.query.count() == 2 for c in Computer.query.all(): assert 'laptop-acer-aohappy-lusea0d010038879a01601' in c.hid assert c.system_uuid is None @@ -599,10 +538,7 @@ def test_wb11_to_wb11_duplicity_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert device.system_uuid is None snapshot_11 = conftest.file_json('system_uuid3.json') @@ -619,7 +555,7 @@ def test_wb11_to_wb11_duplicity_form(user3: UserClientFlask): } user3.post(uri, data=data, content_type="multipart/form-data") - assert Computer.query.count() == 4 + assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: assert 'laptop-acer-aohappy-lusea0d010038879a01601' in device.hid @@ -636,10 +572,7 @@ def test_wb11_smbios_2_5_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert device.system_uuid is None @@ -663,10 +596,7 @@ def test_wb11_smbios_2_5_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert device.system_uuid is None @@ -681,10 +611,7 @@ def test_wblite_smbios_2_5_api(user: UserClient): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' @@ -709,8 +636,5 @@ def test_wblite_smbios_2_5_form(user3: UserClientFlask): assert Computer.query.count() == 2 for device in Computer.query.all(): if device.binding: - assert ( - device.hid - == 'laptop-acer-aohappy-lusea0d010038879a01601-88:ae:1d:a6:f3:d0' - ) + assert device.hid assert str(device.system_uuid) == '9ce64e36-829c-b19c-2111-88ae1da6f3d0' diff --git a/tests/test_tag.py b/tests/test_tag.py index 94c0f23c..cbdd7e5f 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -348,39 +348,6 @@ def test_tag_manual_link_search(app: Devicehub, user: UserClient): assert i['items'] -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_tag_secondary_workbench_link_find(user: UserClient): - """Creates and consumes tags with a secondary id, linking them - through Workbench to a device - and getting them through search.""" - t = Tag('foo', secondary='bar', owner_id=user.user['id']) - db.session.add(t) - db.session.flush() - assert Tag.from_an_id('bar').one() == Tag.from_an_id('foo').one() - with pytest.raises(ResourceNotFound): - Tag.from_an_id('nope').one() - - s = yaml2json('basic.snapshot') - s['device']['tags'] = [{'id': 'foo', 'secondary': 'bar', 'type': 'Tag'}] - snapshot, _ = user.post(json_encode(s), res=Snapshot) - dev = Device.query.filter_by(id=snapshot['device']['id']).one() - device, _ = user.get(res=Device, item=dev.devicehub_id) - desktop = dev.binding.device - assert [] == [x['id'] for x in device['tags']] - assert 'foo' in [x.id for x in desktop.tags] - assert 'bar' in [x.secondary for x in desktop.tags] - - r, _ = user.get( - res=Device, query=[('search', 'foo'), ('filter', {'type': ['Computer']})] - ) - assert len(r['items']) == 1 - r, _ = user.get( - res=Device, query=[('search', 'bar'), ('filter', {'type': ['Computer']})] - ) - assert len(r['items']) == 1 - - @pytest.mark.mvp def test_tag_create_tags_cli_csv(app: Devicehub, user: UserClient): """Checks creating tags with the CLI endpoint using a CSV.""" diff --git a/tests/test_workbench.py b/tests/test_workbench.py index 43c293ea..5f4221e5 100644 --- a/tests/test_workbench.py +++ b/tests/test_workbench.py @@ -15,8 +15,8 @@ from ereuse_devicehub.resources.action.models import ( from ereuse_devicehub.resources.device.exceptions import NeedsId from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.tag.model import Tag -from tests.conftest import file, file_workbench, json_encode, yaml2json from tests import conftest +from tests.conftest import file, file_workbench, json_encode, yaml2json @pytest.mark.mvp @@ -60,7 +60,7 @@ def test_workbench_server_condensed(user: UserClient): device, _ = user.get(res=Device, item=db_dev.devicehub_id) assert device['dataStorageSize'] == 1100 assert device['chassis'] == 'Tower' - assert device['hid'] == 'desktop-d1mr-d1ml-d1s-na1-s' + assert device['hid'] == 'desktop-d1mr-d1ml-d1s' assert device['graphicCardModel'] == device['components'][0]['model'] == 'gc1-1ml' assert device['networkSpeeds'] == [1000, 58] assert device['processorModel'] == device['components'][3]['model'] == 'p1-1ml' @@ -147,10 +147,7 @@ def test_real_hp_11(user: UserClient): s = file('real-hp.snapshot.11') snapshot, _ = user.post(res=em.Snapshot, data=s) pc = snapshot['device'] - assert ( - pc['hid'] - == 'desktop-hewlett-packard-hp_compaq_8100_elite_sff-czc0408yjg-6c:62:6d:81:22:9f' - ) + assert pc['hid'] == 'desktop-hewlett-packard-hp_compaq_8100_elite_sff-czc0408yjg' assert pc['chassis'] == 'Tower' assert set(e['type'] for e in snapshot['actions']) == { 'BenchmarkDataStorage', @@ -192,10 +189,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient): assert pc['model'] == '1001pxd' assert pc['serialNumber'] == 'b8oaas048286' assert pc['manufacturer'] == 'asustek computer inc.' - assert ( - pc['hid'] - == 'laptop-asustek_computer_inc-1001pxd-b8oaas048286-14:da:e9:42:f6:7c' - ) + assert pc['hid'] == 'laptop-asustek_computer_inc.-1001pxd-b8oaas048286' assert len(pc['tags']) == 0 assert pc['networkSpeeds'] == [ 100, @@ -209,14 +203,14 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient): wifi = components[0] assert ( wifi['hid'] == 'networkadapter-qualcomm_atheros-' - 'ar9285_wireless_network_adapter-74_2f_68_8b_fd_c8' + 'ar9285_wireless_network_adapter-74:2f:68:8b:fd:c8' ) assert wifi['serialNumber'] == '74:2f:68:8b:fd:c8' assert wifi['wireless'] eth = components[1] assert ( eth['hid'] == 'networkadapter-qualcomm_atheros-' - 'ar8152_v2_0_fast_ethernet-14_da_e9_42_f6_7c' + 'ar8152_v2.0_fast_ethernet-14:da:e9:42:f6:7c' ) assert eth['speed'] == 100 assert not eth['wireless'] @@ -225,7 +219,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient): assert cpu['cores'] == 1 assert cpu['threads'] == 1 assert cpu['speed'] == 1.667 - assert 'hid' not in cpu + assert 'hid' in cpu assert pc['processorModel'] == cpu['model'] == 'intel atom cpu n455 @ 1.66ghz' db_cpu = Device.query.filter_by(id=cpu['id']).one() cpu, _ = user.get(res=Device, item=db_cpu.devicehub_id) @@ -287,7 +281,7 @@ def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient): assert erase['severity'] == 'Info' assert hdd['privacy']['type'] == 'EraseBasic' mother = components[8] - assert mother['hid'] == 'motherboard-asustek_computer_inc-1001pxd-eee0123456789' + assert mother['hid'] == 'motherboard-asustek_computer_inc.-1001pxd-eee0123456789' @pytest.mark.mvp