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:
parent
9ad217fc5b
commit
d2d48280cb
|
@ -4,7 +4,7 @@ from teal.resource import Converters, Resource
|
||||||
|
|
||||||
from ereuse_devicehub.resources.device import schemas
|
from ereuse_devicehub.resources.device import schemas
|
||||||
from ereuse_devicehub.resources.device.models import Manufacturer
|
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):
|
class DeviceDef(Resource):
|
||||||
|
@ -26,6 +26,13 @@ class DeviceDef(Resource):
|
||||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
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):
|
class ComputerDef(DeviceDef):
|
||||||
VIEW = None
|
VIEW = None
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import uuid
|
||||||
|
from itertools import filterfalse
|
||||||
|
|
||||||
import marshmallow
|
import marshmallow
|
||||||
from flask import current_app as app, render_template, request, Response
|
from flask import current_app as app, render_template, request, Response
|
||||||
from flask.json import jsonify
|
from flask.json import jsonify
|
||||||
from flask_sqlalchemy import Pagination
|
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 import query
|
||||||
from teal.cache import cache
|
from teal.cache import cache
|
||||||
from teal.resource import View
|
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.device.search import DeviceSearch
|
||||||
from ereuse_devicehub.resources.lot.models import LotDeviceDescendants
|
from ereuse_devicehub.resources.lot.models import LotDeviceDescendants
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
from ereuse_devicehub.resources.enums import SnapshotSoftware
|
||||||
|
|
||||||
|
|
||||||
class OfType(f.Str):
|
class OfType(f.Str):
|
||||||
|
@ -152,6 +156,67 @@ class DeviceView(View):
|
||||||
return query.filter(*args['filter']).order_by(*args['sort'])
|
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 ManufacturerView(View):
|
||||||
class FindArgs(marshmallow.Schema):
|
class FindArgs(marshmallow.Schema):
|
||||||
search = marshmallow.fields.Str(required=True,
|
search = marshmallow.fields.Str(required=True,
|
||||||
|
|
Reference in New Issue