Toggle formats when finding lots; add device.lots when GETting devices
This commit is contained in:
parent
a3f6d7877a
commit
df31074775
|
@ -9,7 +9,7 @@ from boltons import urlutils
|
||||||
from citext import CIText
|
from citext import CIText
|
||||||
from ereuse_utils.naming import Naming
|
from ereuse_utils.naming import Naming
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
||||||
Sequence, SmallInteger, Unicode, inspect
|
Sequence, SmallInteger, Unicode, inspect, text
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
|
from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
|
@ -401,7 +401,10 @@ class Manufacturer(db.Model):
|
||||||
__table_args__ = {'schema': 'common'}
|
__table_args__ = {'schema': 'common'}
|
||||||
CSV_DELIMITER = csv.get_dialect('excel').delimiter
|
CSV_DELIMITER = csv.get_dialect('excel').delimiter
|
||||||
|
|
||||||
name = db.Column(CIText(), primary_key=True)
|
name = db.Column(CIText(),
|
||||||
|
primary_key=True,
|
||||||
|
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
|
||||||
|
index=db.Index('name', text('name gin_trgm_ops'), postgresql_using='gin'))
|
||||||
url = db.Column(URL(), unique=True)
|
url = db.Column(URL(), unique=True)
|
||||||
logo = db.Column(URL())
|
logo = db.Column(URL())
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ class Device(Thing):
|
||||||
events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__)
|
events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__)
|
||||||
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
|
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
|
||||||
url = URL(dump_only=True, description=m.Device.url.__doc__)
|
url = URL(dump_only=True, description=m.Device.url.__doc__)
|
||||||
|
lots = NestedOn('Lot', many=True, dump_only=True)
|
||||||
|
|
||||||
@pre_load
|
@pre_load
|
||||||
def from_events_to_events_one(self, data: dict):
|
def from_events_to_events_one(self, data: dict):
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import uuid
|
import uuid
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from enum import Enum
|
||||||
from typing import List, Set
|
from typing import List, Set
|
||||||
|
|
||||||
import marshmallow as ma
|
import marshmallow as ma
|
||||||
from flask import jsonify, request
|
from flask import jsonify, request
|
||||||
|
from marshmallow import Schema as MarshmallowSchema, fields as f
|
||||||
|
from teal import query
|
||||||
|
from teal.marshmallow import EnumField
|
||||||
from teal.resource import View
|
from teal.resource import View
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
|
@ -11,7 +15,23 @@ from ereuse_devicehub.resources.device.models import Device
|
||||||
from ereuse_devicehub.resources.lot.models import Lot, Path
|
from ereuse_devicehub.resources.lot.models import Lot, Path
|
||||||
|
|
||||||
|
|
||||||
|
class Filters(query.Query):
|
||||||
|
name = query.ILike(Lot.name)
|
||||||
|
|
||||||
|
|
||||||
|
class LotFormat(Enum):
|
||||||
|
UiTree = 'UiTree'
|
||||||
|
|
||||||
|
|
||||||
class LotView(View):
|
class LotView(View):
|
||||||
|
class FindArgs(MarshmallowSchema):
|
||||||
|
"""
|
||||||
|
Allowed arguments for the ``find``
|
||||||
|
method (GET collection) endpoint
|
||||||
|
"""
|
||||||
|
format = EnumField(LotFormat, missing=None)
|
||||||
|
filter = f.Nested(Filters, missing=[])
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
l = request.get_json()
|
l = request.get_json()
|
||||||
lot = Lot(**l)
|
lot = Lot(**l)
|
||||||
|
@ -27,13 +47,23 @@ class LotView(View):
|
||||||
return self.schema.jsonify(lot)
|
return self.schema.jsonify(lot)
|
||||||
|
|
||||||
def find(self, args: dict):
|
def find(self, args: dict):
|
||||||
"""Returns all lots as required for DevicehubClient::
|
"""
|
||||||
|
Gets lots.
|
||||||
|
|
||||||
|
By passing the value `UiTree` in the parameter `format`
|
||||||
|
of the query you get a recursive nested suited for ui-tree::
|
||||||
|
|
||||||
[
|
[
|
||||||
{title: 'lot1',
|
{title: 'lot1',
|
||||||
nodes: [{title: 'child1', nodes:[]}]
|
nodes: [{title: 'child1', nodes:[]}]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Note that in this format filters are ignored.
|
||||||
|
|
||||||
|
Otherwise it just returns the standard flat view of lots that
|
||||||
|
you can filter.
|
||||||
"""
|
"""
|
||||||
|
if args['format'] == LotFormat.UiTree:
|
||||||
nodes = []
|
nodes = []
|
||||||
for model in Path.query: # type: Path
|
for model in Path.query: # type: Path
|
||||||
path = deque(model.path.path.split('.'))
|
path = deque(model.path.path.split('.'))
|
||||||
|
@ -42,6 +72,21 @@ class LotView(View):
|
||||||
'items': nodes,
|
'items': nodes,
|
||||||
'url': request.path
|
'url': request.path
|
||||||
})
|
})
|
||||||
|
else:
|
||||||
|
query = Lot.query.filter(*args['filter'])
|
||||||
|
lots = query.paginate(per_page=6)
|
||||||
|
ret = {
|
||||||
|
'items': self.schema.dump(lots.items, many=True, nested=0),
|
||||||
|
'pagination': {
|
||||||
|
'page': lots.page,
|
||||||
|
'perPage': lots.per_page,
|
||||||
|
'total': lots.total,
|
||||||
|
'previous': lots.prev_num,
|
||||||
|
'next': lots.next_num
|
||||||
|
},
|
||||||
|
'url': request.path
|
||||||
|
}
|
||||||
|
return jsonify(ret)
|
||||||
|
|
||||||
def _p(self, nodes: List[dict], path: deque):
|
def _p(self, nodes: List[dict], path: deque):
|
||||||
"""Recursively creates the nested lot structure.
|
"""Recursively creates the nested lot structure.
|
||||||
|
|
|
@ -4,7 +4,7 @@ from flask import g
|
||||||
from ereuse_devicehub.client import UserClient
|
from ereuse_devicehub.client import UserClient
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.devicehub import Devicehub
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
from ereuse_devicehub.resources.device.models import Desktop
|
from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard
|
||||||
from ereuse_devicehub.resources.enums import ComputerChassis
|
from ereuse_devicehub.resources.enums import ComputerChassis
|
||||||
from ereuse_devicehub.resources.lot.models import Lot, LotDevice
|
from ereuse_devicehub.resources.lot.models import Lot, LotDevice
|
||||||
from tests import conftest
|
from tests import conftest
|
||||||
|
@ -23,6 +23,7 @@ In case of error, debug with:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(reason='Components are not added to lots!')
|
||||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||||
def test_lot_device_relationship():
|
def test_lot_device_relationship():
|
||||||
device = Desktop(serial_number='foo',
|
device = Desktop(serial_number='foo',
|
||||||
|
@ -40,6 +41,12 @@ def test_lot_device_relationship():
|
||||||
assert lot_device.created
|
assert lot_device.created
|
||||||
assert lot_device.author_id == g.user.id
|
assert lot_device.author_id == g.user.id
|
||||||
assert device.lots == {lot}
|
assert device.lots == {lot}
|
||||||
|
assert device in lot
|
||||||
|
|
||||||
|
graphic = GraphicCard(serial_number='foo', model='bar')
|
||||||
|
device.components.add(graphic)
|
||||||
|
db.session.flush()
|
||||||
|
assert graphic in lot
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||||
|
@ -209,8 +216,9 @@ def test_post_get_lot(user: UserClient):
|
||||||
assert not l['children']
|
assert not l['children']
|
||||||
|
|
||||||
|
|
||||||
def test_post_add_children_view(user: UserClient):
|
def test_post_add_children_view_ui_tree_normal(user: UserClient):
|
||||||
"""Tests adding children lots to a lot through the view."""
|
"""Tests adding children lots to a lot through the view and
|
||||||
|
GETting the results."""
|
||||||
parent, _ = user.post(({'name': 'Parent'}), res=Lot)
|
parent, _ = user.post(({'name': 'Parent'}), res=Lot)
|
||||||
child, _ = user.post(({'name': 'Child'}), res=Lot)
|
child, _ = user.post(({'name': 'Child'}), res=Lot)
|
||||||
parent, _ = user.post({},
|
parent, _ = user.post({},
|
||||||
|
@ -221,16 +229,29 @@ def test_post_add_children_view(user: UserClient):
|
||||||
child, _ = user.get(res=Lot, item=child['id'])
|
child, _ = user.get(res=Lot, item=child['id'])
|
||||||
assert child['parents'][0]['id'] == parent['id']
|
assert child['parents'][0]['id'] == parent['id']
|
||||||
|
|
||||||
lots = user.get(res=Lot)[0]['items']
|
# Format UiTree
|
||||||
|
lots = user.get(res=Lot, query=[('format', 'UiTree')])[0]['items']
|
||||||
assert len(lots) == 1
|
assert len(lots) == 1
|
||||||
assert lots[0]['name'] == 'Parent'
|
assert lots[0]['name'] == 'Parent'
|
||||||
assert len(lots[0]['nodes']) == 1
|
assert len(lots[0]['nodes']) == 1
|
||||||
assert lots[0]['nodes'][0]['name'] == 'Child'
|
assert lots[0]['nodes'][0]['name'] == 'Child'
|
||||||
|
|
||||||
|
# Normal list format
|
||||||
|
lots = user.get(res=Lot)[0]['items']
|
||||||
|
assert len(lots) == 2
|
||||||
|
assert lots[0]['name'] == 'Parent'
|
||||||
|
assert lots[1]['name'] == 'Child'
|
||||||
|
|
||||||
|
# List format with a filter
|
||||||
|
lots = user.get(res=Lot, query=[('filter', {'name': 'pa'})])[0]['items']
|
||||||
|
assert len(lots) == 1
|
||||||
|
assert lots[0]['name'] == 'Parent'
|
||||||
|
|
||||||
|
|
||||||
def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient):
|
def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient):
|
||||||
"""Tests adding a device to a lot using POST and
|
"""Tests adding a device to a lot using POST and
|
||||||
removing it with DELETE."""
|
removing it with DELETE."""
|
||||||
|
# todo check with components
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
device = Desktop(serial_number='foo',
|
device = Desktop(serial_number='foo',
|
||||||
model='bar',
|
model='bar',
|
||||||
|
@ -244,7 +265,11 @@ def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient):
|
||||||
res=Lot,
|
res=Lot,
|
||||||
item='{}/devices'.format(parent['id']),
|
item='{}/devices'.format(parent['id']),
|
||||||
query=[('id', device_id)])
|
query=[('id', device_id)])
|
||||||
assert lot['devices'][0]['id'] == device_id
|
assert lot['devices'][0]['id'] == device_id, 'Lot contains device'
|
||||||
|
device, _ = user.get(res=Device, item=device_id)
|
||||||
|
assert len(device['lots']) == 1
|
||||||
|
assert device['lots'][0]['id'] == lot['id'], 'Device is inside lot'
|
||||||
|
|
||||||
# Remove the device
|
# Remove the device
|
||||||
lot, _ = user.delete(res=Lot,
|
lot, _ = user.delete(res=Lot,
|
||||||
item='{}/devices'.format(parent['id']),
|
item='{}/devices'.format(parent['id']),
|
||||||
|
|
Reference in New Issue