Add computer monitor; dummy; fix

This commit is contained in:
Xavier Bustamante Talavera 2018-06-20 23:18:15 +02:00
parent 7306055d28
commit 21d1a96aff
23 changed files with 531 additions and 97 deletions

View file

@ -44,12 +44,23 @@ $ sudo -u postgres -i
postgres$ createdb dh-db1
```
Then execute, in the same directory where `app.py` is:
```bash
$ flask init-db
```
This creates the tables in the database you created before.
And then execute, in the same directory where `app.py` is:
Finally, run the app:
```bash
$ flask run
```
See the [Flask quickstart](http://flask.pocoo.org/docs/1.0/quickstart/)
for more info.
Devicehub has many commands that allows you to administrate it. You
can, for example, create a dummy database of devices with ``flask dummy``
or create users with ``flask create-user``. See all the
available commands by just executing ``flask``.

View file

@ -1,14 +1,14 @@
from distutils.version import StrictVersion
from typing import Set
from ereuse_devicehub.resources.device import ComponentDef, ComputerDef, DataStorageDef, \
DesktopDef, DeviceDef, GraphicCardDef, HardDriveDef, LaptopDef, MicrotowerDef, \
from ereuse_devicehub.resources.device import ComponentDef, ComputerDef, ComputerMonitorDef, \
DataStorageDef, DesktopDef, DeviceDef, GraphicCardDef, HardDriveDef, LaptopDef, MicrotowerDef, \
MotherboardDef, NetbookDef, NetworkAdapterDef, ProcessorDef, RamModuleDef, ServerDef, \
SolidStateDriveDef
from ereuse_devicehub.resources.event import AddDef, AggregateRateDef, BenchmarkDataStorageDef, \
BenchmarkDef, BenchmarkProcessorDef, BenchmarkProcessorSysbenchDef, BenchmarkRamSysbenchDef, \
BenchmarkWithRateDef, EraseBasicDef, EraseSectorsDef, EventDef, InstallDef, \
PhotoboxSystemRateDef, PhotoboxUserDef, RateDef, RemoveDef, SnapshotDef, StepDef, \
from ereuse_devicehub.resources.event import AddDef, AggregateRateDef, AppRateDef, \
BenchmarkDataStorageDef, BenchmarkDef, BenchmarkProcessorDef, BenchmarkProcessorSysbenchDef, \
BenchmarkRamSysbenchDef, BenchmarkWithRateDef, EraseBasicDef, EraseSectorsDef, EventDef, \
InstallDef, PhotoboxSystemRateDef, PhotoboxUserDef, RateDef, RemoveDef, SnapshotDef, StepDef, \
StepRandomDef, StepZeroDef, StressTestDef, TestDataStorageDef, TestDef, WorkbenchRateDef
from ereuse_devicehub.resources.inventory import InventoryDef
from ereuse_devicehub.resources.tag import TagDef
@ -19,13 +19,14 @@ from teal.config import Config
class DevicehubConfig(Config):
RESOURCE_DEFINITIONS = {
DeviceDef, ComputerDef, DesktopDef, LaptopDef, NetbookDef, ServerDef,
MicrotowerDef, ComponentDef, GraphicCardDef, DataStorageDef, SolidStateDriveDef,
MicrotowerDef, ComputerMonitorDef, ComponentDef, GraphicCardDef, DataStorageDef,
SolidStateDriveDef,
HardDriveDef, MotherboardDef, NetworkAdapterDef, RamModuleDef, ProcessorDef, UserDef,
OrganizationDef, TagDef, EventDef, AddDef, RemoveDef, EraseBasicDef, EraseSectorsDef,
StepDef, StepZeroDef, StepRandomDef, RateDef, AggregateRateDef, WorkbenchRateDef,
PhotoboxUserDef, PhotoboxSystemRateDef, InstallDef, SnapshotDef, TestDef,
TestDataStorageDef, StressTestDef, WorkbenchRateDef, InventoryDef, BenchmarkDef,
BenchmarkDataStorageDef, BenchmarkWithRateDef, BenchmarkProcessorDef,
BenchmarkDataStorageDef, BenchmarkWithRateDef, AppRateDef, BenchmarkProcessorDef,
BenchmarkProcessorSysbenchDef, BenchmarkRamSysbenchDef
}
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]

View file

@ -5,12 +5,14 @@ from flask_sqlalchemy import SQLAlchemy
from ereuse_devicehub.auth import Auth
from ereuse_devicehub.client import Client
from ereuse_devicehub.db import db
from ereuse_devicehub.dummy.dummy import Dummy
from teal.config import Config as ConfigClass
from teal.teal import Teal
class Devicehub(Teal):
test_client_class = Client
Dummy = Dummy
def __init__(self,
config: ConfigClass,
@ -29,5 +31,4 @@ class Devicehub(Teal):
super().__init__(config, db, import_name, static_url_path, static_folder, static_host,
host_matching, subdomain_matching, template_folder, instance_path,
instance_relative_config, root_path, Auth)
self.dummy = Dummy(self)

View file

View file

@ -0,0 +1,62 @@
from pathlib import Path
import click
import click_spinner
import yaml
from tqdm import tqdm
from ereuse_devicehub.client import UserClient
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.event.models import Snapshot
from ereuse_devicehub.resources.tag.model import Tag
from ereuse_devicehub.resources.user import User
class Dummy:
SNAPSHOTS = (
'workbench-server-1',
'computer-monitor'
)
TAGS = (
'tag1',
'tag2',
'tag3'
)
def __init__(self, app) -> None:
super().__init__()
self.app = app
self.app.cli.command('dummy',
short_help='Creates dummy devices and users.')(self.run)
@click.confirmation_option(prompt='This command deletes the DB in the process. '
'Do you want to continue?')
def run(self):
print('Preparing the database...')
with click_spinner.spinner():
self.app.init_db(erase=True)
user = self.user_client('user@dhub.com', '1234')
user.post(res=Tag, query=[('ids', i) for i in self.TAGS], data={})
print('Creating devices...')
for file_name in tqdm(self.SNAPSHOTS):
snapshot = self.file(file_name)
user.post(res=Snapshot, data=snapshot)
print('Done :-)')
def user_client(self, email: str, password: str):
user = User(email=email, password=password)
db.session.add(user)
db.session.commit()
client = UserClient(application=self.app,
response_wrapper=self.app.response_class,
email=user.email,
password=password)
client.user, _ = client.login(client.email, client.password)
return client
def file(self, name: str):
with Path(__file__) \
.parent \
.joinpath('files') \
.joinpath(name + '.snapshot.yaml').open() as f:
return yaml.load(f)

View file

@ -0,0 +1,17 @@
type: Snapshot
software: AndroidApp
version: '1.0'
device:
type: ComputerMonitor
technology: LCD
manufacturer: Dell
model: 1707FPF
serialNumber: CN0FP446728728541C8S
resolutionWidth: 1920
resolutionHeight: 1080
size: 21.5
events:
- type: AppRate
appearanceRange: A
functionalityRange: C
labelling: False

View file

@ -0,0 +1,118 @@
# A Snapshot Phase 1 with a device
# and 1 GraphicCard, 2 RamModule, 1 Processor, 1 SSD, 1 HDD, 1 Motherboard
# Prerequisites:
# - 2 tags: tag1 and tag2 from the default org
# All numbers are invented
type: Snapshot
uuid: cb8ce6b5-6a1b-4084-b5b9-d8fadad2a015
version: '11.0'
software: Workbench
elapsed: 500
device:
type: Microtower
serialNumber: d1s
model: d1ml
manufacturer: d1mr
tags:
- type: Tag
id: tag1
events:
- type: WorkbenchRate
appearanceRange: A
functionalityRange: B
- type: BenchmarkRamSysbench
rate: 2444
- type: StressTest
elapsed: 300
error: False
components:
- type: GraphicCard
serialNumber: gc1-1s
model: gc1-1ml
manufacturer: gc1-1mr
- type: RamModule
serialNumber: rm1-1s
model: rm1-1ml
manufacturer: rm1-1mr
- type: RamModule
serialNumber: rm2-1s
model: rm2-1ml
manufacturer: rm2-1mr
- type: Processor
model: p1-1s
manufacturer: p1-1mr
events:
- type: BenchmarkProcessor
rate: 2410
- type: BenchmarkProcessorSysbench
rate: 4400
- type: SolidStateDrive
serialNumber: ssd1-1s
model: ssd1-1ml
manufacturer: ssd1-1mr
events:
- type: BenchmarkDataStorage
readSpeed: 20
writeSpeed: 15
elapsed: 21
- type: TestDataStorage
elapsed: 233
firstError: 0
error: False
status: Completed without error
length: Short
lifetime: 99
passedLifetime: 99
assessment: True
powerCycleCount: 11
reallocatedSectorCount: 2
powerCycleCount: 4
reportedUncorrectableErrors: 1
commandTimeout: 11
currentPendingSectorCount: 1
offlineUncorrectable: 33
remainingLifetimePercentage: 1
- type: EraseSectors
error: False
cleanWithZeros: False
startTime: 2018-01-01T10:10:10
endTime: 2018-01-01T12:10:10
secureRandomSteps: 0
steps:
- type: StepRandom
startTime: 2018-01-01T10:10:10
endTime: 2018-01-01T12:10:10
error: False
cleanWithZeros: False
secureRandomSteps: 0
- type: HardDrive
serialNumber: hdd1-1s
model: hdd1-1ml
manufacturer: hdd1-1mr
events:
- type: BenchmarkDataStorage
readSpeed: 10
writeSpeed: 5
- type: EraseSectors
error: False
cleanWithZeros: False
startTime: 2018-01-01T10:10:10
endTime: 2018-01-01T12:10:10
secureRandomSteps: 0
steps:
- type: StepRandom
startTime: 2018-01-01T10:10:10
endTime: 2018-01-01T12:10:10
error: False
cleanWithZeros: False
secureRandomSteps: 0
- type: Install
elapsed: 420
error: False
name: LinuxMint 18.01 32b
- type: Motherboard
serialNumber: mb1-1s
model: mb1-1ml
manufacturer: mb1-1mr

View file

@ -1,6 +1,6 @@
from ereuse_devicehub.resources.device.schemas import Component, Computer, DataStorage, Desktop, \
Device, GraphicCard, HardDrive, Laptop, Microtower, Motherboard, Netbook, NetworkAdapter, \
Processor, RamModule, Server, SolidStateDrive
from ereuse_devicehub.resources.device.schemas import Component, Computer, ComputerMonitor, \
DataStorage, Desktop, Device, GraphicCard, HardDrive, Laptop, Microtower, Motherboard, Netbook, \
NetworkAdapter, Processor, RamModule, Server, SolidStateDrive
from ereuse_devicehub.resources.device.views import DeviceView
from teal.resource import Converters, Resource
@ -36,6 +36,10 @@ class MicrotowerDef(ComputerDef):
SCHEMA = Microtower
class ComputerMonitorDef(DeviceDef):
SCHEMA = ComputerMonitor
class ComponentDef(DeviceDef):
SCHEMA = Component

View file

@ -10,7 +10,8 @@ from sqlalchemy.orm import ColumnProperty, backref, relationship
from sqlalchemy.util import OrderedSet
from sqlalchemy_utils import ColorType
from ereuse_devicehub.resources.enums import DataStorageInterface, RamFormat, RamInterface
from ereuse_devicehub.resources.enums import ComputerMonitorTechnologies, DataStorageInterface, \
RamFormat, RamInterface
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
from ereuse_utils.naming import Naming
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, check_range
@ -18,14 +19,30 @@ from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, c
class Device(Thing):
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
id.comment = """
The identifier of the device for this database.
"""
type = Column(Unicode(STR_SM_SIZE), nullable=False)
hid = Column(Unicode(STR_BIG_SIZE), unique=True)
hid.comment = """
The Hardware ID (HID) is the unique ID traceability systems
use to ID a device globally.
"""
model = Column(Unicode(STR_BIG_SIZE))
manufacturer = Column(Unicode(STR_SIZE))
serial_number = Column(Unicode(STR_SIZE))
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 3))
weight.comment = """
The weight of the device in Kgm.
"""
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 3))
width.comment = """
The width of the device in meters.
"""
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 3))
height.comment = """
The height of the device in meters.
"""
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 3))
color = Column(ColorType)
@ -108,6 +125,28 @@ class Microtower(Computer):
pass
class ComputerMonitor(Device):
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
size = Column(Float(decimal_return_scale=2), check_range('size', 2, 150))
size.comment = """
The size of the monitor in inches.
"""
technology = Column(DBEnum(ComputerMonitorTechnologies))
technology.comment = """
The technology the monitor uses to display the image.
"""
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000))
resolution_width.comment = """
The maximum horizontal resolution the monitor can natively support
in pixels.
"""
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000))
resolution_height.comment = """
The maximum vertical resolution the monitor can natively support
in pixels.
"""
class Component(Device):
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)

View file

@ -1,9 +1,10 @@
from typing import Dict, List, Set
from colour import Color
from sqlalchemy import Column
from sqlalchemy import Column, Integer
from ereuse_devicehub.resources.enums import DataStorageInterface, RamFormat, RamInterface
from ereuse_devicehub.resources.enums import ComputerMonitorTechnologies, DataStorageInterface, \
RamFormat, RamInterface
from ereuse_devicehub.resources.event.models import Event, EventWithMultipleDevices, \
EventWithOneDevice
from ereuse_devicehub.resources.image.models import ImageList
@ -72,6 +73,20 @@ class Microtower(Computer):
pass
class ComputerMonitor(Device):
technology = ... # type: Column
size = ... # type: Column
resolution_width = ... # type: Column
resolution_height = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
technology = ... # type: ComputerMonitorTechnologies
size = ... # type: Integer
resolution_width = ... # type: int
resolution_height = ... # type: int
class Component(Device):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)

View file

@ -1,36 +1,28 @@
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.enums import RamFormat, RamInterface
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
from marshmallow import post_load, pre_load
from marshmallow.fields import Float, Integer, Str
from marshmallow.validate import Length, OneOf, Range
from marshmallow_enum import EnumField
from sqlalchemy.util import OrderedSet
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.device import models as m
from ereuse_devicehub.resources.enums import ComputerMonitorTechnologies, RamFormat, RamInterface
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
from teal.marshmallow import ValidationError
class Device(Thing):
# todo id is dump_only except when in Snapshot
id = Integer(description='The identifier of the device for this database.')
hid = Str(dump_only=True,
description='The Hardware ID is the unique ID traceability systems '
'use to ID a device globally.')
id = Integer(description=m.Device.id, dump_only=True)
hid = Str(dump_only=True, description=m.Device.hid)
tags = NestedOn('Tag', many=True, collection_class=OrderedSet)
model = Str(validate=Length(max=STR_BIG_SIZE))
manufacturer = Str(validate=Length(max=STR_SIZE))
serial_number = Str(data_key='serialNumber')
product_id = Str(data_key='productId')
weight = Float(validate=Range(0.1, 3),
unit=UnitCodes.kgm,
description='The weight of the device in Kgm.')
width = Float(validate=Range(0.1, 3),
unit=UnitCodes.m,
description='The width of the device in meters.')
height = Float(validate=Range(0.1, 3),
unit=UnitCodes.m,
description='The height of the device in meters.')
weight = Float(validate=Range(0.1, 3), unit=UnitCodes.kgm, description=m.Device.weight)
width = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.width)
height = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.height)
events = NestedOn('Event', many=True, dump_only=True)
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
@ -61,7 +53,6 @@ class Device(Thing):
class Computer(Device):
components = NestedOn('Component', many=True, dump_only=True, collection_class=OrderedSet)
pass
class Desktop(Computer):
@ -84,6 +75,18 @@ class Microtower(Computer):
pass
class ComputerMonitor(Device):
size = Float(description=m.ComputerMonitor.size.comment, validate=Range(2, 150))
technology = EnumField(ComputerMonitorTechnologies,
description=m.ComputerMonitor.technology.comment)
resolution_width = Integer(data_key='resolutionWidth',
validate=Range(10, 20000),
description=m.ComputerMonitor.resolution_width.comment)
resolution_height = Integer(data_key='resolutionHeight',
validate=Range(10, 20000),
description=m.ComputerMonitor.resolution_height.comment)
class Component(Device):
parent = NestedOn(Device, dump_only=True)

View file

@ -149,3 +149,14 @@ class DataStorageInterface(Enum):
ATA = 'ATA'
USB = 'USB'
PCI = 'PCI'
@unique
class ComputerMonitorTechnologies(Enum):
CRT = 'Cathode ray tube (CRT)'
TFT = 'Thin-film-transistor liquid-crystal (TFT)'
LED = 'LED-backlit (LED)'
PDP = 'Plasma display panel (Plasma)'
LCD = 'Liquid-crystal display (any of TFT, LED, Blue Phase, IPS)'
OLED = 'Organic light-emitting diode (OLED)'
AMOLED = 'Organic light-emitting diode (AMOLED)'

View file

@ -1,7 +1,7 @@
from typing import Callable, Iterable, Tuple
from ereuse_devicehub.resources.device.sync import Sync
from ereuse_devicehub.resources.event.schemas import Add, AggregateRate, Benchmark, \
from ereuse_devicehub.resources.event.schemas import Add, AggregateRate, AppRate, Benchmark, \
BenchmarkDataStorage, BenchmarkProcessor, BenchmarkProcessorSysbench, BenchmarkRamSysbench, \
BenchmarkWithRate, EraseBasic, EraseSectors, Event, Install, PhotoboxSystemRate, \
PhotoboxUserRate, Rate, Remove, Snapshot, Step, StepRandom, StepZero, StressTest, Test, \
@ -65,6 +65,10 @@ class PhotoboxSystemRateDef(RateDef):
SCHEMA = PhotoboxSystemRate
class AppRateDef(RateDef):
SCHEMA = AppRate
class InstallDef(EventDef):
SCHEMA = Install

View file

@ -2,14 +2,6 @@ from collections import Iterable
from typing import Set, Union
from uuid import uuid4
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Component, Computer, DataStorage, Device
from ereuse_devicehub.resources.enums import AppearanceRange, BOX_RATE_3, BOX_RATE_5, Bios, \
FunctionalityRange, RATE_NEGATIVE, RATE_POSITIVE, RatingRange, RatingSoftware, \
SnapshotExpectedEvents, SnapshotSoftware, TestHardDriveLength
from ereuse_devicehub.resources.image.models import Image
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
from ereuse_devicehub.resources.user.models import User
from flask import g
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, DateTime, Enum as DBEnum, \
Float, ForeignKey, Interval, JSON, SmallInteger, Unicode, event
@ -20,6 +12,14 @@ from sqlalchemy.orm import backref, relationship
from sqlalchemy.orm.events import AttributeEvents as Events
from sqlalchemy.util import OrderedSet
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Component, Computer, DataStorage, Device
from ereuse_devicehub.resources.enums import AppearanceRange, BOX_RATE_3, BOX_RATE_5, Bios, \
FunctionalityRange, RATE_NEGATIVE, RATE_POSITIVE, RatingRange, RatingSoftware, \
SnapshotExpectedEvents, SnapshotSoftware, TestHardDriveLength
from ereuse_devicehub.resources.image.models import Image
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
from ereuse_devicehub.resources.user.models import User
from teal.db import ArrayOfEnum, CASCADE, CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, \
POLYMORPHIC_ON, StrictVersionType, check_range
@ -256,15 +256,18 @@ class StepRandom(Step):
class Snapshot(JoinedTableMixin, EventWithOneDevice):
uuid = Column(UUID(as_uuid=True), nullable=False, unique=True)
uuid = Column(UUID(as_uuid=True), unique=True)
version = Column(StrictVersionType(STR_SM_SIZE), nullable=False)
software = Column(DBEnum(SnapshotSoftware), nullable=False)
elapsed = Column(Interval, nullable=False)
elapsed = Column(Interval)
elapsed.comment = """
For Snapshots made with Workbench, the total amount of time
it took to complete.
"""
expected_events = Column(ArrayOfEnum(DBEnum(SnapshotExpectedEvents)))
class Install(JoinedTableMixin, EventWithOneDevice):
name = Column(Unicode(STR_BIG_SIZE), nullable=False)
elapsed = Column(Interval, nullable=False)
@ -290,6 +293,20 @@ class Rate(JoinedTableMixin, EventWithOneDevice):
def rating_range(self) -> RatingRange:
return RatingRange.from_score(self.rating)
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
#sqlalchemy.ext.declarative.declared_attr>`_
"""
args = {POLYMORPHIC_ID: cls.t}
if cls.t == 'Rate':
args[POLYMORPHIC_ON] = cls.type
return args
class IndividualRate(Rate):
pass
@ -319,18 +336,26 @@ class RateAggregateRate(db.Model):
primary_key=True)
class WorkbenchRate(IndividualRate):
class ManualRate(IndividualRate):
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
labelling = Column(Boolean)
appearance_range = Column(DBEnum(AppearanceRange))
functionality_range = Column(DBEnum(FunctionalityRange))
class WorkbenchRate(ManualRate):
id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id), primary_key=True)
processor = Column(Float(decimal_return_scale=2), check_range('processor', *RATE_POSITIVE))
ram = Column(Float(decimal_return_scale=2), check_range('ram', *RATE_POSITIVE))
data_storage = Column(Float(decimal_return_scale=2),
check_range('data_storage', *RATE_POSITIVE))
graphic_card = Column(Float(decimal_return_scale=2),
check_range('graphic_card', *RATE_POSITIVE))
labelling = Column(Boolean)
bios = Column(DBEnum(Bios))
appearance_range = Column(DBEnum(AppearanceRange))
functionality_range = Column(DBEnum(FunctionalityRange))
class AppRate(ManualRate):
pass
class PhotoboxRate(IndividualRate):
@ -369,6 +394,20 @@ class PhotoboxSystemRate(PhotoboxRate):
class Test(JoinedTableMixin, EventWithOneDevice):
elapsed = Column(Interval, nullable=False)
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
#sqlalchemy.ext.declarative.declared_attr>`_
"""
args = {POLYMORPHIC_ID: cls.t}
if cls.t == 'Test':
args[POLYMORPHIC_ON] = cls.type
return args
class TestDataStorage(Test):
id = Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
@ -396,6 +435,20 @@ class StressTest(Test):
class Benchmark(JoinedTableMixin, EventWithOneDevice):
elapsed = Column(Interval)
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
#sqlalchemy.ext.declarative.declared_attr>`_
"""
args = {POLYMORPHIC_ID: cls.t}
if cls.t == 'Benchmark':
args[POLYMORPHIC_ON] = cls.type
return args
class BenchmarkDataStorage(Benchmark):
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)

View file

@ -139,17 +139,26 @@ class AggregateRate(Rate):
self.ratings = ... # type: Set[IndividualRate]
class WorkbenchRate(IndividualRate):
class ManualRate(IndividualRate):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.labelling = ... # type: bool
self.appearance_range = ... # type: AppearanceRange
self.functionality_range = ... # type: FunctionalityRange
class WorkbenchRate(ManualRate):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.processor = ... # type: float
self.ram = ... # type: float
self.data_storage = ... # type: float
self.graphic_card = ... # type: float
self.labelling = ... # type: bool
self.bios = ... # type: Bios
self.appearance_range = ... # type: AppearanceRange
self.functionality_range = ... # type: FunctionalityRange
class AppRate(ManualRate):
pass
class PhotoboxRate(IndividualRate):

View file

@ -1,3 +1,10 @@
from flask import current_app as app
from marshmallow import ValidationError, validates_schema
from marshmallow.fields import Boolean, DateTime, Float, Integer, List, Nested, String, TimeDelta, \
UUID
from marshmallow.validate import Length, Range
from marshmallow_enum import EnumField
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.device.schemas import Component, Device
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
@ -6,13 +13,6 @@ from ereuse_devicehub.resources.event import models as m
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.user.schemas import User
from flask import current_app as app
from marshmallow import ValidationError, validates_schema
from marshmallow.fields import Boolean, DateTime, Float, Integer, List, Nested, String, TimeDelta, \
UUID
from marshmallow.validate import Length, Range
from marshmallow_enum import EnumField
from teal.marshmallow import Version
from teal.resource import Schema
@ -124,7 +124,7 @@ class PhotoboxRate(IndividualRate):
# todo Image
class PhotoboxUserRate(PhotoboxRate):
class PhotoboxUserRate(IndividualRate):
assembling = Integer()
parts = Integer()
buttons = Integer()
@ -135,18 +135,11 @@ class PhotoboxUserRate(PhotoboxRate):
dirt = Integer()
class PhotoboxSystemRate(PhotoboxRate):
class PhotoboxSystemRate(IndividualRate):
pass
class WorkbenchRate(IndividualRate):
processor = Float()
ram = Float()
data_storage = Float()
graphic_card = Float()
labelling = Boolean(description='Sets if there are labels stuck that should be removed.')
bios = EnumField(Bios, description='How difficult it has been to set the bios to '
'boot from the network.')
class ManualRate(IndividualRate):
appearance_range = EnumField(AppearanceRange,
required=True,
data_key='appearanceRange',
@ -156,6 +149,20 @@ class WorkbenchRate(IndividualRate):
required=True,
data_key='functionalityRange',
description='Grades the defects of a device that affect its usage.')
labelling = Boolean(description='Sets if there are labels stuck that should be removed.')
class AppRate(ManualRate):
pass
class WorkbenchRate(ManualRate):
processor = Float()
ram = Float()
data_storage = Float()
graphic_card = Float()
bios = EnumField(Bios, description='How difficult it has been to set the bios to '
'boot from the network.')
class Install(EventWithOneDevice):
@ -172,7 +179,7 @@ class Snapshot(EventWithOneDevice):
See docs for more info.
"""
uuid = UUID(required=True)
uuid = UUID()
software = EnumField(SnapshotSoftware,
required=True,
description='The software that generated this Snapshot.')
@ -185,7 +192,7 @@ class Snapshot(EventWithOneDevice):
'the async Snapshot.')
device = NestedOn(Device)
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
elapsed = TimeDelta(precision=TimeDelta.SECONDS)
components = NestedOn(Component,
many=True,
description='A list of components that are inside of the device'
@ -206,10 +213,29 @@ class Snapshot(EventWithOneDevice):
@validates_schema
def validate_components_only_workbench(self, data: dict):
if data['software'] != SnapshotSoftware.Workbench:
if data['components'] is not None:
if data.get('components', None) is not None:
raise ValidationError('Only Workbench can add component info',
field_names=['components'])
@validates_schema
def validate_only_workbench_fields(self, data: dict):
"""Ensures workbench has ``elapsed`` and ``uuid`` and no others."""
# todo test
if data['software'] == SnapshotSoftware.Workbench:
if not data.get('uuid', None):
raise ValidationError('Snapshots from Workbench must have uuid',
field_names=['uuid'])
if not data.get('elapsed', None):
raise ValidationError('Snapshots from Workbench must have elapsed',
field_names=['elapsed'])
else:
if data.get('uuid', None):
raise ValidationError('Only Snapshots from Workbench can have uuid',
field_names=['uuid'])
if data.get('elapsed', None):
raise ValidationError('Only Snapshots from Workbench can have elapsed',
field_names=['elapsed'])
class Test(EventWithOneDevice):
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)

