Add Smartphone; remove Inventory and use DeviceView
This commit is contained in:
parent
3bd4168174
commit
94c44e3480
|
@ -7,16 +7,16 @@ from teal.config import Config
|
||||||
from teal.enums import Currency
|
from teal.enums import Currency
|
||||||
from teal.utils import import_resource
|
from teal.utils import import_resource
|
||||||
|
|
||||||
from ereuse_devicehub.resources import agent, device, event, inventory, lot, tag, user
|
from ereuse_devicehub.resources import agent, event, lot, tag, user
|
||||||
|
from ereuse_devicehub.resources.device import definitions
|
||||||
from ereuse_devicehub.resources.enums import PriceSoftware, RatingSoftware
|
from ereuse_devicehub.resources.enums import PriceSoftware, RatingSoftware
|
||||||
|
|
||||||
|
|
||||||
class DevicehubConfig(Config):
|
class DevicehubConfig(Config):
|
||||||
RESOURCE_DEFINITIONS = set(chain(import_resource(device),
|
RESOURCE_DEFINITIONS = set(chain(import_resource(definitions),
|
||||||
import_resource(event),
|
import_resource(event),
|
||||||
import_resource(user),
|
import_resource(user),
|
||||||
import_resource(tag),
|
import_resource(tag),
|
||||||
import_resource(inventory),
|
|
||||||
import_resource(agent),
|
import_resource(agent),
|
||||||
import_resource(lot)))
|
import_resource(lot)))
|
||||||
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
|
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
|
||||||
|
|
|
@ -10,8 +10,8 @@ import yaml
|
||||||
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.resources.agent.models import Person
|
from ereuse_devicehub.resources.agent.models import Person
|
||||||
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
from ereuse_devicehub.resources.event import models as m
|
from ereuse_devicehub.resources.event import models as m
|
||||||
from ereuse_devicehub.resources.inventory import Inventory
|
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
from ereuse_devicehub.resources.user import User
|
from ereuse_devicehub.resources.user import User
|
||||||
|
@ -102,14 +102,13 @@ class Dummy:
|
||||||
assert len(lot['devices'])
|
assert len(lot['devices'])
|
||||||
|
|
||||||
# Keep this at the bottom
|
# Keep this at the bottom
|
||||||
inventory, _ = user.get(res=Inventory)
|
inventory, _ = user.get(res=Device)
|
||||||
assert len(inventory['devices'])
|
assert len(inventory['items'])
|
||||||
assert len(inventory['lots'])
|
|
||||||
|
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'intel')])
|
i, _ = user.get(res=Device, query=[('search', 'intel')])
|
||||||
assert len(i['devices']) == 10
|
assert len(i['items']) == 10
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'pc')])
|
i, _ = user.get(res=Device, query=[('search', 'pc')])
|
||||||
assert len(i['devices']) == 11
|
assert len(i['items']) == 11
|
||||||
print('⭐ Done.')
|
print('⭐ Done.')
|
||||||
|
|
||||||
def user_client(self, email: str, password: str):
|
def user_client(self, email: str, password: str):
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
type: Snapshot
|
||||||
|
software: Web
|
||||||
|
version: '1.0'
|
||||||
|
device:
|
||||||
|
type: Smartphone
|
||||||
|
manufacturer: Apple
|
||||||
|
model: A1586
|
||||||
|
serialNumber: ABCDEF
|
||||||
|
imei: 35686800-004141-20
|
||||||
|
events:
|
||||||
|
- type: AppRate
|
||||||
|
appearanceRange: A
|
||||||
|
functionalityRange: B
|
||||||
|
labelling: False
|
|
@ -1,146 +0,0 @@
|
||||||
from typing import Callable, Iterable, Tuple
|
|
||||||
|
|
||||||
from teal.resource import Converters, Resource
|
|
||||||
|
|
||||||
from ereuse_devicehub.resources.device import schemas
|
|
||||||
from ereuse_devicehub.resources.device.models import Manufacturer
|
|
||||||
from ereuse_devicehub.resources.device.views import DeviceView, ManufacturerView
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceDef(Resource):
|
|
||||||
SCHEMA = schemas.Device
|
|
||||||
VIEW = DeviceView
|
|
||||||
ID_CONVERTER = Converters.int
|
|
||||||
AUTH = False # We manage this at each view
|
|
||||||
|
|
||||||
def __init__(self, app,
|
|
||||||
import_name=__name__, static_folder=None,
|
|
||||||
static_url_path=None,
|
|
||||||
template_folder='templates',
|
|
||||||
url_prefix=None,
|
|
||||||
subdomain=None,
|
|
||||||
url_defaults=None,
|
|
||||||
root_path=None,
|
|
||||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
|
||||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
|
||||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
|
||||||
|
|
||||||
|
|
||||||
class ComputerDef(DeviceDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Computer
|
|
||||||
|
|
||||||
|
|
||||||
class DesktopDef(ComputerDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Desktop
|
|
||||||
|
|
||||||
|
|
||||||
class LaptopDef(ComputerDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Laptop
|
|
||||||
|
|
||||||
|
|
||||||
class ServerDef(ComputerDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Server
|
|
||||||
|
|
||||||
|
|
||||||
class MonitorDef(DeviceDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Monitor
|
|
||||||
|
|
||||||
|
|
||||||
class ComputerMonitorDef(MonitorDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.ComputerMonitor
|
|
||||||
|
|
||||||
|
|
||||||
class TelevisionSetDef(MonitorDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.TelevisionSet
|
|
||||||
|
|
||||||
|
|
||||||
class MobileDef(DeviceDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Mobile
|
|
||||||
|
|
||||||
|
|
||||||
class SmartphoneDef(MobileDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Smartphone
|
|
||||||
|
|
||||||
|
|
||||||
class TabletDef(MobileDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Tablet
|
|
||||||
|
|
||||||
|
|
||||||
class CellphoneDef(MobileDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Cellphone
|
|
||||||
|
|
||||||
|
|
||||||
class ComponentDef(DeviceDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Component
|
|
||||||
|
|
||||||
|
|
||||||
class GraphicCardDef(ComponentDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.GraphicCard
|
|
||||||
|
|
||||||
|
|
||||||
class DataStorageDef(ComponentDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.DataStorage
|
|
||||||
|
|
||||||
|
|
||||||
class HardDriveDef(DataStorageDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.HardDrive
|
|
||||||
|
|
||||||
|
|
||||||
class SolidStateDriveDef(DataStorageDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.SolidStateDrive
|
|
||||||
|
|
||||||
|
|
||||||
class MotherboardDef(ComponentDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Motherboard
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkAdapterDef(ComponentDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.NetworkAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class RamModuleDef(ComponentDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.RamModule
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessorDef(ComponentDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Processor
|
|
||||||
|
|
||||||
|
|
||||||
class SoundCardDef(ComponentDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.SoundCard
|
|
||||||
|
|
||||||
|
|
||||||
class DisplayDef(ComponentDef):
|
|
||||||
VIEW = None
|
|
||||||
SCHEMA = schemas.Display
|
|
||||||
|
|
||||||
|
|
||||||
class ManufacturerDef(Resource):
|
|
||||||
VIEW = ManufacturerView
|
|
||||||
SCHEMA = schemas.Manufacturer
|
|
||||||
AUTH = True
|
|
||||||
|
|
||||||
def init_db(self, db: 'db.SQLAlchemy'):
|
|
||||||
"""Loads the manufacturers to the database."""
|
|
||||||
Manufacturer.add_all_to_session(db.session)
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
from typing import Callable, Iterable, Tuple
|
||||||
|
|
||||||
|
from teal.resource import Converters, Resource
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.device import schemas
|
||||||
|
from ereuse_devicehub.resources.device.models import Manufacturer
|
||||||
|
from ereuse_devicehub.resources.device.views import DeviceView, ManufacturerView
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceDef(Resource):
|
||||||
|
SCHEMA = schemas.Device
|
||||||
|
VIEW = DeviceView
|
||||||
|
ID_CONVERTER = Converters.int
|
||||||
|
AUTH = False # We manage this at each view
|
||||||
|
|
||||||
|
def __init__(self, app,
|
||||||
|
import_name=__name__, static_folder=None,
|
||||||
|
static_url_path=None,
|
||||||
|
template_folder='templates',
|
||||||
|
url_prefix=None,
|
||||||
|
subdomain=None,
|
||||||
|
url_defaults=None,
|
||||||
|
root_path=None,
|
||||||
|
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||||
|
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||||
|
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||||
|
|
||||||
|
|
||||||
|
class ComputerDef(DeviceDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Computer
|
||||||
|
|
||||||
|
|
||||||
|
class DesktopDef(ComputerDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Desktop
|
||||||
|
|
||||||
|
|
||||||
|
class LaptopDef(ComputerDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Laptop
|
||||||
|
|
||||||
|
|
||||||
|
class ServerDef(ComputerDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Server
|
||||||
|
|
||||||
|
|
||||||
|
class MonitorDef(DeviceDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Monitor
|
||||||
|
|
||||||
|
|
||||||
|
class ComputerMonitorDef(MonitorDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.ComputerMonitor
|
||||||
|
|
||||||
|
|
||||||
|
class TelevisionSetDef(MonitorDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.TelevisionSet
|
||||||
|
|
||||||
|
|
||||||
|
class MobileDef(DeviceDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Mobile
|
||||||
|
|
||||||
|
|
||||||
|
class SmartphoneDef(MobileDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Smartphone
|
||||||
|
|
||||||
|
|
||||||
|
class TabletDef(MobileDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Tablet
|
||||||
|
|
||||||
|
|
||||||
|
class CellphoneDef(MobileDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Cellphone
|
||||||
|
|
||||||
|
|
||||||
|
class ComponentDef(DeviceDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Component
|
||||||
|
|
||||||
|
|
||||||
|
class GraphicCardDef(ComponentDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.GraphicCard
|
||||||
|
|
||||||
|
|
||||||
|
class DataStorageDef(ComponentDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.DataStorage
|
||||||
|
|
||||||
|
|
||||||
|
class HardDriveDef(DataStorageDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.HardDrive
|
||||||
|
|
||||||
|
|
||||||
|
class SolidStateDriveDef(DataStorageDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.SolidStateDrive
|
||||||
|
|
||||||
|
|
||||||
|
class MotherboardDef(ComponentDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Motherboard
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkAdapterDef(ComponentDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.NetworkAdapter
|
||||||
|
|
||||||
|
|
||||||
|
class RamModuleDef(ComponentDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.RamModule
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessorDef(ComponentDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Processor
|
||||||
|
|
||||||
|
|
||||||
|
class SoundCardDef(ComponentDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.SoundCard
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayDef(ComponentDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.Display
|
||||||
|
|
||||||
|
|
||||||
|
class ManufacturerDef(Resource):
|
||||||
|
VIEW = ManufacturerView
|
||||||
|
SCHEMA = schemas.Manufacturer
|
||||||
|
AUTH = True
|
||||||
|
|
||||||
|
def init_db(self, db: 'db.SQLAlchemy'):
|
||||||
|
"""Loads the manufacturers to the database."""
|
||||||
|
Manufacturer.add_all_to_session(db.session)
|
|
@ -241,7 +241,7 @@ class Mobile(Device):
|
||||||
|
|
||||||
@validates('imei')
|
@validates('imei')
|
||||||
def validate_imei(self, _, value: int):
|
def validate_imei(self, _, value: int):
|
||||||
if not imei.is_valid(value):
|
if not imei.is_valid(str(value)):
|
||||||
raise ValidationError('{} is not a valid imei.'.format(value))
|
raise ValidationError('{} is not a valid imei.'.format(value))
|
||||||
|
|
||||||
@validates('meid')
|
@validates('meid')
|
||||||
|
|
|
@ -104,13 +104,18 @@ class TelevisionSet(Monitor):
|
||||||
|
|
||||||
|
|
||||||
class Mobile(Device):
|
class Mobile(Device):
|
||||||
imei = Integer(validate=lambda x: imei.validate(str(x)),
|
imei = Integer(description=m.Mobile.imei.comment)
|
||||||
description=m.Mobile.imei.comment)
|
meid = Str(description=m.Mobile.meid.comment)
|
||||||
meid = Str(validate=meid.validate, description=m.Mobile.meid.comment)
|
|
||||||
|
|
||||||
@post_load
|
@pre_load
|
||||||
def convert_meid(self, data: dict):
|
def convert_check_imei(self, data):
|
||||||
if 'meid' in data:
|
if data.get('imei', None):
|
||||||
|
data['imei'] = int(imei.validate(data['imei']))
|
||||||
|
return data
|
||||||
|
|
||||||
|
@pre_load
|
||||||
|
def convert_check_meid(self, data: dict):
|
||||||
|
if data.get('meid', None):
|
||||||
data['meid'] = meid.compact(data['meid'])
|
data['meid'] = meid.compact(data['meid'])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,60 @@ 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 teal import query
|
||||||
from teal.cache import cache
|
from teal.cache import cache
|
||||||
from teal.resource import View
|
from teal.resource import View
|
||||||
|
|
||||||
from ereuse_devicehub import auth
|
from ereuse_devicehub import auth
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.resources import search
|
||||||
from ereuse_devicehub.resources.device.models import Device, Manufacturer
|
from ereuse_devicehub.resources.device.models import Device, Manufacturer
|
||||||
|
from ereuse_devicehub.resources.device.search import DeviceSearch
|
||||||
|
from ereuse_devicehub.resources.event.models import Rate
|
||||||
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
|
||||||
|
|
||||||
|
class OfType(f.Str):
|
||||||
|
def __init__(self, column: db.Column, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.column = column
|
||||||
|
|
||||||
|
def _deserialize(self, value, attr, data):
|
||||||
|
v = super()._deserialize(value, attr, data)
|
||||||
|
return self.column.in_(app.resources[v].subresources_types)
|
||||||
|
|
||||||
|
|
||||||
|
class RateQ(query.Query):
|
||||||
|
rating = query.Between(Rate.rating, f.Float())
|
||||||
|
appearance = query.Between(Rate.appearance, f.Float())
|
||||||
|
functionality = query.Between(Rate.functionality, f.Float())
|
||||||
|
|
||||||
|
|
||||||
|
class TagQ(query.Query):
|
||||||
|
id = query.Or(query.ILike(Tag.id), required=True)
|
||||||
|
org = query.ILike(Tag.org)
|
||||||
|
|
||||||
|
|
||||||
|
class Filters(query.Query):
|
||||||
|
type = query.Or(OfType(Device.type))
|
||||||
|
model = query.ILike(Device.model)
|
||||||
|
manufacturer = query.ILike(Device.manufacturer)
|
||||||
|
serialNumber = query.ILike(Device.serial_number)
|
||||||
|
rating = query.Join(Device.id == Rate.device_id, RateQ)
|
||||||
|
tag = query.Join(Device.id == Tag.id, TagQ)
|
||||||
|
|
||||||
|
|
||||||
|
class Sorting(query.Sort):
|
||||||
|
created = query.SortField(Device.created)
|
||||||
|
|
||||||
|
|
||||||
class DeviceView(View):
|
class DeviceView(View):
|
||||||
|
class FindArgs(marshmallow.Schema):
|
||||||
|
search = f.Str()
|
||||||
|
filter = f.Nested(Filters, missing=[])
|
||||||
|
sort = f.Nested(Sorting, missing=[])
|
||||||
|
page = f.Integer(validate=v.Range(min=1), missing=1)
|
||||||
|
|
||||||
def get(self, id):
|
def get(self, id):
|
||||||
"""
|
"""
|
||||||
|
@ -48,7 +94,29 @@ class DeviceView(View):
|
||||||
@auth.Auth.requires_auth
|
@auth.Auth.requires_auth
|
||||||
def find(self, args: dict):
|
def find(self, args: dict):
|
||||||
"""Gets many devices."""
|
"""Gets many devices."""
|
||||||
return self.schema.jsonify(Device.query, many=True)
|
search_p = args.get('search', None)
|
||||||
|
query = Device.query
|
||||||
|
if search_p:
|
||||||
|
properties = DeviceSearch.properties
|
||||||
|
tags = DeviceSearch.tags
|
||||||
|
query = query.join(DeviceSearch).filter(
|
||||||
|
search.Search.match(properties, search_p) | search.Search.match(tags, search_p)
|
||||||
|
).order_by(
|
||||||
|
search.Search.rank(properties, search_p) + search.Search.rank(tags, search_p)
|
||||||
|
)
|
||||||
|
query = query.filter(*args['filter']).order_by(*args['sort'])
|
||||||
|
devices = query.paginate(page=args['page'], per_page=30) # type: Pagination
|
||||||
|
ret = {
|
||||||
|
'items': self.schema.dump(devices.items, many=True, nested=1),
|
||||||
|
# todo pagination should be in Header like github
|
||||||
|
# https://developer.github.com/v3/guides/traversing-with-pagination/
|
||||||
|
'pagination': {
|
||||||
|
'page': devices.page,
|
||||||
|
'perPage': devices.per_page,
|
||||||
|
'total': devices.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonify(ret)
|
||||||
|
|
||||||
|
|
||||||
class ManufacturerView(View):
|
class ManufacturerView(View):
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
from flask import current_app as app, jsonify
|
|
||||||
from flask_sqlalchemy import Pagination
|
|
||||||
from marshmallow import Schema as MarshmallowSchema
|
|
||||||
from marshmallow.fields import Float, Integer, Nested, Str
|
|
||||||
from marshmallow.validate import Range
|
|
||||||
from sqlalchemy import Column
|
|
||||||
from teal.query import Between, ILike, Join, Or, Query, Sort, SortField
|
|
||||||
from teal.resource import Resource, View
|
|
||||||
|
|
||||||
from ereuse_devicehub.resources import search
|
|
||||||
from ereuse_devicehub.resources.device.models import Device
|
|
||||||
from ereuse_devicehub.resources.device.search import DeviceSearch
|
|
||||||
from ereuse_devicehub.resources.event.models import Rate
|
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
|
||||||
from ereuse_devicehub.resources.schemas import Thing
|
|
||||||
from ereuse_devicehub.resources.tag import Tag
|
|
||||||
|
|
||||||
|
|
||||||
class Inventory(Thing):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RateQ(Query):
|
|
||||||
rating = Between(Rate.rating, Float())
|
|
||||||
appearance = Between(Rate.appearance, Float())
|
|
||||||
functionality = Between(Rate.functionality, Float())
|
|
||||||
|
|
||||||
|
|
||||||
class TagQ(Query):
|
|
||||||
id = Or(ILike(Tag.id), required=True)
|
|
||||||
org = ILike(Tag.org)
|
|
||||||
|
|
||||||
|
|
||||||
class OfType(Str):
|
|
||||||
def __init__(self, column: Column, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.column = column
|
|
||||||
|
|
||||||
def _deserialize(self, value, attr, data):
|
|
||||||
v = super()._deserialize(value, attr, data)
|
|
||||||
return self.column.in_(app.resources[v].subresources_types)
|
|
||||||
|
|
||||||
|
|
||||||
class Filters(Query):
|
|
||||||
type = Or(OfType(Device.type))
|
|
||||||
model = ILike(Device.model)
|
|
||||||
manufacturer = ILike(Device.manufacturer)
|
|
||||||
serialNumber = ILike(Device.serial_number)
|
|
||||||
rating = Join(Device.id == Rate.device_id, RateQ)
|
|
||||||
tag = Join(Device.id == Tag.id, TagQ)
|
|
||||||
|
|
||||||
|
|
||||||
class Sorting(Sort):
|
|
||||||
created = SortField(Device.created)
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryView(View):
|
|
||||||
class FindArgs(MarshmallowSchema):
|
|
||||||
search = Str()
|
|
||||||
filter = Nested(Filters, missing=[])
|
|
||||||
sort = Nested(Sorting, missing=[Device.created.desc()])
|
|
||||||
page = Integer(validate=Range(min=1), missing=1)
|
|
||||||
|
|
||||||
def get(self, id):
|
|
||||||
"""Inventory view
|
|
||||||
---
|
|
||||||
description: Supports the inventory view of ``devicehub-client``; returns
|
|
||||||
all the devices, groups and widgets of this Devicehub instance.
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: The inventory.
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
devices:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/Device'
|
|
||||||
pagination:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
page:
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
perPage:
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
total:
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
"""
|
|
||||||
# todo .format(yaml.load(schema2parameters(self.FindArgs, default_in='path', name='path')))
|
|
||||||
return super().get(id)
|
|
||||||
|
|
||||||
def find(self, args: dict):
|
|
||||||
"""See :meth:`.get` above."""
|
|
||||||
search_p = args.get('search', None)
|
|
||||||
query = Device.query
|
|
||||||
if search_p:
|
|
||||||
properties = DeviceSearch.properties
|
|
||||||
tags = DeviceSearch.tags
|
|
||||||
query = query.join(DeviceSearch).filter(
|
|
||||||
search.Search.match(properties, search_p) | search.Search.match(tags, search_p)
|
|
||||||
).order_by(
|
|
||||||
search.Search.rank(properties, search_p) + search.Search.rank(tags, search_p)
|
|
||||||
)
|
|
||||||
query = query.filter(*args['filter']).order_by(*args['sort'])
|
|
||||||
devices = query.paginate(page=args['page'], per_page=30) # type: Pagination
|
|
||||||
inventory = {
|
|
||||||
'devices': app.resources[Device.t].schema.dump(devices.items, many=True, nested=1),
|
|
||||||
'lots': app.resources[Lot.t].schema.dump(Lot.roots(), many=True, nested=1),
|
|
||||||
'widgets': {},
|
|
||||||
'pagination': {
|
|
||||||
'page': devices.page,
|
|
||||||
'perPage': devices.per_page,
|
|
||||||
'total': devices.total,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return jsonify(inventory)
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryDef(Resource):
|
|
||||||
SCHEMA = Inventory
|
|
||||||
VIEW = InventoryView
|
|
||||||
AUTH = True
|
|
|
@ -7,7 +7,7 @@ from teal.resource import Converters, Resource
|
||||||
from teal.teal import Teal
|
from teal.teal import Teal
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.device import DeviceDef
|
from ereuse_devicehub.resources.device.definitions import DeviceDef
|
||||||
from ereuse_devicehub.resources.tag import schema
|
from ereuse_devicehub.resources.tag import schema
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
from ereuse_devicehub.resources.tag.view import TagDeviceView, TagView, get_device_from_tag
|
from ereuse_devicehub.resources.tag.view import TagDeviceView, TagView, get_device_from_tag
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../ereuse_devicehub/dummy/files/smartphone.snapshot.yaml
|
|
@ -17,7 +17,6 @@ def test_api_docs(client: Client):
|
||||||
docs, _ = client.get('/apidocs')
|
docs, _ = client.get('/apidocs')
|
||||||
assert set(docs['paths'].keys()) == {
|
assert set(docs['paths'].keys()) == {
|
||||||
# todo this does not appear: '/tags/{id}/device',
|
# todo this does not appear: '/tags/{id}/device',
|
||||||
'/inventories/',
|
|
||||||
'/apidocs',
|
'/apidocs',
|
||||||
'/users/',
|
'/users/',
|
||||||
'/devices/',
|
'/devices/',
|
||||||
|
@ -40,4 +39,4 @@ def test_api_docs(client: Client):
|
||||||
'scheme': 'basic',
|
'scheme': 'basic',
|
||||||
'name': 'Authorization'
|
'name': 'Authorization'
|
||||||
}
|
}
|
||||||
assert 78 == len(docs['definitions'])
|
assert 77 == len(docs['definitions'])
|
||||||
|
|
|
@ -24,7 +24,6 @@ from ereuse_devicehub.resources.device.sync import MismatchBetweenTags, Mismatch
|
||||||
from ereuse_devicehub.resources.enums import ComputerChassis, DisplayTech
|
from ereuse_devicehub.resources.enums import ComputerChassis, DisplayTech
|
||||||
from ereuse_devicehub.resources.event import models as m
|
from ereuse_devicehub.resources.event import models as m
|
||||||
from ereuse_devicehub.resources.event.models import Remove, Test
|
from ereuse_devicehub.resources.event.models import Remove, Test
|
||||||
from ereuse_devicehub.resources.inventory import Inventory
|
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
from ereuse_devicehub.resources.user import User
|
from ereuse_devicehub.resources.user import User
|
||||||
from tests import conftest
|
from tests import conftest
|
||||||
|
@ -423,8 +422,8 @@ def test_get_devices(app: Devicehub, user: UserClient):
|
||||||
db.session.add_all((pc, pc1, pc2))
|
db.session.add_all((pc, pc1, pc2))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
devices, _ = user.get(res=Device)
|
devices, _ = user.get(res=Device)
|
||||||
assert tuple(d['id'] for d in devices) == (1, 2, 3, 4, 5)
|
assert tuple(d['id'] for d in devices['items']) == (1, 2, 3, 4, 5)
|
||||||
assert tuple(d['type'] for d in devices) == (
|
assert tuple(d['type'] for d in devices['items']) == (
|
||||||
'Desktop', 'Desktop', 'Laptop', 'NetworkAdapter', 'GraphicCard'
|
'Desktop', 'Desktop', 'Laptop', 'NetworkAdapter', 'GraphicCard'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -463,12 +462,12 @@ def test_device_search_all_devices_token_if_empty(app: Devicehub, user: UserClie
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
app.db.session.execute('TRUNCATE TABLE {}'.format(DeviceSearch.__table__.name))
|
app.db.session.execute('TRUNCATE TABLE {}'.format(DeviceSearch.__table__.name))
|
||||||
app.db.session.commit()
|
app.db.session.commit()
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'Desktop')])
|
i, _ = user.get(res=Device, query=[('search', 'Desktop')])
|
||||||
assert not len(i['devices'])
|
assert not len(i['items'])
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
DeviceSearch.set_all_devices_tokens_if_empty(app.db.session)
|
DeviceSearch.set_all_devices_tokens_if_empty(app.db.session)
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'Desktop')])
|
i, _ = user.get(res=Device, query=[('search', 'Desktop')])
|
||||||
assert not len(i['devices'])
|
assert not len(i['items'])
|
||||||
|
|
||||||
|
|
||||||
def test_manufacturer(user: UserClient):
|
def test_manufacturer(user: UserClient):
|
||||||
|
|
|
@ -5,15 +5,15 @@ 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, Device, Laptop, SolidStateDrive
|
from ereuse_devicehub.resources.device.models import Desktop, Device, Laptop, SolidStateDrive
|
||||||
|
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.inventory import Filters, Inventory, Sorting
|
|
||||||
from tests import conftest
|
from tests import conftest
|
||||||
from tests.conftest import file
|
from tests.conftest import file
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
def test_inventory_filters():
|
def test_device_filters():
|
||||||
schema = Filters()
|
schema = Filters()
|
||||||
q = schema.load({
|
q = schema.load({
|
||||||
'type': ['Computer', 'Laptop'],
|
'type': ['Computer', 'Laptop'],
|
||||||
|
@ -45,14 +45,14 @@ def test_inventory_filters():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
def test_inventory_sort():
|
def test_device_sort():
|
||||||
schema = Sorting()
|
schema = Sorting()
|
||||||
r = next(schema.load({'created': True}))
|
r = next(schema.load({'created': True}))
|
||||||
assert str(r) == 'device.created ASC'
|
assert str(r) == 'device.created ASC'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def inventory_query_dummy(app: Devicehub):
|
def device_query_dummy(app: Devicehub):
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
devices = ( # The order matters ;-)
|
devices = ( # The order matters ;-)
|
||||||
Desktop(serial_number='s1',
|
Desktop(serial_number='s1',
|
||||||
|
@ -75,72 +75,72 @@ def inventory_query_dummy(app: Devicehub):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(inventory_query_dummy.__name__)
|
@pytest.mark.usefixtures(device_query_dummy.__name__)
|
||||||
def test_inventory_query_no_filters(user: UserClient):
|
def test_device_query_no_filters(user: UserClient):
|
||||||
i, _ = user.get(res=Inventory)
|
i, _ = user.get(res=Device)
|
||||||
assert tuple(d['type'] for d in i['devices']) == (
|
assert tuple(d['type'] for d in i['items']) == (
|
||||||
'SolidStateDrive', 'Desktop', 'Laptop', 'Desktop'
|
'Desktop', 'Laptop', 'Desktop', 'SolidStateDrive'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(inventory_query_dummy.__name__)
|
@pytest.mark.usefixtures(device_query_dummy.__name__)
|
||||||
def test_inventory_query_filter_type(user: UserClient):
|
def test_device_query_filter_type(user: UserClient):
|
||||||
i, _ = user.get(res=Inventory, query=[('filter', {'type': ['Desktop', 'Laptop']})])
|
i, _ = user.get(res=Device, query=[('filter', {'type': ['Desktop', 'Laptop']})])
|
||||||
assert tuple(d['type'] for d in i['devices']) == ('Desktop', 'Laptop', 'Desktop')
|
assert tuple(d['type'] for d in i['items']) == ('Desktop', 'Laptop', 'Desktop')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(inventory_query_dummy.__name__)
|
@pytest.mark.usefixtures(device_query_dummy.__name__)
|
||||||
def test_inventory_query_filter_sort(user: UserClient):
|
def test_device_query_filter_sort(user: UserClient):
|
||||||
i, _ = user.get(res=Inventory, query=[
|
i, _ = user.get(res=Device, query=[
|
||||||
('sort', {'created': Sorting.ASCENDING}),
|
('sort', {'created': Sorting.ASCENDING}),
|
||||||
('filter', {'type': ['Computer']})
|
('filter', {'type': ['Computer']})
|
||||||
])
|
])
|
||||||
assert tuple(d['type'] for d in i['devices']) == ('Desktop', 'Laptop', 'Desktop')
|
assert tuple(d['type'] for d in i['items']) == ('Desktop', 'Laptop', 'Desktop')
|
||||||
|
|
||||||
|
|
||||||
def test_inventory_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)
|
||||||
i, _ = user.get(res=Inventory)
|
i, _ = user.get(res=Device)
|
||||||
pc = next(d for d in i['devices'] if d['type'] == 'Desktop')
|
pc = next(d for d in i['items'] if d['type'] == 'Desktop')
|
||||||
assert len(pc['events']) == 4
|
assert len(pc['events']) == 4
|
||||||
assert len(pc['components']) == 3
|
assert len(pc['components']) == 3
|
||||||
assert not pc['tags']
|
assert not pc['tags']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Functionality not yet developed.')
|
@pytest.mark.xfail(reason='Functionality not yet developed.')
|
||||||
def test_inventory_lots_query(user: UserClient):
|
def test_device_lots_query(user: UserClient):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_inventory_query_search(user: UserClient):
|
def test_device_query_search(user: UserClient):
|
||||||
# todo improve
|
# todo improve
|
||||||
user.post(file('basic.snapshot'), res=Snapshot)
|
user.post(file('basic.snapshot'), res=Snapshot)
|
||||||
user.post(file('computer-monitor.snapshot'), res=Snapshot)
|
user.post(file('computer-monitor.snapshot'), res=Snapshot)
|
||||||
user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
|
user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'desktop')])
|
i, _ = user.get(res=Device, query=[('search', 'desktop')])
|
||||||
assert i['devices'][0]['id'] == 1
|
assert i['items'][0]['id'] == 1
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'intel')])
|
i, _ = user.get(res=Device, query=[('search', 'intel')])
|
||||||
assert len(i['devices']) == 1
|
assert len(i['items']) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='No dictionary yet that knows asustek = asus')
|
@pytest.mark.xfail(reason='No dictionary yet that knows asustek = asus')
|
||||||
def test_inventory_query_search_synonyms_asus(user: UserClient):
|
def test_device_query_search_synonyms_asus(user: UserClient):
|
||||||
user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
|
user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'asustek')])
|
i, _ = user.get(res=Device, query=[('search', 'asustek')])
|
||||||
assert len(i['devices']) == 1
|
assert len(i['items']) == 1
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'asus')])
|
i, _ = user.get(res=Device, query=[('search', 'asus')])
|
||||||
assert len(i['devices']) == 1
|
assert len(i['items']) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='No dictionary yet that knows hp = hewlett packard')
|
@pytest.mark.xfail(reason='No dictionary yet that knows hp = hewlett packard')
|
||||||
def test_inventory_query_search_synonyms_intel(user: UserClient):
|
def test_device_query_search_synonyms_intel(user: UserClient):
|
||||||
s = file('real-hp.snapshot.11')
|
s = file('real-hp.snapshot.11')
|
||||||
s['device']['model'] = 'foo' # The model had the word 'HP' in it
|
s['device']['model'] = 'foo' # The model had the word 'HP' in it
|
||||||
user.post(s, res=Snapshot)
|
user.post(s, res=Snapshot)
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'hewlett packard')])
|
i, _ = user.get(res=Device, query=[('search', 'hewlett packard')])
|
||||||
assert len(i['devices']) == 1
|
assert len(i['items']) == 1
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'hewlett')])
|
i, _ = user.get(res=Device, query=[('search', 'hewlett')])
|
||||||
assert len(i['devices']) == 1
|
assert len(i['items']) == 1
|
||||||
i, _ = user.get(res=Inventory, query=[('search', 'hp')])
|
i, _ = user.get(res=Device, query=[('search', 'hp')])
|
||||||
assert len(i['devices']) == 1
|
assert len(i['items']) == 1
|
|
@ -328,6 +328,11 @@ def test_snapshot_computer_monitor(user: UserClient):
|
||||||
snapshot_and_check(user, s, event_types=('AppRate',))
|
snapshot_and_check(user, s, event_types=('AppRate',))
|
||||||
|
|
||||||
|
|
||||||
|
def test_snapshot_mobile_smartphone(user: UserClient):
|
||||||
|
s = file('smartphone.snapshot')
|
||||||
|
snapshot_and_check(user, s, event_types=('AppRate',))
|
||||||
|
|
||||||
|
|
||||||
def test_snapshot_components_none():
|
def test_snapshot_components_none():
|
||||||
"""
|
"""
|
||||||
Tests that a snapshot without components does not
|
Tests that a snapshot without components does not
|
||||||
|
|
Reference in New Issue