Filter devices by being inside lots
This commit is contained in:
parent
7e4a0981a1
commit
863578559c
|
@ -4,7 +4,8 @@ import marshmallow
|
||||||
from flask import current_app as app, render_template, request
|
from flask import current_app as app, render_template, request
|
||||||
from flask.json import jsonify
|
from flask.json import jsonify
|
||||||
from flask_sqlalchemy import Pagination
|
from flask_sqlalchemy import Pagination
|
||||||
from marshmallow import fields as f, validate as v
|
from marshmallow import fields, fields as f, validate as v
|
||||||
|
from sqlalchemy.orm import aliased
|
||||||
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
|
||||||
|
@ -12,9 +13,10 @@ from teal.resource import View
|
||||||
from ereuse_devicehub import auth
|
from ereuse_devicehub import auth
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources import search
|
from ereuse_devicehub.resources import search
|
||||||
from ereuse_devicehub.resources.device.models import Device, Manufacturer
|
from ereuse_devicehub.resources.device.models import Component, Computer, Device, Manufacturer
|
||||||
from ereuse_devicehub.resources.device.search import DeviceSearch
|
from ereuse_devicehub.resources.device.search import DeviceSearch
|
||||||
from ereuse_devicehub.resources.event.models import Rate
|
from ereuse_devicehub.resources.event.models import Rate
|
||||||
|
from ereuse_devicehub.resources.lot.models import Lot, LotDevice
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,16 +41,28 @@ class TagQ(query.Query):
|
||||||
org = query.ILike(Tag.org)
|
org = query.ILike(Tag.org)
|
||||||
|
|
||||||
|
|
||||||
|
class LotQ(query.Query):
|
||||||
|
id = query.Or(query.QueryField(Lot.descendantsq, fields.UUID()))
|
||||||
|
|
||||||
|
|
||||||
class Filters(query.Query):
|
class Filters(query.Query):
|
||||||
|
_parent = aliased(Computer)
|
||||||
|
_device_inside_lot = (Device.id == LotDevice.device_id) & (Lot.id == LotDevice.lot_id)
|
||||||
|
_component_inside_lot_through_parent = (Device.id == Component.id) \
|
||||||
|
& (Component.parent_id == _parent.id) \
|
||||||
|
& (_parent.id == LotDevice.device_id)
|
||||||
|
|
||||||
type = query.Or(OfType(Device.type))
|
type = query.Or(OfType(Device.type))
|
||||||
model = query.ILike(Device.model)
|
model = query.ILike(Device.model)
|
||||||
manufacturer = query.ILike(Device.manufacturer)
|
manufacturer = query.ILike(Device.manufacturer)
|
||||||
serialNumber = query.ILike(Device.serial_number)
|
serialNumber = query.ILike(Device.serial_number)
|
||||||
rating = query.Join(Device.id == Rate.device_id, RateQ)
|
rating = query.Join(Device.id == Rate.device_id, RateQ)
|
||||||
tag = query.Join(Device.id == Tag.id, TagQ)
|
tag = query.Join(Device.id == Tag.device_id, TagQ)
|
||||||
|
lot = query.Join(_device_inside_lot | _component_inside_lot_through_parent, LotQ)
|
||||||
|
|
||||||
|
|
||||||
class Sorting(query.Sort):
|
class Sorting(query.Sort):
|
||||||
|
id = query.SortField(Device.id)
|
||||||
created = query.SortField(Device.created)
|
created = query.SortField(Device.created)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -77,14 +77,27 @@ class Lot(Thing):
|
||||||
.join(self.__class__.paths) \
|
.join(self.__class__.paths) \
|
||||||
.filter(Path.path.lquery(exp.cast('*.{}.*{{1}}'.format(id), LQUERY)))
|
.filter(Path.path.lquery(exp.cast('*.{}.*{{1}}'.format(id), LQUERY)))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def descendants(self):
|
||||||
|
return self.descendantsq(self.id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def descendantsq(cls, id):
|
||||||
|
_id = UUIDLtree.convert(id)
|
||||||
|
return (cls.id == Path.lot_id) & Path.path.lquery(exp.cast('*.{}.*'.format(_id), LQUERY))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parents(self):
|
def parents(self):
|
||||||
|
return self.parentsq(self.id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parentsq(cls, id: UUID):
|
||||||
"""The parent lots."""
|
"""The parent lots."""
|
||||||
id = UUIDLtree.convert(self.id)
|
id = UUIDLtree.convert(id)
|
||||||
i = db.func.index(Path.path, id)
|
i = db.func.index(Path.path, id)
|
||||||
parent_id = db.func.replace(exp.cast(db.func.subpath(Path.path, i - 1, i), TEXT), '_', '-')
|
parent_id = db.func.replace(exp.cast(db.func.subpath(Path.path, i - 1, i), TEXT), '_', '-')
|
||||||
join_clause = parent_id == exp.cast(Lot.id, TEXT)
|
join_clause = parent_id == exp.cast(Lot.id, TEXT)
|
||||||
return self.query.join(Path, join_clause).filter(
|
return cls.query.join(Path, join_clause).filter(
|
||||||
Path.path.lquery(exp.cast('*{{1}}.{}.*'.format(id), LQUERY))
|
Path.path.lquery(exp.cast('*{{1}}.{}.*'.format(id), LQUERY))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -43,14 +43,27 @@ class Lot(Thing):
|
||||||
def children(self) -> LotQuery:
|
def children(self) -> LotQuery:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def descendants(self) -> LotQuery:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def descendantsq(cls, id) -> LotQuery:
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parents(self) -> LotQuery:
|
def parents(self) -> LotQuery:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parentsq(cls, id) -> LotQuery:
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self) -> urlutils.URL:
|
def url(self) -> urlutils.URL:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Path:
|
class Path:
|
||||||
id = ... # type: Column
|
id = ... # type: Column
|
||||||
lot_id = ... # type: Column
|
lot_id = ... # type: Column
|
||||||
|
|
|
@ -8,6 +8,7 @@ from ereuse_devicehub.resources.device.models import Desktop, Device, Laptop, So
|
||||||
from ereuse_devicehub.resources.device.views import Filters, Sorting
|
from ereuse_devicehub.resources.device.views import Filters, Sorting
|
||||||
from ereuse_devicehub.resources.enums import ComputerChassis
|
from ereuse_devicehub.resources.enums import ComputerChassis
|
||||||
from ereuse_devicehub.resources.event.models import Snapshot
|
from ereuse_devicehub.resources.event.models import Snapshot
|
||||||
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from tests import conftest
|
from tests import conftest
|
||||||
from tests.conftest import file
|
from tests.conftest import file
|
||||||
|
|
||||||
|
@ -98,6 +99,50 @@ def test_device_query_filter_sort(user: UserClient):
|
||||||
assert tuple(d['type'] for d in i['items']) == ('Desktop', 'Laptop', 'Desktop')
|
assert tuple(d['type'] for d in i['items']) == ('Desktop', 'Laptop', 'Desktop')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures(device_query_dummy.__name__)
|
||||||
|
def test_device_query_filter_lots(user: UserClient):
|
||||||
|
parent, _ = user.post({'name': 'Parent'}, res=Lot)
|
||||||
|
child, _ = user.post({'name': 'Child'}, res=Lot)
|
||||||
|
parent, _ = user.post({},
|
||||||
|
res=Lot,
|
||||||
|
item='{}/children'.format(parent['id']),
|
||||||
|
query=[('id', child['id'])])
|
||||||
|
i, _ = user.get(res=Device, query=[
|
||||||
|
('filter', {'type': ['Computer']})
|
||||||
|
])
|
||||||
|
lot, _ = user.post({},
|
||||||
|
res=Lot,
|
||||||
|
item='{}/devices'.format(parent['id']),
|
||||||
|
query=[('id', d['id']) for d in i['items'][:-1]])
|
||||||
|
lot, _ = user.post({},
|
||||||
|
res=Lot,
|
||||||
|
item='{}/devices'.format(child['id']),
|
||||||
|
query=[('id', i['items'][-1]['id'])])
|
||||||
|
i, _ = user.get(res=Device, query=[
|
||||||
|
('filter', {'lot': {'id': [parent['id']]}}),
|
||||||
|
('sort', {'id': Sorting.ASCENDING})
|
||||||
|
])
|
||||||
|
assert len(i['items']) == 4
|
||||||
|
assert tuple(x['id'] for x in i['items']) == (1, 2, 3, 4), \
|
||||||
|
'The parent lot contains 2 items plus indirectly the third one, and 1st device the HDD.'
|
||||||
|
|
||||||
|
s, _ = user.get(res=Device, query=[
|
||||||
|
('filter', {'lot': {'id': [child['id']]}})
|
||||||
|
])
|
||||||
|
assert s['items'][0]['chassis'] == 'Microtower', 'The child lot only contains the last device.'
|
||||||
|
s, _ = user.get(res=Device, query=[
|
||||||
|
('filter', {'lot': {'id': [child['id'], parent['id']]}})
|
||||||
|
])
|
||||||
|
assert all(x['id'] == id for x, id in zip(i['items'], (1, 2, 3, 4))), \
|
||||||
|
'Adding both lots is redundant in this case and we have the 4 elements.'
|
||||||
|
i, _ = user.get(res=Device, query=[
|
||||||
|
('filter', {'lot': {'id': [parent['id']]}, 'type': ['Computer']}),
|
||||||
|
('sort', {'id': Sorting.ASCENDING})
|
||||||
|
])
|
||||||
|
assert len(i['items']) == 3
|
||||||
|
assert tuple(x['id'] for x in i['items']) == (1, 2, 3), 'Only computers now'
|
||||||
|
|
||||||
|
|
||||||
def test_device_query(user: UserClient):
|
def test_device_query(user: UserClient):
|
||||||
"""Checks result of inventory."""
|
"""Checks result of inventory."""
|
||||||
user.post(conftest.file('basic.snapshot'), res=Snapshot)
|
user.post(conftest.file('basic.snapshot'), res=Snapshot)
|
||||||
|
|
Reference in a new issue