Merge two devices (#40)

* Add authenticated view to merge two devices

* Assign actions of argument device to device

* Merge device components

* Sloppy implementation to test

* Correctly update base device

* Correctly append actions
This commit is contained in:
fedjo 2020-07-07 14:58:37 +02:00 committed by GitHub
parent 9ad217fc5b
commit d2d48280cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 2 deletions

View File

@ -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

View File

@ -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/<id>/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,