Merge branch 'master' into rate

# Conflicts:
#	ereuse_devicehub/resources/event/models.py
This commit is contained in:
Xavier Bustamante Talavera 2019-04-10 11:20:54 +02:00
commit 8f4231dc1c
32 changed files with 887 additions and 103 deletions

View file

@ -16,7 +16,7 @@ Our main objectives are:
- To highly integrate with existing IT Asset Management Systems. - To highly integrate with existing IT Asset Management Systems.
- To be decentralized. - To be decentralized.
Devicehub is built with [Teal](https://github.com/bustawin/teal) and Devicehub is built with [Teal](https://github.com/ereuse/teal) and
[Flask](http://flask.pocoo.org). [Flask](http://flask.pocoo.org).
## Installing ## Installing

View file

@ -1,15 +1,14 @@
Actions and states
##################
Actions Actions
******* #######
Actions are events performed to devices, changing their **state**. Actions are events performed to devices, changing their **state**.
Actions can have attributes defining Actions can have attributes defining
**where** it happened, **who** performed them, **when**, etc. **where** it happened, **who** performed them, **when**, etc.
Actions are stored in a log for each device. An exemplifying action Actions are stored in a log for each device. An exemplifying action
can be ``Repair``, which dictates that a device has been repaired, can be ``Repair``, which dictates that a device has been repaired,
after this action, the device is in the ``repaired`` state. after this action, the device is in the ``repaired`` state. Another
example is performing a ``Sell`` to agent 1 (now this agent *owns*
the device), and then performing another ``Sell`` to agent 2 (now
agent 2 is the owner).
Devicehub actions inherit from `schema actions Devicehub actions inherit from `schema actions
<http://schema.org/Action>`_, are written in Pascal case and using <http://schema.org/Action>`_, are written in Pascal case and using
@ -19,14 +18,14 @@ is going to be / must be repaired, whereas ``Repair`` states
that the reparation happened. The former actions have the preposition that the reparation happened. The former actions have the preposition
*To* prefixing the verb. *To* prefixing the verb.
Actions and states affect devices in different ways or **dimensions**. :ref:`actions:Actions` and :ref:`states:States` affect devices in
different ways or **dimensions**.
For example, ``Repair`` affects the **physical** dimension of a device, For example, ``Repair`` affects the **physical** dimension of a device,
and ``Sell`` the **political** dimension of a device. A device and ``Sell`` the **political** dimension of a device. A device
can be in several states at the same time, one per dimension; ie. a can be in several states at the same time, one per dimension; ie. a
device can be ``repaired`` (physical) and ``reserved`` (political), device can be ``repaired`` (physical) and ``reserved`` (political),
but not ``repaired`` and ``disposed`` at the same time: but not ``repaired`` and ``disposed`` at the same time:
- Physical actions: The following actions describe and react on the - Physical actions: The following actions describe and react on the
Physical condition of the devices. Physical condition of the devices.
@ -37,7 +36,7 @@ but not ``repaired`` and ``disposed`` at the same time:
- DisposeWaste, Recover - DisposeWaste, Recover
- Association actions: Actions that change the associations users have with devices; - Association actions: Actions that change the associations users have with devices;
ie. the **owners**, **usufructuarees**, **reservees**, ie. the **owners**, **usufructuarees** (*from usufruct*), **reservees** (*from reserve*),
and **physical possessors**. and **physical possessors**.
- Trade - Trade
@ -60,15 +59,8 @@ but not ``repaired`` and ``disposed`` at the same time:
The following index has all the actions (please note we are moving from calling them The following index has all the actions (please note we are moving from calling them
``Event`` to call them ``Action``): ``Event`` to call them ``Action``):
Schema
******
.. dhlist:: .. dhlist::
:module: ereuse_devicehub.resources.event.schemas :module: ereuse_devicehub.resources.event.schemas
States
******
.. autoclass:: ereuse_devicehub.resources.device.states.State
.. uml:: states.puml
.. autoclass:: ereuse_devicehub.resources.device.states.Trading
.. autoclass:: ereuse_devicehub.resources.device.states.Physical

View file

@ -1,6 +1,19 @@
Devices Devices
######### #######
Devices are objects that can be identified, and they are the
main entity in a Devicehub. Refer to :ref:`devices:Device` for more
info.
Schema
******
The following schema represents all the device types and their
properties.
.. dhlist::
:module: ereuse_devicehub.resources.device.schemas
API
***
You can retrieve devices using ``GET /devices/``, or a specific You can retrieve devices using ``GET /devices/``, or a specific
device by ``GET /devices/24``. device by ``GET /devices/24``.
@ -46,5 +59,4 @@ The result is a JSON object with the following fields:
- **next**: The number of the next page, if any. - **next**: The number of the next page, if any.
- **last**: The number of the last page, if any. - **last**: The number of the last page, if any.
.. dhlist::
:module: ereuse_devicehub.resources.device.schemas

View file

@ -14,19 +14,36 @@ reusing devices, created under the project
Our main objectives are: Our main objectives are:
- To offer a common IT Asset Management for donors, receivers and IT - To offer a common IT Asset Management for distributors, refurbishers,
professionals so they can manage devices and exchange them. receivers and other IT professionals so they can manage devices and exchange them.
This is, reusing and ultimately recycling. This is, reusing and ultimately recycling.
- To automatically recollect, analyse, process and share - To automatically recollect, analyse, process and share
(controlling privacy) metadata about devices with other tools of the (controlling privacy) metadata about devices with other tools of the
eReuse ecosystem to guarantee traceability, and to provide inputs for eReuse ecosystem to guarantee traceability, and to provide inputs for
the indicators which measure circularity. the indicators which measure circularity.
- To highly integrate with existing IT Asset Management Systems.
- To be decentralized.
Devicehub is built with `Teal <https://github.com/bustawin/teal>`_ and The main entity of a Devicehub are :ref:`devices:Devices`, which is any object that
`Flask <http://flask.pocoo.org>`_. can be identified. Devices are divided in *types* (like ``Computer``),
and each one defines *properties*, like serial number, weight,
quality rating, pricing, or a list of owners.
We perform :ref:`actions:Actions` on devices, which are events that
change their *state* and *properties*. Examples are sales, reparations,
quality diagnostics, data wiping, and location.
Actions are stored in the traceability log of the device.
Devicehub is decentralized, and each instance is an inventory. We can
share and exchange devices between inventories —like in real live between
organizations.
:ref:`tags:Tags` identify devices through those organizations and their
internal systems. With Devicehub we can manage and print smart tags with
QR and NFC capabilities, operating devices by literally scanning them.
Devicehub is a REST API built with `Teal <https://github.com/ereuse/teal>`_ and
`Flask <http://flask.pocoo.org>`_ using `PostgreSQL <https://www.postgresql.org>`_.
`DevicehubClient <https://github.com/ereuse/devicehubclient>`_ is the
frontend that consumes this API.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
@ -34,6 +51,7 @@ Devicehub is built with `Teal <https://github.com/bustawin/teal>`_ and
api api
devices devices
actions actions
states
tags tags
lots lots

48
docs/states.rst Normal file
View file

@ -0,0 +1,48 @@
States
######
.. note:: In construction.
A mutable property of a device result of applying an
:ref:`actions:Action` to it.
States are represented as properties in :ref:`devices:Device` and
subtypes. They can be steps in a workflow
(like ``sold`` and ``payed``, part of a trading), or properties
describing computed values from applying events (like a list of owners,
or a quality rating).
There are three types of states:
* **Trading**: a workflow of states resulting from applying the action
:ref:`actions:Trade`.
* **Physical**: a workflow of states resulting from applying
physical actions (ref. :ref:`actions:Actions`).
* **Attributes**: miscellaneous device properties that are not part of
a workflow.
.. uml:: states.puml
Trading
*******
Trading states.
:cvar Reserved: The device has been reserved.
:cvar Cancelled: The device has been cancelled.
:cvar Sold: The device has been sold.
:cvar Donated: The device is donated.
:cvar Renting: The device is in renting
:cvar ToBeDisposed: The device is disposed.
This is the end of life of a device.
:cvar ProductDisposed: The device has been removed
from the facility. It does not mean end-of-life.
Physical
********
Physical states.
:cvar ToBeRepaired: The device has been selected for reparation.
:cvar Repaired: The device has been repaired.
:cvar Preparing: The device is going to be or being prepared.
:cvar Prepared: The device has been prepared.
:cvar ReadyToBeUsed: The device is in working conditions.
:cvar InUse: The device is being reported to be in active use.

View file

@ -36,6 +36,7 @@ class SQLAlchemy(SchemaSQLAlchemy):
# manually import them all the time # manually import them all the time
UUID = postgresql.UUID UUID = postgresql.UUID
CIText = citext.CIText CIText = citext.CIText
PSQL_INT_MAX = 2147483648
def drop_all(self, bind='__all__', app=None, common_schema=True): def drop_all(self, bind='__all__', app=None, common_schema=True):
"""A faster nuke-like option to drop everything.""" """A faster nuke-like option to drop everything."""

View file

@ -6,6 +6,10 @@ from ereuse_devicehub.resources.event import models as e
class State(Enum): class State(Enum):
"""A mutable property of a device result of applying an
:ref:`actions:Action` to it.
"""
@classmethod @classmethod
def events(cls): def events(cls):
"""Events participating in this state.""" """Events participating in this state."""

View file

@ -0,0 +1,119 @@
from collections import OrderedDict
from flask import current_app
from ereuse_devicehub.resources.device import models as d
from ereuse_devicehub.resources.event.models import TestDataStorage, BenchmarkDataStorage
class DeviceRow(OrderedDict):
NUMS = {
d.Display.t: 1,
d.Processor.t: 2,
d.GraphicCard.t: 2,
d.Motherboard.t: 1,
d.NetworkAdapter.t: 2,
d.SoundCard.t: 2
}
# TODO Add more fields information
def __init__(self, device: d.Device) -> None:
super().__init__()
self.device = device
# General information about device
self['Type'] = device.t
if isinstance(device, d.Computer):
self['Chassis'] = device.chassis
else:
self['Chassis'] = ''
self['Tag 1'] = self['Tag 2'] = self['Tag 3'] = ''
for i, tag in zip(range(1, 3), device.tags):
self['Tag {}'.format(i)] = format(tag)
self['Serial Number'] = device.serial_number
self['Model'] = device.model
self['Manufacturer'] = device.manufacturer
# self['State'] = device.last_event_of()
self['Registered in'] = format(device.created, '%c')
self['Price'] = device.price
if isinstance(device, d.Computer):
self['Processor'] = device.processor_model
self['RAM (GB)'] = device.ram_size
self['Data Storage Size (MB)'] = device.data_storage_size
rate = device.rate
if rate:
self['Rate'] = rate.rating
self['Range'] = rate.rating_range
self['Processor Rate'] = rate.processor
self['Processor Range'] = rate.workbench.processor_range
self['RAM Rate'] = rate.ram
self['RAM Range'] = rate.workbench.ram_range
self['Data Storage Rate'] = rate.data_storage
self['Data Storage Range'] = rate.workbench.data_storage_range
# More specific information about components
if isinstance(device, d.Computer):
self.components()
def components(self):
"""
Function to get all components information of a device
"""
assert isinstance(self.device, d.Computer)
# todo put an input specific order (non alphabetic) & where are a list of types components
for type in sorted(current_app.resources[d.Component.t].subresources_types): # type: str
max = self.NUMS.get(type, 4)
if type not in ['Component', 'HardDrive', 'SolidStateDrive']:
i = 1
for component in (r for r in self.device.components if r.type == type):
self.fill_component(type, i, component)
i += 1
if i > max:
break
while i <= max:
self.fill_component(type, i)
i += 1
def fill_component(self, type, i, component=None):
"""
Function to put specific information of components in OrderedDict (csv)
:param type: type of component
:param component: device.components
"""
self['{} {}'.format(type, i)] = format(component) if component else ''
self['{} {} Manufacturer'.format(type, i)] = component.serial_number if component else ''
self['{} {} Model'.format(type, i)] = component.serial_number if component else ''
self['{} {} Serial Number'.format(type, i)] = component.serial_number if component else ''
""" Particular fields for component GraphicCard """
if isinstance(component, d.GraphicCard):
self['{} {} Memory (MB)'.format(type, i)] = component.memory
""" Particular fields for component DataStorage.t -> (HardDrive, SolidStateDrive) """
if isinstance(component, d.DataStorage):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Privacy'.format(type, i)] = component.privacy
try:
self['{} {} Lifetime'.format(type, i)] = component.last_event_of(TestDataStorage).lifetime
except:
self['{} {} Lifetime'.format(type, i)] = ''
try:
self['{} {} Reading speed'.format(type, i)] = component.last_event_of(BenchmarkDataStorage).read_speed
except:
self['{} {} Reading speed'.format(type, i)] = ''
try:
self['{} {} Writing speed'.format(type, i)] = component.last_event_of(BenchmarkDataStorage).write_speed
except:
self['{} {} Writing speed'.format(type, i)] = ''
""" Particular fields for component Processor """
if isinstance(component, d.Processor):
self['{} {} Number of cores'.format(type, i)] = component.cores
self['{} {} Speed (GHz)'.format(type, i)] = component.speed
""" Particular fields for component RamModule """
if isinstance(component, d.RamModule):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Speed (MHz)'.format(type, i)] = component.speed
# todo add Display size, ...
# todo add NetworkAdapter speedLink?
# todo add some ComputerAccessories

View file

@ -1,5 +1,8 @@
import csv
import datetime
import enum import enum
import uuid import uuid
from io import StringIO
from typing import Callable, Iterable, Tuple from typing import Callable, Iterable, Tuple
import boltons import boltons
@ -7,11 +10,14 @@ import flask
import flask_weasyprint import flask_weasyprint
import teal.marshmallow import teal.marshmallow
from boltons import urlutils from boltons import urlutils
from flask import make_response
from teal.cache import cache
from teal.resource import Resource from teal.resource import Resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device import models as devs from ereuse_devicehub.resources.device import models as devs
from ereuse_devicehub.resources.device.views import DeviceView from ereuse_devicehub.resources.device.views import DeviceView
from ereuse_devicehub.resources.documents.device_row import DeviceRow
from ereuse_devicehub.resources.event import models as evs from ereuse_devicehub.resources.event import models as evs
@ -97,6 +103,33 @@ class DocumentView(DeviceView):
return flask.render_template('documents/erasure.html', **params) return flask.render_template('documents/erasure.html', **params)
class DevicesDocumentView(DeviceView):
@cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
query = self.query(args)
return self.generate_post_csv(query)
def generate_post_csv(self, query):
"""
Get device query and put information in csv format
:param query:
:return:
"""
data = StringIO()
cw = csv.writer(data)
first = True
for device in query:
d = DeviceRow(device)
if first:
cw.writerow(name for name in d.keys())
first = False
cw.writerow(v for v in d.values())
output = make_response(data.getvalue())
output.headers['Content-Disposition'] = 'attachment; filename=export.csv'
output.headers['Content-type'] = 'text/csv'
return output
class DocumentDef(Resource): class DocumentDef(Resource):
__type__ = 'Document' __type__ = 'Document'
SCHEMA = None SCHEMA = None
@ -124,3 +157,9 @@ class DocumentDef(Resource):
self.add_url_rule('/erasures/', defaults=d, view_func=view, methods=get) self.add_url_rule('/erasures/', defaults=d, view_func=view, methods=get)
self.add_url_rule('/erasures/<{}:{}>'.format(self.ID_CONVERTER.value, self.ID_NAME), self.add_url_rule('/erasures/<{}:{}>'.format(self.ID_CONVERTER.value, self.ID_NAME),
view_func=view, methods=get) view_func=view, methods=get)
devices_view = DevicesDocumentView.as_view('devicesDocumentView',
definition=self,
auth=app.auth)
if self.AUTH:
devices_view = app.auth.requires_auth(devices_view)
self.add_url_rule('/devices/', defaults=d, view_func=devices_view, methods=get)

View file

@ -4,7 +4,7 @@ from teal.resource import Converters, Resource
from ereuse_devicehub.resources.device.sync import Sync from ereuse_devicehub.resources.device.sync import Sync
from ereuse_devicehub.resources.event import schemas from ereuse_devicehub.resources.event import schemas
from ereuse_devicehub.resources.event.views import EventView, SnapshotView from ereuse_devicehub.resources.event.views import EventView
class EventDef(Resource): class EventDef(Resource):
@ -90,13 +90,14 @@ class InstallDef(EventDef):
class SnapshotDef(EventDef): class SnapshotDef(EventDef):
VIEW = SnapshotView VIEW = None
SCHEMA = schemas.Snapshot SCHEMA = schemas.Snapshot
def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None, def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None,
static_url_path=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
url_prefix = '/{}'.format(EventDef.resource)
super().__init__(app, import_name, static_folder, static_url_path, template_folder, super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands) url_prefix, subdomain, url_defaults, root_path, cli_commands)
self.sync = Sync() self.sync = Sync()

View file

@ -546,7 +546,6 @@ class SnapshotRequest(db.Model):
id = Column(UUID(as_uuid=True), ForeignKey(Snapshot.id), primary_key=True) id = Column(UUID(as_uuid=True), ForeignKey(Snapshot.id), primary_key=True)
request = Column(JSON, nullable=False) request = Column(JSON, nullable=False)
snapshot = relationship(Snapshot, snapshot = relationship(Snapshot,
backref=backref('request', backref=backref('request',
lazy=True, lazy=True,
uselist=False, uselist=False,
@ -686,6 +685,15 @@ class TestDataStorage(Test):
t += self.description t += self.description
return t return t
@property
def reported_uncorrectable_errors(self):
return self._reported_uncorrectable_errors
@reported_uncorrectable_errors.setter
def reported_uncorrectable_errors(self, value):
# There is no value for a stratospherically big number
self._reported_uncorrectable_errors = min(value, db.PSQL_INT_MAX)
class StressTest(Test): class StressTest(Test):
"""The act of stressing (putting to the maximum capacity) """The act of stressing (putting to the maximum capacity)
@ -809,7 +817,12 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
""" """
rating = Column(Float(decimal_return_scale=2), check_range('rating', *RATE_POSITIVE)) rating = Column(Float(decimal_return_scale=2), check_range('rating', *RATE_POSITIVE))
rating.comment = """The rating for the content.""" rating.comment = """The rating for the content.
This value is automatically set by rating algorithms. In case that
no algorithm is defined per the device and type of rate, this
value is None.
"""
software = Column(DBEnum(RatingSoftware)) software = Column(DBEnum(RatingSoftware))
software.comment = """The algorithm used to produce this rating.""" software.comment = """The algorithm used to produce this rating."""
version = Column(StrictVersionType) version = Column(StrictVersionType)
@ -817,8 +830,7 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
@property @property
def rating_range(self) -> RatingRange: def rating_range(self) -> RatingRange:
if self.rating: return RatingRange.from_score(self.rating) if self.rating else None
return RatingRange.from_score(self.rating)
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
@ -1224,7 +1236,7 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
@classmethod @classmethod
def to_price(cls, value: Union[Decimal, float], rounding=ROUND) -> Decimal: def to_price(cls, value: Union[Decimal, float], rounding=ROUND) -> Decimal:
"""Returns a Decimal value with the correct scale for Price.price.""" """Returns a Decimal value with the correct scale for Price.price."""
if isinstance(value, float): if isinstance(value, (float, int)):
value = Decimal(value) value = Decimal(value)
# equation from marshmallow.fields.Decimal # equation from marshmallow.fields.Decimal
return value.quantize(Decimal((0, (1,), -cls.SCALE)), rounding=rounding) return value.quantize(Decimal((0, (1,), -cls.SCALE)), rounding=rounding)
@ -1308,8 +1320,8 @@ class EreusePrice(Price):
self.warranty2 = EreusePrice.Type(rate[self.WARRANTY2][role], price) self.warranty2 = EreusePrice.Type(rate[self.WARRANTY2][role], price)
def __init__(self, rating: AggregateRate, **kwargs) -> None: def __init__(self, rating: AggregateRate, **kwargs) -> None:
if rating.rating_range == RatingRange.VERY_LOW: if not rating.rating_range or rating.rating_range == RatingRange.VERY_LOW:
raise ValueError('Cannot compute price for Range.VERY_LOW') raise InvalidRangeForPrice()
# We pass ROUND_UP strategy so price is always greater than what refurbisher... amounts # We pass ROUND_UP strategy so price is always greater than what refurbisher... amounts
price = self.to_price(rating.rating * self.MULTIPLIER[rating.device.__class__], ROUND_UP) price = self.to_price(rating.rating * self.MULTIPLIER[rating.device.__class__], ROUND_UP)
super().__init__(rating=rating, super().__init__(rating=rating,
@ -1643,3 +1655,7 @@ def update_parent(target: Union[EraseBasic, Test, Install], device: Device, _, _
target.parent = None target.parent = None
if isinstance(device, Component): if isinstance(device, Component):
target.parent = device.parent target.parent = device.parent
class InvalidRangeForPrice(ValueError):
pass

View file

@ -4,8 +4,8 @@ from typing import Set, Union
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.enums import RatingSoftware from ereuse_devicehub.resources.enums import RatingSoftware
from ereuse_devicehub.resources.event.models import AggregateRate, EreusePrice, Rate, \ from ereuse_devicehub.resources.event.models import AggregateRate, EreusePrice, \
WorkbenchRate InvalidRangeForPrice, Rate, WorkbenchRate
from ereuse_devicehub.resources.event.rate.workbench import v1_0 from ereuse_devicehub.resources.event.rate.workbench import v1_0
RATE_TYPES = { RATE_TYPES = {
@ -72,7 +72,6 @@ def main(rating_model: WorkbenchRate,
if soft == software and vers == version: if soft == software and vers == version:
aggregation = AggregateRate.from_workbench_rate(rating) aggregation = AggregateRate.from_workbench_rate(rating)
events.add(aggregation) events.add(aggregation)
with suppress(ValueError): with suppress(InvalidRangeForPrice): # We will have exception if range == VERY_LOW
# We will have exception if range == VERY_LOW
events.add(EreusePrice(aggregation)) events.add(EreusePrice(aggregation))
return events return events

View file

@ -12,14 +12,21 @@ from ereuse_devicehub.resources.device.models import Component, Computer
from ereuse_devicehub.resources.enums import SnapshotSoftware from ereuse_devicehub.resources.enums import SnapshotSoftware
from ereuse_devicehub.resources.event.models import Event, Snapshot, WorkbenchRate from ereuse_devicehub.resources.event.models import Event, Snapshot, WorkbenchRate
SUPPORTED_WORKBENCH = StrictVersion('11.0')
class EventView(View): class EventView(View):
def post(self): def post(self):
"""Posts an event.""" """Posts an event."""
json = request.get_json(validate=False) json = request.get_json(validate=False)
if 'type' not in json: if not json or 'type' not in json:
raise ValidationError('Resource needs a type.') raise ValidationError('Resource needs a type.')
e = app.resources[json['type']].schema.load(json) # todo there should be a way to better get subclassess resource
# defs
resource_def = app.resources[json['type']]
e = resource_def.schema.load(json)
if json['type'] == Snapshot.t:
return self.snapshot(e, resource_def)
Model = db.Model._decl_class_registry.data[json['type']]() Model = db.Model._decl_class_registry.data[json['type']]()
event = Model(**e) event = Model(**e)
db.session.add(event) db.session.add(event)
@ -34,25 +41,20 @@ class EventView(View):
event = Event.query.filter_by(id=id).one() event = Event.query.filter_by(id=id).one()
return self.schema.jsonify(event) return self.schema.jsonify(event)
def snapshot(self, snapshot_json: dict, resource_def):
SUPPORTED_WORKBENCH = StrictVersion('11.0')
class SnapshotView(View):
def post(self):
""" """
Performs a Snapshot. Performs a Snapshot.
See `Snapshot` section in docs for more info. See `Snapshot` section in docs for more info.
""" """
s = request.get_json()
# Note that if we set the device / components into the snapshot # Note that if we set the device / components into the snapshot
# model object, when we flush them to the db we will flush # model object, when we flush them to the db we will flush
# snapshot, and we want to wait to flush snapshot at the end # snapshot, and we want to wait to flush snapshot at the end
device = s.pop('device') # type: Computer device = snapshot_json.pop('device') # type: Computer
components = s.pop('components') \ components = None
if s['software'] == SnapshotSoftware.Workbench else None # type: List[Component] if snapshot_json['software'] == SnapshotSoftware.Workbench:
snapshot = Snapshot(**s) components = snapshot_json.pop('components') # type: List[Component]
snapshot = Snapshot(**snapshot_json)
# Remove new events from devices so they don't interfere with sync # Remove new events from devices so they don't interfere with sync
events_device = set(e for e in device.events_one) events_device = set(e for e in device.events_one)
@ -62,10 +64,9 @@ class SnapshotView(View):
for component in components: for component in components:
component.events_one.clear() component.events_one.clear()
# noinspection PyArgumentList
assert not device.events_one assert not device.events_one
assert all(not c.events_one for c in components) if components else True 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) db_device, remove_events = resource_def.sync.run(device, components)
snapshot.device = db_device snapshot.device = db_device
snapshot.events |= remove_events | events_device # Set events to snapshot snapshot.events |= remove_events | events_device # Set events to snapshot
# commit will change the order of the components by what # commit will change the order of the components by what

View file

@ -8,4 +8,3 @@ class Inventory(Thing):
id = mf.String(dump_only=True) id = mf.String(dump_only=True)
name = mf.String(dump_only=True) name = mf.String(dump_only=True)
tag_provider = teal.marshmallow.URL(dump_only=True, data_key='tagProvider') tag_provider = teal.marshmallow.URL(dump_only=True, data_key='tagProvider')
tag_token = mf.UUID(dump_only=True, data_key='tagToken')

View file

@ -24,6 +24,7 @@ class User(Thing):
backref=db.backref('users', lazy=True, collection_class=set), backref=db.backref('users', lazy=True, collection_class=set),
secondary=lambda: UserInventory.__table__, secondary=lambda: UserInventory.__table__,
collection_class=set) collection_class=set)
# todo set restriction that user has, at least, one active db # todo set restriction that user has, at least, one active db
def __init__(self, email, password=None, inventories=None) -> None: def __init__(self, email, password=None, inventories=None) -> None:
@ -41,6 +42,10 @@ class User(Thing):
def __repr__(self) -> str: def __repr__(self) -> str:
return '<User {0.email}>'.format(self) return '<User {0.email}>'.format(self)
@property
def type(self) -> str:
return self.__class__.__name__
@property @property
def individual(self): def individual(self):
"""The individual associated for this database, or None.""" """The individual associated for this database, or None."""

View file

@ -24,7 +24,7 @@ requests[security]==2.19.1
requests-mock==1.5.2 requests-mock==1.5.2
SQLAlchemy==1.2.17 SQLAlchemy==1.2.17
SQLAlchemy-Utils==0.33.11 SQLAlchemy-Utils==0.33.11
teal==0.2.0a36 teal==0.2.0a38
webargs==4.0.0 webargs==4.0.0
Werkzeug==0.14.1 Werkzeug==0.14.1
sqlalchemy-citext==1.3.post0 sqlalchemy-citext==1.3.post0

View file

@ -29,7 +29,7 @@ setup(
long_description=long_description, long_description=long_description,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
install_requires=[ install_requires=[
'teal>=0.2.0a36', # teal always first 'teal>=0.2.0a38', # teal always first
'click', 'click',
'click-spinner', 'click-spinner',
'ereuse-utils[naming, test, session, cli]>=0.4b21', 'ereuse-utils[naming, test, session, cli]>=0.4b21',

View file

@ -5,16 +5,16 @@ device:
type: Desktop type: Desktop
chassis: Tower chassis: Tower
components: components:
- manufacturer: p1c1m - manufacturer: p1c1m
serialNumber: p1c1s serialNumber: p1c1s
type: Motherboard type: Motherboard
- manufacturer: p1c2m - manufacturer: p1c2m
serialNumber: p1c2s serialNumber: p1c2s
model: p1c2 model: p1c2
speed: 1.23 speed: 1.23
cores: 2 cores: 2
type: Processor type: Processor
- manufacturer: p1c3m - manufacturer: p1c3m
serialNumber: p1c3s serialNumber: p1c3s
type: GraphicCard type: GraphicCard
memory: 1.5 memory: 1.5
@ -22,3 +22,4 @@ elapsed: 25
software: Workbench software: Workbench
uuid: 76860eca-c3fd-41f6-a801-6af7bd8cf832 uuid: 76860eca-c3fd-41f6-a801-6af7bd8cf832
version: '11.0' version: '11.0'
type: Snapshot

View file

@ -5,10 +5,10 @@ device:
type: Desktop type: Desktop
chassis: Microtower chassis: Microtower
components: components:
- manufacturer: p2c1m - manufacturer: p2c1m
serialNumber: p2c1s serialNumber: p2c1s
type: Motherboard type: Motherboard
- manufacturer: p1c2m - manufacturer: p1c2m
serialNumber: p1c2s serialNumber: p1c2s
model: p1c2 model: p1c2
speed: 1.23 speed: 1.23
@ -18,3 +18,4 @@ elapsed: 25
software: Workbench software: Workbench
uuid: f2e02261-87a1-4a50-b9b7-92c0e476e5f2 uuid: f2e02261-87a1-4a50-b9b7-92c0e476e5f2
version: '11.0' version: '11.0'
type: Snapshot

View file

@ -5,13 +5,13 @@ device:
type: Desktop type: Desktop
chassis: Microtower chassis: Microtower
components: components:
- manufacturer: p1c2m - manufacturer: p1c2m
serialNumber: p1c2s serialNumber: p1c2s
model: p1c2 model: p1c2
type: Processor type: Processor
cores: 2 cores: 2
speed: 1.23 speed: 1.23
- manufacturer: p1c3m - manufacturer: p1c3m
serialNumber: p1c3s serialNumber: p1c3s
type: GraphicCard type: GraphicCard
memory: 1.5 memory: 1.5
@ -19,3 +19,4 @@ elapsed: 30
software: Workbench software: Workbench
uuid: 3be271b6-5ef4-47d8-8237-5e1133eebfc6 uuid: 3be271b6-5ef4-47d8-8237-5e1133eebfc6
version: '11.0' version: '11.0'
type: Snapshot

View file

@ -5,12 +5,12 @@ device:
type: Desktop type: Desktop
chassis: Tower chassis: Tower
components: components:
- manufacturer: p1c4m - manufacturer: p1c4m
serialNumber: p1c4s serialNumber: p1c4s
type: NetworkAdapter type: NetworkAdapter
speed: 1000 speed: 1000
wireless: False wireless: False
- manufacturer: p1c3m - manufacturer: p1c3m
serialNumber: p1c3s serialNumber: p1c3s
type: GraphicCard type: GraphicCard
memory: 1.5 memory: 1.5
@ -18,3 +18,4 @@ elapsed: 25
software: Workbench software: Workbench
uuid: fd007eb4-48e3-454a-8763-169491904c6e uuid: fd007eb4-48e3-454a-8763-169491904c6e
version: '11.0' version: '11.0'
type: Snapshot

2
tests/files/basic.csv Normal file
View file

@ -0,0 +1,2 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price,Processor,RAM (GB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number
Desktop,Microtower,,,,d1s,d1ml,d1mr,Tue Mar 5 19:54:18 2019,,p1ml,0,0,0.8,Very low,1.0,Very low,1.0,Very low,1.0,Very low,,,,,,,,,,,,,,,,,,,,,"GraphicCard 2: model gc1ml, S/N gc1s",gc1s,gc1s,gc1s,,,,,,,,,,,,,,,,,,"Processor 4: model p1ml, S/N p1s",p1s,p1s,p1s,,1.6,,,,,"RamModule 3: model rm1ml, S/N rm1s",rm1s,rm1s,rm1s,,1333,,,,,,,,,,,,,,,,,,,,
1 Type Chassis Tag 1 Tag 2 Tag 3 Serial Number Model Manufacturer Registered in Price Processor RAM (GB) Data Storage Size (MB) Rate Range Processor Rate Processor Range RAM Rate RAM Range Data Storage Rate Data Storage Range DataStorage 1 DataStorage 1 Manufacturer DataStorage 1 Model DataStorage 1 Serial Number DataStorage 2 DataStorage 2 Manufacturer DataStorage 2 Model DataStorage 2 Serial Number DataStorage 3 DataStorage 3 Manufacturer DataStorage 3 Model DataStorage 3 Serial Number DataStorage 4 DataStorage 4 Manufacturer DataStorage 4 Model DataStorage 4 Serial Number Display 1 Display 1 Manufacturer Display 1 Model Display 1 Serial Number GraphicCard 1 GraphicCard 1 Manufacturer GraphicCard 1 Model GraphicCard 1 Serial Number GraphicCard 1 Memory (MB) GraphicCard 2 GraphicCard 2 Manufacturer GraphicCard 2 Model GraphicCard 2 Serial Number Motherboard 1 Motherboard 1 Manufacturer Motherboard 1 Model Motherboard 1 Serial Number NetworkAdapter 1 NetworkAdapter 1 Manufacturer NetworkAdapter 1 Model NetworkAdapter 1 Serial Number NetworkAdapter 2 NetworkAdapter 2 Manufacturer NetworkAdapter 2 Model NetworkAdapter 2 Serial Number Processor 1 Processor 1 Manufacturer Processor 1 Model Processor 1 Serial Number Processor 1 Number of cores Processor 1 Speed (GHz) Processor 2 Processor 2 Manufacturer Processor 2 Model Processor 2 Serial Number RamModule 1 RamModule 1 Manufacturer RamModule 1 Model RamModule 1 Serial Number RamModule 1 Size (MB) RamModule 1 Speed (MHz) RamModule 2 RamModule 2 Manufacturer RamModule 2 Model RamModule 2 Serial Number RamModule 3 RamModule 3 Manufacturer RamModule 3 Model RamModule 3 Serial Number RamModule 4 RamModule 4 Manufacturer RamModule 4 Model RamModule 4 Serial Number SoundCard 1 SoundCard 1 Manufacturer SoundCard 1 Model SoundCard 1 Serial Number SoundCard 2 SoundCard 2 Manufacturer SoundCard 2 Model SoundCard 2 Serial Number
2 Desktop Microtower d1s d1ml d1mr Tue Mar 5 19:54:18 2019 p1ml 0 0 0.8 Very low 1.0 Very low 1.0 Very low 1.0 Very low GraphicCard 2: model gc1ml, S/N gc1s gc1s gc1s gc1s Processor 4: model p1ml, S/N p1s p1s p1s p1s 1.6 RamModule 3: model rm1ml, S/N rm1s rm1s rm1s rm1s 1333

View file

@ -0,0 +1,2 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price
ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Wed Oct 24 20:57:18 2018
1 Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price
2 ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Wed Oct 24 20:57:18 2018

View file

@ -0,0 +1,121 @@
{
"closed": true,
"components": [
{
"events": [],
"manufacturer": "Intel Corporation",
"model": "NM10/ICH7 Family High Definition Audio Controller",
"serialNumber": null,
"type": "SoundCard"
},
{
"events": [],
"manufacturer": "Broadcom Inc. and subsidiaries",
"model": "NetLink BCM5786 Gigabit Ethernet PCI Express",
"serialNumber": "00:1a:6b:5e:7f:10",
"speed": 1000,
"type": "NetworkAdapter",
"wireless": false
},
{
"events": [],
"format": "DIMM",
"interface": "DDR",
"manufacturer": null,
"model": null,
"serialNumber": null,
"size": 1024,
"speed": 133.0,
"type": "RamModule"
},
{
"events": [],
"format": "DIMM",
"interface": "DDR",
"manufacturer": null,
"model": null,
"serialNumber": null,
"size": 1024,
"speed": 133.0,
"type": "RamModule"
},
{
"address": 64,
"events": [
{
"elapsed": 33,
"rate": 32.9274,
"type": "BenchmarkProcessorSysbench"
},
{
"elapsed": 0,
"rate": 8771.5,
"type": "BenchmarkProcessor"
}
],
"manufacturer": "Intel Corp.",
"model": "Intel Core2 Duo CPU E4500 @ 2.20GHz",
"serialNumber": null,
"speed": 1.1,
"threads": 2,
"type": "Processor"
},
{
"events": [],
"manufacturer": "Intel Corporation",
"memory": 256.0,
"model": "82946GZ/GL Integrated Graphics Controller",
"serialNumber": null,
"type": "GraphicCard"
},
{
"events": [],
"firewire": 0,
"manufacturer": "LENOVO",
"model": "LENOVO",
"pcmcia": 0,
"serial": 1,
"serialNumber": null,
"slots": 0,
"type": "Motherboard",
"usb": 5
}
],
"device": {
"chassis": "Microtower",
"events": [
{
"appearanceRange": "D",
"biosRange": "E",
"functionalityRange": "D",
"type": "WorkbenchRate"
},
{
"elapsed": 300,
"severity": "Info",
"type": "StressTest"
},
{
"elapsed": 2,
"rate": 1.4968,
"type": "BenchmarkRamSysbench"
}
],
"manufacturer": "LENOVO",
"model": "9644W8N",
"serialNumber": "0169622",
"type": "Desktop"
},
"elapsed": 338,
"endTime": "2019-02-13T11:57:31.378330+00:00",
"expectedEvents": [
"Benchmark",
"TestDataStorage",
"StressTest",
"Install"
],
"software": "Workbench",
"type": "Snapshot",
"uuid": "d7904bd3-7d0f-4918-86b1-e21bfab738f9",
"version": "11.0b5"
}

2
tests/files/keyboard.csv Normal file
View file

@ -0,0 +1,2 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price
Keyboard,,,,,bar,foo,baz,Wed Oct 24 21:01:48 2018
1 Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price
2 Keyboard,,,,,bar,foo,baz,Wed Oct 24 21:01:48 2018

View file

@ -0,0 +1,170 @@
{
"closed": true,
"components": [
{
"events": [],
"manufacturer": "Qualcomm Atheros",
"model": "QCA9565 / AR9565 Wireless Network Adapter",
"serialNumber": "ac:e0:10:c2:e3:ac",
"type": "NetworkAdapter",
"wireless": true
},
{
"events": [],
"manufacturer": "Realtek Semiconductor Co., Ltd.",
"model": "RTL810xE PCI Express Fast Ethernet controller",
"serialNumber": "30:8d:99:25:6c:d9",
"speed": 100,
"type": "NetworkAdapter",
"wireless": false
},
{
"events": [],
"manufacturer": "Advanced Micro Devices, Inc. AMD/ATI",
"model": "Kabini HDMI/DP Audio",
"serialNumber": null,
"type": "SoundCard"
},
{
"events": [],
"manufacturer": "Chicony Electronics Co.,Ltd.",
"model": "HP Webcam",
"serialNumber": "0x0001",
"type": "SoundCard"
},
{
"events": [],
"manufacturer": "Advanced Micro Devices, Inc. AMD",
"model": "FCH Azalia Controller",
"serialNumber": null,
"type": "SoundCard"
},
{
"events": [],
"format": "SODIMM",
"interface": "DDR3",
"manufacturer": "Hynix",
"model": "HMT451S6AFR8A-PB",
"serialNumber": "11743764",
"size": 4096,
"speed": 667.0,
"type": "RamModule"
},
{
"address": 64,
"cores": 2,
"events": [
{
"elapsed": 0,
"rate": 3992.32,
"type": "BenchmarkProcessor"
},
{
"elapsed": 65,
"rate": 65.3007,
"type": "BenchmarkProcessorSysbench"
}
],
"manufacturer": "Advanced Micro Devices AMD",
"model": "AMD E1-2100 APU with Radeon HD Graphics",
"serialNumber": null,
"speed": 0.9,
"threads": 2,
"type": "Processor"
},
{
"events": [
{
"elapsed": 12,
"readSpeed": 90.0,
"type": "BenchmarkDataStorage",
"writeSpeed": 30.7
},
{
"assessment": true,
"commandTimeout": 1341,
"currentPendingSectorCount": 0,
"elapsed": 113,
"length": "Short",
"lifetime": 1782,
"offlineUncorrectable": 0,
"powerCycleCount": 806,
"reallocatedSectorCount": 224,
"reportedUncorrectableErrors": 9961472,
"severity": "Info",
"status": "Completed without error",
"type": "TestDataStorage"
},
{
"address": 32,
"elapsed": 690,
"name": "LinuxMint-19-x86-es-2018-12.fsa",
"severity": "Info",
"type": "Install"
}
],
"interface": "ATA",
"manufacturer": null,
"model": "HGST HTS545050A7",
"serialNumber": "TE85134N34LNSN",
"size": 476940,
"type": "HardDrive"
},
{
"events": [],
"manufacturer": "Advanced Micro Devices, Inc. AMD/ATI",
"memory": 256.0,
"model": "Kabini Radeon HD 8210",
"serialNumber": null,
"type": "GraphicCard"
},
{
"events": [],
"firewire": 0,
"manufacturer": "Hewlett-Packard",
"model": "21F7",
"pcmcia": 0,
"serial": 1,
"serialNumber": "PEHERF41U8P9TV",
"slots": 0,
"type": "Motherboard",
"usb": 5
}
],
"device": {
"chassis": "Netbook",
"events": [
{
"appearanceRange": "A",
"functionalityRange": "A",
"type": "WorkbenchRate"
},
{
"elapsed": 300,
"severity": "Info",
"type": "StressTest"
},
{
"elapsed": 6,
"rate": 5.8783,
"type": "BenchmarkRamSysbench"
}
],
"manufacturer": "Hewlett-Packard",
"model": "HP 255 G3 Notebook",
"serialNumber": "CND52270FW",
"type": "Laptop"
},
"elapsed": 1194,
"endTime": "2019-02-13T10:13:50.535387+00:00",
"expectedEvents": [
"Benchmark",
"TestDataStorage",
"StressTest",
"Install"
],
"software": "Workbench",
"type": "Snapshot",
"uuid": "ca564895-567e-4ac2-9a0d-2d1402528687",
"version": "11.0b5"
}

View file

@ -0,0 +1,17 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price,Processor,RAM (GB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number
Laptop,Netbook,,,,b8oaas048286,1001pxd,asustek computer inc.,Wed Mar 6 18:22:05 2019,,intel atom cpu n455 @ 1.66ghz,1024,238475,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium,,,,,,,,,,,,,,,,,,,,,"GraphicCard 5: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None",,,,256,,,,,"Motherboard 10: model 1001pxd, S/N eee0123456789",eee0123456789,eee0123456789,eee0123456789,"NetworkAdapter 2: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8",74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,"NetworkAdapter 3: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c",14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,"Processor 4: model intel atom cpu n455 @ 1.66ghz, S/N None",,,,1,1.667,,,,,"RamModule 8: model None, S/N None",,,,1024,667,,,,,,,,,,,,,"SoundCard 6: model nm10/ich7 family high definition audio controller, S/N None",,,,"SoundCard 7: model usb 2.0 uvc vga webcam, S/N 0x0001",0x0001,0x0001,0x0001
NetworkAdapter,,,,,74:2f:68:8b:fd:c8,ar9285 wireless network adapter,qualcomm atheros,Wed Mar 6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
NetworkAdapter,,,,,14:da:e9:42:f6:7c,ar8152 v2.0 fast ethernet,qualcomm atheros,Wed Mar 6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
Processor,,,,,,intel atom cpu n455 @ 1.66ghz,intel corp.,Wed Mar 6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
GraphicCard,,,,,,atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller,intel corporation,Wed Mar 6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
SoundCard,,,,,,nm10/ich7 family high definition audio controller,intel corporation,Wed Mar 6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
SoundCard,,,,,0x0001,usb 2.0 uvc vga webcam,azurewave,Wed Mar 6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
RamModule,,,,,,,,Wed Mar 6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
HardDrive,,,,,e2024242cv86hj,hts54322,hitachi,Wed Mar6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
Motherboard,,,,,eee0123456789,1001pxd,asustek computer inc.,Wed Mar 6 18:22:05 2019,,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium
Desktop,Microtower,,,,d1s,d1ml,d1mr,Wed Mar 6 18:22:06 2019,,p1ml,0,0,0.8,Very low,1.0,Very low,1.0,Very low,1.0,Very low,,,,,,,,,,,,,,,,,,,,,"GraphicCard 12: model gc1ml, S/N gc1s",gc1s,gc1s,gc1s,,,,,,,,,,,,,,,,,,"Processor 14: model p1ml, S/N p1s",p1s,p1s,p1s,,1.6,,,,,"RamModule 13: model rm1ml, S/N rm1s",rm1s,rm1s,rm1s,,1333,,,,,,,,,,,,,,,,,,,,
GraphicCard,,,,,gc1s,gc1ml,gc1mr,Wed Mar 6 18:22:06 2019,,0.8,Very low,1.0,Very low,1.0,Very low,1.0,Very low
RamModule,,,,,rm1s,rm1ml,rm1mr,Wed Mar 6 18:22:06 2019,,0.8,Very low,1.0,Very low,1.0,Very low,1.0,Very low
Processor,,,,,p1s,p1ml,p1mr,Wed Mar 6 18:22:06 2019,,0.8,Very low,1.0,Very low,1.0,Very low,1.0,Very low
Keyboard,,,,,bar,foo,baz,Wed Mar 6 18:22:06 2019,
ComputerMonitor,,,,,cn0fp446728728541c8s,1707fpf,dell,Wed Mar 6 18:22:06 2019,
Can't render this file because it has a wrong number of fields in line 3.

View file

@ -0,0 +1,2 @@
Type,Chassis,Tag 1,Tag 2,Tag 3,Serial Number,Model,Manufacturer,Registered in,Price,Processor,RAM (GB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range,DataStorage 1,DataStorage 1 Manufacturer,DataStorage 1 Model,DataStorage 1 Serial Number,DataStorage 2,DataStorage 2 Manufacturer,DataStorage 2 Model,DataStorage 2 Serial Number,DataStorage 3,DataStorage 3 Manufacturer,DataStorage 3 Model,DataStorage 3 Serial Number,DataStorage 4,DataStorage 4 Manufacturer,DataStorage 4 Model,DataStorage 4 Serial Number,Display 1,Display 1 Manufacturer,Display 1 Model,Display 1 Serial Number,GraphicCard 1,GraphicCard 1 Manufacturer,GraphicCard 1 Model,GraphicCard 1 Serial Number,GraphicCard 1 Memory (MB),GraphicCard 2,GraphicCard 2 Manufacturer,GraphicCard 2 Model,GraphicCard 2 Serial Number,Motherboard 1,Motherboard 1 Manufacturer,Motherboard 1 Model,Motherboard 1 Serial Number,NetworkAdapter 1,NetworkAdapter 1 Manufacturer,NetworkAdapter 1 Model,NetworkAdapter 1 Serial Number,NetworkAdapter 2,NetworkAdapter 2 Manufacturer,NetworkAdapter 2 Model,NetworkAdapter 2 Serial Number,Processor 1,Processor 1 Manufacturer,Processor 1 Model,Processor 1 Serial Number,Processor 1 Number of cores,Processor 1 Speed (GHz),Processor 2,Processor 2 Manufacturer,Processor 2 Model,Processor 2 Serial Number,RamModule 1,RamModule 1 Manufacturer,RamModule 1 Model,RamModule 1 Serial Number,RamModule 1 Size (MB),RamModule 1 Speed (MHz),RamModule 2,RamModule 2 Manufacturer,RamModule 2 Model,RamModule 2 Serial Number,RamModule 3,RamModule 3 Manufacturer,RamModule 3 Model,RamModule 3 Serial Number,RamModule 4,RamModule 4 Manufacturer,RamModule 4 Model,RamModule 4 Serial Number,SoundCard 1,SoundCard 1 Manufacturer,SoundCard 1 Model,SoundCard 1 Serial Number,SoundCard 2,SoundCard 2 Manufacturer,SoundCard 2 Model,SoundCard 2 Serial Number
Laptop,Netbook,,,,b8oaas048286,1001pxd,asustek computer inc.,Tue Mar 5 19:56:08 2019,,intel atom cpu n455 @ 1.66ghz,1024,238475,1.73,Very low,1.0,Very low,1.53,Very low,3.76,Medium,,,,,,,,,,,,,,,,,,,,,"GraphicCard 5: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None",,,,256,,,,,"Motherboard 10: model 1001pxd, S/N eee0123456789",eee0123456789,eee0123456789,eee0123456789,"NetworkAdapter 2: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8",74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,74:2f:68:8b:fd:c8,"NetworkAdapter 3: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c",14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,14:da:e9:42:f6:7c,"Processor 4: model intel atom cpu n455 @ 1.66ghz, S/N None",,,,1,1.667,,,,,"RamModule 8: model None, S/N None",,,,1024,667,,,,,,,,,,,,,"SoundCard 6: model nm10/ich7 family high definition audio controller, S/N None",,,,"SoundCard 7: model usb 2.0 uvc vga webcam, S/N 0x0001",0x0001,0x0001,0x0001
1 Type Chassis Tag 1 Tag 2 Tag 3 Serial Number Model Manufacturer Registered in Price Processor RAM (GB) Data Storage Size (MB) Rate Range Processor Rate Processor Range RAM Rate RAM Range Data Storage Rate Data Storage Range DataStorage 1 DataStorage 1 Manufacturer DataStorage 1 Model DataStorage 1 Serial Number DataStorage 2 DataStorage 2 Manufacturer DataStorage 2 Model DataStorage 2 Serial Number DataStorage 3 DataStorage 3 Manufacturer DataStorage 3 Model DataStorage 3 Serial Number DataStorage 4 DataStorage 4 Manufacturer DataStorage 4 Model DataStorage 4 Serial Number Display 1 Display 1 Manufacturer Display 1 Model Display 1 Serial Number GraphicCard 1 GraphicCard 1 Manufacturer GraphicCard 1 Model GraphicCard 1 Serial Number GraphicCard 1 Memory (MB) GraphicCard 2 GraphicCard 2 Manufacturer GraphicCard 2 Model GraphicCard 2 Serial Number Motherboard 1 Motherboard 1 Manufacturer Motherboard 1 Model Motherboard 1 Serial Number NetworkAdapter 1 NetworkAdapter 1 Manufacturer NetworkAdapter 1 Model NetworkAdapter 1 Serial Number NetworkAdapter 2 NetworkAdapter 2 Manufacturer NetworkAdapter 2 Model NetworkAdapter 2 Serial Number Processor 1 Processor 1 Manufacturer Processor 1 Model Processor 1 Serial Number Processor 1 Number of cores Processor 1 Speed (GHz) Processor 2 Processor 2 Manufacturer Processor 2 Model Processor 2 Serial Number RamModule 1 RamModule 1 Manufacturer RamModule 1 Model RamModule 1 Serial Number RamModule 1 Size (MB) RamModule 1 Speed (MHz) RamModule 2 RamModule 2 Manufacturer RamModule 2 Model RamModule 2 Serial Number RamModule 3 RamModule 3 Manufacturer RamModule 3 Model RamModule 3 Serial Number RamModule 4 RamModule 4 Manufacturer RamModule 4 Model RamModule 4 Serial Number SoundCard 1 SoundCard 1 Manufacturer SoundCard 1 Model SoundCard 1 Serial Number SoundCard 2 SoundCard 2 Manufacturer SoundCard 2 Model SoundCard 2 Serial Number
2 Laptop Netbook b8oaas048286 1001pxd asustek computer inc. Tue Mar 5 19:56:08 2019 intel atom cpu n455 @ 1.66ghz 1024 238475 1.73 Very low 1.0 Very low 1.53 Very low 3.76 Medium GraphicCard 5: model atom processor d4xx/d5xx/n4xx/n5xx integrated graphics controller, S/N None 256 Motherboard 10: model 1001pxd, S/N eee0123456789 eee0123456789 eee0123456789 eee0123456789 NetworkAdapter 2: model ar9285 wireless network adapter, S/N 74:2f:68:8b:fd:c8 74:2f:68:8b:fd:c8 74:2f:68:8b:fd:c8 74:2f:68:8b:fd:c8 NetworkAdapter 3: model ar8152 v2.0 fast ethernet, S/N 14:da:e9:42:f6:7c 14:da:e9:42:f6:7c 14:da:e9:42:f6:7c 14:da:e9:42:f6:7c Processor 4: model intel atom cpu n455 @ 1.66ghz, S/N None 1 1.667 RamModule 8: model None, S/N None 1024 667 SoundCard 6: model nm10/ich7 family high definition audio controller, S/N None SoundCard 7: model usb 2.0 uvc vga webcam, S/N 0x0001 0x0001 0x0001 0x0001

View file

@ -21,7 +21,6 @@ def test_api_docs(client: Client):
'/users/', '/users/',
'/devices/', '/devices/',
'/tags/', '/tags/',
'/snapshots/',
'/users/login/', '/users/login/',
'/events/', '/events/',
'/lots/', '/lots/',
@ -29,6 +28,7 @@ def test_api_docs(client: Client):
'/lots/{id}/children', '/lots/{id}/children',
'/lots/{id}/devices', '/lots/{id}/devices',
'/documents/erasures/', '/documents/erasures/',
'/documents/devices/',
'/documents/static/{filename}', '/documents/static/{filename}',
'/tags/{tag_id}/device/{device_id}', '/tags/{tag_id}/device/{device_id}',
'/devices/static/{filename}' '/devices/static/{filename}'

30
tests/test_db.py Normal file
View file

@ -0,0 +1,30 @@
import datetime
from uuid import UUID
from teal.db import UniqueViolation
def test_unique_violation():
class IntegrityErrorMock:
def __init__(self) -> None:
self.params = {
'uuid': UUID('f5efd26e-8754-46bc-87bf-fbccc39d60d9'),
'version': '11.0',
'software': 'Workbench', 'elapsed': datetime.timedelta(0, 4),
'expected_events': None,
'id': UUID('dbdef3d8-2cac-48cb-adb8-419bc3e59687')
}
def __str__(self):
return """(psycopg2.IntegrityError) duplicate key value violates unique constraint "snapshot_uuid_key"
DETAIL: Key (uuid)=(f5efd26e-8754-46bc-87bf-fbccc39d60d9) already exists.
[SQL: 'INSERT INTO snapshot (uuid, version, software, elapsed, expected_events, id)
VALUES (%(uuid)s, %(version)s, %(software)s, %(elapsed)s, CAST(%(expected_events)s
AS snapshotexpectedevents[]), %(id)s)'] [parameters: {'uuid': UUID('f5efd26e-8754-46bc-87bf-fbccc39d60d9'),
'version': '11.0', 'software': 'Workbench', 'elapsed': datetime.timedelta(0, 4), 'expected_events': None,
'id': UUID('dbdef3d8-2cac-48cb-adb8-419bc3e59687')}] (Background on this error at: http://sqlalche.me/e/gkpj)"""
u = UniqueViolation(IntegrityErrorMock())
assert u.constraint == 'snapshot_uuid_key'
assert u.field_name == 'uuid'
assert u.field_value == UUID('f5efd26e-8754-46bc-87bf-fbccc39d60d9')

169
tests/test_reports.py Normal file
View file

@ -0,0 +1,169 @@
import csv
from datetime import datetime
from io import StringIO
from pathlib import Path
from ereuse_devicehub.client import UserClient
from ereuse_devicehub.resources.documents import documents
from ereuse_devicehub.resources.event.models import Snapshot
from tests.conftest import file
def test_export_basic_snapshot(user: UserClient):
"""
Test export device information in a csv file
"""
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('basic.csv').open() as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
assert isinstance(datetime.strptime(export_csv[1][8], '%c'), datetime), \
'Register in field is not a datetime'
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8] + fixture_csv[1][9:]
export_csv[1] = export_csv[1][:8] + export_csv[1][9:]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
def test_export_full_snapshot(user: UserClient):
"""
Test a export device with all information and a lot of components
"""
snapshot, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('real-eee-1001pxd.csv').open() \
as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
assert isinstance(datetime.strptime(export_csv[1][8], '%c'), datetime), \
'Register in field is not a datetime'
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8] + fixture_csv[1][9:]
export_csv[1] = export_csv[1][:8] + export_csv[1][9:]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
def test_export_empty(user: UserClient):
"""
Test to check works correctly exporting csv without any information (no snapshot)
"""
csv_str, _ = user.get(res=documents.DocumentDef.t,
accept='text/csv',
item='devices/')
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
assert len(export_csv) == 0, 'Csv is not empty'
def test_export_computer_monitor(user: UserClient):
"""
Test a export device type computer monitor
"""
snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['ComputerMonitor']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('computer-monitor.csv').open() \
as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8]
export_csv[1] = export_csv[1][:8]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
def test_export_keyboard(user: UserClient):
"""
Test a export device type keyboard
"""
snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv',
query=[('filter', {'type': ['Keyboard']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('keyboard.csv').open() as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:8]
export_csv[1] = export_csv[1][:8]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
def test_export_multiple_devices(user: UserClient):
"""
Test a export multiple devices (Computers and other types) with different information
"""
# Post all devices snapshots
snapshot_pc, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
snapshot_empty, _ = user.post(file('basic.snapshot'), res=Snapshot)
snapshot_keyboard, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
snapshot_monitor, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
# need query param??
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
accept='text/csv')
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('multiples_devices.csv').open() \
as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
max_range = max(len(export_csv), len(fixture_csv)) - 1
# check if all devices information is correct
for i in range(1, max_range):
if isinstance(datetime.strptime(export_csv[i][8], '%c'), datetime):
export_csv[i] = export_csv[i][:8] + export_csv[i][9:]
fixture_csv[i] = fixture_csv[i][:8] + fixture_csv[i][9:]
assert fixture_csv[i] == export_csv[i], 'Some fields are not equal'

View file

@ -453,3 +453,14 @@ def test_snapshot_keyboard(user: UserClient):
snapshot = snapshot_and_check(user, s, event_types=('ManualRate',)) snapshot = snapshot_and_check(user, s, event_types=('ManualRate',))
keyboard = snapshot['device'] keyboard = snapshot['device']
assert keyboard['layout'] == 'ES' assert keyboard['layout'] == 'ES'
def test_pc_rating_rate_none(user: UserClient):
"""Tests a Snapshot with EraseSectors."""
s = file('desktop-9644w8n-lenovo-0169622.snapshot')
snapshot, _ = user.post(res=Snapshot, data=s)
def test_pc_2(user: UserClient):
s = file('laptop-hp_255_g3_notebook-hewlett-packard-cnd52270fw.snapshot')
snapshot, _ = user.post(res=Snapshot, data=s)