View file

@ -8,7 +8,8 @@ from sqlalchemy.util import OrderedSet
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Component, Computer
from ereuse_devicehub.resources.enums import RatingSoftware, SnapshotSoftware
from ereuse_devicehub.resources.event.models import Event, Snapshot, TestDataStorage, WorkbenchRate
from ereuse_devicehub.resources.event.models import Event, ManualRate, Snapshot, TestDataStorage, \
WorkbenchRate
from teal.resource import View
@ -40,14 +41,15 @@ class SnapshotView(View):
# Remove new events from devices so they don't interfere with sync
events_device = set(e for e in device.events_one)
events_components = tuple(set(e for e in component.events_one) for component in components)
device.events_one.clear()
if components:
events_components = tuple(set(e for e in c.events_one) for c in components)
for component in components:
component.events_one.clear()
# noinspection PyArgumentList
assert not device.events_one
assert all(not c.events_one for c in components)
assert all(not c.events_one for c in components) if components else True
db_device, remove_events = self.resource_def.sync.run(device, components)
snapshot.device = db_device
snapshot.events |= remove_events | events_device
@ -56,16 +58,18 @@ class SnapshotView(View):
ordered_components = OrderedSet(x for x in snapshot.components)
for event in events_device:
if isinstance(event, ManualRate):
event.algorithm_software = RatingSoftware.Ereuse
event.algorithm_version = StrictVersion('1.0')
if isinstance(event, WorkbenchRate):
# todo process workbench rate
event.data_storage = 2
event.graphic_card = 4
event.processor = 1
event.algorithm_software = RatingSoftware.Ereuse
event.algorithm_version = StrictVersion('1.0')
# Add the new events to the db-existing devices and components
db_device.events_one |= events_device
if components:
for component, events in zip(ordered_components, events_components):
component.events_one |= events
snapshot.events |= events

