diff --git a/ereuse_devicehub/resources/device/definitions.py b/ereuse_devicehub/resources/device/definitions.py index 559e5428..47f276e2 100644 --- a/ereuse_devicehub/resources/device/definitions.py +++ b/ereuse_devicehub/resources/device/definitions.py @@ -4,7 +4,7 @@ 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, ManufacturerView +from ereuse_devicehub.resources.device.views import DeviceView, DeviceMergeView, ManufacturerView class DeviceDef(Resource): @@ -26,6 +26,13 @@ class DeviceDef(Resource): 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) + if self.AUTH: + device_merge = app.auth.requires_auth(device_merge) + self.add_url_rule('/<{}:{}>/merge/'.format(self.ID_CONVERTER.value, self.ID_NAME), + view_func=device_merge, + methods={'POST'}) + class ComputerDef(DeviceDef): VIEW = None diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 2a7a5efd..ac169402 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -1,10 +1,13 @@ import datetime +import uuid +from itertools import filterfalse import marshmallow from flask import current_app as app, render_template, request, Response from flask.json import jsonify from flask_sqlalchemy import Pagination -from marshmallow import fields, fields as f, validate as v, ValidationError +from marshmallow import fields, fields as f, validate as v, ValidationError, \ + Schema as MarshmallowSchema from teal import query from teal.cache import cache from teal.resource import View @@ -19,6 +22,7 @@ from ereuse_devicehub.resources.device.models import Device, Manufacturer, Compu from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.lot.models import LotDeviceDescendants from ereuse_devicehub.resources.tag.model import Tag +from ereuse_devicehub.resources.enums import SnapshotSoftware class OfType(f.Str): @@ -152,6 +156,67 @@ class DeviceView(View): return query.filter(*args['filter']).order_by(*args['sort']) +class DeviceMergeView(View): + + """View for merging two devices + Ex. ``device//merge/id=X``. + """ + class FindArgs(MarshmallowSchema): + id = fields.Integer() + + def get_merge_id(self) -> uuid.UUID: + args = self.QUERY_PARSER.parse(self.find_args, request, locations=('querystring',)) + return args['id'] + + def post(self, id: uuid.UUID): + device = Device.query.filter_by(id=id).one() + with_device = Device.query.filter_by(id=self.get_merge_id()).one() + self.merge_devices(device, with_device) + + db.session().final_flush() + ret = self.schema.jsonify(device) + ret.status_code = 201 + + db.session.commit() + return ret + + def merge_devices(self, base_device, with_device): + """Merge the current device with `with_device` by + adding all `with_device` actions under the current device. + + This operation is highly costly as it forces refreshing + many models in session. + """ + snapshots = sorted(filterfalse(lambda x: not isinstance(x, actions.Snapshot), (base_device.actions + with_device.actions))) + workbench_snapshots = [ s for s in snapshots if s.software == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid)] + latest_snapshot_device = [ d for d in (base_device, with_device) if d.id == snapshots[-1].device.id][0] + latest_snapshotworkbench_device = [ d for d in (base_device, with_device) if d.id == workbench_snapshots[-1].device.id][0] + # Adding actions of with_device + with_actions_one = [a for a in with_device.actions if isinstance(a, actions.ActionWithOneDevice)] + with_actions_multiple = [a for a in with_device.actions if isinstance(a, actions.ActionWithMultipleDevices)] + + for action in with_actions_one: + if action.parent: + action.parent = base_device + else: + base_device.actions_one.add(action) + for action in with_actions_multiple: + if action.parent: + action.parent = base_device + else: + base_device.actions_multiple.add(action) + + # Keeping the components of latest SnapshotWorkbench + base_device.components = latest_snapshotworkbench_device.components + + # Properties from latest Snapshot + base_device.type = latest_snapshot_device.type + base_device.hid = latest_snapshot_device.hid + base_device.manufacturer = latest_snapshot_device.manufacturer + base_device.model = latest_snapshot_device.model + base_device.chassis = latest_snapshot_device.chassis + + class ManufacturerView(View): class FindArgs(marshmallow.Schema): search = marshmallow.fields.Str(required=True,