Add sphinx extension dhclass; generate better API docs; remove cache for lots
This commit is contained in:
parent
eabf6aad54
commit
198a89a0b1
271
docs/actions.rst
271
docs/actions.rst
|
@ -1,6 +1,9 @@
|
|||
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.
|
||||
|
@ -8,13 +11,6 @@ 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.
|
||||
|
||||
Actions and 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.
|
||||
|
||||
Devicehub actions inherit from `schema actions
|
||||
<http://schema.org/Action>`_, are written in Pascal case and using
|
||||
a verb in infinitive. Some verbs represent the willingness or
|
||||
|
@ -23,240 +19,49 @@ is going to be / must be repaired, whereas ``Repair`` states
|
|||
that the reparation happened. The former actions have the preposition
|
||||
*To* prefixing the verb.
|
||||
|
||||
In the following section we define the actions and states.
|
||||
To see how to perform actions to the Devicehub API head
|
||||
to the `Swagger docs
|
||||
<https://app.swaggerhub.com/apis/ereuse/devicehub/0.2>`_.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
actions
|
||||
|
||||
.. uml:: actions.puml
|
||||
Actions and 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
|
||||
:class:`ereuse_devicehub.resources.device.states.Physical` condition
|
||||
of the devices.
|
||||
- Physical actions: The following actions describe and react on the
|
||||
Physical condition of the devices.
|
||||
|
||||
ToPrepare and Prepare
|
||||
==================
|
||||
Prepare
|
||||
-------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Prepare
|
||||
ToPrepare
|
||||
---------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.ToPrepare
|
||||
- ToPrepare and prepare.
|
||||
- ToRepair, Repair
|
||||
- ReadyToUse
|
||||
- Live
|
||||
- DisposeWaste, Recover
|
||||
|
||||
ToRepair, Repair
|
||||
================
|
||||
Repair
|
||||
------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Repair
|
||||
ToRepair
|
||||
--------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.ToRepair
|
||||
- Association actions: Actions that change the associations users have with devices;
|
||||
ie. the **owners**, **usufructuarees**, **reservees**,
|
||||
and **physical possessors**.
|
||||
|
||||
ReadyToUse
|
||||
==========
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.ReadyToUse
|
||||
- Trade
|
||||
- Transfer
|
||||
- Organize
|
||||
|
||||
Live
|
||||
====
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Live
|
||||
- Internal state actions: Actions providing metadata about devices that don't usually change
|
||||
their state.
|
||||
|
||||
DisposeWaste, Recover
|
||||
=====================
|
||||
``RecyclingCenter`` users have two extra special events:
|
||||
- ``DisposeWaste``: The device has been disposed in an unspecified
|
||||
manner.
|
||||
- ``Recover``: The device has been scrapped and its materials have
|
||||
been recovered under a new product.
|
||||
|
||||
See `ToDisposeProduct, DisposeProduct`_.
|
||||
|
||||
.. todo:: Events not developed yet.
|
||||
|
||||
Association actions
|
||||
*******************
|
||||
Actions that change the associations users have with devices;
|
||||
ie. the **owners**, **usufructuarees**, **reservees**,
|
||||
and **physical possessors**.
|
||||
|
||||
There are three sub-dimensions: **trade**, **transfer**,
|
||||
and **organize** actions.
|
||||
|
||||
.. uml:: association-events.puml
|
||||
|
||||
Trade
|
||||
=====
|
||||
|
||||
.. todo Not fully developed.
|
||||
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Trade
|
||||
|
||||
Sell
|
||||
----
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Sell
|
||||
|
||||
Donate
|
||||
------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Donate
|
||||
|
||||
Rent
|
||||
----
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Rent
|
||||
|
||||
CancelTrade
|
||||
-----------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.CancelTrade
|
||||
|
||||
ToDisposeProduct, DisposeProduct
|
||||
--------------------------------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.DisposeProduct
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.ToDisposeProduct
|
||||
|
||||
Transfer actions
|
||||
================
|
||||
The act of transferring/moving devices from one place to another.
|
||||
|
||||
Receive
|
||||
-------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Receive
|
||||
.. autoclass:: ereuse_devicehub.resources.enums.ReceiverRole
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoattribute:: ereuse_devicehub.resources.device.models.Device.physical_possessor
|
||||
|
||||
Organize actions
|
||||
================
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Organize
|
||||
|
||||
Reserve, CancelReservation
|
||||
-------------------------
|
||||
Not fully developed.
|
||||
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Reserve
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.CancelReservation
|
||||
|
||||
Assign, Accept, Reject
|
||||
----------------------
|
||||
Not developed.
|
||||
|
||||
``Assign`` allocates devices to an user. The purpose or meaning
|
||||
of the association is defined by the users.
|
||||
|
||||
``Accept`` and ``Reject`` allow users to accept and reject the
|
||||
assignments.
|
||||
|
||||
.. todo:: shall we add ``Deassign`` or make ``Assign``
|
||||
always define all active users?
|
||||
Assign won't be developed until further notice.
|
||||
|
||||
Internal state actions
|
||||
**********************
|
||||
Actions providing metadata about devices that don't usually change
|
||||
their state.
|
||||
|
||||
Snapshot
|
||||
========
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Snapshot
|
||||
- Snapshot
|
||||
- Add, remove
|
||||
- Erase
|
||||
- Install
|
||||
- Test
|
||||
- Benchmark
|
||||
- Rate
|
||||
- Price
|
||||
|
||||
|
||||
Add, Remove
|
||||
===========
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Add
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Remove
|
||||
The following index has all the actions (please note we are moving from calling them
|
||||
``Event`` to call them ``Action``):
|
||||
|
||||
Erase
|
||||
=====
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.EraseBasic
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.EraseSectors
|
||||
.. autoclass:: ereuse_devicehub.resources.enums.ErasureStandards
|
||||
:members:
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.ErasePhysical
|
||||
.. autoclass:: ereuse_devicehub.resources.enums.PhysicalErasureMethod
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Install
|
||||
=======
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Install
|
||||
|
||||
Test
|
||||
====
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Test
|
||||
|
||||
TestDataStorage
|
||||
---------------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.TestDataStorage
|
||||
|
||||
StressTest
|
||||
----------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.StressTest
|
||||
|
||||
Benchmark
|
||||
=========
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Benchmark
|
||||
|
||||
|
||||
BenchmarkDataStorage
|
||||
--------------------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkDataStorage
|
||||
|
||||
|
||||
BenchmarkWithRate
|
||||
-----------------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkWithRate
|
||||
|
||||
|
||||
BenchmarkProcessor
|
||||
------------------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkProcessor
|
||||
|
||||
|
||||
BenchmarkProcessorSysbench
|
||||
--------------------------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkProcessorSysbench
|
||||
|
||||
|
||||
BenchmarkRamSysbench
|
||||
--------------------
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkRamSysbench
|
||||
|
||||
Rate
|
||||
====
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Rate
|
||||
|
||||
The following are the values the appearance, performance, and
|
||||
functionality grade can have:
|
||||
|
||||
.. autoclass:: ereuse_devicehub.resources.enums.AppearanceRange
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: ereuse_devicehub.resources.enums.FunctionalityRange
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: ereuse_devicehub.resources.enums.RatingRange
|
||||
|
||||
Price
|
||||
=====
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Price
|
||||
|
||||
Migrate
|
||||
=======
|
||||
Not done.
|
||||
|
||||
.. autoclass:: ereuse_devicehub.resources.event.models.Migrate
|
||||
|
||||
Locate
|
||||
======
|
||||
todo
|
||||
.. todo !!
|
||||
.. dhlist::
|
||||
:module: ereuse_devicehub.resources.event.schemas
|
||||
|
||||
|
||||
States
|
||||
|
@ -266,8 +71,4 @@ States
|
|||
.. uml:: states.puml
|
||||
|
||||
.. autoclass:: ereuse_devicehub.resources.device.states.Trading
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: ereuse_devicehub.resources.device.states.Physical
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
Using the API
|
||||
#############
|
||||
|
||||
Devicehub is a REST API on the web that partially extends Schema.org's
|
||||
ontology and it is formatted in JSON.
|
||||
|
||||
The main resource are devices. However, you do not perform operations
|
||||
directly against them (there is no ``POST /device``),
|
||||
as you use an Action / Event to do so (you only ``GET /devices``).
|
||||
For example, to upload information of devices with tests, erasures, etcetera, use
|
||||
the action/event ``POST /snapshot`` (:ref:`devices-snapshot`).
|
||||
|
||||
Login
|
||||
*****
|
||||
To use the API, you need first to log in with an existing account from the DeviceHub.
|
||||
Perform ``POST /users/login/`` with the email and password fields filled::
|
||||
|
||||
POST /users/login/
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
{
|
||||
"email": "user@dhub.com",
|
||||
"password: "1234"
|
||||
}
|
||||
|
||||
Upon success, you are answered with the account object, containing a Token field::
|
||||
|
||||
{
|
||||
"id": "...",
|
||||
"token: "A base 64 codified token",
|
||||
"type": "User",
|
||||
"inventories": [{"type": "Inventory", id: "db1", ...}, ...],
|
||||
...
|
||||
}
|
||||
|
||||
From this moment, any other following operation against
|
||||
the API requires the following HTTP Header:
|
||||
``Authorization: Basic token``. This is, the word **Basic**
|
||||
followed with a **space** and then the **token**,
|
||||
obtained from the account object above, **exactly as it is**.
|
||||
|
||||
.. _authenticate-requests:
|
||||
|
||||
|
||||
Authenticate requests
|
||||
---------------------
|
||||
To explain how to operate with resources like events or devices, we
|
||||
use one as an example: obtaining devices. The template of
|
||||
a request is::
|
||||
|
||||
GET <inventory>/devices/
|
||||
Accept: application/json
|
||||
Authorization: Basic <token>
|
||||
|
||||
And an example is::
|
||||
|
||||
GET acme/devices/
|
||||
Accept: application/json
|
||||
Authorization: Basic myTokenInBase64
|
||||
|
||||
Let's go through the variables:
|
||||
|
||||
- ``<inventory>`` is the name of the inventory where you operate.
|
||||
You get this value from the ``User`` object returned from the login.
|
||||
The ``inventories`` field contains a set of databases the account
|
||||
can operate with, being the first inventory the default one.
|
||||
- ``<token>`` is the token of the account.
|
||||
|
||||
See :ref:`devices:devices` for more information on how to query
|
||||
devices.
|
133
docs/conf.py
133
docs/conf.py
|
@ -18,6 +18,19 @@
|
|||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
import importlib
|
||||
import inspect
|
||||
from typing import Union
|
||||
|
||||
from docutils.parsers.rst import Directive, directives
|
||||
from docutils.statemachine import StringList, string2lines
|
||||
from marshmallow.fields import DateTime, Field
|
||||
from marshmallow.schema import SchemaMeta
|
||||
from teal.enums import Country, Currency, Layouts, Subdivision
|
||||
from teal.marshmallow import EnumField
|
||||
|
||||
from ereuse_devicehub.marshmallow import NestedOn
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
|
||||
project = 'Devicehub'
|
||||
copyright = '2018, eReuse.org team'
|
||||
|
@ -176,3 +189,123 @@ html_favicon = 'img/favicon.ico'
|
|||
# autosectionlabel
|
||||
autosectionlabel_prefix_document = True
|
||||
autodoc_member_order = 'bysource'
|
||||
|
||||
import docutils.nodes as n
|
||||
|
||||
|
||||
class DhlistDirective(Directive):
|
||||
"""Generates documentation from Devicehub Schema.
|
||||
|
||||
This requires :py:class:`ereuse_devicehub.resources.schemas.SchemaMeta`.
|
||||
You will find in that module more information.
|
||||
"""
|
||||
has_content = False
|
||||
|
||||
# Definition of passed-in options
|
||||
option_spec = {'module': directives.unchanged}
|
||||
|
||||
def _import(self, module):
|
||||
for obj in vars(module).values():
|
||||
if inspect.isclass(obj):
|
||||
if isinstance(obj, SchemaMeta) and hasattr(obj, '_base_class'):
|
||||
yield obj
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
module = importlib.import_module(self.options['module'])
|
||||
things = tuple(self._import(module))
|
||||
|
||||
sections = []
|
||||
sections.append(self.links(things)) # Make index
|
||||
for thng in things: # type: Thing
|
||||
# Generate a section for each class, with a title,
|
||||
# fields description and a paragraph
|
||||
section = n.section(ids=[self._id(thng)])
|
||||
section += n.title(thng.__name__, thng.__name__)
|
||||
section += self.parse('*Extends {}*'.format(thng._base_class))
|
||||
if thng.__doc__:
|
||||
section += self.parse(thng.__doc__)
|
||||
fields = n.field_list()
|
||||
for key, f in thng._own:
|
||||
name = n.field_name(text=f.data_key or key)
|
||||
body = [
|
||||
self.parse('{} {}'.format(self.type(f), f.metadata.get('description', '')))
|
||||
]
|
||||
if isinstance(f, EnumField):
|
||||
body.append(self._parse_enum_field(f))
|
||||
attrs = n.field_list()
|
||||
if f.dump_only:
|
||||
attrs += self.field('Submit', 'No.')
|
||||
if f.required:
|
||||
attrs += self.field('Required', f.required)
|
||||
fields += n.field('', name, n.field_body('', *body, attrs))
|
||||
section += fields
|
||||
sections.append(section)
|
||||
return sections
|
||||
|
||||
def _parse_enum_field(self, f):
|
||||
from ereuse_devicehub.resources.device import states
|
||||
if issubclass(f.enum, (Subdivision, Currency, Country, Layouts, states.State)):
|
||||
return self.parse(f.enum.__doc__)
|
||||
else:
|
||||
enum_fields = n.field_list()
|
||||
for el in f.enum:
|
||||
enum_fields += self.field(el.name, el.value)
|
||||
return enum_fields
|
||||
|
||||
def field(self, name: str, body: Union[str, bool]):
|
||||
"""Generates a field node with a name and a paragraph body."""
|
||||
if isinstance(body, bool):
|
||||
body = 'Yes.' if body else 'No.'
|
||||
body = str(body) if body else ''
|
||||
return n.field('', n.field_name(text=name), n.field_body('', self.parse(body)))
|
||||
|
||||
def type(self, field: Field):
|
||||
"""Parses the type field."""
|
||||
if isinstance(field, NestedOn):
|
||||
t = ''
|
||||
if field.many:
|
||||
t = 'List of '
|
||||
t = t + str(field.schema.t)
|
||||
elif isinstance(field, EnumField):
|
||||
t = field.enum.__name__
|
||||
elif isinstance(field, DateTime):
|
||||
t = 'Date time (ISO 8601 with timezone)'
|
||||
else:
|
||||
t = field.__class__.__name__
|
||||
if 'str' in t.lower():
|
||||
t = 'Text'
|
||||
if 'unit' in field.metadata:
|
||||
t = t + ' ({})'.format(field.metadata['unit'])
|
||||
return t + '.'
|
||||
|
||||
def links(self, things, parent='Schema'):
|
||||
"""Generates an index of things with inheritance awareness."""
|
||||
l = n.bullet_list('')
|
||||
for child in (c for c in things if c._base_class == parent):
|
||||
ref = n.reference(text=child.__name__)
|
||||
ref['refuri'] = '#{}'.format(self._id(child))
|
||||
p = n.paragraph()
|
||||
p += ref
|
||||
l += n.list_item('', p)
|
||||
sub_list = self.links(things, parent=child.__name__)
|
||||
if sub_list:
|
||||
l += sub_list
|
||||
return l
|
||||
|
||||
def _id(self, thing):
|
||||
"""Generate an id to use as html anchors."""
|
||||
return n.make_id('dh-{}'.format(thing.__name__))
|
||||
|
||||
def parse(self, text) -> n.container:
|
||||
"""Parses text possibly containing ReST stuff and adds it in
|
||||
a node."""
|
||||
p = n.container('')
|
||||
self.state.nested_parse(StringList(string2lines(inspect.cleandoc(text))), 0, p)
|
||||
return p
|
||||
# return publish_doctree(text).children
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_directive('dhlist', DhlistDirective)
|
||||
return {'version': '0.1'}
|
||||
|
|
|
@ -37,19 +37,14 @@ Result
|
|||
******
|
||||
The result is a JSON object with the following fields:
|
||||
|
||||
- **devices**: A list of devices.
|
||||
- **groups**: A list of groups.
|
||||
- **widgets**: A dictionary of widgets.
|
||||
- **pagination**: Pagination information:
|
||||
|
||||
- **items**: A list of devices.
|
||||
- **pagination**:
|
||||
- **page**: The page you requested in the ``page`` param of the query,
|
||||
or ``1``.
|
||||
- **perPage**: How many devices are in every page, fixed to ``30``.
|
||||
- **total**: How many total devices passed the filters.
|
||||
- **next**: The number of the next page, if any.
|
||||
- **last**: The number of the last page, if any.
|
||||
|
||||
Models
|
||||
******
|
||||
|
||||
.. automodule:: ereuse_devicehub.resources.device.models
|
||||
:members:
|
||||
:member-order: bysource
|
||||
.. dhlist::
|
||||
:module: ereuse_devicehub.resources.device.schemas
|
||||
|
|
|
@ -31,10 +31,9 @@ Devicehub is built with `Teal <https://github.com/bustawin/teal>`_ and
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
processes
|
||||
actions
|
||||
agents
|
||||
api
|
||||
devices
|
||||
actions
|
||||
tags
|
||||
lots
|
||||
|
||||
|
|
|
@ -27,11 +27,11 @@ class Dummy:
|
|||
)
|
||||
"""Tags to create."""
|
||||
ET = (
|
||||
('A0000000000001', 'DT-AAAAA'),
|
||||
('A0000000000002', 'DT-BBBBB'),
|
||||
('A0000000000003', 'DT-CCCCC'),
|
||||
('04970DA2A15984', 'DT-BRRAB'),
|
||||
('04e4bc5af95980', 'DT-XXXXX')
|
||||
('DT-AAAAA', 'A0000000000001'),
|
||||
('DT-BBBBB', 'A0000000000002'),
|
||||
('DT-CCCCC', 'A0000000000003'),
|
||||
('DT-BRRAB', '04970DA2A15984'),
|
||||
('DT-XXXXX', '04e4bc5af95980')
|
||||
)
|
||||
"""eTags to create."""
|
||||
ORG = 'eReuse.org CAT', '-t', 'G-60437761', '-c', 'ES'
|
||||
|
|
|
@ -29,44 +29,75 @@ from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
|||
|
||||
|
||||
class Device(Thing):
|
||||
"""
|
||||
Base class for any type of physical object that can be identified.
|
||||
"""Base class for any type of physical object that can be identified.
|
||||
|
||||
Device partly extends `Schema's IndividualProduct <https
|
||||
://schema.org/IndividualProduct>`_, adapting it to our
|
||||
use case.
|
||||
|
||||
A device requires an identification method, ideally a serial number,
|
||||
although it can be identified only with tags too. More ideally
|
||||
both methods are used.
|
||||
|
||||
Devices can contain ``Components``, which are just a type of device
|
||||
(it is a recursive relationship).
|
||||
"""
|
||||
EVENT_SORT_KEY = attrgetter('created')
|
||||
|
||||
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
||||
id.comment = """
|
||||
The identifier of the device for this database.
|
||||
The identifier of the device for this database. Used only
|
||||
internally for software; users should not use this.
|
||||
"""
|
||||
type = Column(Unicode(STR_SM_SIZE), nullable=False, index=True)
|
||||
hid = Column(Unicode(), check_lower('hid'), unique=True)
|
||||
hid.comment = """
|
||||
The Hardware ID (HID) is the unique ID traceability systems
|
||||
use to ID a device globally.
|
||||
use to ID a device globally. This field is auto-generated
|
||||
from Devicehub using literal identifiers from the device,
|
||||
so it can re-generated *offline*.
|
||||
|
||||
The HID is the result of joining the type of device, S/N,
|
||||
manufacturer name, and model. Devices that do not have one
|
||||
of these fields cannot generate HID, thus not guaranteeing
|
||||
global uniqueness.
|
||||
"""
|
||||
model = Column(Unicode(), check_lower('model'))
|
||||
model.comment = """The model or brand of the device in lower case.
|
||||
|
||||
Devices usually report one of both (model or brand). This value
|
||||
must be consistent through time.
|
||||
"""
|
||||
manufacturer = Column(Unicode(), check_lower('manufacturer'))
|
||||
manufacturer.comment = """The normalized name of the manufacturer
|
||||
in lower case.
|
||||
|
||||
Although as of now Devicehub does not enforce normalization,
|
||||
users can choose a list of normalized manufacturer names
|
||||
from the own ``/manufacturers`` REST endpoint.
|
||||
"""
|
||||
serial_number = Column(Unicode(), check_lower('serial_number'))
|
||||
serial_number.comment = """The serial number of the device in lower case."""
|
||||
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
|
||||
weight.comment = """
|
||||
The weight of the device in Kgm.
|
||||
The weight of the device.
|
||||
"""
|
||||
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
|
||||
width.comment = """
|
||||
The width of the device in meters.
|
||||
The width of the device.
|
||||
"""
|
||||
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
|
||||
height.comment = """
|
||||
The height of the device in meters.
|
||||
The height of the device.
|
||||
"""
|
||||
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
|
||||
depth.comment = """
|
||||
The depth of the device in meters.
|
||||
The depth of the device.
|
||||
"""
|
||||
color = Column(ColorType)
|
||||
color.comment = """The predominant color of the device."""
|
||||
production_date = Column(db.TIMESTAMP(timezone=True))
|
||||
production_date.comment = """The date of production of the item."""
|
||||
production_date.comment = """The date of production of the device."""
|
||||
|
||||
_NON_PHYSICAL_PROPS = {
|
||||
'id',
|
||||
|
@ -91,11 +122,13 @@ class Device(Thing):
|
|||
@property
|
||||
def events(self) -> list:
|
||||
"""
|
||||
All the events where the device participated, including
|
||||
1) events performed directly to the device, 2) events performed
|
||||
to a component, and 3) events performed to a parent device.
|
||||
All the events where the device participated, including:
|
||||
|
||||
Events are returned by ascending creation time.
|
||||
1. Events performed directly to the device.
|
||||
2. Events performed to a component.
|
||||
3. Events performed to a parent device.
|
||||
|
||||
Events are returned by ascending ``created`` time.
|
||||
"""
|
||||
return sorted(chain(self.events_multiple, self.events_one), key=self.EVENT_SORT_KEY)
|
||||
|
||||
|
@ -198,7 +231,7 @@ class Device(Thing):
|
|||
device is working if the list is empty.
|
||||
|
||||
This property returns, for the last test performed of each type,
|
||||
the one with the worst severity of them, or `None` if no
|
||||
the one with the worst ``severity`` of them, or ``None`` if no
|
||||
test has been executed.
|
||||
"""
|
||||
from ereuse_devicehub.resources.event.models import Test
|
||||
|
@ -292,8 +325,18 @@ class DisplayMixin:
|
|||
|
||||
|
||||
class Computer(Device):
|
||||
"""A chassis with components inside that can be processed
|
||||
automatically with Workbench Computer.
|
||||
|
||||
Computer is broadly extended by ``Desktop``, ``Laptop``, and
|
||||
``Server``. The property ``chassis`` defines it more granularly.
|
||||
"""
|
||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
chassis = Column(DBEnum(ComputerChassis), nullable=False)
|
||||
chassis.comment = """The physical form of the computer.
|
||||
|
||||
It is a subset of the Linux definition of DMI / DMI decode.
|
||||
"""
|
||||
|
||||
def __init__(self, chassis, **kwargs) -> None:
|
||||
chassis = ComputerChassis(chassis)
|
||||
|
@ -342,7 +385,7 @@ class Computer(Device):
|
|||
|
||||
@property
|
||||
def privacy(self):
|
||||
"""Returns the privacy of all DataStorage components when
|
||||
"""Returns the privacy of all ``DataStorage`` components when
|
||||
it is not None.
|
||||
"""
|
||||
return set(
|
||||
|
@ -395,6 +438,8 @@ class Projector(Monitor):
|
|||
|
||||
|
||||
class Mobile(Device):
|
||||
"""A mobile device consisting of smartphones, tablets, and cellphones."""
|
||||
|
||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
imei = Column(BigInteger)
|
||||
imei.comment = """
|
||||
|
@ -432,6 +477,7 @@ class Cellphone(Mobile):
|
|||
|
||||
|
||||
class Component(Device):
|
||||
"""A device that can be inside another device."""
|
||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
|
||||
parent_id = Column(BigInteger, ForeignKey(Computer.id), index=True)
|
||||
|
@ -481,6 +527,7 @@ class GraphicCard(JoinedComponentTableMixin, Component):
|
|||
|
||||
|
||||
class DataStorage(JoinedComponentTableMixin, Component):
|
||||
"""A device that stores information."""
|
||||
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
||||
size.comment = """
|
||||
The size of the data-storage in MB.
|
||||
|
@ -548,14 +595,21 @@ class NetworkAdapter(JoinedComponentTableMixin, NetworkMixin, Component):
|
|||
|
||||
|
||||
class Processor(JoinedComponentTableMixin, Component):
|
||||
"""The CPU."""
|
||||
speed = Column(Float, check_range('speed', 0.1, 15))
|
||||
speed.comment = """The regular CPU speed."""
|
||||
cores = Column(SmallInteger, check_range('cores', 1, 10))
|
||||
cores.comment = """The number of regular cores."""
|
||||
threads = Column(SmallInteger, check_range('threads', 1, 20))
|
||||
threads.comment = """The number of threads per core."""
|
||||
address = Column(SmallInteger, check_range('address', 8, 256))
|
||||
address.comment = """The address of the CPU: 8, 16, 32, 64, 128 or 256 bits."""
|
||||
|
||||
|
||||
class RamModule(JoinedComponentTableMixin, Component):
|
||||
"""A stick of RAM."""
|
||||
size = Column(SmallInteger, check_range('size', min=128, max=17000))
|
||||
size.comment = """The capacity of the RAM stick."""
|
||||
speed = Column(SmallInteger, check_range('speed', min=100, max=10000))
|
||||
interface = Column(DBEnum(RamInterface))
|
||||
format = Column(DBEnum(RamFormat))
|
||||
|
@ -568,14 +622,15 @@ class SoundCard(JoinedComponentTableMixin, Component):
|
|||
class Display(JoinedComponentTableMixin, DisplayMixin, Component):
|
||||
"""
|
||||
The display of a device. This is used in all devices that have
|
||||
displays but that it is not their main treat, like laptops,
|
||||
mobiles, smart-watches, and so on; excluding then ComputerMonitor
|
||||
and Television Set.
|
||||
displays but that it is not their main part, like laptops,
|
||||
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
|
||||
and ``TelevisionSet``.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ComputerAccessory(Device):
|
||||
"""Computer peripherals and similar accessories."""
|
||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
pass
|
||||
|
||||
|
@ -597,6 +652,7 @@ class MemoryCardReader(ComputerAccessory):
|
|||
|
||||
|
||||
class Networking(NetworkMixin, Device):
|
||||
"""Routers, switches, hubs..."""
|
||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
|
||||
|
||||
|
@ -641,6 +697,7 @@ class Microphone(Sound):
|
|||
|
||||
|
||||
class Video(Device):
|
||||
"""Devices related to video treatment."""
|
||||
pass
|
||||
|
||||
|
||||
|
@ -653,6 +710,7 @@ class Videoconference(Video):
|
|||
|
||||
|
||||
class Cooking(Device):
|
||||
"""Cooking devices."""
|
||||
pass
|
||||
|
||||
|
||||
|
@ -661,6 +719,11 @@ class Mixer(Cooking):
|
|||
|
||||
|
||||
class Manufacturer(db.Model):
|
||||
"""The normalized information about a manufacturer.
|
||||
|
||||
Ideally users should use the names from this list when submitting
|
||||
devices.
|
||||
"""
|
||||
__table_args__ = {'schema': 'common'}
|
||||
CSV_DELIMITER = csv.get_dialect('excel').delimiter
|
||||
|
||||
|
@ -668,8 +731,11 @@ class Manufacturer(db.Model):
|
|||
primary_key=True,
|
||||
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
|
||||
index=db.Index('name', text('name gin_trgm_ops'), postgresql_using='gin'))
|
||||
name.comment = """The normalized name of the manufacturer."""
|
||||
url = db.Column(URL(), unique=True)
|
||||
url.comment = """An URL to a page describing the manufacturer."""
|
||||
logo = db.Column(URL())
|
||||
logo.comment = """An URL pointing to the logo of the manufacturer."""
|
||||
|
||||
@classmethod
|
||||
def add_all_to_session(cls, session: db.Session):
|
||||
|
|
|
@ -287,11 +287,13 @@ class Processor(Component):
|
|||
speed = ... # type: Column
|
||||
cores = ... # type: Column
|
||||
address = ... # type: Column
|
||||
threads = ... # type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.speed = ... # type: float
|
||||
self.cores = ... # type: int
|
||||
self.threads = ... # type: int
|
||||
self.address = ... # type: int
|
||||
|
||||
|
||||
|
|
|
@ -15,12 +15,13 @@ from ereuse_devicehub.resources.schemas import Thing, UnitCodes
|
|||
|
||||
|
||||
class Device(Thing):
|
||||
__doc__ = m.Device.__doc__
|
||||
id = Integer(description=m.Device.id.comment, dump_only=True)
|
||||
hid = SanitizedStr(lower=True, dump_only=True, description=m.Device.hid.comment)
|
||||
tags = NestedOn('Tag',
|
||||
many=True,
|
||||
collection_class=OrderedSet,
|
||||
description='The set of tags that identify the device.')
|
||||
description='A set of tags that identify the device.')
|
||||
model = SanitizedStr(lower=True, validate=Length(max=STR_BIG_SIZE))
|
||||
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
|
||||
serial_number = SanitizedStr(lower=True, data_key='serialNumber')
|
||||
|
@ -75,29 +76,54 @@ class Device(Thing):
|
|||
|
||||
|
||||
class Computer(Device):
|
||||
components = NestedOn('Component', many=True, dump_only=True, collection_class=OrderedSet)
|
||||
chassis = EnumField(enums.ComputerChassis, required=True)
|
||||
ram_size = Integer(dump_only=True, data_key='ramSize')
|
||||
data_storage_size = Integer(dump_only=True, data_key='dataStorageSize')
|
||||
processor_model = Str(dump_only=True, data_key='processorModel')
|
||||
graphic_card_model = Str(dump_only=True, data_key='graphicCardModel')
|
||||
network_speeds = List(Integer(dump_only=True), dump_only=True, data_key='networkSpeeds')
|
||||
privacy = NestedOn('Event', many=True, dump_only=True, collection_class=set)
|
||||
__doc__ = m.Computer.__doc__
|
||||
components = NestedOn('Component',
|
||||
many=True,
|
||||
dump_only=True,
|
||||
collection_class=OrderedSet,
|
||||
description='The components that are inside this computer.')
|
||||
chassis = EnumField(enums.ComputerChassis,
|
||||
required=True,
|
||||
description=m.Computer.chassis.comment)
|
||||
ram_size = Integer(dump_only=True,
|
||||
data_key='ramSize',
|
||||
description=m.Computer.ram_size.__doc__)
|
||||
data_storage_size = Integer(dump_only=True,
|
||||
data_key='dataStorageSize',
|
||||
description=m.Computer.data_storage_size.__doc__)
|
||||
processor_model = Str(dump_only=True,
|
||||
data_key='processorModel',
|
||||
description=m.Computer.processor_model.__doc__)
|
||||
graphic_card_model = Str(dump_only=True,
|
||||
data_key='graphicCardModel',
|
||||
description=m.Computer.graphic_card_model.__doc__)
|
||||
network_speeds = List(Integer(dump_only=True),
|
||||
dump_only=True,
|
||||
data_key='networkSpeeds',
|
||||
description=m.Computer.network_speeds.__doc__)
|
||||
privacy = NestedOn('Event',
|
||||
many=True,
|
||||
dump_only=True,
|
||||
collection_class=set,
|
||||
description=m.Computer.privacy.__doc__)
|
||||
|
||||
|
||||
class Desktop(Computer):
|
||||
pass
|
||||
__doc__ = m.Desktop.__doc__
|
||||
|
||||
|
||||
class Laptop(Computer):
|
||||
pass
|
||||
layout = EnumField(Layouts, description=m.Laptop.layout.comment)
|
||||
__doc__ = m.Laptop.__doc__
|
||||
|
||||
|
||||
class Server(Computer):
|
||||
pass
|
||||
__doc__ = m.Server.__doc__
|
||||
|
||||
|
||||
class DisplayMixin:
|
||||
__doc__ = m.DisplayMixin.__doc__
|
||||
|
||||
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150))
|
||||
technology = EnumField(enums.DisplayTech,
|
||||
description=m.DisplayMixin.technology.comment)
|
||||
|
@ -113,6 +139,8 @@ class DisplayMixin:
|
|||
|
||||
|
||||
class NetworkMixin:
|
||||
__doc__ = m.NetworkMixin.__doc__
|
||||
|
||||
speed = Integer(validate=Range(min=10, max=10000),
|
||||
unit=UnitCodes.mbps,
|
||||
description=m.NetworkAdapter.speed.comment)
|
||||
|
@ -120,18 +148,20 @@ class NetworkMixin:
|
|||
|
||||
|
||||
class Monitor(DisplayMixin, Device):
|
||||
pass
|
||||
__doc__ = m.Monitor.__doc__
|
||||
|
||||
|
||||
class ComputerMonitor(Monitor):
|
||||
pass
|
||||
__doc__ = m.ComputerMonitor.__doc__
|
||||
|
||||
|
||||
class TelevisionSet(Monitor):
|
||||
pass
|
||||
__doc__ = m.TelevisionSet.__doc__
|
||||
|
||||
|
||||
class Mobile(Device):
|
||||
__doc__ = m.Mobile.__doc__
|
||||
|
||||
imei = Integer(description=m.Mobile.imei.comment)
|
||||
meid = Str(description=m.Mobile.meid.comment)
|
||||
|
||||
|
@ -149,28 +179,34 @@ class Mobile(Device):
|
|||
|
||||
|
||||
class Smartphone(Mobile):
|
||||
pass
|
||||
__doc__ = m.Smartphone.__doc__
|
||||
|
||||
|
||||
class Tablet(Mobile):
|
||||
pass
|
||||
__doc__ = m.Tablet.__doc__
|
||||
|
||||
|
||||
class Cellphone(Mobile):
|
||||
pass
|
||||
__doc__ = m.Cellphone.__doc__
|
||||
|
||||
|
||||
class Component(Device):
|
||||
__doc__ = m.Component.__doc__
|
||||
|
||||
parent = NestedOn(Device, dump_only=True)
|
||||
|
||||
|
||||
class GraphicCard(Component):
|
||||
__doc__ = m.GraphicCard.__doc__
|
||||
|
||||
memory = Integer(validate=Range(0, 10000),
|
||||
unit=UnitCodes.mbyte,
|
||||
description=m.GraphicCard.memory.comment)
|
||||
|
||||
|
||||
class DataStorage(Component):
|
||||
__doc__ = m.DataStorage.__doc__
|
||||
|
||||
size = Integer(validate=Range(0, 10 ** 8),
|
||||
unit=UnitCodes.mbyte,
|
||||
description=m.DataStorage.size.comment)
|
||||
|
@ -179,128 +215,147 @@ class DataStorage(Component):
|
|||
|
||||
|
||||
class HardDrive(DataStorage):
|
||||
pass
|
||||
__doc__ = m.HardDrive.__doc__
|
||||
|
||||
|
||||
class SolidStateDrive(DataStorage):
|
||||
pass
|
||||
__doc__ = m.SolidStateDrive.__doc__
|
||||
|
||||
|
||||
class Motherboard(Component):
|
||||
__doc__ = m.Motherboard.__doc__
|
||||
|
||||
slots = Integer(validate=Range(0, 20),
|
||||
description=m.Motherboard.slots.comment)
|
||||
usb = Integer(validate=Range(0, 20))
|
||||
firewire = Integer(validate=Range(0, 20))
|
||||
serial = Integer(validate=Range(0, 20))
|
||||
pcmcia = Integer(validate=Range(0, 20))
|
||||
usb = Integer(validate=Range(0, 20), description=m.Motherboard.usb.comment)
|
||||
firewire = Integer(validate=Range(0, 20), description=m.Motherboard.firewire.comment)
|
||||
serial = Integer(validate=Range(0, 20), description=m.Motherboard.serial.comment)
|
||||
pcmcia = Integer(validate=Range(0, 20), description=m.Motherboard.pcmcia.comment)
|
||||
|
||||
|
||||
class NetworkAdapter(NetworkMixin, Component):
|
||||
pass
|
||||
__doc__ = m.NetworkAdapter.__doc__
|
||||
|
||||
|
||||
class Processor(Component):
|
||||
speed = Float(validate=Range(min=0.1, max=15), unit=UnitCodes.ghz)
|
||||
cores = Integer(validate=Range(min=1, max=10))
|
||||
threads = Integer(validate=Range(min=1, max=20))
|
||||
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}))
|
||||
__doc__ = m.Processor.__doc__
|
||||
|
||||
speed = Float(validate=Range(min=0.1, max=15),
|
||||
unit=UnitCodes.ghz,
|
||||
description=m.Processor.speed.comment)
|
||||
cores = Integer(validate=Range(min=1, max=10), description=m.Processor.cores.comment)
|
||||
threads = Integer(validate=Range(min=1, max=20), description=m.Processor.threads.comment)
|
||||
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}),
|
||||
description=m.Processor.address.comment)
|
||||
|
||||
|
||||
class RamModule(Component):
|
||||
size = Integer(validate=Range(min=128, max=17000), unit=UnitCodes.mbyte)
|
||||
__doc__ = m.RamModule.__doc__
|
||||
|
||||
size = Integer(validate=Range(min=128, max=17000),
|
||||
unit=UnitCodes.mbyte,
|
||||
description=m.RamModule.size.comment)
|
||||
speed = Integer(validate=Range(min=100, max=10000), unit=UnitCodes.mhz)
|
||||
interface = EnumField(enums.RamInterface)
|
||||
format = EnumField(enums.RamFormat)
|
||||
|
||||
|
||||
class SoundCard(Component):
|
||||
pass
|
||||
__doc__ = m.SoundCard.__doc__
|
||||
|
||||
|
||||
class Display(DisplayMixin, Component):
|
||||
pass
|
||||
__doc__ = m.Display.__doc__
|
||||
|
||||
|
||||
class Manufacturer(Schema):
|
||||
__doc__ = m.Manufacturer.__doc__
|
||||
|
||||
name = String(dump_only=True)
|
||||
url = URL(dump_only=True)
|
||||
logo = URL(dump_only=True)
|
||||
|
||||
|
||||
class ComputerAccessory(Device):
|
||||
pass
|
||||
__doc__ = m.ComputerAccessory.__doc__
|
||||
|
||||
|
||||
class Mouse(ComputerAccessory):
|
||||
pass
|
||||
__doc__ = m.Mouse.__doc__
|
||||
|
||||
|
||||
class MemoryCardReader(ComputerAccessory):
|
||||
pass
|
||||
__doc__ = m.MemoryCardReader.__doc__
|
||||
|
||||
|
||||
class SAI(ComputerAccessory):
|
||||
pass
|
||||
__doc__ = m.SAI.__doc__
|
||||
|
||||
|
||||
class Keyboard(ComputerAccessory):
|
||||
__doc__ = m.Keyboard.__doc__
|
||||
|
||||
layout = EnumField(Layouts)
|
||||
|
||||
|
||||
class Networking(NetworkMixin, Device):
|
||||
pass
|
||||
__doc__ = m.Networking.__doc__
|
||||
|
||||
|
||||
class Router(Networking):
|
||||
pass
|
||||
__doc__ = m.Router.__doc__
|
||||
|
||||
|
||||
class Switch(Networking):
|
||||
pass
|
||||
__doc__ = m.Switch.__doc__
|
||||
|
||||
|
||||
class Hub(Networking):
|
||||
pass
|
||||
__doc__ = m.Hub.__doc__
|
||||
|
||||
|
||||
class WirelessAccessPoint(Networking):
|
||||
pass
|
||||
__doc__ = m.WirelessAccessPoint.__doc__
|
||||
|
||||
|
||||
class Printer(Device):
|
||||
wireless = Boolean(required=True, missing=False)
|
||||
scanning = Boolean(required=True, missing=False)
|
||||
technology = EnumField(enums.PrinterTechnology, required=True)
|
||||
monochrome = Boolean(required=True, missing=True)
|
||||
__doc__ = m.Printer.__doc__
|
||||
|
||||
wireless = Boolean(required=True, missing=False, description=m.Printer.wireless.comment)
|
||||
scanning = Boolean(required=True, missing=False, description=m.Printer.scanning.comment)
|
||||
technology = EnumField(enums.PrinterTechnology,
|
||||
required=True,
|
||||
description=m.Printer.technology.comment)
|
||||
monochrome = Boolean(required=True, missing=True, description=m.Printer.monochrome.comment)
|
||||
|
||||
|
||||
class LabelPrinter(Printer):
|
||||
pass
|
||||
__doc__ = m.LabelPrinter.__doc__
|
||||
|
||||
|
||||
class Sound(Device):
|
||||
pass
|
||||
__doc__ = m.Sound.__doc__
|
||||
|
||||
|
||||
class Microphone(Sound):
|
||||
pass
|
||||
__doc__ = m.Microphone.__doc__
|
||||
|
||||
|
||||
class Video(Device):
|
||||
pass
|
||||
__doc__ = m.Video.__doc__
|
||||
|
||||
|
||||
class VideoScaler(Video):
|
||||
pass
|
||||
__doc__ = m.VideoScaler.__doc__
|
||||
|
||||
|
||||
class Videoconference(Video):
|
||||
pass
|
||||
__doc__ = m.Videoconference.__doc__
|
||||
|
||||
|
||||
class Cooking(Device):
|
||||
pass
|
||||
__doc__ = m.Cooking.__doc__
|
||||
|
||||
|
||||
class Mixer(Cooking):
|
||||
pass
|
||||
__doc__ = m.Mixer.__doc__
|
||||
|
|
|
@ -16,6 +16,19 @@ class State(Enum):
|
|||
|
||||
|
||||
class Trading(State):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
Reserved = e.Reserve
|
||||
Cancelled = e.CancelTrade
|
||||
Sold = e.Sell
|
||||
|
@ -27,6 +40,16 @@ class Trading(State):
|
|||
|
||||
|
||||
class Physical(State):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
ToBeRepaired = e.ToRepair
|
||||
Repaired = e.Repair
|
||||
Preparing = e.ToPrepare
|
||||
|
|
|
@ -278,17 +278,15 @@ class PrinterTechnology(Enum):
|
|||
|
||||
class Severity(IntEnum):
|
||||
"""A flag evaluating the event execution. Ex. failed events
|
||||
have the value `Severity.Error`.
|
||||
have the value `Severity.Error`. Devicehub uses 4 severity levels:
|
||||
|
||||
Devicehub uses 4 severity levels:
|
||||
|
||||
- Info: default neutral severity. The event succeeded.
|
||||
- Notice: The event succeeded but it is raising awareness.
|
||||
* Info: default neutral severity. The event succeeded.
|
||||
* Notice: The event succeeded but it is raising awareness.
|
||||
Notices are not usually that important but something
|
||||
(good or bad) worth checking.
|
||||
- Warning: The event succeeded but there is something important
|
||||
* Warning: The event succeeded but there is something important
|
||||
to check negatively affecting the event.
|
||||
- Error: the event failed.
|
||||
* Error: the event failed.
|
||||
|
||||
Devicehub specially raises user awareness when an event
|
||||
has a Severity of ``Warning`` or greater.
|
||||
|
|
|
@ -44,6 +44,10 @@ class JoinedTableMixin:
|
|||
|
||||
|
||||
class Event(Thing):
|
||||
"""Event performed on a device.
|
||||
|
||||
This class extends `Schema's Action <https://schema.org/Action>`_.
|
||||
"""
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||
type = Column(Unicode, nullable=False, index=True)
|
||||
name = Column(CIText(), default='', nullable=False)
|
||||
|
@ -1179,6 +1183,9 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices):
|
|||
|
||||
Performing trade events changes the *Trading* state of the
|
||||
device —:class:`ereuse_devicehub.resources.device.states.Trading`.
|
||||
|
||||
This class and its inheritors
|
||||
extend `Schema's Trade <http://schema.org/TradeAction>`_.
|
||||
"""
|
||||
shipping_date = Column(DateTime)
|
||||
shipping_date.comment = """
|
||||
|
|
|
@ -10,18 +10,19 @@ from teal.resource import Schema
|
|||
|
||||
from ereuse_devicehub.marshmallow import NestedOn
|
||||
from ereuse_devicehub.resources import enums
|
||||
from ereuse_devicehub.resources.agent.schemas import Agent
|
||||
from ereuse_devicehub.resources.device.schemas import Component, Computer, Device
|
||||
from ereuse_devicehub.resources.agent import schemas as s_agent
|
||||
from ereuse_devicehub.resources.device import schemas as s_device
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
||||
PhysicalErasureMethod, PriceSoftware, RATE_POSITIVE, RatingRange, RatingSoftware, ReceiverRole, \
|
||||
Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
|
||||
from ereuse_devicehub.resources.event import models as m
|
||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
from ereuse_devicehub.resources.user.schemas import User
|
||||
from ereuse_devicehub.resources.user import schemas as s_user
|
||||
|
||||
|
||||
class Event(Thing):
|
||||
__doc__ = m.Event.__doc__
|
||||
id = UUID(dump_only=True)
|
||||
name = SanitizedStr(default='',
|
||||
validate=Length(max=STR_BIG_SIZE),
|
||||
|
@ -32,31 +33,34 @@ class Event(Thing):
|
|||
start_time = DateTime(data_key='startTime', description=m.Event.start_time.comment)
|
||||
end_time = DateTime(data_key='endTime', description=m.Event.end_time.comment)
|
||||
snapshot = NestedOn('Snapshot', dump_only=True)
|
||||
agent = NestedOn(Agent, description=m.Event.agent_id.comment)
|
||||
author = NestedOn(User, dump_only=True, exclude=('token',))
|
||||
components = NestedOn(Component, dump_only=True, many=True)
|
||||
parent = NestedOn(Computer, dump_only=True, description=m.Event.parent_id.comment)
|
||||
agent = NestedOn(s_agent.Agent, description=m.Event.agent_id.comment)
|
||||
author = NestedOn(s_user.User, dump_only=True, exclude=('token',))
|
||||
components = NestedOn(s_device.Component, dump_only=True, many=True)
|
||||
parent = NestedOn(s_device.Computer, dump_only=True, description=m.Event.parent_id.comment)
|
||||
url = URL(dump_only=True, description=m.Event.url.__doc__)
|
||||
|
||||
|
||||
class EventWithOneDevice(Event):
|
||||
device = NestedOn(Device, only_query='id')
|
||||
__doc__ = m.EventWithOneDevice.__doc__
|
||||
device = NestedOn(s_device.Device, only_query='id')
|
||||
|
||||
|
||||
class EventWithMultipleDevices(Event):
|
||||
devices = NestedOn(Device, many=True, only_query='id', collection_class=OrderedSet)
|
||||
__doc__ = m.EventWithMultipleDevices.__doc__
|
||||
devices = NestedOn(s_device.Device, many=True, only_query='id', collection_class=OrderedSet)
|
||||
|
||||
|
||||
class Add(EventWithOneDevice):
|
||||
pass
|
||||
__doc__ = m.Add.__doc__
|
||||
|
||||
|
||||
class Remove(EventWithOneDevice):
|
||||
pass
|
||||
__doc__ = m.Remove.__doc__
|
||||
|
||||
|
||||
class Allocate(EventWithMultipleDevices):
|
||||
to = NestedOn(User,
|
||||
__doc__ = m.Allocate.__doc__
|
||||
to = NestedOn(s_user.User,
|
||||
description='The user the devices are allocated to.')
|
||||
organization = SanitizedStr(validate=Length(max=STR_SIZE),
|
||||
description='The organization where the '
|
||||
|
@ -64,7 +68,8 @@ class Allocate(EventWithMultipleDevices):
|
|||
|
||||
|
||||
class Deallocate(EventWithMultipleDevices):
|
||||
from_rel = Nested(User,
|
||||
__doc__ = m.Deallocate.__doc__
|
||||
from_rel = Nested(s_user.User,
|
||||
data_key='from',
|
||||
description='The user where the devices are not allocated to anymore.')
|
||||
organization = SanitizedStr(validate=Length(max=STR_SIZE),
|
||||
|
@ -73,20 +78,23 @@ class Deallocate(EventWithMultipleDevices):
|
|||
|
||||
|
||||
class EraseBasic(EventWithOneDevice):
|
||||
__doc__ = m.EraseBasic.__doc__
|
||||
steps = NestedOn('Step', many=True)
|
||||
standards = f.List(EnumField(enums.ErasureStandards), dump_only=True)
|
||||
certificate = URL(dump_only=True)
|
||||
|
||||
|
||||
class EraseSectors(EraseBasic):
|
||||
pass
|
||||
__doc__ = m.EraseSectors.__doc__
|
||||
|
||||
|
||||
class ErasePhysical(EraseBasic):
|
||||
__doc__ = m.ErasePhysical.__doc__
|
||||
method = EnumField(PhysicalErasureMethod, description=PhysicalErasureMethod.__doc__)
|
||||
|
||||
|
||||
class Step(Schema):
|
||||
__doc__ = m.Step.__doc__
|
||||
type = String(description='Only required when it is nested.')
|
||||
start_time = DateTime(required=True, data_key='startTime')
|
||||
end_time = DateTime(required=True, data_key='endTime')
|
||||
|
@ -94,14 +102,15 @@ class Step(Schema):
|
|||
|
||||
|
||||
class StepZero(Step):
|
||||
pass
|
||||
__doc__ = m.StepZero.__doc__
|
||||
|
||||
|
||||
class StepRandom(Step):
|
||||
pass
|
||||
__doc__ = m.StepRandom.__doc__
|
||||
|
||||
|
||||
class Rate(EventWithOneDevice):
|
||||
__doc__ = m.Rate.__doc__
|
||||
rating = Integer(validate=Range(*RATE_POSITIVE),
|
||||
dump_only=True,
|
||||
description=m.Rate.rating.comment)
|
||||
|
@ -116,10 +125,11 @@ class Rate(EventWithOneDevice):
|
|||
|
||||
|
||||
class IndividualRate(Rate):
|
||||
pass
|
||||
__doc__ = m.IndividualRate.__doc__
|
||||
|
||||
|
||||
class ManualRate(IndividualRate):
|
||||
__doc__ = m.ManualRate.__doc__
|
||||
appearance_range = EnumField(AppearanceRange,
|
||||
required=True,
|
||||
data_key='appearanceRange',
|
||||
|
@ -132,6 +142,7 @@ class ManualRate(IndividualRate):
|
|||
|
||||
|
||||
class WorkbenchRate(ManualRate):
|
||||
__doc__ = m.WorkbenchRate.__doc__
|
||||
processor = Float()
|
||||
ram = Float()
|
||||
data_storage = Float()
|
||||
|
@ -147,6 +158,7 @@ class WorkbenchRate(ManualRate):
|
|||
|
||||
|
||||
class AggregateRate(Rate):
|
||||
__doc__ = m.AggregateRate.__doc__
|
||||
workbench = NestedOn(WorkbenchRate, dump_only=True,
|
||||
description=m.AggregateRate.workbench_id.comment)
|
||||
manual = NestedOn(ManualRate,
|
||||
|
@ -176,6 +188,7 @@ class AggregateRate(Rate):
|
|||
|
||||
|
||||
class Price(EventWithOneDevice):
|
||||
__doc__ = m.Price.__doc__
|
||||
currency = EnumField(Currency, required=True, description=m.Price.currency.comment)
|
||||
price = Decimal(places=m.Price.SCALE,
|
||||
rounding=m.Price.ROUND,
|
||||
|
@ -187,6 +200,8 @@ class Price(EventWithOneDevice):
|
|||
|
||||
|
||||
class EreusePrice(Price):
|
||||
__doc__ = m.EreusePrice.__doc__
|
||||
|
||||
class Service(MarshmallowSchema):
|
||||
class Type(MarshmallowSchema):
|
||||
amount = Float()
|
||||
|
@ -202,6 +217,7 @@ class EreusePrice(Price):
|
|||
|
||||
|
||||
class Install(EventWithOneDevice):
|
||||
__doc__ = m.Install.__doc__
|
||||
name = SanitizedStr(validate=Length(min=4, max=STR_BIG_SIZE),
|
||||
required=True,
|
||||
description='The name of the OS installed.')
|
||||
|
@ -210,6 +226,7 @@ class Install(EventWithOneDevice):
|
|||
|
||||
|
||||
class Snapshot(EventWithOneDevice):
|
||||
__doc__ = m.Snapshot.__doc__
|
||||
"""
|
||||
The Snapshot updates the state of the device with information about
|
||||
its components and events performed at them.
|
||||
|
@ -229,7 +246,7 @@ class Snapshot(EventWithOneDevice):
|
|||
'the async Snapshot.')
|
||||
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS)
|
||||
components = NestedOn(Component,
|
||||
components = NestedOn(s_device.Component,
|
||||
many=True,
|
||||
description='A list of components that are inside of the device'
|
||||
'at the moment of this Snapshot.'
|
||||
|
@ -274,10 +291,12 @@ class Snapshot(EventWithOneDevice):
|
|||
|
||||
|
||||
class Test(EventWithOneDevice):
|
||||
__doc__ = m.Test.__doc__
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
|
||||
|
||||
class TestDataStorage(Test):
|
||||
__doc__ = m.TestDataStorage.__doc__
|
||||
length = EnumField(TestDataStorageLength, required=True)
|
||||
status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True)
|
||||
lifetime = TimeDelta(precision=TimeDelta.HOURS)
|
||||
|
@ -292,55 +311,59 @@ class TestDataStorage(Test):
|
|||
|
||||
|
||||
class StressTest(Test):
|
||||
pass
|
||||
__doc__ = m.StressTest.__doc__
|
||||
|
||||
|
||||
class Benchmark(EventWithOneDevice):
|
||||
__doc__ = m.Benchmark.__doc__
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
|
||||
|
||||
class BenchmarkDataStorage(Benchmark):
|
||||
__doc__ = m.BenchmarkDataStorage.__doc__
|
||||
read_speed = Float(required=True, data_key='readSpeed')
|
||||
write_speed = Float(required=True, data_key='writeSpeed')
|
||||
|
||||
|
||||
class BenchmarkWithRate(Benchmark):
|
||||
__doc__ = m.BenchmarkWithRate.__doc__
|
||||
rate = Float(required=True)
|
||||
|
||||
|
||||
class BenchmarkProcessor(BenchmarkWithRate):
|
||||
pass
|
||||
__doc__ = m.BenchmarkProcessor.__doc__
|
||||
|
||||
|
||||
class BenchmarkProcessorSysbench(BenchmarkProcessor):
|
||||
pass
|
||||
__doc__ = m.BenchmarkProcessorSysbench.__doc__
|
||||
|
||||
|
||||
class BenchmarkRamSysbench(BenchmarkWithRate):
|
||||
pass
|
||||
__doc__ = m.BenchmarkRamSysbench.__doc__
|
||||
|
||||
|
||||
class ToRepair(EventWithMultipleDevices):
|
||||
pass
|
||||
__doc__ = m.ToRepair.__doc__
|
||||
|
||||
|
||||
class Repair(EventWithMultipleDevices):
|
||||
pass
|
||||
__doc__ = m.Repair.__doc__
|
||||
|
||||
|
||||
class ReadyToUse(EventWithMultipleDevices):
|
||||
pass
|
||||
__doc__ = m.ReadyToUse.__doc__
|
||||
|
||||
|
||||
class ToPrepare(EventWithMultipleDevices):
|
||||
pass
|
||||
__doc__ = m.ToPrepare.__doc__
|
||||
|
||||
|
||||
class Prepare(EventWithMultipleDevices):
|
||||
pass
|
||||
__doc__ = m.Prepare.__doc__
|
||||
|
||||
|
||||
class Live(EventWithOneDevice):
|
||||
__doc__ = m.Live.__doc__
|
||||
ip = IP(dump_only=True)
|
||||
subdivision_confidence = Integer(dump_only=True, data_key='subdivisionConfidence')
|
||||
subdivision = EnumField(Subdivision, dump_only=True)
|
||||
|
@ -353,60 +376,63 @@ class Live(EventWithOneDevice):
|
|||
|
||||
|
||||
class Organize(EventWithMultipleDevices):
|
||||
pass
|
||||
__doc__ = m.Organize.__doc__
|
||||
|
||||
|
||||
class Reserve(Organize):
|
||||
pass
|
||||
__doc__ = m.Reserve.__doc__
|
||||
|
||||
|
||||
class CancelReservation(Organize):
|
||||
pass
|
||||
__doc__ = m.CancelReservation.__doc__
|
||||
|
||||
|
||||
class Trade(EventWithMultipleDevices):
|
||||
__doc__ = m.Trade.__doc__
|
||||
shipping_date = DateTime(data_key='shippingDate')
|
||||
invoice_number = SanitizedStr(validate=Length(max=STR_SIZE), data_key='invoiceNumber')
|
||||
price = NestedOn(Price)
|
||||
to = NestedOn(Agent, only_query='id', required=True, comment=m.Trade.to_comment)
|
||||
to = NestedOn(s_agent.Agent, only_query='id', required=True, comment=m.Trade.to_comment)
|
||||
confirms = NestedOn(Organize)
|
||||
|
||||
|
||||
class Sell(Trade):
|
||||
pass
|
||||
__doc__ = m.Sell.__doc__
|
||||
|
||||
|
||||
class Donate(Trade):
|
||||
pass
|
||||
__doc__ = m.Donate.__doc__
|
||||
|
||||
|
||||
class Rent(Trade):
|
||||
pass
|
||||
__doc__ = m.Rent.__doc__
|
||||
|
||||
|
||||
class CancelTrade(Trade):
|
||||
pass
|
||||
__doc__ = m.CancelTrade.__doc__
|
||||
|
||||
|
||||
class ToDisposeProduct(Trade):
|
||||
pass
|
||||
__doc__ = m.ToDisposeProduct.__doc__
|
||||
|
||||
|
||||
class DisposeProduct(Trade):
|
||||
pass
|
||||
__doc__ = m.DisposeProduct.__doc__
|
||||
|
||||
|
||||
class Receive(EventWithMultipleDevices):
|
||||
__doc__ = m.Receive.__doc__
|
||||
role = EnumField(ReceiverRole)
|
||||
|
||||
|
||||
class Migrate(EventWithMultipleDevices):
|
||||
__doc__ = m.Migrate.__doc__
|
||||
other = URL()
|
||||
|
||||
|
||||
class MigrateTo(Migrate):
|
||||
pass
|
||||
__doc__ = m.MigrateTo.__doc__
|
||||
|
||||
|
||||
class MigrateFrom(Migrate):
|
||||
pass
|
||||
__doc__ = m.MigrateFrom.__doc__
|
||||
|
|
|
@ -2,7 +2,7 @@ from marshmallow import fields as f
|
|||
from teal.marshmallow import SanitizedStr, URL
|
||||
|
||||
from ereuse_devicehub.marshmallow import NestedOn
|
||||
from ereuse_devicehub.resources.device.schemas import Device
|
||||
from ereuse_devicehub.resources.device import schemas as s_device
|
||||
from ereuse_devicehub.resources.lot import models as m
|
||||
from ereuse_devicehub.resources.models import STR_SIZE
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
|
@ -13,7 +13,7 @@ class Lot(Thing):
|
|||
name = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
|
||||
description = SanitizedStr(description=m.Lot.description.comment)
|
||||
closed = f.Boolean(missing=False, description=m.Lot.closed.comment)
|
||||
devices = NestedOn(Device, many=True, dump_only=True)
|
||||
devices = NestedOn(s_device.Device, many=True, dump_only=True)
|
||||
children = NestedOn('Lot', many=True, dump_only=True)
|
||||
parents = NestedOn('Lot', many=True, dump_only=True)
|
||||
url = URL(dump_only=True, description=m.Lot.url.__doc__)
|
||||
|
|
|
@ -9,21 +9,26 @@ STR_XSM_SIZE = 16
|
|||
|
||||
|
||||
class Thing(db.Model):
|
||||
"""The base class of all Devicehub resources.
|
||||
|
||||
This is a loose copy of
|
||||
`schema.org's Thing class <https://schema.org/Thing>`_
|
||||
using only needed fields.
|
||||
"""
|
||||
__abstract__ = True
|
||||
# todo make updated to auto-update
|
||||
updated = db.Column(db.TIMESTAMP(timezone=True),
|
||||
nullable=False,
|
||||
index=True,
|
||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
||||
updated.comment = """
|
||||
When this was last changed.
|
||||
The last time Devicehub recorded a change for this thing.
|
||||
"""
|
||||
created = db.Column(db.TIMESTAMP(timezone=True),
|
||||
nullable=False,
|
||||
index=True,
|
||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
||||
created.comment = """
|
||||
When Devicehub created this.
|
||||
When Devicehub created this.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from marshmallow import post_load
|
||||
from marshmallow.fields import DateTime, List, String
|
||||
from marshmallow.schema import SchemaMeta
|
||||
from teal.marshmallow import URL
|
||||
from teal.resource import Schema
|
||||
|
||||
|
@ -18,10 +20,59 @@ class UnitCodes(Enum):
|
|||
kgm = 'KGM'
|
||||
m = 'MTR'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
# The following SchemaMeta modifications allow us to generate
|
||||
# documentation using our directive. This is their only purpose.
|
||||
# Marshmallow's meta class removes variables from our defined
|
||||
# classes, so we put some home made proxies in order to intercept
|
||||
# those values and safe them in our classes.
|
||||
# What we do is:
|
||||
# 1. Make our ``Meta`` class be the superclass of Marshmallow's
|
||||
# SchemaMeta and provide a new that stores in class, so we
|
||||
# can save some vars.
|
||||
# 2. Substitute SchemaMeta.get_declared_fields with our own method
|
||||
# that saves more variables.
|
||||
# Then the directive in our docs/config.py file reads these variables
|
||||
# generating the documentation.
|
||||
|
||||
class Meta(type):
|
||||
|
||||
def __new__(cls, *args, **kw) -> Any:
|
||||
base_name = args[1][0].__name__
|
||||
y = super().__new__(cls, *args, **kw)
|
||||
y._base_class = base_name
|
||||
return y
|
||||
|
||||
|
||||
SchemaMeta.__bases__ = Meta,
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_declared_fields(mcs, klass, cls_fields, inherited_fields, dict_cls):
|
||||
klass._own = cls_fields
|
||||
klass._inherited = inherited_fields
|
||||
return dict_cls(inherited_fields + cls_fields)
|
||||
|
||||
|
||||
SchemaMeta.get_declared_fields = get_declared_fields
|
||||
|
||||
_type_description = """The name of the type of Thing,
|
||||
like "Device" or "Receive". This is the same as JSON-LD ``@type``.
|
||||
|
||||
This field is required when submitting values
|
||||
so Devicehub knows the type of object. Devicehub always returns this
|
||||
value.
|
||||
"""
|
||||
|
||||
|
||||
class Thing(Schema):
|
||||
type = String(description='Only required when it is nested.')
|
||||
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
|
||||
type = String(description=_type_description)
|
||||
same_as = List(URL(dump_only=True),
|
||||
dump_only=True,
|
||||
data_key='sameAs')
|
||||
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment)
|
||||
created = DateTime('iso', dump_only=True, description=m.Thing.created.comment)
|
||||
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import datetime
|
||||
|
||||
import teal.cache
|
||||
from flask import Response, current_app as app, g, redirect, request
|
||||
from flask_sqlalchemy import Pagination
|
||||
from teal.marshmallow import ValidationError
|
||||
|
@ -22,7 +19,6 @@ class TagView(View):
|
|||
res = self._post_one()
|
||||
return res
|
||||
|
||||
@teal.cache.cache(datetime.timedelta(minutes=1))
|
||||
def find(self, args: dict):
|
||||
tags = Tag.query.filter(Tag.is_printable_q()) \
|
||||
.order_by(Tag.created.desc()) \
|
||||
|
|
Reference in New Issue