View file

@ -1,7 +1,6 @@
from click import argument, option
from flask import current_app as app
from ereuse_devicehub import devicehub
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.user.models import Organization, User
from ereuse_devicehub.resources.user.schemas import User as UserS
@ -16,7 +15,7 @@ class UserDef(Resource):
ID_CONVERTER = Converters.uuid
AUTH = True
def __init__(self, app: 'devicehub.Devicehub', import_name=__package__, static_folder=None,
def __init__(self, app, import_name=__package__, static_folder=None,
static_url_path=None, template_folder=None, url_prefix=None, subdomain=None,
url_defaults=None, root_path=None):
cli_commands = ((self.create_user, 'create-user'),)

View file

@ -11,14 +11,16 @@ setup(
include_package_data=True,
description='A system to manage devices focusing reuse.',
install_requires=[
'teal>=0.2.0a1',
'teal>=0.2.0a2',
'marshmallow_enum',
'ereuse-utils [Naming]>=0.3.0b1',
'ereuse-utils [Naming]>=0.3.0b2',
'psycopg2-binary',
'sqlalchemy-utils',
'requests',
'requests-toolbelt',
'hashids'
'hashids',
'tqdm',
'click-spinner'
],
tests_requires=[
'pytest',

View file

@ -53,11 +53,12 @@ def app_context(app: Devicehub):
def user(app: Devicehub) -> UserClient:
"""Gets a client with a logged-in dummy user."""
with app.app_context():
user = create_user()
password = 'foo'
user = create_user(password=password)
client = UserClient(application=app,
response_wrapper=app.response_class,
email=user.email,
password='foo')
password=password)
client.user, _ = client.login(client.email, client.password)
return client

View file

@ -0,0 +1,17 @@
type: Snapshot
software: AndroidApp
version: '1.0'
device:
type: ComputerMonitor
technology: LCD
manufacturer: Dell
model: 1707FPF
serialNumber: CN0FP446728728541C8S
resolutionWidth: 1920
resolutionHeight: 1080
size: 21.5
events:
- type: AppRate
appearanceRange: A
functionalityRange: C
labelling: False

View file

@ -10,11 +10,12 @@ from ereuse_devicehub.client import UserClient
from ereuse_devicehub.db import db
from ereuse_devicehub.devicehub import Devicehub
from ereuse_devicehub.resources.device.exceptions import NeedsId
from ereuse_devicehub.resources.device.models import Component, Computer, Desktop, Device, \
GraphicCard, Laptop, Microtower, Motherboard, NetworkAdapter
from ereuse_devicehub.resources.device.models import Component, Computer, ComputerMonitor, Desktop, \
Device, GraphicCard, Laptop, Microtower, Motherboard, NetworkAdapter
from ereuse_devicehub.resources.device.schemas import Device as DeviceS
from ereuse_devicehub.resources.device.sync import MismatchBetweenTags, MismatchBetweenTagsAndHid, \
Sync
from ereuse_devicehub.resources.enums import ComputerMonitorTechnologies
from ereuse_devicehub.resources.event.models import Remove, Test
from ereuse_devicehub.resources.tag.model import Tag
from ereuse_devicehub.resources.user import User
@ -396,3 +397,16 @@ def test_get_devices(app: Devicehub, user: UserClient):
assert tuple(d['id'] for d in devices) == (1, 2, 3, 4, 5)
assert tuple(d['type'] for d in devices) == ('Desktop', 'Microtower',
'Laptop', 'NetworkAdapter', 'GraphicCard')
@pytest.mark.usefixtures('app_context')
def test_computer_monitor():
m = ComputerMonitor(technology=ComputerMonitorTechnologies.LCD,
manufacturer='foo',
model='bar',
serial_number='foo-bar',
resolution_width=1920,
resolution_height=1080,
size=14.5)
db.session.add(m)
db.session.commit()

View file

@ -69,10 +69,12 @@ def snapshot_and_check(user: UserClient,
assert event['type'] != 'Receive', 'All Remove events must be before the Add ones'
assert input_snapshot['device']
assert_similar_device(input_snapshot['device'], snapshot['device'])
if input_snapshot.get('components', None):
assert_similar_components(input_snapshot['components'], snapshot['components'])
assert all(c['parent'] == snapshot['device']['id'] for c in snapshot['components']), \
'Components must be in their parent'
if perform_second_snapshot:
if 'uuid' in input_snapshot:
input_snapshot['uuid'] = uuid4()
return snapshot_and_check(user, input_snapshot, event_types, perform_second_snapshot=False)
else:
@ -330,3 +332,24 @@ def test_erase(user: UserClient):
assert step['secureRandomSteps'] == 1
assert step['cleanWithZeros'] is True
assert 'num' not in step
def test_snapshot_computer_monitor(user: UserClient):
s = file('computer-monitor.snapshot')
snapshot_and_check(user, s, event_types=('AppRate',))
def test_snapshot_components_none():
"""
Tests that a snapshot without components does not
remove them from the computer.
"""
# todo test
pass
def test_snapshot_components_empty():
"""
Tests that a snapshot whose components are an empty list remove
all its components.
"""