diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d69d4a2..ec1dff26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,35 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ml). ## testing +- [added] #414 add new vars in the settings file for wb. + +## [2.5.0] - 2022-11-30 +- [added] #407 erasure section with tabs in top. +- [added] #411 add new generic device as Other. +- [changed] #409 add backend pagination instead of javascript. +- [changed] #410 change teh top search for advanced search. +- [fixed] #412 show in snapshots log, type upload correctly. +- [fixed] #413 put order in documents. +- [fixed] #415 put prefix of lot in result of search. + +## [2.4.3] - 2022-11-18 +- [added] #386 add registration module. +- [added] #387 add template settings for Secure Erasure. +- [added] #397 add obada standard export. +- [added] #402 add reset password module. +- [added] #406 add orphans disks page. +- [changed] #391 add dhid in table and export of Erasure section. +- [changed] #395 change response for the new api to workbench. +- [changed] #396 modularize commands. +- [fixed] #388 lock update different motherboard with the same id. +- [fixed] #389 some datastorage without placeholder. +- [fixed] #390 fix image in form edit device. +- [fixed] #398 placeholder in new components. +- [fixed] #399 add api_host in config. +- [fixed] #401 db_host need to be api address. +- [fixed] #403 change delimiter in obada export. +- [fixed] #404 javascript select all devices. +- [fixed] #405 update pillow. ## [2.4.2] - 2022-10-18 - [added] #373 Enhancement - UX Lots. diff --git a/docs/conf.py b/docs/conf.py index a1b4e42e..73d3ccc9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,6 @@ from teal.enums import Country, Currency, Layouts, Subdivision from teal.marshmallow import EnumField from ereuse_devicehub.marshmallow import NestedOn -from ereuse_devicehub.resources.schemas import Thing project = 'Devicehub' copyright = '2020, eReuse.org team' @@ -56,7 +55,7 @@ extensions = [ 'sphinx.ext.viewcode', 'sphinxcontrib.plantuml', 'sphinx.ext.autosectionlabel', - 'sphinx.ext.autodoc' + 'sphinx.ext.autodoc', ] # Add any paths that contain templates here, relative to this directory. @@ -126,15 +125,12 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -144,18 +140,20 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'Devicehub.tex', 'Devicehub Documentation', - 'eReuse.org team', 'manual'), + ( + master_doc, + 'Devicehub.tex', + 'Devicehub Documentation', + 'eReuse.org team', + 'manual', + ), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'devicehub', 'Devicehub Documentation', - [author], 1) -] +man_pages = [(master_doc, 'devicehub', 'Devicehub Documentation', [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -163,9 +161,15 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Devicehub', 'Devicehub Documentation', - author, 'Devicehub', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + 'Devicehub', + 'Devicehub Documentation', + author, + 'Devicehub', + 'One line description of project.', + 'Miscellaneous', + ), ] # -- Extension configuration ------------------------------------------------- @@ -199,6 +203,7 @@ class DhlistDirective(Directive): This requires :py:class:`ereuse_devicehub.resources.schemas.SchemaMeta`. You will find in that module more information. """ + has_content = False # Definition of passed-in options @@ -216,7 +221,7 @@ class DhlistDirective(Directive): sections = [] sections.append(self.links(things)) # Make index - for thng in things: # type: Thing + for thng in things: # Generate a section for each class, with a title, # fields description and a paragraph section = n.section(ids=[self._id(thng)]) @@ -228,7 +233,9 @@ class DhlistDirective(Directive): for key, f in thng._own: name = n.field_name(text=f.data_key or key) body = [ - self.parse('{} {}'.format(self.type(f), f.metadata.get('description', ''))) + self.parse( + '{} {}'.format(self.type(f), f.metadata.get('description', '')) + ) ] if isinstance(f, EnumField): body.append(self._parse_enum_field(f)) @@ -244,6 +251,7 @@ class DhlistDirective(Directive): def _parse_enum_field(self, f): from ereuse_devicehub.resources.device import states + if issubclass(f.enum, (Subdivision, Currency, Country, Layouts, states.State)): return self.parse(f.enum.__doc__) else: @@ -298,7 +306,7 @@ class DhlistDirective(Directive): def parse(self, text) -> n.container: """Parses text possibly containing ReST stuff and adds it in - a node.""" + a node.""" p = n.container('') self.state.nested_parse(StringList(string2lines(inspect.cleandoc(text))), 0, p) return p diff --git a/ereuse_devicehub/__init__.py b/ereuse_devicehub/__init__.py index 60be088d..50062f87 100644 --- a/ereuse_devicehub/__init__.py +++ b/ereuse_devicehub/__init__.py @@ -1 +1 @@ -__version__ = "2.4.2" +__version__ = "2.5.0" diff --git a/ereuse_devicehub/config.py b/ereuse_devicehub/config.py index 79a9b75b..a41c9640 100644 --- a/ereuse_devicehub/config.py +++ b/ereuse_devicehub/config.py @@ -1,6 +1,5 @@ from distutils.version import StrictVersion from itertools import chain -from typing import Set from decouple import config from teal.auth import TokenAuth @@ -44,7 +43,7 @@ class DevicehubConfig(Config): import_resource(metric_def), ), ) - PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str] + PASSWORD_SCHEMES = {'pbkdf2_sha256'} SECRET_KEY = config('SECRET_KEY') DB_USER = config('DB_USER', 'dhub') DB_PASSWORD = config('DB_PASSWORD', 'ereuse') diff --git a/ereuse_devicehub/dummy/dummy.py b/ereuse_devicehub/dummy/dummy.py index 7947a85b..ee7f7c15 100644 --- a/ereuse_devicehub/dummy/dummy.py +++ b/ereuse_devicehub/dummy/dummy.py @@ -1,7 +1,6 @@ import itertools import json from pathlib import Path -from typing import Set import click import click_spinner @@ -109,7 +108,7 @@ class Dummy: files = tuple(Path(__file__).parent.joinpath('files').iterdir()) print('done.') sample_pc = None # We treat this one as a special sample for demonstrations - pcs = set() # type: Set[int] + pcs = set() with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar: for path in bar: with path.open() as f: diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index cb24baa8..0769796d 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -50,11 +50,15 @@ from ereuse_devicehub.resources.device.models import ( Keyboard, Laptop, MemoryCardReader, + Monitor, Mouse, + Other, Placeholder, + Projector, Server, Smartphone, Tablet, + TelevisionSet, ) from ereuse_devicehub.resources.documents.models import DataWipeDocument from ereuse_devicehub.resources.enums import Severity @@ -81,16 +85,23 @@ DEVICES = { ], "Mobile, tablet & smartphone": [ "All Mobile", - "Mobile", "Tablet", "Smartphone", "Cellphone", ], "Drives & Storage": [ "All DataStorage", - "HardDrives", + "HardDrive", "SolidStageDrive", ], + "Accessories": [ + "All Accessories", + "Mouse", + "MemoryCardReader", + "SAI", + "Keyboard", + ], + "Other Devices": ["Other"], } COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer'] @@ -98,6 +109,8 @@ COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer'] MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"] MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"] STORAGE = ["HardDrive", "SolidStateDrive"] +ACCESSORIES = ["Mouse", "MemoryCardReader", "SAI", "Keyboard"] +OTHERS = ["Other"] class AdvancedSearchForm(FlaskForm): @@ -170,7 +183,7 @@ class FilterForm(FlaskForm): # Generic Filters if "All Devices" == self.device_type: - filter_type = COMPUTERS + MONITORS + MOBILE + filter_type = COMPUTERS + MONITORS + MOBILE + OTHERS elif "All Computers" == self.device_type: filter_type = COMPUTERS @@ -184,6 +197,9 @@ class FilterForm(FlaskForm): elif "All DataStorage" == self.device_type: filter_type = STORAGE + elif "All Accessories" == self.device_type: + filter_type = ACCESSORIES + if filter_type: self.devices = self.devices.filter(Device.type.in_(filter_type)) @@ -374,10 +390,14 @@ class NewDeviceForm(FlaskForm): "Tablet": Tablet, "Cellphone": Cellphone, "ComputerMonitor": ComputerMonitor, + "Monitor": Monitor, + "TelevisionSet": TelevisionSet, + "Projector": Projector, "Mouse": Mouse, "Keyboard": Keyboard, "SAI": SAI, "MemoryCardReader": MemoryCardReader, + "Other": Other, } def reset_from_obj(self): diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 93740af5..b9c3a659 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -55,15 +55,26 @@ devices = Blueprint('inventory', __name__, url_prefix='/inventory') logger = logging.getLogger(__name__) +PER_PAGE = 20 + + class DeviceListMixin(GenericMixin): template_name = 'inventory/device_list.html' def get_context(self, lot_id=None, all_devices=False): super().get_context() + page = int(request.args.get('page', 1)) + per_page = int(request.args.get('per_page', PER_PAGE)) + filter = request.args.get('filter', "All+Computers") + # import pdb; pdb.set_trace() + lots = self.context['lots'] form_filter = FilterForm(lots, lot_id, all_devices=all_devices) - devices = form_filter.search() + devices = form_filter.search().paginate(page=page, per_page=per_page) + devices.first = per_page * devices.page - per_page + 1 + devices.last = len(devices.items) + devices.first - 1 + lot = None form_transfer = '' form_delivery = '' @@ -92,6 +103,7 @@ class DeviceListMixin(GenericMixin): 'tags': self.get_user_tags(), 'list_devices': self.get_selected_devices(form_new_action), 'all_devices': all_devices, + 'filter': filter, } ) @@ -115,15 +127,43 @@ class DeviceListMixin(GenericMixin): class ErasureListView(DeviceListMixin): template_name = 'inventory/erasure_list.html' - def dispatch_request(self): + def dispatch_request(self, orphans=0): self.get_context() - self.get_devices() + self.get_devices(orphans) return flask.render_template(self.template_name, **self.context) - def get_devices(self): + def get_devices(self, orphans): + page = int(request.args.get('page', 1)) + per_page = int(request.args.get('per_page', PER_PAGE)) + erasure = EraseBasic.query.filter_by(author=g.user).order_by( EraseBasic.created.desc() ) + if orphans: + schema = app.config.get('SCHEMA') + _user = g.user.id + sql = f""" + select action.id from {schema}.action as action + inner join {schema}.erase_basic as erase + on action.id=erase.id + inner join {schema}.device as device + on device.id=action.parent_id + inner join {schema}.placeholder as placeholder + on placeholder.binding_id=device.id + where (action.parent_id is null or placeholder.kangaroo=true) + and action.author_id='{_user}' + """ + ids = (e[0] for e in db.session.execute(sql)) + erasure = ( + EraseBasic.query.filter(EraseBasic.id.in_(ids)) + .filter_by(author=g.user) + .order_by(EraseBasic.created.desc()) + ) + self.context['orphans'] = True + + erasure = erasure.paginate(page=page, per_page=per_page) + erasure.first = per_page * erasure.page - per_page + 1 + erasure.last = len(erasure.items) + erasure.first - 1 self.context['erasure'] = erasure @@ -1174,43 +1214,17 @@ class SnapshotListView(GenericMixin): return flask.render_template(self.template_name, **self.context) def get_snapshots_log(self): + page = int(request.args.get('page', 1)) + per_page = int(request.args.get('per_page', PER_PAGE)) + snapshots_log = SnapshotsLog.query.filter( SnapshotsLog.owner == g.user ).order_by(SnapshotsLog.created.desc()) - logs = {} - for snap in snapshots_log: - try: - system_uuid = snap.snapshot.device.system_uuid or '' - except AttributeError: - system_uuid = '' - if snap.snapshot_uuid not in logs: - logs[snap.snapshot_uuid] = { - 'sid': snap.sid, - 'snapshot_uuid': snap.snapshot_uuid, - 'version': snap.version, - 'device': snap.get_device(), - 'system_uuid': system_uuid, - 'status': snap.get_status(), - 'severity': snap.severity, - 'created': snap.created, - 'type_device': snap.get_type_device(), - 'original_dhid': snap.get_original_dhid(), - 'new_device': snap.get_new_device(), - } - continue - - if snap.created > logs[snap.snapshot_uuid]['created']: - logs[snap.snapshot_uuid]['created'] = snap.created - - if snap.severity > logs[snap.snapshot_uuid]['severity']: - logs[snap.snapshot_uuid]['severity'] = snap.severity - logs[snap.snapshot_uuid]['status'] = snap.get_status() - - result = sorted(logs.values(), key=lambda d: d['created']) - result.reverse() - - return result + snapshots_log = snapshots_log.paginate(page=page, per_page=per_page) + snapshots_log.first = per_page * snapshots_log.page - per_page + 1 + snapshots_log.last = len(snapshots_log.items) + snapshots_log.first - 1 + return snapshots_log class SnapshotDetailView(GenericMixin): @@ -1340,10 +1354,17 @@ class PlaceholderLogListView(GenericMixin): return flask.render_template(self.template_name, **self.context) def get_placeholders_log(self): + page = int(request.args.get('page', 1)) + per_page = int(request.args.get('per_page', PER_PAGE)) + placeholder_log = PlaceholdersLog.query.filter( PlaceholdersLog.owner == g.user ).order_by(PlaceholdersLog.created.desc()) + placeholder_log = placeholder_log.paginate(page=page, per_page=per_page) + placeholder_log.first = per_page * placeholder_log.page - per_page + 1 + placeholder_log.last = len(placeholder_log.items) + placeholder_log.first - 1 + return placeholder_log @@ -1452,3 +1473,7 @@ devices.add_url_rule( devices.add_url_rule( '/device/erasure/', view_func=ErasureListView.as_view('device_erasure_list') ) +devices.add_url_rule( + '/device/erasure//', + view_func=ErasureListView.as_view('device_erasure_list_orphans'), +) diff --git a/ereuse_devicehub/migrations/versions/410aadae7652_device_other.py b/ereuse_devicehub/migrations/versions/410aadae7652_device_other.py new file mode 100644 index 00000000..1d22d1dd --- /dev/null +++ b/ereuse_devicehub/migrations/versions/410aadae7652_device_other.py @@ -0,0 +1,39 @@ +"""device other + +Revision ID: 410aadae7652 +Revises: d65745749e34 +Create Date: 2022-11-29 12:00:40.272121 + +""" +import sqlalchemy as sa +from alembic import context, op + +# revision identifiers, used by Alembic. +revision = '410aadae7652' +down_revision = 'd65745749e34' +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( + 'other', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint( + ['id'], + [f'{get_inv()}.device.id'], + ), + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}', + ) + + +def downgrade(): + op.drop_table('other', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/migrations/versions/af038a8a388c_add_settings_version_to_snapshots.py b/ereuse_devicehub/migrations/versions/af038a8a388c_add_settings_version_to_snapshots.py new file mode 100644 index 00000000..1b7b5a20 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/af038a8a388c_add_settings_version_to_snapshots.py @@ -0,0 +1,35 @@ +"""add settings_version to snapshots + +Revision ID: af038a8a388c +Revises: 410aadae7652 +Create Date: 2022-11-30 16:21:05.768024 + +""" +import citext +import sqlalchemy as sa +from alembic import context, op + +# revision identifiers, used by Alembic. +revision = 'af038a8a388c' +down_revision = '410aadae7652' +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( + 'snapshot', + sa.Column('settings_version', citext.CIText(), nullable=True), + schema=f'{get_inv()}', + ) + + +def downgrade(): + op.drop_column('snapshot', 'settings_version', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/parser/computer.py b/ereuse_devicehub/parser/computer.py index afd2e17d..b41ce833 100644 --- a/ereuse_devicehub/parser/computer.py +++ b/ereuse_devicehub/parser/computer.py @@ -4,7 +4,7 @@ from contextlib import suppress from datetime import datetime from fractions import Fraction from math import hypot -from typing import Iterator, List, Optional, Type, TypeVar +from typing import Iterator, List, Optional, TypeVar import dateutil.parser from ereuse_utils import getter, text @@ -404,7 +404,7 @@ class Computer(Device): chassis value. """ - COMPONENTS = list(Component.__subclasses__()) # type: List[Type[Component]] + COMPONENTS = list(Component.__subclasses__()) COMPONENTS.remove(Motherboard) def __init__(self, node: dict) -> None: diff --git a/ereuse_devicehub/parser/models.py b/ereuse_devicehub/parser/models.py index 4d298af0..38ddbaf7 100644 --- a/ereuse_devicehub/parser/models.py +++ b/ereuse_devicehub/parser/models.py @@ -73,11 +73,25 @@ class SnapshotsLog(Thing): snapshots = [] for s in self.snapshot.device.actions: if s == self.snapshot: - continue + break if s.type == self.snapshot.type: snapshots.append(s) return snapshots and 'Update' or 'New Device' + def get_system_uuid(self): + try: + return self.snapshot.device.system_uuid or '' + except AttributeError: + return '' + + def get_version(self): + if not self.snapshot: + return self.version + settings_version = self.snapshot.settings_version or '' + settings_version = "".join([x[0] for x in settings_version.split(' ') if x]) + + return "{} ({})".format(self.version, settings_version) + class PlaceholdersLog(Thing): """A Placeholder log.""" diff --git a/ereuse_devicehub/parser/snapshot.py b/ereuse_devicehub/parser/snapshot.py index 559a7f48..490797af 100644 --- a/ereuse_devicehub/parser/snapshot.py +++ b/ereuse_devicehub/parser/snapshot.py @@ -1,7 +1,6 @@ from datetime import datetime, timezone -from typing import List -from ereuse_workbench.computer import Component, Computer, DataStorage +from ereuse_workbench.computer import Computer, DataStorage from ereuse_workbench.utils import Dumpeable @@ -24,8 +23,8 @@ class Snapshot(Dumpeable): self.endTime = datetime.now(timezone.utc) self.closed = False self.elapsed = None - self.device = None # type: Computer - self.components = None # type: List[Component] + self.device = None + self.components = None self._storages = None def computer(self): diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index a5121748..6060e037 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -676,6 +676,7 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice): of time it took to complete. """ sid = Column(CIText(), nullable=True) + settings_version = Column(CIText(), nullable=True) is_server_erase = Column(Boolean(), nullable=True) def get_last_lifetimes(self): diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index f24e2c83..da70c49a 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -453,6 +453,7 @@ class Snapshot(ActionWithOneDevice): 'Order is preserved, so the component num 0 when' 'submitting is the component num 0 when returning it back.', ) + settings_version = String(required=False) @validates_schema def validate_workbench_version(self, data: dict): diff --git a/ereuse_devicehub/resources/device/definitions.py b/ereuse_devicehub/resources/device/definitions.py index cdc99c05..d5678135 100644 --- a/ereuse_devicehub/resources/device/definitions.py +++ b/ereuse_devicehub/resources/device/definitions.py @@ -4,7 +4,11 @@ from teal.resource import Converters, Resource from ereuse_devicehub.resources.device import schemas from ereuse_devicehub.resources.device.models import Manufacturer -from ereuse_devicehub.resources.device.views import DeviceView, DeviceMergeView, ManufacturerView +from ereuse_devicehub.resources.device.views import ( + DeviceMergeView, + DeviceView, + ManufacturerView, +) class DeviceDef(Resource): @@ -13,25 +17,42 @@ class DeviceDef(Resource): ID_CONVERTER = Converters.string AUTH = False # We manage this at each view - def __init__(self, app, - import_name=__name__, - static_folder='static', - static_url_path=None, - template_folder='templates', - url_prefix=None, - subdomain=None, - url_defaults=None, - root_path=None, - cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder='static', + static_url_path=None, + template_folder='templates', + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) - device_merge = DeviceMergeView.as_view('merge-devices', definition=self, auth=app.auth) + device_merge = DeviceMergeView.as_view( + 'merge-devices', definition=self, auth=app.auth + ) if self.AUTH: device_merge = app.auth.requires_auth(device_merge) - path = '/<{value}:dev1_id>/merge/<{value}:dev2_id>'.format(value=self.ID_CONVERTER.value) + path = '/<{value}:dev1_id>/merge/<{value}:dev2_id>'.format( + value=self.ID_CONVERTER.value + ) # self.add_url_rule(path, view_func=device_merge, methods={'POST'}) @@ -40,11 +61,31 @@ class ComputerDef(DeviceDef): VIEW = None SCHEMA = schemas.Computer - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class DesktopDef(ComputerDef): @@ -66,11 +107,31 @@ class MonitorDef(DeviceDef): VIEW = None SCHEMA = schemas.Monitor - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class ComputerMonitorDef(MonitorDef): @@ -83,15 +144,40 @@ class TelevisionSetDef(MonitorDef): SCHEMA = schemas.TelevisionSet +class ProjectorDef(MonitorDef): + VIEW = None + SCHEMA = schemas.Projector + + class MobileDef(DeviceDef): VIEW = None SCHEMA = schemas.Mobile - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class SmartphoneDef(MobileDef): @@ -113,11 +199,31 @@ class ComponentDef(DeviceDef): VIEW = None SCHEMA = schemas.Component - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class GraphicCardDef(ComponentDef): @@ -184,11 +290,31 @@ class ComputerAccessoryDef(DeviceDef): VIEW = None SCHEMA = schemas.ComputerAccessory - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class MouseDef(ComputerAccessoryDef): @@ -215,11 +341,31 @@ class NetworkingDef(DeviceDef): VIEW = None SCHEMA = schemas.Networking - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class RouterDef(NetworkingDef): @@ -246,11 +392,31 @@ class PrinterDef(DeviceDef): VIEW = None SCHEMA = schemas.Printer - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class LabelPrinterDef(PrinterDef): @@ -262,11 +428,31 @@ class SoundDef(DeviceDef): VIEW = None SCHEMA = schemas.Sound - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class MicrophoneDef(SoundDef): @@ -278,11 +464,31 @@ class VideoDef(DeviceDef): VIEW = None SCHEMA = schemas.Video - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class VideoScalerDef(VideoDef): @@ -299,11 +505,31 @@ class CookingDef(DeviceDef): VIEW = None SCHEMA = schemas.Cooking - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class Mixer(CookingDef): @@ -315,11 +541,31 @@ class DIYAndGardeningDef(DeviceDef): VIEW = None SCHEMA = schemas.DIYAndGardening - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class DrillDef(DIYAndGardeningDef): @@ -331,22 +577,62 @@ class PackOfScrewdriversDef(DIYAndGardeningDef): VIEW = None SCHEMA = schemas.PackOfScrewdrivers - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class HomeDef(DeviceDef): VIEW = None SCHEMA = schemas.Home - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class DehumidifierDef(HomeDef): @@ -363,11 +649,31 @@ class RecreationDef(DeviceDef): VIEW = None SCHEMA = schemas.Recreation - def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) class BikeDef(RecreationDef): @@ -389,3 +695,35 @@ class ManufacturerDef(Resource): """Loads the manufacturers to the database.""" if exclude_schema != 'common': Manufacturer.add_all_to_session(db.session) + + +class OtherDef(DeviceDef): + VIEW = None + SCHEMA = schemas.Computer + SCHEMA = schemas.Other + + def __init__( + self, + app, + import_name=__name__, + static_folder=None, + static_url_path=None, + template_folder=None, + url_prefix=None, + subdomain=None, + url_defaults=None, + root_path=None, + cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(), + ): + super().__init__( + app, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_commands, + ) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 3df44688..61e8cae7 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -635,6 +635,14 @@ class Device(Thing): return self.binding.device.devicehub_id return self.devicehub_id + @property + def my_partner(self): + if self.placeholder and self.placeholder.binding: + return self.placeholder.binding + if self.binding: + return self.binding.device + return self + @property def get_updated(self): if self.placeholder and self.placeholder.binding: @@ -1394,6 +1402,19 @@ class DataStorage(JoinedComponentTableMixin, Component): except LookupError: return None + @property + def orphan(self): + if not self.parent: + return True + + if self.parent.placeholder and self.parent.placeholder.kangaroo: + return True + + if self.parent.binding and self.parent.binding.kangaroo: + return True + + return False + class HardDrive(DataStorage): pass @@ -1684,3 +1705,11 @@ def create_code_tag(mapper, connection, device): # from flask_sqlalchemy import event # event.listen(Device, 'after_insert', create_code_tag, propagate=True) + + +class Other(Device): + """ + Used for put in there all devices than not have actualy a class + """ + + id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) diff --git a/ereuse_devicehub/resources/device/schemas.py b/ereuse_devicehub/resources/device/schemas.py index c217b3dd..76a1fee2 100644 --- a/ereuse_devicehub/resources/device/schemas.py +++ b/ereuse_devicehub/resources/device/schemas.py @@ -270,6 +270,10 @@ class TelevisionSet(Monitor): __doc__ = m.TelevisionSet.__doc__ +class Projector(Monitor): + __doc__ = m.Projector.__doc__ + + class Mobile(Device): __doc__ = m.Mobile.__doc__ @@ -578,3 +582,7 @@ class Bike(Recreation): class Racket(Recreation): pass + + +class Other(Device): + pass diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index c3416db2..80922e47 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -306,7 +306,7 @@ class LotDeviceView(LotBaseChildrenView): dev_qry = Device.query.filter(Device.id.in_(ids)).filter(Device.owner == g.user) for dev in dev_qry: - if isinstance(dev, DataStorage) and dev.parent: + if isinstance(dev, DataStorage) and not dev.orphan: continue devices.add(dev) diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index 7f211637..07af62d1 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -172,7 +172,9 @@ class TradeDocument(Thing): return sorted(ev for ev in actions if ev.severity >= Severity.Warning) def __lt__(self, other): - return self.id < other.id + if self.id and other.id: + return self.id < other.id + return False def __str__(self) -> str: return '{0.file_name}'.format(self) diff --git a/ereuse_devicehub/static/js/main.js b/ereuse_devicehub/static/js/main.js index 474658d5..7bea4a96 100644 --- a/ereuse_devicehub/static/js/main.js +++ b/ereuse_devicehub/static/js/main.js @@ -48,12 +48,12 @@ /** * Search bar toggle - */ if (select(".search-bar-toggle")) { on("click", ".search-bar-toggle", (e) => { select(".search-bar").classList.toggle("search-bar-show") }) } + */ /** * Navbar links active state on scroll diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html index 57e13f3a..f3865433 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html @@ -8,6 +8,12 @@ + @@ -344,6 +346,38 @@ {{ form_filter.filter.data or "Computer" }}

