diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index a56d41ce..f06a6ab0 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -52,6 +52,7 @@ from ereuse_devicehub.resources.device.models import ( Cellphone, Computer, ComputerMonitor, + DataStorage, Desktop, Device, Keyboard, @@ -852,7 +853,13 @@ class NewActionForm(ActionFormMixin): if not is_valid: return False - if self.type.data in ['Allocate', 'Deallocate', 'Trade', 'DataWipe']: + if self.type.data in [ + 'Allocate', + 'Deallocate', + 'Trade', + 'DataWipe', + 'EraseDataWipe', + ]: return False return True @@ -1068,15 +1075,26 @@ class DataWipeForm(ActionFormMixin): Model = db.Model._decl_class_registry.data[self.type.data]() self.instance = Model() devices = self.devices.data + if not self.document.success.data: + self.severity.data = Severity.Error.name severity = self.severity.data self.devices.data = self._devices self.severity.data = Severity[self.severity.data] document = copy.copy(self.document) del self.document - self.populate_obj(self.instance) - self.instance.document = document.form._obj - db.session.add(self.instance) + for dev in self._devices: + ac = None + for hd in dev.components: + if not isinstance(hd, DataStorage): + continue + ac = Model() + self.populate_obj(ac) + ac.parent = dev + ac.device = hd + ac.device_id = hd.id + ac.document = document.form._obj + db.session.add(ac) db.session.commit() self.devices.data = devices diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 7e5a7999..c00a8cb1 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -1176,9 +1176,9 @@ class ExportsView(View): row = [ ac.device.serial_number.upper(), ac.device.dhid, - ac.snapshot.uuid, + ac.snapshot.uuid if ac.snapshot else '', ac.type, - ac.get_phid(), + ac.parent.phid() if ac.parent else '', ac.severity, ac.created.strftime('%Y-%m-%d %H:%M:%S'), ] @@ -1192,11 +1192,12 @@ class ExportsView(View): if device.placeholder and device.placeholder.binding: device = device.placeholder.binding if isinstance(device, Computer): - for privacy in device.privacy: - erasures.append(privacy) + for ac in device.last_erase_action: + erasures.append(ac) elif isinstance(device, DataStorage): - if device.privacy: - erasures.append(device.privacy) + ac = device.last_erase_action + if ac: + erasures.append(ac) return erasures def get_costum_details(self, erasures): @@ -1264,9 +1265,14 @@ class ExportsView(View): 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' + result_success = 0 + result_failed = 0 + for e in erasures: + result = e.severity.get_public_name() + if "Failed" == result: + result_failed += 1 + if "Success" == result: + result_success += 1 erasures = sorted(erasures, key=lambda x: x.end_time) erasures_on_server = sorted(erasures_on_server, key=lambda x: x.end_time) @@ -1283,7 +1289,8 @@ class ExportsView(View): 'software': software, 'my_data': my_data, 'n_computers': n_computers, - 'result': result, + 'result_success': result_success, + 'result_failed': result_failed, 'customer_details': customer_details, 'erasure_hosts': erasures_host, 'erasures_normal': erasures_normal, diff --git a/ereuse_devicehub/migrations/versions/5169765e2653_add_new_erase_data_wipe.py b/ereuse_devicehub/migrations/versions/5169765e2653_add_new_erase_data_wipe.py new file mode 100644 index 00000000..1621a547 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/5169765e2653_add_new_erase_data_wipe.py @@ -0,0 +1,45 @@ +"""add new erase_data_wipe + +Revision ID: 5169765e2653 +Revises: 2f2ef041483a +Create Date: 2023-05-23 10:34:46.312074 + +""" +import sqlalchemy as sa +from alembic import context, op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '5169765e2653' +down_revision = '2f2ef041483a' +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( + 'erase_data_wipe', + sa.Column('document_id', sa.BigInteger(), nullable=False), + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.ForeignKeyConstraint( + ['document_id'], + [f'{get_inv()}.document.id'], + ), + sa.ForeignKeyConstraint( + ['id'], + [f'{get_inv()}.erase_basic.id'], + ), + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}', + ) + + +def downgrade(): + op.drop_table('erase_data_wipe', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/action/__init__.py b/ereuse_devicehub/resources/action/__init__.py index 96c966f4..9f83ad1d 100644 --- a/ereuse_devicehub/resources/action/__init__.py +++ b/ereuse_devicehub/resources/action/__init__.py @@ -246,6 +246,11 @@ class DataWipeDef(ActionDef): SCHEMA = schemas.DataWipe +class EraseDataWipe(ActionDef): + VIEW = None + SCHEMA = schemas.EraseDataWipe + + class AllocateDef(ActionDef): VIEW = AllocateView SCHEMA = schemas.Allocate diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 5a05256d..822387bc 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -489,6 +489,8 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice): """ if self.snapshot: return self.snapshot.device.phid() + if self.parent: + return self.parent.phid() return '' def register_proof(self): @@ -590,6 +592,45 @@ class ErasePhysical(EraseBasic): return "Physical" +class EraseDataWipe(EraseBasic): + """The device has been selected for insert one proof of erease disk.""" + + id = Column(UUID(as_uuid=True), ForeignKey(EraseBasic.id), primary_key=True) + document_comment = """The user that gets the device due this deal.""" + document_id = db.Column( + BigInteger, db.ForeignKey('data_wipe_document.id'), nullable=False + ) + document = db.relationship( + 'DataWipeDocument', + backref=backref('erase_actions', lazy=True, cascade=CASCADE_OWN), + primaryjoin='EraseDataWipe.document_id == DataWipeDocument.id', + ) + + def get_public_name(self): + return "EraseDataWipe" + + def __format__(self, format_spec: str) -> str: + v = '' + if 't' in format_spec: + v += '{} {}.'.format(self.type, self.severity) + if 's' in format_spec: + if not self.document: + v += 'On {}'.format(self.date_str) + return v + software = self.document.software or '' + url = self.document.url or '' + v += 'Software: {}, {}. '.format(software, url) + v += 'On {}'.format(self.date_str) + return v + + @property + def date_str(self): + day = self.created + if self.document: + day = self.document.date or self.end_time or self.created + return '{:%c}'.format(day) + + class Step(db.Model): erasure_id = Column( UUID(as_uuid=True), @@ -1670,6 +1711,7 @@ class ToPrepare(ActionWithMultipleDevices): class DataWipe(JoinedTableMixin, ActionWithMultipleDevices): + # class DataWipe(JoinedWithOneDeviceMixin, ActionWithOneDevice): """The device has been selected for insert one proof of erease disk.""" document_comment = """The user that gets the device due this deal.""" diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 6b10ca69..4760edf6 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -45,8 +45,8 @@ from ereuse_devicehub.resources.tradedocument import schemas as s_document from ereuse_devicehub.resources.tradedocument.models import TradeDocument from ereuse_devicehub.resources.user import schemas as s_user from ereuse_devicehub.resources.user.models import User -from ereuse_devicehub.teal.enums import Country, Currency, Subdivision -from ereuse_devicehub.teal.marshmallow import IP, URL, EnumField, SanitizedStr, Version +from ereuse_devicehub.teal.enums import Currency +from ereuse_devicehub.teal.marshmallow import URL, EnumField, SanitizedStr, Version from ereuse_devicehub.teal.resource import Schema @@ -588,6 +588,11 @@ class DataWipe(ActionWithMultipleDevicesCheckingOwner): document = NestedOn(s_generic_document.DataWipeDocument, only_query='id') +class EraseDataWipe(ActionWithMultipleDevicesCheckingOwner): + __doc__ = m.DataWipe.__doc__ + document = NestedOn(s_generic_document.DataWipeDocument, only_query='id') + + class Live(ActionWithOneDevice): __doc__ = m.Live.__doc__ """ @@ -808,12 +813,12 @@ class Trade(ActionWithMultipleDevices): @pre_load def adding_devices(self, data: dict): - if not 'devices' in data.keys(): + if 'devices' not in data.keys(): data['devices'] = [] @validates_schema def validate_lot(self, data: dict): - if not g.user.email in [data['user_from_email'], data['user_to_email']]: + if g.user.email not in [data['user_from_email'], data['user_to_email']]: txt = "you need to be one of the users of involved in the Trade" raise ValidationError(txt) @@ -879,7 +884,7 @@ class Trade(ActionWithMultipleDevices): txt = "you need one user for to do a trade" raise ValidationError(txt) - if not g.user.email in [user_from, user_to]: + if g.user.email not in [user_from, user_to]: txt = "you need to be one of participate of the action" raise ValidationError(txt) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index f1bd47a5..e7538809 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -827,7 +827,8 @@ class Device(Thing): ).first() def set_hid(self): - if 'property_hid' in app.blueprints.keys(): + is_component = isinstance(self, Component) + if 'property_hid' in app.blueprints.keys() and not is_component: try: from ereuse_devicehub.modules.device.utils import set_hid @@ -1396,6 +1397,22 @@ class Computer(Device): if privacy ) + @property + def last_erase_action(self): + components = self.components + if self.placeholder and self.placeholder.binding: + components = self.placeholder.binding.components + + return set( + ac + for ac in ( + hdd.last_erase_action + for hdd in components + if isinstance(hdd, DataStorage) + ) + if ac + ) + @property def external_document_erasure(self): """Returns the external ``DataStorage`` proof of erasure.""" @@ -1609,12 +1626,40 @@ class DataStorage(JoinedComponentTableMixin, Component): ev = None return ev + @property + def last_erase_action(self): + erase_auto = None + erase_manual = None + + if self.binding: + erase_auto = self.privacy + erase_manual = self.binding.device.privacy + if self.placeholder: + erase_manual = self.privacy + if self.placeholder.binding: + erase_auto = self.placeholder.binding.privacy + + if erase_auto and erase_manual: + return ( + erase_auto + if erase_auto.created > erase_manual.created + else erase_manual + ) + if erase_manual: + return erase_manual + if erase_auto: + return erase_auto + return None + def __format__(self, format_spec): v = super().__format__(format_spec) if 's' in format_spec: v += ' – {} GB'.format(self.size // 1000 if self.size else '?') return v + def get_size(self): + return '{} GB'.format(self.size // 1000 if self.size else '?') + @property def external_document_erasure(self): """Returns the external ``DataStorage`` proof of erasure.""" diff --git a/ereuse_devicehub/resources/documents/device_row.py b/ereuse_devicehub/resources/documents/device_row.py index ba165e0b..aac1f56a 100644 --- a/ereuse_devicehub/resources/documents/device_row.py +++ b/ereuse_devicehub/resources/documents/device_row.py @@ -429,11 +429,14 @@ class DeviceRow(BaseDeviceRow): self['{} {} Size (MB)'.format(ctype, i)] = none2str(component.size) - component_actions = sorted(component.actions, key=lambda x: x.created) + component_actions = [ac for ac in component.actions] + if component.binding: + component_actions.extend(component.binding.device.actions) + component_actions = sorted(component_actions, key=lambda x: x.created) erasures = [ a for a in component_actions - if a.type in ['EraseBasic', 'EraseSectors', 'DataWipe'] + if a.type in ['EraseBasic', 'EraseSectors', 'DataWipe', 'EraseDataWipe'] ] erasure = erasures[-1] if erasures else None if not erasure: @@ -441,7 +444,7 @@ class DeviceRow(BaseDeviceRow): 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': + elif hasattr(erasure, 'type') and erasure.type in ['DataWipe', 'EraseDataWipe']: self['Erasure {} {}'.format(ctype, i)] = none2str(component.chid) serial_number = none2str(component.serial_number) self['Erasure {} {} Serial Number'.format(ctype, i)] = serial_number diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index 82976a38..344e9f66 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -209,7 +209,7 @@
  • - + DataWipe diff --git a/ereuse_devicehub/templates/inventory/erasure.html b/ereuse_devicehub/templates/inventory/erasure.html index 681f31a6..5674ae9c 100644 --- a/ereuse_devicehub/templates/inventory/erasure.html +++ b/ereuse_devicehub/templates/inventory/erasure.html @@ -66,91 +66,108 @@ - -
    +
    - -
    -

    Data Sanitization Certificate

    -
    - -
    -
    - Entity Information +
    +

    {{ date_report }}, {{ software }}

    +
    + + +
    +
    - - + - - - - + +
    - 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 '' }} +
    +
    +
    -
    - 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 '' }} + + + + + + + + + + + + + + +
    + Entity Information +
    + Name: + + {{ customer_details and customer_details.company_name or ''}} +
    + Location: + + {{ customer_details and customer_details.location or '' }} +
    +
    +
    +
    Summary @@ -158,6 +175,16 @@
    + {% if customer_details and customer_details.transfer %} + + + + + {% endif %} {% if erasure_hosts %} @@ -192,10 +219,18 @@ + + + +
    + Code Transfer: + + {{ customer_details.transfer.code or '' }} +
    @@ -166,7 +193,7 @@ {% for e in erasure_hosts %} {% if e.serial_number %} - {{ e.serial_number.upper() }}{% if not loop.last %},{% endif %} + {{ (e.serial_number or '').upper() }}{% if not loop.last %},{% endif %} {% endif %} {% endfor %}
    - Sanitization result: + N° result Success: - {{ result }} + {{ result_success }} +
    + N° result Failed: + + {{ result_failed }}
    @@ -288,10 +323,10 @@ {% for erasure in erasures %} - {{ erasure.device.serial_number and erasure.device.serial_number.upper() or '' }} + {{ (erasure.device.serial_number or '').upper() }} - {{ erasure.parent.serial_number and erasure.parent.serial_number.upper() or '' }} + {{ (erasure.parent.serial_number or '').upper() }} {{ erasure.get_public_name() }} @@ -311,39 +346,38 @@
    {% 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.chid }}
    -
    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.chid }}
    -
    Tags: {{ erasure.device.parent.tags }}
    - {% endif %} + {% if loop.index == 1 %} +
    +

    Technical Details

    +
    {% endif %} +

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

    +
    +
    Storage Drive:
    +
    Model: {{ erasure.device.model }}
    +
    SN: {{ (erasure.device.serial_number or '').upper() }}
    +
    Size: {{ erasure.device.get_size()}}
    + {% if erasure.parent %} +
    +
    Computer Host:
    +
    Model: {{ erasure.parent.model }}
    +
    SN: {{ (erasure.parent.serial_number or '').upper() }}
    +
    DHID: {{ erasure.parent.dhid }}
    + {% endif %} +
    Erasure:
    {{ erasure.__format__('ts') }}
    {% if erasure.steps %}
    Erasure steps:
    -
    -
      - {% for step in erasure.steps %} -
    1. {{ step.__format__('') }}
    2. - {% endfor %} -
    -
    - {% endif %} +
    +
      + {% for step in erasure.steps %} +
    1. {{ step.__format__('') }}
    2. + {% endfor %} +
    +
    + {% endif %} {% if erasure.device.proofs %}
    DLT Proofs:
    diff --git a/tests/test_basic.py b/tests/test_basic.py index bf78792d..3922e959 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -125,4 +125,4 @@ def test_api_docs(client: Client): 'scheme': 'basic', 'name': 'Authorization', } - assert len(docs['definitions']) == 134 + assert len(docs['definitions']) == 135 diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index b5bd45ab..342619c7 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -1375,6 +1375,34 @@ def test_action_datawipe(user3: UserClientFlask): assert dev.binding.device.devicehub_id in body +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_action_erasedatawipe(user3: UserClientFlask): + snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') + dev = snap.device + uri = '/inventory/device/' + user3.get(uri) + + b_file = b'1234567890' + file_name = "my_file.doc" + file_upload = (BytesIO(b_file), file_name) + + data = { + 'csrf_token': generate_csrf(), + 'type': "EraseDataWipe", + 'severity': "Info", + 'devices': "{}".format(dev.binding.device.id), + 'document-file_name': file_upload, + } + + uri = '/inventory/action/datawipe/add/' + body, status = user3.post(uri, data=data, content_type="multipart/form-data") + assert status == '200 OK' + assert dev.binding.device.actions[-1].type == 'EraseDataWipe' + assert 'Action "EraseDataWipe" created successfully!' in body + assert dev.binding.device.devicehub_id in body + + @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_wb_settings(user3: UserClientFlask):