Prototyping
This commit is contained in:
parent
f93cc993e7
commit
c4b6553c8c
0
ereuse_devicehub/__init__.py
Normal file
0
ereuse_devicehub/__init__.py
Normal file
6
ereuse_devicehub/auth.py
Normal file
6
ereuse_devicehub/auth.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from teal.auth import TokenAuth
|
||||||
|
|
||||||
|
|
||||||
|
class Auth(TokenAuth):
|
||||||
|
pass
|
||||||
|
|
5
ereuse_devicehub/client.py
Normal file
5
ereuse_devicehub/client.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from teal.client import Client as TealClient
|
||||||
|
|
||||||
|
|
||||||
|
class Client(TealClient):
|
||||||
|
pass
|
6
ereuse_devicehub/config.py
Normal file
6
ereuse_devicehub/config.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from ereuse_devicehub.resources.device import DeviceDef
|
||||||
|
from teal.config import Config
|
||||||
|
|
||||||
|
|
||||||
|
class DevicehubConfig(Config):
|
||||||
|
RESOURCE_DEFINITIONS = (DeviceDef,)
|
5
ereuse_devicehub/db.py
Normal file
5
ereuse_devicehub/db.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
from teal.db import Model
|
||||||
|
|
||||||
|
db = SQLAlchemy(model_class=Model)
|
6
ereuse_devicehub/devicehub.py
Normal file
6
ereuse_devicehub/devicehub.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from ereuse_devicehub.client import Client
|
||||||
|
from teal.teal import Teal
|
||||||
|
|
||||||
|
|
||||||
|
class Devicehub(Teal):
|
||||||
|
test_client_class = Client
|
0
ereuse_devicehub/resources/__init__.py
Normal file
0
ereuse_devicehub/resources/__init__.py
Normal file
9
ereuse_devicehub/resources/device/__init__.py
Normal file
9
ereuse_devicehub/resources/device/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from ereuse_devicehub.resources.device.schemas import Device
|
||||||
|
from ereuse_devicehub.resources.device.views import DeviceView
|
||||||
|
from teal.resource import Resource, Converters
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceDef(Resource):
|
||||||
|
SCHEMA = Device
|
||||||
|
VIEW = DeviceView
|
||||||
|
ID_CONVERTER = Converters.int
|
88
ereuse_devicehub/resources/device/models.py
Normal file
88
ereuse_devicehub/resources/device/models.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
from sqlalchemy import BigInteger, Column, Float, ForeignKey, Integer, Sequence, SmallInteger, \
|
||||||
|
Unicode
|
||||||
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
from sqlalchemy.orm import backref, relationship
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.model import STR_BIG_SIZE, STR_SIZE, Thing, check_range
|
||||||
|
from teal.db import POLYMORPHIC_ID, POLYMORPHIC_ON, CASCADE
|
||||||
|
|
||||||
|
|
||||||
|
class Device(Thing):
|
||||||
|
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
||||||
|
type = Column(Unicode)
|
||||||
|
pid = Column(Unicode(STR_SIZE), unique=True)
|
||||||
|
gid = Column(Unicode(STR_SIZE), unique=True)
|
||||||
|
hid = Column(Unicode(STR_BIG_SIZE), unique=True)
|
||||||
|
model = Column(Unicode(STR_BIG_SIZE))
|
||||||
|
manufacturer = Column(Unicode(STR_SIZE))
|
||||||
|
serial_number = Column(Unicode(STR_SIZE))
|
||||||
|
weight = Column(Float(precision=3), check_range('weight', min=0.1))
|
||||||
|
width = Column(Float(precision=3), check_range('width', min=0.1))
|
||||||
|
height = Column(Float(precision=3), check_range('height', min=0.1))
|
||||||
|
|
||||||
|
@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.__name__}
|
||||||
|
if cls.__name__ == 'Device':
|
||||||
|
args[POLYMORPHIC_ON] = cls.type
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
class Computer(Device):
|
||||||
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Desktop(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Laptop(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Netbook(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Server(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Microtower(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Component(Device):
|
||||||
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
|
parent_id = Column(BigInteger, ForeignKey('computer.id'))
|
||||||
|
parent = relationship(Computer,
|
||||||
|
backref=backref('components', lazy=True, cascade=CASCADE),
|
||||||
|
primaryjoin='Component.parent_id == Computer.id')
|
||||||
|
|
||||||
|
|
||||||
|
class GraphicCard(Component):
|
||||||
|
memory = Column(SmallInteger, check_range('memory', min=0.1))
|
||||||
|
|
||||||
|
|
||||||
|
class HardDrive(Component):
|
||||||
|
size = Column(Integer, check_range('size', min=0.1))
|
||||||
|
|
||||||
|
|
||||||
|
class Motherboard(Component):
|
||||||
|
slots = Column(SmallInteger, check_range('slots'))
|
||||||
|
usb = Column(SmallInteger, check_range('usb'))
|
||||||
|
firewire = Column(SmallInteger, check_range('firewire'))
|
||||||
|
serial = Column(SmallInteger, check_range('serial'))
|
||||||
|
pcmcia = Column(SmallInteger, check_range('pcmcia'))
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkAdapter(Component):
|
||||||
|
speed = Column(SmallInteger, check_range('speed'))
|
78
ereuse_devicehub/resources/device/schemas.py
Normal file
78
ereuse_devicehub/resources/device/schemas.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
from marshmallow.fields import Float, Integer, Nested, Str
|
||||||
|
from marshmallow.validate import Length, Range
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.model import STR_BIG_SIZE, STR_SIZE
|
||||||
|
from ereuse_devicehub.resources.schema import Thing
|
||||||
|
|
||||||
|
|
||||||
|
class Device(Thing):
|
||||||
|
id = Str(dump_only=True)
|
||||||
|
hid = Str(dump_only=True,
|
||||||
|
description='The Hardware ID is the unique ID traceability systems '
|
||||||
|
'use to ID a device globally.')
|
||||||
|
pid = Str(description='The PID identifies a device under a circuit or platform.',
|
||||||
|
validate=Length(max=STR_SIZE))
|
||||||
|
gid = Str(description='The Giver ID links the device to the giver\'s (donor, seller)'
|
||||||
|
'internal inventory.',
|
||||||
|
validate=Length(max=STR_SIZE))
|
||||||
|
model = Str(validate=Length(max=STR_BIG_SIZE))
|
||||||
|
manufacturer = Str(validate=Length(max=STR_SIZE))
|
||||||
|
serial_number = Str(load_from='serialNumber', dump_to='serialNumber')
|
||||||
|
product_id = Str(load_from='productId', dump_to='productId')
|
||||||
|
weight = Float(validate=Range(0.1, 3))
|
||||||
|
width = Float(validate=Range(0.1, 3))
|
||||||
|
height = Float(validate=Range(0.1, 3))
|
||||||
|
events = Nested('Event', many=True, dump_only=True, only='id')
|
||||||
|
|
||||||
|
|
||||||
|
class Computer(Device):
|
||||||
|
components = Nested('Component', many=True, dump_only=True, only='id')
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Desktop(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Laptop(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Netbook(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Server(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Microtower(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Component(Device):
|
||||||
|
parent = Nested(Device, dump_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class GraphicCard(Component):
|
||||||
|
memory = Integer(validate=Range(0, 10000))
|
||||||
|
|
||||||
|
|
||||||
|
class HardDrive(Component):
|
||||||
|
size = Integer(validate=Range(0, 10 ** 8))
|
||||||
|
erasure = Nested('EraseBasic', load_only=True)
|
||||||
|
erasures = Nested('EraseBasic', dump_only=True, many=True)
|
||||||
|
tests = Nested('TestHardDrive', many=True)
|
||||||
|
benchmarks = Nested('BenchmarkHardDrive', many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Motherboard(Component):
|
||||||
|
slots = Integer(validate=Range(1, 20), description='PCI slots the motherboard has.')
|
||||||
|
usb = Integer(validate=Range(0, 20))
|
||||||
|
firewire = Integer(validate=Range(0, 20))
|
||||||
|
serial = Integer(validate=Range(0, 20))
|
||||||
|
pcmcia = Integer(validate=Range(0, 20))
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkAdapter(Component):
|
||||||
|
speed = Integer(validate=Range(min=10, max=10000))
|
8
ereuse_devicehub/resources/device/views.py
Normal file
8
ereuse_devicehub/resources/device/views.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from teal.resource import View
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceView(View):
|
||||||
|
|
||||||
|
def one(self, id):
|
||||||
|
"""Gets one device."""
|
||||||
|
raise NotImplementedError
|
7
ereuse_devicehub/resources/event/__init__.py
Normal file
7
ereuse_devicehub/resources/event/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from ereuse_devicehub.resources.event.views import EventView
|
||||||
|
from teal.resource import Resource
|
||||||
|
|
||||||
|
|
||||||
|
class EventDef(Resource):
|
||||||
|
SCHEMA = None
|
||||||
|
VIEW = EventView
|
219
ereuse_devicehub/resources/event/models.py
Normal file
219
ereuse_devicehub/resources/event/models.py
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, DateTime, Enum as DBEnum, \
|
||||||
|
ForeignKey, Integer, Interval, JSON, Sequence, SmallInteger, Unicode
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
from sqlalchemy.orm import backref, relationship
|
||||||
|
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
|
from ereuse_devicehub.resources.model import STR_SIZE, Thing, check_range
|
||||||
|
from ereuse_devicehub.resources.user.model import User
|
||||||
|
from teal.db import CASCADE, CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, POLYMORPHIC_ON
|
||||||
|
|
||||||
|
|
||||||
|
class JoinedTableMixin:
|
||||||
|
@declared_attr
|
||||||
|
def id(cls):
|
||||||
|
return Column(BigInteger, ForeignKey(Event.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Event(Thing):
|
||||||
|
id = Column(BigInteger, Sequence('event_seq'), primary_key=True)
|
||||||
|
date = Column(DateTime)
|
||||||
|
secured = Column(Boolean, default=False, nullable=False)
|
||||||
|
type = Column(Unicode)
|
||||||
|
incidence = Column(Boolean, default=False, nullable=False)
|
||||||
|
|
||||||
|
snapshot_id = Column(BigInteger, ForeignKey('snapshot.id',
|
||||||
|
use_alter=True,
|
||||||
|
name='snapshot_events'))
|
||||||
|
snapshot = relationship('Snapshot',
|
||||||
|
backref=backref('events', lazy=True, cascade=CASCADE),
|
||||||
|
primaryjoin='Event.snapshot_id == Snapshot.id')
|
||||||
|
|
||||||
|
author_id = Column(BigInteger, ForeignKey(User.id), nullable=False)
|
||||||
|
author = relationship(User,
|
||||||
|
backref=backref('events', lazy=True),
|
||||||
|
primaryjoin=author_id == User.id)
|
||||||
|
|
||||||
|
components = relationship(Device,
|
||||||
|
backref=backref('events_components', lazy=True),
|
||||||
|
secondary=lambda: EventComponent.__table__)
|
||||||
|
|
||||||
|
@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.__name__}
|
||||||
|
if cls.__name__ == 'Event':
|
||||||
|
args[POLYMORPHIC_ON] = cls.type
|
||||||
|
if JoinedTableMixin in cls.mro():
|
||||||
|
args[INHERIT_COND] = cls.id == Event.id
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
class EventComponent(db.Model):
|
||||||
|
device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
event_id = Column(BigInteger, ForeignKey(Event.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class EventWithOneDevice(Event):
|
||||||
|
device_id = Column(BigInteger, ForeignKey(Device.id), nullable=False)
|
||||||
|
device = relationship(Device,
|
||||||
|
backref=backref('events_one', lazy=True, cascade=CASCADE),
|
||||||
|
primaryjoin=Device.id == device_id)
|
||||||
|
|
||||||
|
|
||||||
|
class EventWithMultipleDevices(Event):
|
||||||
|
"""
|
||||||
|
Note that these events are not deleted when a device is deleted.
|
||||||
|
"""
|
||||||
|
devices = relationship(Device,
|
||||||
|
backref=backref('events', lazy=True),
|
||||||
|
secondary=lambda: EventDevice.__table__)
|
||||||
|
|
||||||
|
|
||||||
|
class EventDevice(db.Model):
|
||||||
|
device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
event_id = Column(BigInteger, ForeignKey(EventWithMultipleDevices.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Add(EventWithOneDevice):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Remove(EventWithOneDevice):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Allocate(JoinedTableMixin, EventWithMultipleDevices):
|
||||||
|
to_id = Column(BigInteger, ForeignKey(User.id))
|
||||||
|
to = relationship(User, primaryjoin=User.id == to_id)
|
||||||
|
|
||||||
|
|
||||||
|
class Deallocate(JoinedTableMixin, EventWithMultipleDevices):
|
||||||
|
from_id = Column(BigInteger, ForeignKey(User.id))
|
||||||
|
from_rel = relationship(User, primaryjoin=User.id == from_id)
|
||||||
|
|
||||||
|
|
||||||
|
class EraseBasic(JoinedTableMixin, EventWithOneDevice):
|
||||||
|
starting_time = Column(DateTime, nullable=False)
|
||||||
|
ending_time = Column(DateTime, nullable=False)
|
||||||
|
secure_random_steps = Column(SmallInteger, check_range('secure_random_steps', min=0),
|
||||||
|
nullable=False)
|
||||||
|
success = Column(Boolean, nullable=False)
|
||||||
|
clean_with_zeros = Column(Boolean, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class EraseSectors(EraseBasic):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StepTypes(Enum):
|
||||||
|
Zeros = 1
|
||||||
|
Random = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Step(db.Model):
|
||||||
|
id = Column(BigInteger, Sequence('step_seq'), primary_key=True)
|
||||||
|
num = Column(SmallInteger, primary_key=True)
|
||||||
|
type = Column(DBEnum(StepTypes), nullable=False)
|
||||||
|
success = Column(Boolean, nullable=False)
|
||||||
|
starting_time = Column(DateTime, nullable=False)
|
||||||
|
ending_time = Column(DateTime, CheckConstraint('ending_time > starting_time'), nullable=False)
|
||||||
|
secure_random_steps = Column(SmallInteger, check_range('secure_random_steps', min=0),
|
||||||
|
nullable=False)
|
||||||
|
clean_with_zeros = Column(Boolean, nullable=False)
|
||||||
|
|
||||||
|
erasure_id = Column(BigInteger, ForeignKey(EraseBasic.id))
|
||||||
|
erasure = relationship(EraseBasic, backref=backref('steps', cascade=CASCADE_OWN))
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareType(Enum):
|
||||||
|
Workbench = 'Workbench'
|
||||||
|
AndroidApp = 'AndroidApp'
|
||||||
|
Web = 'Web'
|
||||||
|
DesktopApp = 'DesktopApp'
|
||||||
|
|
||||||
|
|
||||||
|
class Appearance(Enum):
|
||||||
|
"""Grades the imperfections that aesthetically affect the device, but not its usage."""
|
||||||
|
Z = '0. The device is new.'
|
||||||
|
A = 'A. Is like new (without visual damage)'
|
||||||
|
B = 'B. Is in really good condition (small visual damage in difficult places to spot)'
|
||||||
|
C = 'C. Is in good condition (small visual damage in parts that are easy to spot, not screens)'
|
||||||
|
D = 'D. Is acceptable (visual damage in visible parts, not ¬screens)'
|
||||||
|
E = 'E. Is unacceptable (considerable visual damage that can affect usage)'
|
||||||
|
|
||||||
|
|
||||||
|
class Functionality(Enum):
|
||||||
|
A = 'A. Everything works perfectly (buttons, and in case of screens there are no scratches)'
|
||||||
|
B = 'B. There is a button difficult to press or a small scratch in an edge of a screen'
|
||||||
|
C = 'C. A non-important button (or similar) doesn\'t work; screen has multiple scratches in edges'
|
||||||
|
D = 'D. Multiple buttons don\'t work; screen has visual damage resulting in uncomfortable usage'
|
||||||
|
|
||||||
|
|
||||||
|
class Bios(Enum):
|
||||||
|
A = 'A. If by pressing a key you could access a boot menu with the network boot'
|
||||||
|
B = 'B. You had to get into the BIOS, and in less than 5 steps you could set the network boot'
|
||||||
|
C = 'C. Like B, but with more than 5 steps'
|
||||||
|
D = 'D. Like B or C, but you had to unlock the BIOS (i.e. by removing the battery)'
|
||||||
|
E = 'E. The device could not be booted through the network.'
|
||||||
|
|
||||||
|
|
||||||
|
class Snapshot(JoinedTableMixin, EventWithOneDevice):
|
||||||
|
uuid = Column(UUID(as_uuid=True), nullable=False, unique=True)
|
||||||
|
version = Column(Unicode, nullable=False)
|
||||||
|
snapshot_software = Column(DBEnum(SoftwareType), nullable=False)
|
||||||
|
appearance = Column(DBEnum(Appearance), nullable=False)
|
||||||
|
appearance_score = Column(SmallInteger, nullable=False)
|
||||||
|
functionality = Column(DBEnum(Functionality), nullable=False)
|
||||||
|
functionality_score = Column(SmallInteger, check_range('functionality_score', min=0, max=5),
|
||||||
|
nullable=False)
|
||||||
|
labelling = Column(Boolean, nullable=False)
|
||||||
|
bios = Column(DBEnum(Bios), nullable=False)
|
||||||
|
condition = Column(SmallInteger, check_range('condition', min=0, max=5), nullable=False)
|
||||||
|
elapsed = Column(Interval, nullable=False)
|
||||||
|
install_name = Column(Unicode)
|
||||||
|
install_elapsed = Column(Interval)
|
||||||
|
install_success = Column(Boolean)
|
||||||
|
inventory_elapsed = Column(Interval)
|
||||||
|
|
||||||
|
|
||||||
|
class SnapshotRequest(db.Model):
|
||||||
|
id = Column(BigInteger, ForeignKey(Snapshot.id), primary_key=True)
|
||||||
|
request = Column(JSON, nullable=False)
|
||||||
|
|
||||||
|
snapshot = relationship(Snapshot, backref=backref('request', lazy=True, uselist=False,
|
||||||
|
cascade=CASCADE_OWN))
|
||||||
|
|
||||||
|
|
||||||
|
class Test(JoinedTableMixin, EventWithOneDevice):
|
||||||
|
elapsed = Column(Interval, nullable=False)
|
||||||
|
success = Column(Boolean, nullable=False)
|
||||||
|
|
||||||
|
snapshot = relationship(Snapshot, backref=backref('tests', lazy=True, cascade=CASCADE_OWN))
|
||||||
|
|
||||||
|
|
||||||
|
class TestHardDriveLength(Enum):
|
||||||
|
Short = 'Short'
|
||||||
|
Extended = 'Extended'
|
||||||
|
|
||||||
|
|
||||||
|
class TestHardDrive(Test):
|
||||||
|
length = Column(DBEnum(TestHardDriveLength), nullable=False) # todo from type
|
||||||
|
status = Column(Unicode(STR_SIZE), nullable=False)
|
||||||
|
lifetime = Column(Interval, nullable=False)
|
||||||
|
first_error = Column(Integer)
|
||||||
|
# todo error becomes Test.success
|
||||||
|
|
||||||
|
|
||||||
|
class StressTest(Test):
|
||||||
|
pass
|
0
ereuse_devicehub/resources/event/remove/__init__.py
Normal file
0
ereuse_devicehub/resources/event/remove/__init__.py
Normal file
7
ereuse_devicehub/resources/event/remove/views.py
Normal file
7
ereuse_devicehub/resources/event/remove/views.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from teal.resource import View
|
||||||
|
|
||||||
|
|
||||||
|
class Remove(View):
|
||||||
|
def post(self):
|
||||||
|
"""Removes a component from a computer."""
|
||||||
|
pass
|
7
ereuse_devicehub/resources/event/snapshot/__init__.py
Normal file
7
ereuse_devicehub/resources/event/snapshot/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from ereuse_devicehub.resources.event import EventDef
|
||||||
|
from ereuse_devicehub.resources.event.snapshot.views import SnapshotView
|
||||||
|
|
||||||
|
|
||||||
|
class SnapshotDef(EventDef):
|
||||||
|
VIEW = SnapshotView
|
||||||
|
SCHEMA = None
|
17
ereuse_devicehub/resources/event/snapshot/views.py
Normal file
17
ereuse_devicehub/resources/event/snapshot/views.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from teal.resource import View
|
||||||
|
|
||||||
|
|
||||||
|
class SnapshotView(View):
|
||||||
|
def post(self):
|
||||||
|
"""Creates a Snapshot."""
|
||||||
|
return super().post()
|
||||||
|
|
||||||
|
def delete(self, id):
|
||||||
|
"""Deletes a Snapshot"""
|
||||||
|
return super().delete(id)
|
||||||
|
|
||||||
|
def patch(self, id):
|
||||||
|
"""Modifies a Snapshot"""
|
||||||
|
return super().patch(id)
|
||||||
|
|
||||||
|
|
7
ereuse_devicehub/resources/event/views.py
Normal file
7
ereuse_devicehub/resources/event/views.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from teal.resource import View
|
||||||
|
|
||||||
|
|
||||||
|
class EventView(View):
|
||||||
|
def one(self, id):
|
||||||
|
"""Gets one event."""
|
||||||
|
return super().one(id)
|
20
ereuse_devicehub/resources/model.py
Normal file
20
ereuse_devicehub/resources/model.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import CheckConstraint
|
||||||
|
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
|
||||||
|
STR_SIZE = 64
|
||||||
|
STR_BIG_SIZE = 128
|
||||||
|
STR_SM_SIZE = 32
|
||||||
|
|
||||||
|
|
||||||
|
def check_range(column: str, min=1, max=None) -> CheckConstraint:
|
||||||
|
constraint = '>= {}'.format(min) if max is None else 'BETWEEN {} AND {}'.format(min, max)
|
||||||
|
return CheckConstraint('{} {}'.format(column, constraint))
|
||||||
|
|
||||||
|
|
||||||
|
class Thing(db.Model):
|
||||||
|
__abstract__ = True
|
||||||
|
updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
|
||||||
|
created = db.Column(db.DateTime, default=datetime.utcnow)
|
10
ereuse_devicehub/resources/schema.py
Normal file
10
ereuse_devicehub/resources/schema.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from marshmallow.fields import DateTime, List, Str, URL, Nested
|
||||||
|
from teal.resource import Schema
|
||||||
|
|
||||||
|
|
||||||
|
class Thing(Schema):
|
||||||
|
url = URL(dump_only=True, description='The URL of the resource.')
|
||||||
|
same_as = List(URL(dump_only=True), dump_only=True)
|
||||||
|
updated = DateTime('iso', dump_only=True)
|
||||||
|
created = DateTime('iso', dump_only=True)
|
||||||
|
author = Nested('User', only='id', dump_only=True)
|
0
ereuse_devicehub/resources/user/__init__.py
Normal file
0
ereuse_devicehub/resources/user/__init__.py
Normal file
9
ereuse_devicehub/resources/user/model.py
Normal file
9
ereuse_devicehub/resources/user/model.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from sqlalchemy import BigInteger, Column, Sequence
|
||||||
|
from sqlalchemy_utils import EmailType
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.model import Thing
|
||||||
|
|
||||||
|
|
||||||
|
class User(Thing):
|
||||||
|
id = Column(BigInteger, Sequence('user_seq'), primary_key=True)
|
||||||
|
email = Column(EmailType, nullable=False)
|
34
setup.py
Normal file
34
setup.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="eReuse_Devicehub",
|
||||||
|
version='0.0.1',
|
||||||
|
packages=find_packages(),
|
||||||
|
url='https://github.com/ereuse/devicehub-teal',
|
||||||
|
license='Affero',
|
||||||
|
author='eReuse.org team',
|
||||||
|
author_email='x.bustamante@ereuse.org',
|
||||||
|
description='A system to manage devices focused in reusing them.',
|
||||||
|
install_requires=[
|
||||||
|
'teal',
|
||||||
|
'marshmallow_enum',
|
||||||
|
'ereuse-utils [Naming]',
|
||||||
|
'psycopg2-binary',
|
||||||
|
'sqlalchemy-utils'
|
||||||
|
],
|
||||||
|
tests_requires=[
|
||||||
|
'pytest',
|
||||||
|
'pytest-datadir'
|
||||||
|
],
|
||||||
|
classifiers={
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: BSD License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||||
|
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||||
|
},
|
||||||
|
)
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
31
tests/conftest.py
Normal file
31
tests/conftest.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ereuse_devicehub.client import Client
|
||||||
|
from ereuse_devicehub.config import DevicehubConfig
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(DevicehubConfig):
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/dh_test'
|
||||||
|
SQLALCHEMY_BINDS = {
|
||||||
|
'common': 'postgresql://localhost/dh_test_common'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def config():
|
||||||
|
return TestConfig()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def app(config: TestConfig) -> Devicehub:
|
||||||
|
app = Devicehub(config=config, db=db)
|
||||||
|
db.create_all(app=app)
|
||||||
|
yield app
|
||||||
|
db.drop_all(app=app)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def client(app: Devicehub) -> Client:
|
||||||
|
return app.test_client()
|
15
tests/test_basic.py
Normal file
15
tests/test_basic.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
|
from ereuse_devicehub.resources.device.models import Desktop, NetworkAdapter
|
||||||
|
from ereuse_devicehub.resources.event.models import Snapshot, SoftwareType, Appearance, \
|
||||||
|
Functionality, Bios
|
||||||
|
from ereuse_devicehub.resources.user.model import User
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
|
def test_init(app: Devicehub):
|
||||||
|
"""Tests app initialization."""
|
||||||
|
pass
|
40
tests/test_device.py
Normal file
40
tests/test_device.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
|
from ereuse_devicehub.resources.device.models import Desktop, GraphicCard, NetworkAdapter, Device
|
||||||
|
|
||||||
|
|
||||||
|
def test_device_model(app: Devicehub):
|
||||||
|
with app.test_request_context():
|
||||||
|
pc = Desktop(model='p1mo', manufacturer='p1ma', serial_number='p1s')
|
||||||
|
pc.components = components = [
|
||||||
|
NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s'),
|
||||||
|
GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
|
||||||
|
]
|
||||||
|
db.session.add(pc)
|
||||||
|
db.session.commit()
|
||||||
|
pc = Desktop.query.one()
|
||||||
|
assert pc.serial_number == 'p1s'
|
||||||
|
assert pc.components == components
|
||||||
|
network_adapter = NetworkAdapter.query.one()
|
||||||
|
assert network_adapter.parent == pc
|
||||||
|
|
||||||
|
# Removing a component from pc doesn't delete the component
|
||||||
|
del pc.components[0]
|
||||||
|
db.session.commit()
|
||||||
|
pc = Device.query.first() # this is the same as querying for Desktop directly
|
||||||
|
assert pc.components[0].type == GraphicCard.__name__
|
||||||
|
network_adapter = NetworkAdapter.query.one()
|
||||||
|
assert network_adapter not in pc.components
|
||||||
|
assert network_adapter.parent is None
|
||||||
|
|
||||||
|
# Deleting the pc deletes everything
|
||||||
|
gcard = GraphicCard.query.one()
|
||||||
|
db.session.delete(pc)
|
||||||
|
assert pc.id == 1
|
||||||
|
assert Desktop.query.first() is None
|
||||||
|
db.session.commit()
|
||||||
|
assert Desktop.query.first() is None
|
||||||
|
assert network_adapter.id == 2
|
||||||
|
assert NetworkAdapter.query.first() is not None, 'We removed the network adaptor'
|
||||||
|
assert gcard.id == 3, 'We should still hold a reference to a zombie graphic card'
|
||||||
|
assert GraphicCard.query.first() is None, 'We should have deleted it –it was inside the pc'
|
48
tests/test_event.py
Normal file
48
tests/test_event.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
|
from ereuse_devicehub.resources.device.models import Microtower, Device
|
||||||
|
from ereuse_devicehub.resources.event.models import Snapshot, SoftwareType, Appearance, \
|
||||||
|
Functionality, Bios, SnapshotRequest, TestHardDrive, StressTest
|
||||||
|
from ereuse_devicehub.resources.user.model import User
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
|
def test_event_model(app: Devicehub):
|
||||||
|
"""
|
||||||
|
Tests creating a Snapshot with its relationships ensuring correct
|
||||||
|
DB mapping.
|
||||||
|
"""
|
||||||
|
with app.test_request_context():
|
||||||
|
user = User(email='foo@bar.com')
|
||||||
|
device = Microtower(serial_number='a1')
|
||||||
|
snapshot = Snapshot(uuid=uuid4(),
|
||||||
|
date=datetime.now(),
|
||||||
|
version='1.0',
|
||||||
|
snapshot_software=SoftwareType.DesktopApp,
|
||||||
|
appearance=Appearance.A,
|
||||||
|
appearance_score=5,
|
||||||
|
functionality=Functionality.A,
|
||||||
|
functionality_score=5,
|
||||||
|
labelling=False,
|
||||||
|
bios=Bios.C,
|
||||||
|
condition=5,
|
||||||
|
elapsed=timedelta(seconds=25))
|
||||||
|
snapshot.device = device
|
||||||
|
snapshot.author = user
|
||||||
|
snapshot.request = SnapshotRequest(request={'foo': 'bar'})
|
||||||
|
|
||||||
|
db.session.add(snapshot)
|
||||||
|
db.session.commit()
|
||||||
|
device = Microtower.query.one() # type: Microtower
|
||||||
|
assert device.events_one[0].type == Snapshot.__name__
|
||||||
|
db.session.delete(device)
|
||||||
|
db.session.commit()
|
||||||
|
assert Snapshot.query.one_or_none() is None
|
||||||
|
assert SnapshotRequest.query.one_or_none() is None
|
||||||
|
assert User.query.one() is not None
|
||||||
|
assert Microtower.query.one_or_none() is None
|
||||||
|
assert Device.query.one_or_none() is None
|
||||||
|
|
0
tests/test_models.py
Normal file
0
tests/test_models.py
Normal file
6
tests/test_schema.py
Normal file
6
tests/test_schema.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
|
||||||
|
def test_device_schema():
|
||||||
|
"""Tests device schema."""
|
||||||
|
pass
|
Reference in a new issue