+
+
+ +
+ +
+
@@ -362,7 +396,7 @@ - {% for dev in devices %} + {% for dev in devices.items %} {% if dev.placeholder and (not dev.parent_id or dev.parent.placeholder.kangaroo) %}
@@ -421,6 +455,57 @@ {% endfor %}
+
+
+ Showing {{ devices.first }} to {{ devices.last }} of {{ devices.total }} entries +
+ +
+
@@ -592,10 +677,25 @@ {% include "inventory/alert_lots_changes.html" %} + {% if config['DEBUG'] %} diff --git a/ereuse_devicehub/templates/inventory/erasure_list.html b/ereuse_devicehub/templates/inventory/erasure_list.html index e33bed45..2f36de71 100644 --- a/ereuse_devicehub/templates/inventory/erasure_list.html +++ b/ereuse_devicehub/templates/inventory/erasure_list.html @@ -18,9 +18,54 @@
-
+ +
+ {% if orphans %} + + {% endif %} + {% if orphans %} + + {% endif %} + +
+
+
+ +
+ +
+
@@ -59,10 +161,10 @@ - {% for ac in erasure %} + {% for ac in erasure.items %}
- {% if ac.device.phid() %} + {% if ac.device.get_type_logo() %} + + {% endif %} {{ ac.device.serial_number.upper() }} {% else %} + {% if ac.device.get_type_logo() %} + + {% endif %} {{ ac.device.serial_number.upper() }} {% endif %} + {% if ac.device.my_partner.lots | length > 0 %} +
+ {% for lot in ac.device.my_partner.get_lots_for_template() %} + {{ lot }} + {% endfor %} +
+ {% endif %}
{% if ac.device.phid() %} @@ -91,7 +206,7 @@ - {{ ac.snapshot.uuid }} + {{ ac.snapshot.uuid }} @@ -115,135 +230,54 @@ {% endfor %}
- +
+
+
+ Showing {{ erasure.first }} to {{ erasure.last }} of {{ erasure.total }} entries +
+
- {% if lot and not lot.is_temporary %} -
-
Documents
- - - - - - - - - {% for doc in lot.trade.documents %} - - - - - {% endfor %} - -
FileUploaded on
- {% if doc.url %} - {{ doc.file_name}} - {% else %} - {{ doc.file_name}} - {% endif %} - - {{ doc.created.strftime('%H:%M %d-%m-%Y')}} -
-
-
-
Transfer
-
- {{ form_transfer.csrf_token }} - - {% for field in form_transfer %} - {% if field != form_transfer.csrf_token %} -
- {% if field != form_transfer.type %} - {{ field.label(class_="form-label") }} - {% if field == form_transfer.code %} - * - {% endif %} - {{ field }} - {{ field.description }} - {% if field.errors %} -

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

