Merge branch 'master' into rate
# Conflicts: # ereuse_devicehub/resources/event/models.py
This commit is contained in:
commit
8f4231dc1c
|
@ -16,7 +16,7 @@ Our main objectives are:
|
|||
- To highly integrate with existing IT Asset Management Systems.
|
||||
- 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).
|
||||
|
||||
## Installing
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
Actions and states
|
||||
##################
|
||||
|
||||
Actions
|
||||
*******
|
||||
|
||||
#######
|
||||
Actions are events performed to devices, changing their **state**.
|
||||
Actions can have attributes defining
|
||||
**where** it happened, **who** performed them, **when**, etc.
|
||||
Actions are stored in a log for each device. An exemplifying action
|
||||
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
|
||||
<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
|
||||
*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,
|
||||
and ``Sell`` the **political** dimension of a device. A device
|
||||
can be in several states at the same time, one per dimension; ie. a
|
||||
device can be ``repaired`` (physical) and ``reserved`` (political),
|
||||
but not ``repaired`` and ``disposed`` at the same time:
|
||||
|
||||
|
||||
- Physical actions: The following actions describe and react on the
|
||||
Physical condition of the devices.
|
||||
|
||||
|
@ -37,7 +36,7 @@ but not ``repaired`` and ``disposed`` at the same time:
|
|||
- DisposeWaste, Recover
|
||||
|
||||
- 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**.
|
||||
|
||||
- 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
|
||||
``Event`` to call them ``Action``):
|
||||
|
||||
Schema
|
||||
******
|
||||
|
||||
.. dhlist::
|
||||
: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
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
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
|
||||
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.
|
||||
- **last**: The number of the last page, if any.
|
||||
|
||||
.. dhlist::
|
||||
:module: ereuse_devicehub.resources.device.schemas
|
||||
|
||||
|
|
|
@ -14,19 +14,36 @@ reusing devices, created under the project
|
|||
|
||||
Our main objectives are:
|
||||
|
||||
- To offer a common IT Asset Management for donors, receivers and IT
|
||||
professionals so they can manage devices and exchange them.
|
||||
This is, reusing –and ultimately recycling.
|
||||
- To offer a common IT Asset Management for distributors, refurbishers,
|
||||
receivers and other IT professionals so they can manage devices and exchange them.
|
||||
This is, reusing —and ultimately recycling.
|
||||
- To automatically recollect, analyse, process and share
|
||||
(controlling privacy) metadata about devices with other tools of the
|
||||
eReuse ecosystem to guarantee traceability, and to provide inputs for
|
||||
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
|
||||
`Flask <http://flask.pocoo.org>`_.
|
||||
The main entity of a Devicehub are :ref:`devices:Devices`, which is any object that
|
||||
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
|
||||
front–end that consumes this API.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
@ -34,6 +51,7 @@ Devicehub is built with `Teal <https://github.com/bustawin/teal>`_ and
|
|||
api
|
||||
devices
|
||||
actions
|
||||
states
|
||||
tags
|
||||
lots
|
||||
|
||||
|
|
48
docs/states.rst
Normal file
48
docs/states.rst
Normal 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
|
||||
sub–types. 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.
|
|
@ -36,6 +36,7 @@ class SQLAlchemy(SchemaSQLAlchemy):
|
|||
# manually import them all the time
|
||||
UUID = postgresql.UUID
|
||||
CIText = citext.CIText
|
||||
PSQL_INT_MAX = 2147483648
|
||||
|
||||
def drop_all(self, bind='__all__', app=None, common_schema=True):
|
||||
"""A faster nuke-like option to drop everything."""
|
||||
|
|
|
@ -6,6 +6,10 @@ from ereuse_devicehub.resources.event import models as e
|
|||
|
||||
|
||||
class State(Enum):
|
||||
"""A mutable property of a device result of applying an
|
||||
:ref:`actions:Action` to it.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def events(cls):
|
||||
"""Events participating in this state."""
|
||||
|
|
119
ereuse_devicehub/resources/documents/device_row.py
Normal file
119
ereuse_devicehub/resources/documents/device_row.py
Normal 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
|
|
@ -1,5 +1,8 @@
|
|||
import csv
|
||||
import datetime
|
||||
import enum
|
||||
import uuid
|
||||
from io import StringIO
|
||||
from typing import Callable, Iterable, Tuple
|
||||
|
||||
import boltons
|
||||
|
@ -7,11 +10,14 @@ import flask
|
|||
import flask_weasyprint
|
||||
import teal.marshmallow
|
||||
from boltons import urlutils
|
||||
from flask import make_response
|
||||
from teal.cache import cache
|
||||
from teal.resource import Resource
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.device import models as devs
|
||||
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
|
||||
|
||||
|
||||
|
@ -97,6 +103,33 @@ class DocumentView(DeviceView):
|
|||
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):
|
||||
__type__ = 'Document'
|
||||
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/<{}:{}>'.format(self.ID_CONVERTER.value, self.ID_NAME),
|
||||
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)
|
||||
|
|
|
@ -4,7 +4,7 @@ from teal.resource import Converters, Resource
|
|||
|
||||
from ereuse_devicehub.resources.device.sync import Sync
|
||||
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):
|
||||
|
@ -90,13 +90,14 @@ class InstallDef(EventDef):
|
|||
|
||||
|
||||
class SnapshotDef(EventDef):
|
||||
VIEW = SnapshotView
|
||||
VIEW = None
|
||||
SCHEMA = schemas.Snapshot
|
||||
|
||||
def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
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,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
self.sync = Sync()
|
||||
|
|
|
@ -546,7 +546,6 @@ class SnapshotRequest(db.Model):
|
|||
id = Column(UUID(as_uuid=True), ForeignKey(Snapshot.id), primary_key=True)
|
||||
request = Column(JSON, nullable=False)
|
||||
snapshot = relationship(Snapshot,
|
||||
|
||||
backref=backref('request',
|
||||
lazy=True,
|
||||
uselist=False,
|
||||
|
@ -686,6 +685,15 @@ class TestDataStorage(Test):
|
|||
t += self.description
|
||||
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):
|
||||
"""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.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.comment = """The algorithm used to produce this rating."""
|
||||
version = Column(StrictVersionType)
|
||||
|
@ -817,8 +830,7 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
|
||||
@property
|
||||
def rating_range(self) -> RatingRange:
|
||||
if self.rating:
|
||||
return RatingRange.from_score(self.rating)
|
||||
return RatingRange.from_score(self.rating) if self.rating else None
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
|
@ -1224,7 +1236,7 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
@classmethod
|
||||
def to_price(cls, value: Union[Decimal, float], rounding=ROUND) -> Decimal:
|
||||
"""Returns a Decimal value with the correct scale for Price.price."""
|
||||
if isinstance(value, float):
|
||||
if isinstance(value, (float, int)):
|
||||
value = Decimal(value)
|
||||
# equation from marshmallow.fields.Decimal
|
||||
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)
|
||||
|
||||
def __init__(self, rating: AggregateRate, **kwargs) -> None:
|
||||
if rating.rating_range == RatingRange.VERY_LOW:
|
||||
raise ValueError('Cannot compute price for Range.VERY_LOW')
|
||||
if not rating.rating_range or rating.rating_range == RatingRange.VERY_LOW:
|
||||
raise InvalidRangeForPrice()
|
||||
# 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)
|
||||
super().__init__(rating=rating,
|
||||
|
@ -1643,3 +1655,7 @@ def update_parent(target: Union[EraseBasic, Test, Install], device: Device, _, _
|
|||
target.parent = None
|
||||
if isinstance(device, Component):
|
||||
target.parent = device.parent
|
||||
|
||||
|
||||
class InvalidRangeForPrice(ValueError):
|
||||
pass
|
||||
|
|
|
@ -4,8 +4,8 @@ from typing import Set, Union
|
|||
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.enums import RatingSoftware
|
||||
from ereuse_devicehub.resources.event.models import AggregateRate, EreusePrice, Rate, \
|
||||
WorkbenchRate
|
||||
from ereuse_devicehub.resources.event.models import AggregateRate, EreusePrice, \
|
||||
InvalidRangeForPrice, Rate, WorkbenchRate
|
||||
from ereuse_devicehub.resources.event.rate.workbench import v1_0
|
||||
|
||||
RATE_TYPES = {
|
||||
|
@ -72,7 +72,6 @@ def main(rating_model: WorkbenchRate,
|
|||
if soft == software and vers == version:
|
||||
aggregation = AggregateRate.from_workbench_rate(rating)
|
||||
events.add(aggregation)
|
||||
with suppress(ValueError):
|
||||
# We will have exception if range == VERY_LOW
|
||||
with suppress(InvalidRangeForPrice): # We will have exception if range == VERY_LOW
|
||||
events.add(EreusePrice(aggregation))
|
||||
return events
|
||||
|
|
|
@ -12,14 +12,21 @@ from ereuse_devicehub.resources.device.models import Component, Computer
|
|||
from ereuse_devicehub.resources.enums import SnapshotSoftware
|
||||
from ereuse_devicehub.resources.event.models import Event, Snapshot, WorkbenchRate
|
||||
|
||||
SUPPORTED_WORKBENCH = StrictVersion('11.0')
|
||||
|
||||
|
||||
class EventView(View):
|
||||
def post(self):
|
||||
"""Posts an event."""
|
||||
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.')
|
||||
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']]()
|
||||
event = Model(**e)
|
||||
db.session.add(event)
|
||||
|
@ -34,25 +41,20 @@ class EventView(View):
|
|||
event = Event.query.filter_by(id=id).one()
|
||||
return self.schema.jsonify(event)
|
||||
|
||||
|
||||
SUPPORTED_WORKBENCH = StrictVersion('11.0')
|
||||
|
||||
|
||||
class SnapshotView(View):
|
||||
def post(self):
|
||||
def snapshot(self, snapshot_json: dict, resource_def):
|
||||
"""
|
||||
Performs a Snapshot.
|
||||
|
||||
See `Snapshot` section in docs for more info.
|
||||
"""
|
||||
s = request.get_json()
|
||||
# Note that if we set the device / components into the snapshot
|
||||
# model object, when we flush them to the db we will flush
|
||||
# snapshot, and we want to wait to flush snapshot at the end
|
||||
device = s.pop('device') # type: Computer
|
||||
components = s.pop('components') \
|
||||
if s['software'] == SnapshotSoftware.Workbench else None # type: List[Component]
|
||||
snapshot = Snapshot(**s)
|
||||
device = snapshot_json.pop('device') # type: Computer
|
||||
components = None
|
||||
if snapshot_json['software'] == SnapshotSoftware.Workbench:
|
||||
components = snapshot_json.pop('components') # type: List[Component]
|
||||
snapshot = Snapshot(**snapshot_json)
|
||||
|
||||
# Remove new events from devices so they don't interfere with sync
|
||||
events_device = set(e for e in device.events_one)
|
||||
|
@ -62,10 +64,9 @@ class SnapshotView(View):
|
|||
for component in components:
|
||||
component.events_one.clear()
|
||||
|
||||
# noinspection PyArgumentList
|
||||
assert not device.events_one
|
||||
assert all(not c.events_one for c in components) 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.events |= remove_events | events_device # Set events to snapshot
|
||||
# commit will change the order of the components by what
|
||||
|
|
|
@ -8,4 +8,3 @@ class Inventory(Thing):
|
|||
id = mf.String(dump_only=True)
|
||||
name = mf.String(dump_only=True)
|
||||
tag_provider = teal.marshmallow.URL(dump_only=True, data_key='tagProvider')
|
||||
tag_token = mf.UUID(dump_only=True, data_key='tagToken')
|
||||
|
|
|
@ -24,6 +24,7 @@ class User(Thing):
|
|||
backref=db.backref('users', lazy=True, collection_class=set),
|
||||
secondary=lambda: UserInventory.__table__,
|
||||
collection_class=set)
|
||||
|
||||
# todo set restriction that user has, at least, one active db
|
||||
|
||||
def __init__(self, email, password=None, inventories=None) -> None:
|
||||
|
@ -41,6 +42,10 @@ class User(Thing):
|
|||
def __repr__(self) -> str:
|
||||
return '<User {0.email}>'.format(self)
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
||||
@property
|
||||
def individual(self):
|
||||
"""The individual associated for this database, or None."""
|
||||
|
|
|
@ -24,7 +24,7 @@ requests[security]==2.19.1
|
|||
requests-mock==1.5.2
|
||||
SQLAlchemy==1.2.17
|
||||
SQLAlchemy-Utils==0.33.11
|
||||
teal==0.2.0a36
|
||||
teal==0.2.0a38
|
||||
webargs==4.0.0
|
||||
Werkzeug==0.14.1
|
||||
sqlalchemy-citext==1.3.post0
|
||||
|
|
2
setup.py
2
setup.py
|
@ -29,7 +29,7 @@ setup(
|
|||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
install_requires=[
|
||||
'teal>=0.2.0a36', # teal always first
|
||||
'teal>=0.2.0a38', # teal always first
|
||||
'click',
|
||||
'click-spinner',
|
||||
'ereuse-utils[naming, test, session, cli]>=0.4b21',
|
||||
|
|
|
@ -5,16 +5,16 @@ device:
|
|||
type: Desktop
|
||||
chassis: Tower
|
||||
components:
|
||||
- manufacturer: p1c1m
|
||||
- manufacturer: p1c1m
|
||||
serialNumber: p1c1s
|
||||
type: Motherboard
|
||||
- manufacturer: p1c2m
|
||||
- manufacturer: p1c2m
|
||||
serialNumber: p1c2s
|
||||
model: p1c2
|
||||
speed: 1.23
|
||||
cores: 2
|
||||
type: Processor
|
||||
- manufacturer: p1c3m
|
||||
- manufacturer: p1c3m
|
||||
serialNumber: p1c3s
|
||||
type: GraphicCard
|
||||
memory: 1.5
|
||||
|
@ -22,3 +22,4 @@ elapsed: 25
|
|||
software: Workbench
|
||||
uuid: 76860eca-c3fd-41f6-a801-6af7bd8cf832
|
||||
version: '11.0'
|
||||
type: Snapshot
|
||||
|
|
|
@ -5,10 +5,10 @@ device:
|
|||
type: Desktop
|
||||
chassis: Microtower
|
||||
components:
|
||||
- manufacturer: p2c1m
|
||||
- manufacturer: p2c1m
|
||||
serialNumber: p2c1s
|
||||
type: Motherboard
|
||||
- manufacturer: p1c2m
|
||||
- manufacturer: p1c2m
|
||||
serialNumber: p1c2s
|
||||
model: p1c2
|
||||
speed: 1.23
|
||||
|
@ -18,3 +18,4 @@ elapsed: 25
|
|||
software: Workbench
|
||||
uuid: f2e02261-87a1-4a50-b9b7-92c0e476e5f2
|
||||
version: '11.0'
|
||||
type: Snapshot
|
||||
|
|
|
@ -5,13 +5,13 @@ device:
|
|||
type: Desktop
|
||||
chassis: Microtower
|
||||
components:
|
||||
- manufacturer: p1c2m
|
||||
- manufacturer: p1c2m
|
||||
serialNumber: p1c2s
|
||||
model: p1c2
|
||||
type: Processor
|
||||
cores: 2
|
||||
speed: 1.23
|
||||
- manufacturer: p1c3m
|
||||
- manufacturer: p1c3m
|
||||
serialNumber: p1c3s
|
||||
type: GraphicCard
|
||||
memory: 1.5
|
||||
|
@ -19,3 +19,4 @@ elapsed: 30
|
|||
software: Workbench
|
||||
uuid: 3be271b6-5ef4-47d8-8237-5e1133eebfc6
|
||||
version: '11.0'
|
||||
type: Snapshot
|
||||
|
|
|
@ -5,12 +5,12 @@ device:
|
|||
type: Desktop
|
||||
chassis: Tower
|
||||
components:
|
||||
- manufacturer: p1c4m
|
||||
- manufacturer: p1c4m
|
||||
serialNumber: p1c4s
|
||||
type: NetworkAdapter
|
||||
speed: 1000
|
||||
wireless: False
|
||||
- manufacturer: p1c3m
|
||||
- manufacturer: p1c3m
|
||||
serialNumber: p1c3s
|
||||
type: GraphicCard
|
||||
memory: 1.5
|
||||
|
@ -18,3 +18,4 @@ elapsed: 25
|
|||
software: Workbench
|
||||
uuid: fd007eb4-48e3-454a-8763-169491904c6e
|
||||
version: '11.0'
|
||||
type: Snapshot
|
||||
|
|
2
tests/files/basic.csv
Normal file
2
tests/files/basic.csv
Normal 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,,,,,,,,,,,,,,,,,,,,
|
|
2
tests/files/computer-monitor.csv
Normal file
2
tests/files/computer-monitor.csv
Normal 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
|
|
121
tests/files/desktop-9644w8n-lenovo-0169622.snapshot.yaml
Normal file
121
tests/files/desktop-9644w8n-lenovo-0169622.snapshot.yaml
Normal 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
2
tests/files/keyboard.csv
Normal 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
|
|
|
@ -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"
|
||||
}
|
17
tests/files/multiples_devices.csv
Normal file
17
tests/files/multiples_devices.csv
Normal 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.
|
2
tests/files/real-eee-1001pxd.csv
Normal file
2
tests/files/real-eee-1001pxd.csv
Normal 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
|
|
|
@ -21,7 +21,6 @@ def test_api_docs(client: Client):
|
|||
'/users/',
|
||||
'/devices/',
|
||||
'/tags/',
|
||||
'/snapshots/',
|
||||
'/users/login/',
|
||||
'/events/',
|
||||
'/lots/',
|
||||
|
@ -29,6 +28,7 @@ def test_api_docs(client: Client):
|
|||
'/lots/{id}/children',
|
||||
'/lots/{id}/devices',
|
||||
'/documents/erasures/',
|
||||
'/documents/devices/',
|
||||
'/documents/static/{filename}',
|
||||
'/tags/{tag_id}/device/{device_id}',
|
||||
'/devices/static/{filename}'
|
||||
|
|
30
tests/test_db.py
Normal file
30
tests/test_db.py
Normal 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
169
tests/test_reports.py
Normal 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'
|
|
@ -453,3 +453,14 @@ def test_snapshot_keyboard(user: UserClient):
|
|||
snapshot = snapshot_and_check(user, s, event_types=('ManualRate',))
|
||||
keyboard = snapshot['device']
|
||||
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)
|
||||
|
|
Reference in a new issue