Toggle formats when finding lots; add device.lots when GETting devices

This commit is contained in:
Xavier Bustamante Talavera 2018-10-08 17:32:45 +02:00
parent a3f6d7877a
commit df31074775
4 changed files with 91 additions and 17 deletions

View file

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

View file

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

View file

@ -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,21 +47,46 @@ 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.
"""
nodes = []
for model in Path.query: # type: Path
path = deque(model.path.path.split('.'))
self._p(nodes, path)
return jsonify({
'items': nodes,
'url': request.path
})
if args['format'] == LotFormat.UiTree:
nodes = []
for model in Path.query: # type: Path
path = deque(model.path.path.split('.'))
self._p(nodes, path)
return jsonify({
'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.

View file

@ -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']),