- {% endif %} - {% endif %} -
- {% endif %} - {% endfor %} - -
- Cancel - -
-
-
-
-
Delivery Note
-
- {{ form_delivery.csrf_token }} - - {% for field in form_delivery %} - {% if field != form_delivery.csrf_token %} -
- {% if field != form_delivery.type %} - {{ field.label(class_="form-label") }} - {{ field }} - {{ field.description }} - {% if field.errors %} -

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

- {% endif %} - {% endif %} -
- {% endif %} - {% endfor %} - - {% if lot.transfer and form_receiver.is_editable() %} -
- Cancel - -
- {% endif %} -
-
-
-
Receiver Note
-
- {{ form_receiver.csrf_token }} - - {% for field in form_receiver %} - {% if field != form_receiver.csrf_token %} -
- {% if field != form_receiver.type %} - {{ field.label(class_="form-label") }} - {{ field }} - {{ field.description }} - {% if field.errors %} -

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

- {% endif %} - {% endif %} -
- {% endif %} - {% endfor %} - - {% if lot.transfer and form_receiver.is_editable() %} -
- Cancel - -
- {% endif %} -
-
- {% endif %}
@@ -255,21 +289,32 @@
-{% include "inventory/lot_delete_modal.html" %} -{% include "inventory/actions.html" %} -{% include "inventory/allocate.html" %} -{% include "inventory/data_wipe.html" %} -{% include "inventory/trade.html" %} {% include "inventory/alert_export_error.html" %} {% include "inventory/alert_lots_changes.html" %} + + {% if config['DEBUG'] %} {% else %} diff --git a/ereuse_devicehub/templates/inventory/placeholder_log_list.html b/ereuse_devicehub/templates/inventory/placeholder_log_list.html index aaff827d..bc16ccab 100644 --- a/ereuse_devicehub/templates/inventory/placeholder_log_list.html +++ b/ereuse_devicehub/templates/inventory/placeholder_log_list.html @@ -22,6 +22,38 @@
+
+
+ +
+ +
+
@@ -34,7 +66,7 @@ - {% for log in placeholders_log %} + {% for log in placeholders_log.items %}
{{ log.phid }} @@ -58,6 +90,38 @@ {% endfor %}
+
+
+ Showing {{ placeholders_log.first }} to {{ placeholders_log.last }} of {{ placeholders_log.total }} entries +
+ +
@@ -75,6 +139,18 @@ + {% endblock main %} diff --git a/ereuse_devicehub/templates/inventory/search.html b/ereuse_devicehub/templates/inventory/search.html index 9f724586..57f62975 100644 --- a/ereuse_devicehub/templates/inventory/search.html +++ b/ereuse_devicehub/templates/inventory/search.html @@ -16,7 +16,7 @@
-
+
@@ -307,8 +307,8 @@ {% if dev.lots | length > 0 %}
- {% for lot in dev.lots %} - {{ lot.name }} + {% for lot in dev.get_lots_for_template() %} + {{ lot }} {% endfor %}
{% endif %} diff --git a/ereuse_devicehub/templates/inventory/snapshots_list.html b/ereuse_devicehub/templates/inventory/snapshots_list.html index 4c35f44e..126d3ac3 100644 --- a/ereuse_devicehub/templates/inventory/snapshots_list.html +++ b/ereuse_devicehub/templates/inventory/snapshots_list.html @@ -22,6 +22,38 @@
+
+
+ +
+ +
+
@@ -29,7 +61,6 @@ - @@ -39,7 +70,7 @@ - {% for snap in snapshots_log %} + {% for snap in snapshots_log.items %} -
Snapshot UUID Version DHIDSystem UUID Status Type Upload Type Device
{% if snap.sid and snap.snapshot_uuid %} @@ -56,29 +87,26 @@ {% endif %} - {{ snap.version }} + {{ snap.get_version() }} - {% if snap.device %} + {% if snap.get_device() %} - {{ snap.device }} + {{ snap.get_device() }} {% endif %} - {{ snap.system_uuid }} + {{ snap.get_status() }} - {{ snap.status }} + {{ snap.get_new_device() }} - {{ snap.new_device }} + {{ snap.get_type_device() }} - {{ snap.type_device }} - - {{ snap.original_dhid }} + {{ snap.get_original_dhid() }} {{ snap.created.strftime('%Y-%m-%d %H:%M') }} @@ -93,6 +121,38 @@
+
+
+ Showing {{ snapshots_log.first }} to {{ snapshots_log.last }} of {{ snapshots_log.total }} entries +
+ +
@@ -109,6 +169,18 @@ + {% endblock main %} diff --git a/ereuse_devicehub/templates/labels/print_labels.html b/ereuse_devicehub/templates/labels/print_labels.html index 0e36d68d..e8db7d0c 100644 --- a/ereuse_devicehub/templates/labels/print_labels.html +++ b/ereuse_devicehub/templates/labels/print_labels.html @@ -32,7 +32,7 @@
-
+
@@ -41,7 +41,7 @@ data-model="{{ dev.model or '' }}" data-tags="{{ dev.list_tags() }}" data-phid="{{ dev.phid() }}" - data-sid="{{ dev.sid or '' }}">{{ dev.devicehub_id }} + data-sid="{{ dev.sid or '' }}">{{ dev.dhid }}
@@ -192,7 +192,7 @@ {% endblock main %} diff --git a/ereuse_devicehub/templates/workbench/wbSettings.ini b/ereuse_devicehub/templates/workbench/wbSettings.ini index b711ae7d..b28d64a0 100644 --- a/ereuse_devicehub/templates/workbench/wbSettings.ini +++ b/ereuse_devicehub/templates/workbench/wbSettings.ini @@ -14,8 +14,8 @@ WB_SMART_TEST = short WB_ERASE = EraseBasic WB_ERASE_STEPS = 1 WB_ERASE_LEADING_ZEROS = False +VERSION = "Basic Erasure (BE)" -WB_DEBUG = True {% elif baseline_erease %} DH_HOST = {{ api_host }} DH_DATABASE = {{ schema }} @@ -28,6 +28,11 @@ WB_SMART_TEST = short WB_ERASE = EraseSectors WB_ERASE_STEPS = {{ erase_steps }} WB_ERASE_LEADING_ZEROS = True +VERSION = {%if erase_steps < 3 %}"Baseline Secure Erasure (BSE)"{% else %}"Enhanced Secure Erasure (ESE)"{% endif %} -WB_DEBUG = True -{% endif %} +{% else %} +SNAPSHOTS_PATH = /mnt +LOGS_PATH = /mnt +VERSION = "Basic Metadata (BM)" + +{% endif %} \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py index 3d7a0ad8..7322e77b 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -56,6 +56,7 @@ def test_api_docs(client: Client): '/inventory/device/{id}/', '/inventory/device/{dhid}/binding/', '/inventory/device/erasure/', + '/inventory/device/erasure/{orphans}/', '/inventory/all/device/', '/inventory/export/{export_id}/', '/inventory/lot/add/', @@ -116,4 +117,4 @@ def test_api_docs(client: Client): 'scheme': 'basic', 'name': 'Authorization', } - assert len(docs['definitions']) == 132 + assert len(docs['definitions']) == 134 diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index f9f82928..f9f2299e 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -2368,6 +2368,9 @@ def test_upload_snapshot_smartphone(user3: UserClientFlask): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_list_erasures(user3: UserClientFlask): + from flask import current_app as app + + app.config['SCHEMA'] = 'test' uri = '/inventory/upload-snapshot/' file_name = 'erase-sectors-2-hdd.snapshot.yaml' body, status = user3.get(uri) @@ -2393,6 +2396,19 @@ def test_list_erasures(user3: UserClientFlask): assert status == '200 OK' assert txt in body + uri = '/inventory/device/erasure/1/' + body, status = user3.get(uri) + assert status == '200 OK' + assert txt not in body + + dev = Device.query.first() + dev.binding.kangaroo = True + db.session.commit() + + body, status = user3.get(uri) + assert status == '200 OK' + assert txt in body + @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) @@ -2454,6 +2470,7 @@ def test_bug_3831_documents(user3: UserClientFlask): assert 'Delete Lot' in body assert 'Incoming Lot' in body + lot_id = Lot.query.all()[1].id uri = f'/inventory/lot/{lot_id}/trade-document/add/' body, status = user3.get(uri) @@ -2471,8 +2488,16 @@ def test_bug_3831_documents(user3: UserClientFlask): } uri = f'/inventory/lot/{lot_id}/trade-document/add/' - # body, status = user3.post(uri, data=data, content_type="multipart/form-data") - # assert status == '200 OK' + body, status = user3.post(uri, data=data, content_type="multipart/form-data") + assert status == '200 OK' + + # Second document + uri = f'/inventory/lot/{lot_id}/trade-document/add/' + file_upload = (BytesIO(b_file), file_name) + data['file'] = file_upload + data['csrf_token'] = generate_csrf() + body, status = user3.post(uri, data=data, content_type="multipart/form-data") + assert status == '200 OK' @pytest.mark.mvp