Add inventory and missing fields
This commit is contained in:
parent
02a0332dd6
commit
b56fbeeca7
|
@ -139,12 +139,12 @@ is as follows:
|
||||||
``Snapshot``.
|
``Snapshot``.
|
||||||
3. In **T3**, WorkbenchServer submits the ``Erase`` with the ``Snapshot``
|
3. In **T3**, WorkbenchServer submits the ``Erase`` with the ``Snapshot``
|
||||||
and ``component`` IDs from 1, linking it to them. It repeats
|
and ``component`` IDs from 1, linking it to them. It repeats
|
||||||
this for all the erased data storage devices; **T2+Tn** being
|
this for all the erased data storage devices; **T3+Tn** being
|
||||||
*n* the erased data storage devices.
|
*n* the erased data storage devices.
|
||||||
4. WorkbenchServer does like in 3. but for the event ``Install``,
|
4. WorkbenchServer does like in 3. but for the event ``Install``,
|
||||||
finishing in **T2+Tn+Tx**, being *x* the number of data storage
|
finishing in **T3+Tn+Tx**, being *x* the number of data storage
|
||||||
devices with an OS installed into.
|
devices with an OS installed into.
|
||||||
5. In **T2+Tn+Tx**, when all *expected events* have been performed,
|
5. In **T3+Tn+Tx**, when all *expected events* have been performed,
|
||||||
Devicehub **closes** the ``Snapshot`` from 1.
|
Devicehub **closes** the ``Snapshot`` from 1.
|
||||||
|
|
||||||
Optionally, Devicehub understands receiving a ``Snapshot`` with all
|
Optionally, Devicehub understands receiving a ``Snapshot`` with all
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
Getting
|
||||||
|
=======
|
||||||
|
|
||||||
|
Devicehub uses the same path to get devices and lots.
|
||||||
|
|
||||||
|
To get the lot information ::
|
||||||
|
|
||||||
|
GET /inventory/24
|
||||||
|
|
||||||
|
You can specifically filter devices::
|
||||||
|
|
||||||
|
GET /inventory?devices?
|
||||||
|
GET /inventory/24?type=24&type=44&status={"name": "Reserved", "updated": "2018-01-01"}
|
||||||
|
GET /inventory/25?price=24&price=21
|
||||||
|
|
||||||
|
GET /devices/4?
|
||||||
|
|
||||||
|
Returns devices that matches the filters and the lots that contain them.
|
||||||
|
If the filters are applied to the lots, it returns the matched lots
|
||||||
|
and the devices that contain them.
|
||||||
|
You can join filters.
|
||||||
|
|
|
@ -9,6 +9,7 @@ from ereuse_devicehub.resources.event import AddDef, AggregateRateDef, EventDef,
|
||||||
PhotoboxSystemRateDef, PhotoboxUserDef, RateDef, RemoveDef, SnapshotDef, StepDef, \
|
PhotoboxSystemRateDef, PhotoboxUserDef, RateDef, RemoveDef, SnapshotDef, StepDef, \
|
||||||
StepRandomDef, StepZeroDef, TestDataStorageDef, TestDef, WorkbenchRateDef, EraseBasicDef, \
|
StepRandomDef, StepZeroDef, TestDataStorageDef, TestDef, WorkbenchRateDef, EraseBasicDef, \
|
||||||
EraseSectorsDef
|
EraseSectorsDef
|
||||||
|
from ereuse_devicehub.resources.inventory import InventoryDef
|
||||||
from ereuse_devicehub.resources.tag import TagDef
|
from ereuse_devicehub.resources.tag import TagDef
|
||||||
from ereuse_devicehub.resources.user import OrganizationDef, UserDef
|
from ereuse_devicehub.resources.user import OrganizationDef, UserDef
|
||||||
from teal.config import Config
|
from teal.config import Config
|
||||||
|
@ -22,7 +23,7 @@ class DevicehubConfig(Config):
|
||||||
OrganizationDef, TagDef, EventDef, AddDef, RemoveDef, EraseBasicDef, EraseSectorsDef,
|
OrganizationDef, TagDef, EventDef, AddDef, RemoveDef, EraseBasicDef, EraseSectorsDef,
|
||||||
StepDef, StepZeroDef, StepRandomDef, RateDef, AggregateRateDef, WorkbenchRateDef,
|
StepDef, StepZeroDef, StepRandomDef, RateDef, AggregateRateDef, WorkbenchRateDef,
|
||||||
PhotoboxUserDef, PhotoboxSystemRateDef, InstallDef, SnapshotDef, TestDef,
|
PhotoboxUserDef, PhotoboxSystemRateDef, InstallDef, SnapshotDef, TestDef,
|
||||||
TestDataStorageDef, WorkbenchRateDef
|
TestDataStorageDef, WorkbenchRateDef, InventoryDef
|
||||||
}
|
}
|
||||||
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
|
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
|
||||||
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/dh-db1' # type: str
|
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/dh-db1' # type: str
|
||||||
|
|
|
@ -4,12 +4,13 @@ from operator import attrgetter
|
||||||
from typing import Dict, Set
|
from typing import Dict, Set
|
||||||
|
|
||||||
from sqlalchemy import BigInteger, Column, Float, ForeignKey, Integer, Sequence, SmallInteger, \
|
from sqlalchemy import BigInteger, Column, Float, ForeignKey, Integer, Sequence, SmallInteger, \
|
||||||
Unicode, inspect
|
Unicode, inspect, Enum as DBEnum
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.orm import ColumnProperty, backref, relationship
|
from sqlalchemy.orm import ColumnProperty, backref, relationship
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
from sqlalchemy_utils import ColorType
|
from sqlalchemy_utils import ColorType
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.enums import DataStorageInterface, RamInterface, RamFormat
|
||||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
|
||||||
from ereuse_utils.naming import Naming
|
from ereuse_utils.naming import Naming
|
||||||
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, check_range
|
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, check_range
|
||||||
|
@ -151,6 +152,7 @@ class GraphicCard(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
class DataStorage(JoinedComponentTableMixin, Component):
|
class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
||||||
|
interface = Column(DBEnum(DataStorageInterface))
|
||||||
|
|
||||||
|
|
||||||
class HardDrive(DataStorage):
|
class HardDrive(DataStorage):
|
||||||
|
@ -182,3 +184,5 @@ class Processor(JoinedComponentTableMixin, Component):
|
||||||
class RamModule(JoinedComponentTableMixin, Component):
|
class RamModule(JoinedComponentTableMixin, Component):
|
||||||
size = Column(SmallInteger, check_range('size', min=128, max=17000))
|
size = Column(SmallInteger, check_range('size', min=128, max=17000))
|
||||||
speed = Column(Float, check_range('speed', min=100, max=10000))
|
speed = Column(Float, check_range('speed', min=100, max=10000))
|
||||||
|
interface = Column(DBEnum(RamInterface))
|
||||||
|
format = Column(DBEnum(RamFormat))
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from marshmallow.fields import Float, Integer, Str
|
from marshmallow.fields import Float, Integer, Str
|
||||||
from marshmallow.validate import Length, OneOf, Range
|
from marshmallow.validate import Length, OneOf, Range
|
||||||
|
from marshmallow_enum import EnumField
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
|
|
||||||
from ereuse_devicehub.marshmallow import NestedOn
|
from ereuse_devicehub.marshmallow import NestedOn
|
||||||
|
from ereuse_devicehub.resources.enums import RamInterface, RamFormat
|
||||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||||
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
|
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
|
||||||
|
|
||||||
|
@ -105,3 +107,5 @@ class Processor(Component):
|
||||||
class RamModule(Component):
|
class RamModule(Component):
|
||||||
size = Integer(validate=Range(min=128, max=17000), unit=UnitCodes.mbyte)
|
size = Integer(validate=Range(min=128, max=17000), unit=UnitCodes.mbyte)
|
||||||
speed = Float(validate=Range(min=100, max=10000), unit=UnitCodes.mhz)
|
speed = Float(validate=Range(min=100, max=10000), unit=UnitCodes.mhz)
|
||||||
|
interface = EnumField(RamInterface)
|
||||||
|
format = EnumField(RamFormat)
|
||||||
|
|
|
@ -3,7 +3,6 @@ from enum import Enum, IntEnum, unique
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class SnapshotSoftware(Enum):
|
class SnapshotSoftware(Enum):
|
||||||
"""The algorithm_software used to perform the Snapshot."""
|
"""The algorithm_software used to perform the Snapshot."""
|
||||||
|
@ -125,3 +124,28 @@ class SnapshotExpectedEvents(Enum):
|
||||||
|
|
||||||
BOX_RATE_5 = 1, 5
|
BOX_RATE_5 = 1, 5
|
||||||
BOX_RATE_3 = 1, 3
|
BOX_RATE_3 = 1, 3
|
||||||
|
|
||||||
|
|
||||||
|
# After looking at own databases
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class RamInterface(Enum):
|
||||||
|
DDR = 'DDR'
|
||||||
|
DDR2 = 'DDR2'
|
||||||
|
DDR3 = 'DDR3'
|
||||||
|
DDR4 = 'DDR4'
|
||||||
|
DDR5 = 'DDR5'
|
||||||
|
DDR6 = 'DDR6'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class RamFormat(Enum):
|
||||||
|
DIMM = 'DIMM'
|
||||||
|
SODIMM = 'SODIMM'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class DataStorageInterface(Enum):
|
||||||
|
ATA = 'ATA'
|
||||||
|
USB = 'USB'
|
||||||
|
PCI = 'PCI'
|
||||||
|
|
|
@ -30,18 +30,37 @@ class JoinedTableMixin:
|
||||||
|
|
||||||
class Event(Thing):
|
class Event(Thing):
|
||||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
title = Column(Unicode(STR_BIG_SIZE), default='', nullable=False)
|
name = Column(Unicode(STR_BIG_SIZE), default='', nullable=False)
|
||||||
|
name.comment = """
|
||||||
|
A name or title for the event. Used when searching for events.
|
||||||
|
"""
|
||||||
type = Column(Unicode)
|
type = Column(Unicode)
|
||||||
incidence = Column(Boolean, default=False, nullable=False)
|
incidence = Column(Boolean, default=False, nullable=False)
|
||||||
closed = Column(Boolean, default=True, nullable=False)
|
incidence.comment = """
|
||||||
|
Should this event be reviewed due some anomaly?
|
||||||
"""
|
"""
|
||||||
|
closed = Column(Boolean, default=True, nullable=False)
|
||||||
|
closed.comment = """
|
||||||
Whether the author has finished the event.
|
Whether the author has finished the event.
|
||||||
After this is set to True, no modifications are allowed.
|
After this is set to True, no modifications are allowed.
|
||||||
"""
|
"""
|
||||||
error = Column(Boolean, default=False, nullable=False)
|
error = Column(Boolean, default=False, nullable=False)
|
||||||
|
error.comment = """
|
||||||
|
Did the event fail?
|
||||||
|
For example, a failure in ``Erase`` means that the data storage
|
||||||
|
unit did not erase correctly.
|
||||||
|
"""
|
||||||
description = Column(Unicode, default='', nullable=False)
|
description = Column(Unicode, default='', nullable=False)
|
||||||
|
description.comment = """
|
||||||
|
A comment about the event.
|
||||||
|
"""
|
||||||
date = Column(DateTime)
|
date = Column(DateTime)
|
||||||
|
date.comment = """
|
||||||
|
When this event happened.
|
||||||
|
Leave it blank if it is happening now
|
||||||
|
(the field ``created`` is used instead).
|
||||||
|
This is used for example when creating events retroactively.
|
||||||
|
"""
|
||||||
snapshot_id = Column(UUID(as_uuid=True), ForeignKey('snapshot.id',
|
snapshot_id = Column(UUID(as_uuid=True), ForeignKey('snapshot.id',
|
||||||
use_alter=True,
|
use_alter=True,
|
||||||
name='snapshot_events'))
|
name='snapshot_events'))
|
||||||
|
@ -347,6 +366,31 @@ class StressTest(Test):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Benchmark(EventWithOneDevice):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BenchmarkDataStorage(Benchmark):
|
||||||
|
readSpeed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||||
|
writeSpeed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class BenchmarkWithRate(Benchmark):
|
||||||
|
rate = Column(SmallInteger, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class BenchmarkProcessor(BenchmarkWithRate):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BenchmarkProcessorSysbench(BenchmarkProcessor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BenchmarkRamSysbench(BenchmarkWithRate):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Listeners
|
# Listeners
|
||||||
@event.listens_for(TestDataStorage.device, 'set', retval=True, propagate=True)
|
@event.listens_for(TestDataStorage.device, 'set', retval=True, propagate=True)
|
||||||
@event.listens_for(Install.device, 'set', retval=True, propagate=True)
|
@event.listens_for(Install.device, 'set', retval=True, propagate=True)
|
||||||
|
|
|
@ -17,9 +17,10 @@ from teal.db import Model
|
||||||
|
|
||||||
class Event(Thing):
|
class Event(Thing):
|
||||||
id = ... # type: Column
|
id = ... # type: Column
|
||||||
title = ... # type: Column
|
name = ... # type: Column
|
||||||
date = ... # type: Column
|
date = ... # type: Column
|
||||||
type = ... # type: Column
|
type = ... # type: Column
|
||||||
|
error = ... # type: Column
|
||||||
incidence = ... # type: Column
|
incidence = ... # type: Column
|
||||||
description = ... # type: Column
|
description = ... # type: Column
|
||||||
finalized = ... # type: Column
|
finalized = ... # type: Column
|
||||||
|
@ -32,7 +33,7 @@ class Event(Thing):
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.id = ... # type: UUID
|
self.id = ... # type: UUID
|
||||||
self.title = ... # type: str
|
self.name = ... # type: str
|
||||||
self.type = ... # type: str
|
self.type = ... # type: str
|
||||||
self.incidence = ... # type: bool
|
self.incidence = ... # type: bool
|
||||||
self.closed = ... # type: bool
|
self.closed = ... # type: bool
|
||||||
|
@ -108,6 +109,9 @@ class SnapshotRequest(Model):
|
||||||
|
|
||||||
|
|
||||||
class Rate(EventWithOneDevice):
|
class Rate(EventWithOneDevice):
|
||||||
|
rating = ... # type: Column
|
||||||
|
appearance = ... # type: Column
|
||||||
|
functionality = ... # type: Column
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.rating = ... # type: float
|
self.rating = ... # type: float
|
||||||
|
|
|
@ -8,6 +8,7 @@ from ereuse_devicehub.marshmallow import NestedOn
|
||||||
from ereuse_devicehub.resources.device.schemas import Component, Device
|
from ereuse_devicehub.resources.device.schemas import Component, Device
|
||||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
||||||
RATE_POSITIVE, RatingSoftware, SnapshotExpectedEvents, SnapshotSoftware, TestHardDriveLength
|
RATE_POSITIVE, RatingSoftware, SnapshotExpectedEvents, SnapshotSoftware, TestHardDriveLength
|
||||||
|
from ereuse_devicehub.resources.event import models as m
|
||||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||||
from ereuse_devicehub.resources.schemas import Thing
|
from ereuse_devicehub.resources.schemas import Thing
|
||||||
from ereuse_devicehub.resources.user.schemas import User
|
from ereuse_devicehub.resources.user.schemas import User
|
||||||
|
@ -17,18 +18,14 @@ from teal.resource import Schema
|
||||||
|
|
||||||
class Event(Thing):
|
class Event(Thing):
|
||||||
id = Integer(dump_only=True)
|
id = Integer(dump_only=True)
|
||||||
title = String(default='',
|
name = String(default='', validate=Length(STR_BIG_SIZE), description=m.Event.name.comment)
|
||||||
validate=Length(STR_BIG_SIZE),
|
date = DateTime('iso', description=m.Event.date.comment)
|
||||||
description='A name or title for the event. Used when searching for events.')
|
error = Boolean(default=False, description=m.Event.error.comment)
|
||||||
date = DateTime('iso', description='When this event happened. '
|
incidence = Boolean(default=False, description=m.Event.incidence.comment)
|
||||||
'Leave it blank if it is happening now. '
|
|
||||||
'This is used when creating events retroactively.')
|
|
||||||
error = Boolean(default=False, description='Did the event fail?')
|
|
||||||
incidence = Boolean(default=False,
|
|
||||||
description='Should this event be reviewed due some anomaly?')
|
|
||||||
snapshot = NestedOn('Snapshot', dump_only=True)
|
snapshot = NestedOn('Snapshot', dump_only=True)
|
||||||
components = NestedOn(Component, dump_only=True, many=True)
|
components = NestedOn(Component, dump_only=True, many=True)
|
||||||
description = String(default='', description='A comment about the event.')
|
description = String(default='', description=m.Event.description.comment)
|
||||||
|
author = NestedOn(User, dump_only=True, exclude=('token',))
|
||||||
|
|
||||||
|
|
||||||
class EventWithOneDevice(Event):
|
class EventWithOneDevice(Event):
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
from flask import current_app as app, jsonify
|
||||||
|
from marshmallow import Schema as MarshmallowSchema
|
||||||
|
from marshmallow.fields import Float, Nested, Str
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
|
from ereuse_devicehub.resources.event.models import Rate
|
||||||
|
from ereuse_devicehub.resources.tag import Tag
|
||||||
|
from teal.marshmallow import IsType
|
||||||
|
from teal.query import Between, Equal, ILike, Or, Query
|
||||||
|
from teal.resource import Resource, Schema, View
|
||||||
|
|
||||||
|
|
||||||
|
class Inventory(Schema):
|
||||||
|
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 Filters(Query):
|
||||||
|
type = Or(Equal(Device.type, Str(validate=IsType(Device.t))))
|
||||||
|
model = ILike(Device.model)
|
||||||
|
manufacturer = ILike(Device.manufacturer)
|
||||||
|
serialNumber = ILike(Device.serial_number)
|
||||||
|
rating = Nested(RateQ) # todo db join
|
||||||
|
tag = Nested(TagQ) # todo db join
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryView(View):
|
||||||
|
class FindArgs(MarshmallowSchema):
|
||||||
|
where = Nested(Filters, default={})
|
||||||
|
|
||||||
|
def find(self, args):
|
||||||
|
devices = Device.query.filter_by()
|
||||||
|
inventory = {
|
||||||
|
'devices': app.resources[Device.t].schema.dump()
|
||||||
|
}
|
||||||
|
return jsonify(inventory)
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryDef(Resource):
|
||||||
|
SCHEMA = Inventory
|
||||||
|
VIEW = InventoryView
|
||||||
|
AUTH = True
|
|
@ -11,4 +11,10 @@ STR_XSM_SIZE = 16
|
||||||
class Thing(db.Model):
|
class Thing(db.Model):
|
||||||
__abstract__ = True
|
__abstract__ = True
|
||||||
updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
|
updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
|
||||||
|
updated.comment = """
|
||||||
|
When this was last changed.
|
||||||
|
"""
|
||||||
created = db.Column(db.DateTime, default=datetime.utcnow)
|
created = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
created.comment = """
|
||||||
|
When Devicehub created this.
|
||||||
|
"""
|
||||||
|
|
|
@ -3,7 +3,7 @@ from enum import Enum
|
||||||
from marshmallow import post_load
|
from marshmallow import post_load
|
||||||
from marshmallow.fields import DateTime, List, String, URL
|
from marshmallow.fields import DateTime, List, String, URL
|
||||||
|
|
||||||
from ereuse_devicehub.marshmallow import NestedOn
|
from ereuse_devicehub.resources import models as m
|
||||||
from teal.resource import Schema
|
from teal.resource import Schema
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,9 +22,8 @@ class Thing(Schema):
|
||||||
type = String(description='Only required when it is nested.')
|
type = String(description='Only required when it is nested.')
|
||||||
url = URL(dump_only=True, description='The URL of the resource.')
|
url = URL(dump_only=True, description='The URL of the resource.')
|
||||||
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
|
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
|
||||||
updated = DateTime('iso', dump_only=True)
|
updated = DateTime('iso', dump_only=True, description=m.Thing.updated)
|
||||||
created = DateTime('iso', dump_only=True)
|
created = DateTime('iso', dump_only=True, description=m.Thing.created)
|
||||||
author = NestedOn('User', dump_only=True, exclude=('token',))
|
|
||||||
|
|
||||||
@post_load
|
@post_load
|
||||||
def remove_type(self, data: dict):
|
def remove_type(self, data: dict):
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -8,6 +8,7 @@ setup(
|
||||||
license='Affero',
|
license='Affero',
|
||||||
author='eReuse.org team',
|
author='eReuse.org team',
|
||||||
author_email='x.bustamante@ereuse.org',
|
author_email='x.bustamante@ereuse.org',
|
||||||
|
include_package_data=True,
|
||||||
description='A system to manage devices focusing reuse.',
|
description='A system to manage devices focusing reuse.',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'teal>=0.2.0a1',
|
'teal>=0.2.0a1',
|
||||||
|
@ -34,5 +35,5 @@ setup(
|
||||||
'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
|
'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
'License :: OSI Approved :: GNU Affero General Public License v3'
|
'License :: OSI Approved :: GNU Affero General Public License v3'
|
||||||
],
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import pytest
|
||||||
|
from sqlalchemy.sql.elements import BinaryExpression
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
|
from ereuse_devicehub.resources.inventory import Filters, InventoryView
|
||||||
|
from teal.utils import compiled
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('app_context')
|
||||||
|
def test_inventory_filters():
|
||||||
|
schema = Filters()
|
||||||
|
q = schema.load({
|
||||||
|
'type': ['Microtower', 'Laptop'],
|
||||||
|
'manufacturer': 'Dell',
|
||||||
|
'rating': {
|
||||||
|
'rating': [3, 6],
|
||||||
|
'appearance': [2, 4]
|
||||||
|
},
|
||||||
|
'tag': {
|
||||||
|
'id': ['bcn-', 'activa-02']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s, params = compiled(Device, q)
|
||||||
|
# Order between query clauses can change
|
||||||
|
assert '(device.type = %(type_1)s OR device.type = %(type_2)s)' in s
|
||||||
|
assert 'device.manufacturer ILIKE %(manufacturer_1)s' in s
|
||||||
|
assert 'rate.rating BETWEEN %(rating_1)s AND %(rating_2)s' in s
|
||||||
|
assert 'rate.appearance BETWEEN %(appearance_1)s AND %(appearance_2)s' in s
|
||||||
|
assert '(tag.id ILIKE %(id_1)s OR tag.id ILIKE %(id_2)s)' in s
|
||||||
|
assert params == {
|
||||||
|
'type_1': 'Microtower',
|
||||||
|
'rating_2': 6.0,
|
||||||
|
'manufacturer_1': 'Dell%',
|
||||||
|
'appearance_1': 2.0,
|
||||||
|
'appearance_2': 4.0,
|
||||||
|
'id_1': 'bcn-%',
|
||||||
|
'rating_1': 3.0,
|
||||||
|
'id_2': 'activa-02%',
|
||||||
|
'type_2': 'Laptop'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('app_context')
|
||||||
|
def test_inventory_query():
|
||||||
|
schema = InventoryView.FindArgs()
|
||||||
|
args = schema.load({
|
||||||
|
'where': {'type': ['Computer']}
|
||||||
|
})
|
||||||
|
assert isinstance(args['where'], BinaryExpression), '``where`` must be a SQLAlchemy query'
|
Reference in New Issue