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 ereuse_utils.naming import Naming
|
||||
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.orm import ColumnProperty, backref, relationship, validates
|
||||
from sqlalchemy.util import OrderedSet
|
||||
|
@ -401,7 +401,10 @@ class Manufacturer(db.Model):
|
|||
__table_args__ = {'schema': 'common'}
|
||||
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)
|
||||
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_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
|
||||
url = URL(dump_only=True, description=m.Device.url.__doc__)
|
||||
lots = NestedOn('Lot', many=True, dump_only=True)
|
||||
|
||||
@pre_load
|
||||
def from_events_to_events_one(self, data: dict):
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import uuid
|
||||
from collections import deque
|
||||
from enum import Enum
|
||||
from typing import List, Set
|
||||
|
||||
import marshmallow as ma
|
||||
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 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
|
||||
|
||||
|
||||
class Filters(query.Query):
|
||||
name = query.ILike(Lot.name)
|
||||
|
||||
|
||||
class LotFormat(Enum):
|
||||
UiTree = 'UiTree'
|
||||
|
||||
|
||||
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):
|
||||
l = request.get_json()
|
||||
lot = Lot(**l)
|
||||
|
@ -27,13 +47,23 @@ class LotView(View):
|
|||
return self.schema.jsonify(lot)
|
||||
|
||||
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',
|
||||
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 = []
|
||||
for model in Path.query: # type: Path
|
||||
path = deque(model.path.path.split('.'))
|
||||
|
@ -42,6 +72,21 @@ class LotView(View):
|
|||
'items': nodes,
|
||||
'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):
|
||||
"""Recursively creates the nested lot structure.
|
||||
|
|
|
@ -4,7 +4,7 @@ from flask import g
|
|||
from ereuse_devicehub.client import UserClient
|
||||
from ereuse_devicehub.db import db
|
||||
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.lot.models import Lot, LotDevice
|
||||
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__)
|
||||
def test_lot_device_relationship():
|
||||
device = Desktop(serial_number='foo',
|
||||
|
@ -40,6 +41,12 @@ def test_lot_device_relationship():
|
|||
assert lot_device.created
|
||||
assert lot_device.author_id == g.user.id
|
||||
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__)
|
||||
|
@ -209,8 +216,9 @@ def test_post_get_lot(user: UserClient):
|
|||
assert not l['children']
|
||||
|
||||
|
||||
def test_post_add_children_view(user: UserClient):
|
||||
"""Tests adding children lots to a lot through the view."""
|
||||
def test_post_add_children_view_ui_tree_normal(user: UserClient):
|
||||
"""Tests adding children lots to a lot through the view and
|
||||
GETting the results."""
|
||||
parent, _ = user.post(({'name': 'Parent'}), res=Lot)
|
||||
child, _ = user.post(({'name': 'Child'}), res=Lot)
|
||||
parent, _ = user.post({},
|
||||
|
@ -221,16 +229,29 @@ def test_post_add_children_view(user: UserClient):
|
|||
child, _ = user.get(res=Lot, item=child['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 lots[0]['name'] == 'Parent'
|
||||
assert len(lots[0]['nodes']) == 1
|
||||
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):
|
||||
"""Tests adding a device to a lot using POST and
|
||||
removing it with DELETE."""
|
||||
# todo check with components
|
||||
with app.app_context():
|
||||
device = Desktop(serial_number='foo',
|
||||
model='bar',
|
||||
|
@ -244,7 +265,11 @@ def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient):
|
|||
res=Lot,
|
||||
item='{}/devices'.format(parent['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
|
||||
lot, _ = user.delete(res=Lot,
|
||||
item='{}/devices'.format(parent['id']),
|
||||
|
|
Reference in a new issue