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 and states
|
||||||
##################
|
##################
|
||||||
|
|
||||||
|
Actions
|
||||||
|
*******
|
||||||
|
|
||||||
Actions are events performed to devices, changing their **state**.
|
Actions are events performed to devices, changing their **state**.
|
||||||
Actions can have attributes defining
|
Actions can have attributes defining
|
||||||
**where** it happened, **who** performed them, **when**, etc.
|
**where** it happened, **who** performed them, **when**, etc.
|
||||||
|
@ -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,
|
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.
|
||||||
|
|
||||||
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
|
Devicehub actions inherit from `schema actions
|
||||||
<http://schema.org/Action>`_, are written in Pascal case and using
|
<http://schema.org/Action>`_, are written in Pascal case and using
|
||||||
a verb in infinitive. Some verbs represent the willingness or
|
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
|
that the reparation happened. The former actions have the preposition
|
||||||
*To* prefixing the verb.
|
*To* prefixing the verb.
|
||||||
|
|
||||||
In the following section we define the actions and states.
|
Actions and states affect devices in different ways or **dimensions**.
|
||||||
To see how to perform actions to the Devicehub API head
|
For example, ``Repair`` affects the **physical** dimension of a device,
|
||||||
to the `Swagger docs
|
and ``Sell`` the **political** dimension of a device. A device
|
||||||
<https://app.swaggerhub.com/apis/ereuse/devicehub/0.2>`_.
|
can be in several states at the same time, one per dimension; ie. a
|
||||||
|
device can be ``repaired`` (physical) and ``reserved`` (political),
|
||||||
.. toctree::
|
but not ``repaired`` and ``disposed`` at the same time:
|
||||||
:maxdepth: 4
|
|
||||||
|
|
||||||
actions
|
|
||||||
|
|
||||||
.. uml:: actions.puml
|
|
||||||
|
|
||||||
|
|
||||||
Physical Actions
|
- Physical actions: The following actions describe and react on the
|
||||||
****************
|
Physical condition of the devices.
|
||||||
The following actions describe and react on the
|
|
||||||
:class:`ereuse_devicehub.resources.device.states.Physical` condition
|
|
||||||
of the devices.
|
|
||||||
|
|
||||||
ToPrepare and Prepare
|
- ToPrepare and prepare.
|
||||||
==================
|
- ToRepair, Repair
|
||||||
Prepare
|
- ReadyToUse
|
||||||
-------
|
- Live
|
||||||
.. autoclass:: ereuse_devicehub.resources.event.models.Prepare
|
- DisposeWaste, Recover
|
||||||
ToPrepare
|
|
||||||
---------
|
|
||||||
.. autoclass:: ereuse_devicehub.resources.event.models.ToPrepare
|
|
||||||
|
|
||||||
ToRepair, Repair
|
- Association actions: Actions that change the associations users have with devices;
|
||||||
================
|
ie. the **owners**, **usufructuarees**, **reservees**,
|
||||||
Repair
|
and **physical possessors**.
|
||||||
------
|
|
||||||
.. autoclass:: ereuse_devicehub.resources.event.models.Repair
|
|
||||||
ToRepair
|
|
||||||
--------
|
|
||||||
.. autoclass:: ereuse_devicehub.resources.event.models.ToRepair
|
|
||||||
|
|
||||||
ReadyToUse
|
- Trade
|
||||||
==========
|
- Transfer
|
||||||
.. autoclass:: ereuse_devicehub.resources.event.models.ReadyToUse
|
- Organize
|
||||||
|
|
||||||
Live
|
- Internal state actions: Actions providing metadata about devices that don't usually change
|
||||||
====
|
their state.
|
||||||
.. autoclass:: ereuse_devicehub.resources.event.models.Live
|
|
||||||
|
|
||||||
DisposeWaste, Recover
|
- Snapshot
|
||||||
=====================
|
- Add, remove
|
||||||
``RecyclingCenter`` users have two extra special events:
|
- Erase
|
||||||
- ``DisposeWaste``: The device has been disposed in an unspecified
|
- Install
|
||||||
manner.
|
- Test
|
||||||
- ``Recover``: The device has been scrapped and its materials have
|
- Benchmark
|
||||||
been recovered under a new product.
|
- Rate
|
||||||
|
- Price
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
Add, Remove
|
The following index has all the actions (please note we are moving from calling them
|
||||||
===========
|
``Event`` to call them ``Action``):
|
||||||
.. autoclass:: ereuse_devicehub.resources.event.models.Add
|
|
||||||
.. autoclass:: ereuse_devicehub.resources.event.models.Remove
|
|
||||||
|
|
||||||
Erase
|
.. dhlist::
|
||||||
=====
|
:module: ereuse_devicehub.resources.event.schemas
|
||||||
.. 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 !!
|
|
||||||
|
|
||||||
|
|
||||||
States
|
States
|
||||||
|
@ -266,8 +71,4 @@ States
|
||||||
.. uml:: states.puml
|
.. uml:: states.puml
|
||||||
|
|
||||||
.. autoclass:: ereuse_devicehub.resources.device.states.Trading
|
.. autoclass:: ereuse_devicehub.resources.device.states.Trading
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
.. autoclass:: ereuse_devicehub.resources.device.states.Physical
|
.. 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 -----------------------------------------------------
|
# -- 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'
|
project = 'Devicehub'
|
||||||
copyright = '2018, eReuse.org team'
|
copyright = '2018, eReuse.org team'
|
||||||
|
@ -176,3 +189,123 @@ html_favicon = 'img/favicon.ico'
|
||||||
# autosectionlabel
|
# autosectionlabel
|
||||||
autosectionlabel_prefix_document = True
|
autosectionlabel_prefix_document = True
|
||||||
autodoc_member_order = 'bysource'
|
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:
|
The result is a JSON object with the following fields:
|
||||||
|
|
||||||
- **devices**: A list of devices.
|
- **items**: A list of devices.
|
||||||
- **groups**: A list of groups.
|
- **pagination**:
|
||||||
- **widgets**: A dictionary of widgets.
|
|
||||||
- **pagination**: Pagination information:
|
|
||||||
|
|
||||||
- **page**: The page you requested in the ``page`` param of the query,
|
- **page**: The page you requested in the ``page`` param of the query,
|
||||||
or ``1``.
|
or ``1``.
|
||||||
- **perPage**: How many devices are in every page, fixed to ``30``.
|
- **perPage**: How many devices are in every page, fixed to ``30``.
|
||||||
- **total**: How many total devices passed the filters.
|
- **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
|
.. dhlist::
|
||||||
******
|
:module: ereuse_devicehub.resources.device.schemas
|
||||||
|
|
||||||
.. automodule:: ereuse_devicehub.resources.device.models
|
|
||||||
:members:
|
|
||||||
:member-order: bysource
|
|
||||||
|
|
|
@ -31,10 +31,9 @@ Devicehub is built with `Teal <https://github.com/bustawin/teal>`_ and
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
processes
|
api
|
||||||
actions
|
|
||||||
agents
|
|
||||||
devices
|
devices
|
||||||
|
actions
|
||||||
tags
|
tags
|
||||||
lots
|
lots
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,11 @@ class Dummy:
|
||||||
)
|
)
|
||||||
"""Tags to create."""
|
"""Tags to create."""
|
||||||
ET = (
|
ET = (
|
||||||
('A0000000000001', 'DT-AAAAA'),
|
('DT-AAAAA', 'A0000000000001'),
|
||||||
('A0000000000002', 'DT-BBBBB'),
|
('DT-BBBBB', 'A0000000000002'),
|
||||||
('A0000000000003', 'DT-CCCCC'),
|
('DT-CCCCC', 'A0000000000003'),
|
||||||
('04970DA2A15984', 'DT-BRRAB'),
|
('DT-BRRAB', '04970DA2A15984'),
|
||||||
('04e4bc5af95980', 'DT-XXXXX')
|
('DT-XXXXX', '04e4bc5af95980')
|
||||||
)
|
)
|
||||||
"""eTags to create."""
|
"""eTags to create."""
|
||||||
ORG = 'eReuse.org CAT', '-t', 'G-60437761', '-c', 'ES'
|
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):
|
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')
|
EVENT_SORT_KEY = attrgetter('created')
|
||||||
|
|
||||||
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
||||||
id.comment = """
|
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)
|
type = Column(Unicode(STR_SM_SIZE), nullable=False, index=True)
|
||||||
hid = Column(Unicode(), check_lower('hid'), unique=True)
|
hid = Column(Unicode(), check_lower('hid'), unique=True)
|
||||||
hid.comment = """
|
hid.comment = """
|
||||||
The Hardware ID (HID) is the unique ID traceability systems
|
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 = 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 = 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 = 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 = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
|
||||||
weight.comment = """
|
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 = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
|
||||||
width.comment = """
|
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 = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
|
||||||
height.comment = """
|
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 = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
|
||||||
depth.comment = """
|
depth.comment = """
|
||||||
The depth of the device in meters.
|
The depth of the device.
|
||||||
"""
|
"""
|
||||||
color = Column(ColorType)
|
color = Column(ColorType)
|
||||||
color.comment = """The predominant color of the device."""
|
color.comment = """The predominant color of the device."""
|
||||||
production_date = Column(db.TIMESTAMP(timezone=True))
|
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 = {
|
_NON_PHYSICAL_PROPS = {
|
||||||
'id',
|
'id',
|
||||||
|
@ -91,11 +122,13 @@ class Device(Thing):
|
||||||
@property
|
@property
|
||||||
def events(self) -> list:
|
def events(self) -> list:
|
||||||
"""
|
"""
|
||||||
All the events where the device participated, including
|
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.
|
|
||||||
|
|
||||||
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)
|
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.
|
device is working if the list is empty.
|
||||||
|
|
||||||
This property returns, for the last test performed of each type,
|
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.
|
test has been executed.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.event.models import Test
|
from ereuse_devicehub.resources.event.models import Test
|
||||||
|
@ -292,8 +325,18 @@ class DisplayMixin:
|
||||||
|
|
||||||
|
|
||||||
class Computer(Device):
|
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)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
chassis = Column(DBEnum(ComputerChassis), nullable=False)
|
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:
|
def __init__(self, chassis, **kwargs) -> None:
|
||||||
chassis = ComputerChassis(chassis)
|
chassis = ComputerChassis(chassis)
|
||||||
|
@ -342,7 +385,7 @@ class Computer(Device):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def privacy(self):
|
def privacy(self):
|
||||||
"""Returns the privacy of all DataStorage components when
|
"""Returns the privacy of all ``DataStorage`` components when
|
||||||
it is not None.
|
it is not None.
|
||||||
"""
|
"""
|
||||||
return set(
|
return set(
|
||||||
|
@ -395,6 +438,8 @@ class Projector(Monitor):
|
||||||
|
|
||||||
|
|
||||||
class Mobile(Device):
|
class Mobile(Device):
|
||||||
|
"""A mobile device consisting of smartphones, tablets, and cellphones."""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
imei = Column(BigInteger)
|
imei = Column(BigInteger)
|
||||||
imei.comment = """
|
imei.comment = """
|
||||||
|
@ -432,6 +477,7 @@ class Cellphone(Mobile):
|
||||||
|
|
||||||
|
|
||||||
class Component(Device):
|
class Component(Device):
|
||||||
|
"""A device that can be inside another device."""
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
parent_id = Column(BigInteger, ForeignKey(Computer.id), index=True)
|
parent_id = Column(BigInteger, ForeignKey(Computer.id), index=True)
|
||||||
|
@ -481,6 +527,7 @@ class GraphicCard(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
|
|
||||||
class DataStorage(JoinedComponentTableMixin, Component):
|
class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
|
"""A device that stores information."""
|
||||||
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
||||||
size.comment = """
|
size.comment = """
|
||||||
The size of the data-storage in MB.
|
The size of the data-storage in MB.
|
||||||
|
@ -548,14 +595,21 @@ class NetworkAdapter(JoinedComponentTableMixin, NetworkMixin, Component):
|
||||||
|
|
||||||
|
|
||||||
class Processor(JoinedComponentTableMixin, Component):
|
class Processor(JoinedComponentTableMixin, Component):
|
||||||
|
"""The CPU."""
|
||||||
speed = Column(Float, check_range('speed', 0.1, 15))
|
speed = Column(Float, check_range('speed', 0.1, 15))
|
||||||
|
speed.comment = """The regular CPU speed."""
|
||||||
cores = Column(SmallInteger, check_range('cores', 1, 10))
|
cores = Column(SmallInteger, check_range('cores', 1, 10))
|
||||||
|
cores.comment = """The number of regular cores."""
|
||||||
threads = Column(SmallInteger, check_range('threads', 1, 20))
|
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 = 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):
|
class RamModule(JoinedComponentTableMixin, Component):
|
||||||
|
"""A stick of RAM."""
|
||||||
size = Column(SmallInteger, check_range('size', min=128, max=17000))
|
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))
|
speed = Column(SmallInteger, check_range('speed', min=100, max=10000))
|
||||||
interface = Column(DBEnum(RamInterface))
|
interface = Column(DBEnum(RamInterface))
|
||||||
format = Column(DBEnum(RamFormat))
|
format = Column(DBEnum(RamFormat))
|
||||||
|
@ -568,14 +622,15 @@ class SoundCard(JoinedComponentTableMixin, Component):
|
||||||
class Display(JoinedComponentTableMixin, DisplayMixin, Component):
|
class Display(JoinedComponentTableMixin, DisplayMixin, Component):
|
||||||
"""
|
"""
|
||||||
The display of a device. This is used in all devices that have
|
The display of a device. This is used in all devices that have
|
||||||
displays but that it is not their main treat, like laptops,
|
displays but that it is not their main part, like laptops,
|
||||||
mobiles, smart-watches, and so on; excluding then ComputerMonitor
|
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
|
||||||
and Television Set.
|
and ``TelevisionSet``.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ComputerAccessory(Device):
|
class ComputerAccessory(Device):
|
||||||
|
"""Computer peripherals and similar accessories."""
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -597,6 +652,7 @@ class MemoryCardReader(ComputerAccessory):
|
||||||
|
|
||||||
|
|
||||||
class Networking(NetworkMixin, Device):
|
class Networking(NetworkMixin, Device):
|
||||||
|
"""Routers, switches, hubs..."""
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -641,6 +697,7 @@ class Microphone(Sound):
|
||||||
|
|
||||||
|
|
||||||
class Video(Device):
|
class Video(Device):
|
||||||
|
"""Devices related to video treatment."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -653,6 +710,7 @@ class Videoconference(Video):
|
||||||
|
|
||||||
|
|
||||||
class Cooking(Device):
|
class Cooking(Device):
|
||||||
|
"""Cooking devices."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -661,6 +719,11 @@ class Mixer(Cooking):
|
||||||
|
|
||||||
|
|
||||||
class Manufacturer(db.Model):
|
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'}
|
__table_args__ = {'schema': 'common'}
|
||||||
CSV_DELIMITER = csv.get_dialect('excel').delimiter
|
CSV_DELIMITER = csv.get_dialect('excel').delimiter
|
||||||
|
|
||||||
|
@ -668,8 +731,11 @@ class Manufacturer(db.Model):
|
||||||
primary_key=True,
|
primary_key=True,
|
||||||
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
|
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
|
||||||
index=db.Index('name', text('name gin_trgm_ops'), postgresql_using='gin'))
|
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 = db.Column(URL(), unique=True)
|
||||||
|
url.comment = """An URL to a page describing the manufacturer."""
|
||||||
logo = db.Column(URL())
|
logo = db.Column(URL())
|
||||||
|
logo.comment = """An URL pointing to the logo of the manufacturer."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_all_to_session(cls, session: db.Session):
|
def add_all_to_session(cls, session: db.Session):
|
||||||
|
|
|
@ -287,11 +287,13 @@ class Processor(Component):
|
||||||
speed = ... # type: Column
|
speed = ... # type: Column
|
||||||
cores = ... # type: Column
|
cores = ... # type: Column
|
||||||
address = ... # type: Column
|
address = ... # type: Column
|
||||||
|
threads = ... # type: Column
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.speed = ... # type: float
|
self.speed = ... # type: float
|
||||||
self.cores = ... # type: int
|
self.cores = ... # type: int
|
||||||
|
self.threads = ... # type: int
|
||||||
self.address = ... # type: int
|
self.address = ... # type: int
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,13 @@ from ereuse_devicehub.resources.schemas import Thing, UnitCodes
|
||||||
|
|
||||||
|
|
||||||
class Device(Thing):
|
class Device(Thing):
|
||||||
|
__doc__ = m.Device.__doc__
|
||||||
id = Integer(description=m.Device.id.comment, dump_only=True)
|
id = Integer(description=m.Device.id.comment, dump_only=True)
|
||||||
hid = SanitizedStr(lower=True, dump_only=True, description=m.Device.hid.comment)
|
hid = SanitizedStr(lower=True, dump_only=True, description=m.Device.hid.comment)
|
||||||
tags = NestedOn('Tag',
|
tags = NestedOn('Tag',
|
||||||
many=True,
|
many=True,
|
||||||
collection_class=OrderedSet,
|
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))
|
model = SanitizedStr(lower=True, validate=Length(max=STR_BIG_SIZE))
|
||||||
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
|
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
|
||||||
serial_number = SanitizedStr(lower=True, data_key='serialNumber')
|
serial_number = SanitizedStr(lower=True, data_key='serialNumber')
|
||||||
|
@ -75,29 +76,54 @@ class Device(Thing):
|
||||||
|
|
||||||
|
|
||||||
class Computer(Device):
|
class Computer(Device):
|
||||||
components = NestedOn('Component', many=True, dump_only=True, collection_class=OrderedSet)
|
__doc__ = m.Computer.__doc__
|
||||||
chassis = EnumField(enums.ComputerChassis, required=True)
|
components = NestedOn('Component',
|
||||||
ram_size = Integer(dump_only=True, data_key='ramSize')
|
many=True,
|
||||||
data_storage_size = Integer(dump_only=True, data_key='dataStorageSize')
|
dump_only=True,
|
||||||
processor_model = Str(dump_only=True, data_key='processorModel')
|
collection_class=OrderedSet,
|
||||||
graphic_card_model = Str(dump_only=True, data_key='graphicCardModel')
|
description='The components that are inside this computer.')
|
||||||
network_speeds = List(Integer(dump_only=True), dump_only=True, data_key='networkSpeeds')
|
chassis = EnumField(enums.ComputerChassis,
|
||||||
privacy = NestedOn('Event', many=True, dump_only=True, collection_class=set)
|
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):
|
class Desktop(Computer):
|
||||||
pass
|
__doc__ = m.Desktop.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Laptop(Computer):
|
class Laptop(Computer):
|
||||||
pass
|
layout = EnumField(Layouts, description=m.Laptop.layout.comment)
|
||||||
|
__doc__ = m.Laptop.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Server(Computer):
|
class Server(Computer):
|
||||||
pass
|
__doc__ = m.Server.__doc__
|
||||||
|
|
||||||
|
|
||||||
class DisplayMixin:
|
class DisplayMixin:
|
||||||
|
__doc__ = m.DisplayMixin.__doc__
|
||||||
|
|
||||||
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150))
|
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150))
|
||||||
technology = EnumField(enums.DisplayTech,
|
technology = EnumField(enums.DisplayTech,
|
||||||
description=m.DisplayMixin.technology.comment)
|
description=m.DisplayMixin.technology.comment)
|
||||||
|
@ -113,6 +139,8 @@ class DisplayMixin:
|
||||||
|
|
||||||
|
|
||||||
class NetworkMixin:
|
class NetworkMixin:
|
||||||
|
__doc__ = m.NetworkMixin.__doc__
|
||||||
|
|
||||||
speed = Integer(validate=Range(min=10, max=10000),
|
speed = Integer(validate=Range(min=10, max=10000),
|
||||||
unit=UnitCodes.mbps,
|
unit=UnitCodes.mbps,
|
||||||
description=m.NetworkAdapter.speed.comment)
|
description=m.NetworkAdapter.speed.comment)
|
||||||
|
@ -120,18 +148,20 @@ class NetworkMixin:
|
||||||
|
|
||||||
|
|
||||||
class Monitor(DisplayMixin, Device):
|
class Monitor(DisplayMixin, Device):
|
||||||
pass
|
__doc__ = m.Monitor.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ComputerMonitor(Monitor):
|
class ComputerMonitor(Monitor):
|
||||||
pass
|
__doc__ = m.ComputerMonitor.__doc__
|
||||||
|
|
||||||
|
|
||||||
class TelevisionSet(Monitor):
|
class TelevisionSet(Monitor):
|
||||||
pass
|
__doc__ = m.TelevisionSet.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Mobile(Device):
|
class Mobile(Device):
|
||||||
|
__doc__ = m.Mobile.__doc__
|
||||||
|
|
||||||
imei = Integer(description=m.Mobile.imei.comment)
|
imei = Integer(description=m.Mobile.imei.comment)
|
||||||
meid = Str(description=m.Mobile.meid.comment)
|
meid = Str(description=m.Mobile.meid.comment)
|
||||||
|
|
||||||
|
@ -149,28 +179,34 @@ class Mobile(Device):
|
||||||
|
|
||||||
|
|
||||||
class Smartphone(Mobile):
|
class Smartphone(Mobile):
|
||||||
pass
|
__doc__ = m.Smartphone.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Tablet(Mobile):
|
class Tablet(Mobile):
|
||||||
pass
|
__doc__ = m.Tablet.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Cellphone(Mobile):
|
class Cellphone(Mobile):
|
||||||
pass
|
__doc__ = m.Cellphone.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Component(Device):
|
class Component(Device):
|
||||||
|
__doc__ = m.Component.__doc__
|
||||||
|
|
||||||
parent = NestedOn(Device, dump_only=True)
|
parent = NestedOn(Device, dump_only=True)
|
||||||
|
|
||||||
|
|
||||||
class GraphicCard(Component):
|
class GraphicCard(Component):
|
||||||
|
__doc__ = m.GraphicCard.__doc__
|
||||||
|
|
||||||
memory = Integer(validate=Range(0, 10000),
|
memory = Integer(validate=Range(0, 10000),
|
||||||
unit=UnitCodes.mbyte,
|
unit=UnitCodes.mbyte,
|
||||||
description=m.GraphicCard.memory.comment)
|
description=m.GraphicCard.memory.comment)
|
||||||
|
|
||||||
|
|
||||||
class DataStorage(Component):
|
class DataStorage(Component):
|
||||||
|
__doc__ = m.DataStorage.__doc__
|
||||||
|
|
||||||
size = Integer(validate=Range(0, 10 ** 8),
|
size = Integer(validate=Range(0, 10 ** 8),
|
||||||
unit=UnitCodes.mbyte,
|
unit=UnitCodes.mbyte,
|
||||||
description=m.DataStorage.size.comment)
|
description=m.DataStorage.size.comment)
|
||||||
|
@ -179,128 +215,147 @@ class DataStorage(Component):
|
||||||
|
|
||||||
|
|
||||||
class HardDrive(DataStorage):
|
class HardDrive(DataStorage):
|
||||||
pass
|
__doc__ = m.HardDrive.__doc__
|
||||||
|
|
||||||
|
|
||||||
class SolidStateDrive(DataStorage):
|
class SolidStateDrive(DataStorage):
|
||||||
pass
|
__doc__ = m.SolidStateDrive.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Motherboard(Component):
|
class Motherboard(Component):
|
||||||
|
__doc__ = m.Motherboard.__doc__
|
||||||
|
|
||||||
slots = Integer(validate=Range(0, 20),
|
slots = Integer(validate=Range(0, 20),
|
||||||
description=m.Motherboard.slots.comment)
|
description=m.Motherboard.slots.comment)
|
||||||
usb = Integer(validate=Range(0, 20))
|
usb = Integer(validate=Range(0, 20), description=m.Motherboard.usb.comment)
|
||||||
firewire = Integer(validate=Range(0, 20))
|
firewire = Integer(validate=Range(0, 20), description=m.Motherboard.firewire.comment)
|
||||||
serial = Integer(validate=Range(0, 20))
|
serial = Integer(validate=Range(0, 20), description=m.Motherboard.serial.comment)
|
||||||
pcmcia = Integer(validate=Range(0, 20))
|
pcmcia = Integer(validate=Range(0, 20), description=m.Motherboard.pcmcia.comment)
|
||||||
|
|
||||||
|
|
||||||
class NetworkAdapter(NetworkMixin, Component):
|
class NetworkAdapter(NetworkMixin, Component):
|
||||||
pass
|
__doc__ = m.NetworkAdapter.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Processor(Component):
|
class Processor(Component):
|
||||||
speed = Float(validate=Range(min=0.1, max=15), unit=UnitCodes.ghz)
|
__doc__ = m.Processor.__doc__
|
||||||
cores = Integer(validate=Range(min=1, max=10))
|
|
||||||
threads = Integer(validate=Range(min=1, max=20))
|
speed = Float(validate=Range(min=0.1, max=15),
|
||||||
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}))
|
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):
|
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)
|
speed = Integer(validate=Range(min=100, max=10000), unit=UnitCodes.mhz)
|
||||||
interface = EnumField(enums.RamInterface)
|
interface = EnumField(enums.RamInterface)
|
||||||
format = EnumField(enums.RamFormat)
|
format = EnumField(enums.RamFormat)
|
||||||
|
|
||||||
|
|
||||||
class SoundCard(Component):
|
class SoundCard(Component):
|
||||||
pass
|
__doc__ = m.SoundCard.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Display(DisplayMixin, Component):
|
class Display(DisplayMixin, Component):
|
||||||
pass
|
__doc__ = m.Display.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Manufacturer(Schema):
|
class Manufacturer(Schema):
|
||||||
|
__doc__ = m.Manufacturer.__doc__
|
||||||
|
|
||||||
name = String(dump_only=True)
|
name = String(dump_only=True)
|
||||||
url = URL(dump_only=True)
|
url = URL(dump_only=True)
|
||||||
logo = URL(dump_only=True)
|
logo = URL(dump_only=True)
|
||||||
|
|
||||||
|
|
||||||
class ComputerAccessory(Device):
|
class ComputerAccessory(Device):
|
||||||
pass
|
__doc__ = m.ComputerAccessory.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Mouse(ComputerAccessory):
|
class Mouse(ComputerAccessory):
|
||||||
pass
|
__doc__ = m.Mouse.__doc__
|
||||||
|
|
||||||
|
|
||||||
class MemoryCardReader(ComputerAccessory):
|
class MemoryCardReader(ComputerAccessory):
|
||||||
pass
|
__doc__ = m.MemoryCardReader.__doc__
|
||||||
|
|
||||||
|
|
||||||
class SAI(ComputerAccessory):
|
class SAI(ComputerAccessory):
|
||||||
pass
|
__doc__ = m.SAI.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Keyboard(ComputerAccessory):
|
class Keyboard(ComputerAccessory):
|
||||||
|
__doc__ = m.Keyboard.__doc__
|
||||||
|
|
||||||
layout = EnumField(Layouts)
|
layout = EnumField(Layouts)
|
||||||
|
|
||||||
|
|
||||||
class Networking(NetworkMixin, Device):
|
class Networking(NetworkMixin, Device):
|
||||||
pass
|
__doc__ = m.Networking.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Router(Networking):
|
class Router(Networking):
|
||||||
pass
|
__doc__ = m.Router.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Switch(Networking):
|
class Switch(Networking):
|
||||||
pass
|
__doc__ = m.Switch.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Hub(Networking):
|
class Hub(Networking):
|
||||||
pass
|
__doc__ = m.Hub.__doc__
|
||||||
|
|
||||||
|
|
||||||
class WirelessAccessPoint(Networking):
|
class WirelessAccessPoint(Networking):
|
||||||
pass
|
__doc__ = m.WirelessAccessPoint.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Printer(Device):
|
class Printer(Device):
|
||||||
wireless = Boolean(required=True, missing=False)
|
__doc__ = m.Printer.__doc__
|
||||||
scanning = Boolean(required=True, missing=False)
|
|
||||||
technology = EnumField(enums.PrinterTechnology, required=True)
|
wireless = Boolean(required=True, missing=False, description=m.Printer.wireless.comment)
|
||||||
monochrome = Boolean(required=True, missing=True)
|
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):
|
class LabelPrinter(Printer):
|
||||||
pass
|
__doc__ = m.LabelPrinter.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Sound(Device):
|
class Sound(Device):
|
||||||
pass
|
__doc__ = m.Sound.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Microphone(Sound):
|
class Microphone(Sound):
|
||||||
pass
|
__doc__ = m.Microphone.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Video(Device):
|
class Video(Device):
|
||||||
pass
|
__doc__ = m.Video.__doc__
|
||||||
|
|
||||||
|
|
||||||
class VideoScaler(Video):
|
class VideoScaler(Video):
|
||||||
pass
|
__doc__ = m.VideoScaler.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Videoconference(Video):
|
class Videoconference(Video):
|
||||||
pass
|
__doc__ = m.Videoconference.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Cooking(Device):
|
class Cooking(Device):
|
||||||
pass
|
__doc__ = m.Cooking.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Mixer(Cooking):
|
class Mixer(Cooking):
|
||||||
pass
|
__doc__ = m.Mixer.__doc__
|
||||||
|
|
|
@ -16,6 +16,19 @@ class State(Enum):
|
||||||
|
|
||||||
|
|
||||||
class Trading(State):
|
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
|
Reserved = e.Reserve
|
||||||
Cancelled = e.CancelTrade
|
Cancelled = e.CancelTrade
|
||||||
Sold = e.Sell
|
Sold = e.Sell
|
||||||
|
@ -27,6 +40,16 @@ class Trading(State):
|
||||||
|
|
||||||
|
|
||||||
class Physical(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
|
ToBeRepaired = e.ToRepair
|
||||||
Repaired = e.Repair
|
Repaired = e.Repair
|
||||||
Preparing = e.ToPrepare
|
Preparing = e.ToPrepare
|
||||||
|
|
|
@ -278,17 +278,15 @@ class PrinterTechnology(Enum):
|
||||||
|
|
||||||
class Severity(IntEnum):
|
class Severity(IntEnum):
|
||||||
"""A flag evaluating the event execution. Ex. failed events
|
"""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
|
Notices are not usually that important but something
|
||||||
(good or bad) worth checking.
|
(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.
|
to check negatively affecting the event.
|
||||||
- Error: the event failed.
|
* Error: the event failed.
|
||||||
|
|
||||||
Devicehub specially raises user awareness when an event
|
Devicehub specially raises user awareness when an event
|
||||||
has a Severity of ``Warning`` or greater.
|
has a Severity of ``Warning`` or greater.
|
||||||
|
|
|
@ -44,6 +44,10 @@ class JoinedTableMixin:
|
||||||
|
|
||||||
|
|
||||||
class Event(Thing):
|
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)
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
type = Column(Unicode, nullable=False, index=True)
|
type = Column(Unicode, nullable=False, index=True)
|
||||||
name = Column(CIText(), default='', nullable=False)
|
name = Column(CIText(), default='', nullable=False)
|
||||||
|
@ -1179,6 +1183,9 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices):
|
||||||
|
|
||||||
Performing trade events changes the *Trading* state of the
|
Performing trade events changes the *Trading* state of the
|
||||||
device —:class:`ereuse_devicehub.resources.device.states.Trading`.
|
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 = Column(DateTime)
|
||||||
shipping_date.comment = """
|
shipping_date.comment = """
|
||||||
|
|
|
@ -10,18 +10,19 @@ from teal.resource import Schema
|
||||||
|
|
||||||
from ereuse_devicehub.marshmallow import NestedOn
|
from ereuse_devicehub.marshmallow import NestedOn
|
||||||
from ereuse_devicehub.resources import enums
|
from ereuse_devicehub.resources import enums
|
||||||
from ereuse_devicehub.resources.agent.schemas import Agent
|
from ereuse_devicehub.resources.agent import schemas as s_agent
|
||||||
from ereuse_devicehub.resources.device.schemas import Component, Computer, Device
|
from ereuse_devicehub.resources.device import schemas as s_device
|
||||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
||||||
PhysicalErasureMethod, PriceSoftware, RATE_POSITIVE, RatingRange, RatingSoftware, ReceiverRole, \
|
PhysicalErasureMethod, PriceSoftware, RATE_POSITIVE, RatingRange, RatingSoftware, ReceiverRole, \
|
||||||
Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
|
Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
|
||||||
from ereuse_devicehub.resources.event import models as m
|
from ereuse_devicehub.resources.event import models as m
|
||||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||||
from ereuse_devicehub.resources.schemas import Thing
|
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):
|
class Event(Thing):
|
||||||
|
__doc__ = m.Event.__doc__
|
||||||
id = UUID(dump_only=True)
|
id = UUID(dump_only=True)
|
||||||
name = SanitizedStr(default='',
|
name = SanitizedStr(default='',
|
||||||
validate=Length(max=STR_BIG_SIZE),
|
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)
|
start_time = DateTime(data_key='startTime', description=m.Event.start_time.comment)
|
||||||
end_time = DateTime(data_key='endTime', description=m.Event.end_time.comment)
|
end_time = DateTime(data_key='endTime', description=m.Event.end_time.comment)
|
||||||
snapshot = NestedOn('Snapshot', dump_only=True)
|
snapshot = NestedOn('Snapshot', dump_only=True)
|
||||||
agent = NestedOn(Agent, description=m.Event.agent_id.comment)
|
agent = NestedOn(s_agent.Agent, description=m.Event.agent_id.comment)
|
||||||
author = NestedOn(User, dump_only=True, exclude=('token',))
|
author = NestedOn(s_user.User, dump_only=True, exclude=('token',))
|
||||||
components = NestedOn(Component, dump_only=True, many=True)
|
components = NestedOn(s_device.Component, dump_only=True, many=True)
|
||||||
parent = NestedOn(Computer, dump_only=True, description=m.Event.parent_id.comment)
|
parent = NestedOn(s_device.Computer, dump_only=True, description=m.Event.parent_id.comment)
|
||||||
url = URL(dump_only=True, description=m.Event.url.__doc__)
|
url = URL(dump_only=True, description=m.Event.url.__doc__)
|
||||||
|
|
||||||
|
|
||||||
class EventWithOneDevice(Event):
|
class EventWithOneDevice(Event):
|
||||||
device = NestedOn(Device, only_query='id')
|
__doc__ = m.EventWithOneDevice.__doc__
|
||||||
|
device = NestedOn(s_device.Device, only_query='id')
|
||||||
|
|
||||||
|
|
||||||
class EventWithMultipleDevices(Event):
|
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):
|
class Add(EventWithOneDevice):
|
||||||
pass
|
__doc__ = m.Add.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Remove(EventWithOneDevice):
|
class Remove(EventWithOneDevice):
|
||||||
pass
|
__doc__ = m.Remove.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Allocate(EventWithMultipleDevices):
|
class Allocate(EventWithMultipleDevices):
|
||||||
to = NestedOn(User,
|
__doc__ = m.Allocate.__doc__
|
||||||
|
to = NestedOn(s_user.User,
|
||||||
description='The user the devices are allocated to.')
|
description='The user the devices are allocated to.')
|
||||||
organization = SanitizedStr(validate=Length(max=STR_SIZE),
|
organization = SanitizedStr(validate=Length(max=STR_SIZE),
|
||||||
description='The organization where the '
|
description='The organization where the '
|
||||||
|
@ -64,7 +68,8 @@ class Allocate(EventWithMultipleDevices):
|
||||||
|
|
||||||
|
|
||||||
class Deallocate(EventWithMultipleDevices):
|
class Deallocate(EventWithMultipleDevices):
|
||||||
from_rel = Nested(User,
|
__doc__ = m.Deallocate.__doc__
|
||||||
|
from_rel = Nested(s_user.User,
|
||||||
data_key='from',
|
data_key='from',
|
||||||
description='The user where the devices are not allocated to anymore.')
|
description='The user where the devices are not allocated to anymore.')
|
||||||
organization = SanitizedStr(validate=Length(max=STR_SIZE),
|
organization = SanitizedStr(validate=Length(max=STR_SIZE),
|
||||||
|
@ -73,20 +78,23 @@ class Deallocate(EventWithMultipleDevices):
|
||||||
|
|
||||||
|
|
||||||
class EraseBasic(EventWithOneDevice):
|
class EraseBasic(EventWithOneDevice):
|
||||||
|
__doc__ = m.EraseBasic.__doc__
|
||||||
steps = NestedOn('Step', many=True)
|
steps = NestedOn('Step', many=True)
|
||||||
standards = f.List(EnumField(enums.ErasureStandards), dump_only=True)
|
standards = f.List(EnumField(enums.ErasureStandards), dump_only=True)
|
||||||
certificate = URL(dump_only=True)
|
certificate = URL(dump_only=True)
|
||||||
|
|
||||||
|
|
||||||
class EraseSectors(EraseBasic):
|
class EraseSectors(EraseBasic):
|
||||||
pass
|
__doc__ = m.EraseSectors.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ErasePhysical(EraseBasic):
|
class ErasePhysical(EraseBasic):
|
||||||
|
__doc__ = m.ErasePhysical.__doc__
|
||||||
method = EnumField(PhysicalErasureMethod, description=PhysicalErasureMethod.__doc__)
|
method = EnumField(PhysicalErasureMethod, description=PhysicalErasureMethod.__doc__)
|
||||||
|
|
||||||
|
|
||||||
class Step(Schema):
|
class Step(Schema):
|
||||||
|
__doc__ = m.Step.__doc__
|
||||||
type = String(description='Only required when it is nested.')
|
type = String(description='Only required when it is nested.')
|
||||||
start_time = DateTime(required=True, data_key='startTime')
|
start_time = DateTime(required=True, data_key='startTime')
|
||||||
end_time = DateTime(required=True, data_key='endTime')
|
end_time = DateTime(required=True, data_key='endTime')
|
||||||
|
@ -94,14 +102,15 @@ class Step(Schema):
|
||||||
|
|
||||||
|
|
||||||
class StepZero(Step):
|
class StepZero(Step):
|
||||||
pass
|
__doc__ = m.StepZero.__doc__
|
||||||
|
|
||||||
|
|
||||||
class StepRandom(Step):
|
class StepRandom(Step):
|
||||||
pass
|
__doc__ = m.StepRandom.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Rate(EventWithOneDevice):
|
class Rate(EventWithOneDevice):
|
||||||
|
__doc__ = m.Rate.__doc__
|
||||||
rating = Integer(validate=Range(*RATE_POSITIVE),
|
rating = Integer(validate=Range(*RATE_POSITIVE),
|
||||||
dump_only=True,
|
dump_only=True,
|
||||||
description=m.Rate.rating.comment)
|
description=m.Rate.rating.comment)
|
||||||
|
@ -116,10 +125,11 @@ class Rate(EventWithOneDevice):
|
||||||
|
|
||||||
|
|
||||||
class IndividualRate(Rate):
|
class IndividualRate(Rate):
|
||||||
pass
|
__doc__ = m.IndividualRate.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ManualRate(IndividualRate):
|
class ManualRate(IndividualRate):
|
||||||
|
__doc__ = m.ManualRate.__doc__
|
||||||
appearance_range = EnumField(AppearanceRange,
|
appearance_range = EnumField(AppearanceRange,
|
||||||
required=True,
|
required=True,
|
||||||
data_key='appearanceRange',
|
data_key='appearanceRange',
|
||||||
|
@ -132,6 +142,7 @@ class ManualRate(IndividualRate):
|
||||||
|
|
||||||
|
|
||||||
class WorkbenchRate(ManualRate):
|
class WorkbenchRate(ManualRate):
|
||||||
|
__doc__ = m.WorkbenchRate.__doc__
|
||||||
processor = Float()
|
processor = Float()
|
||||||
ram = Float()
|
ram = Float()
|
||||||
data_storage = Float()
|
data_storage = Float()
|
||||||
|
@ -147,6 +158,7 @@ class WorkbenchRate(ManualRate):
|
||||||
|
|
||||||
|
|
||||||
class AggregateRate(Rate):
|
class AggregateRate(Rate):
|
||||||
|
__doc__ = m.AggregateRate.__doc__
|
||||||
workbench = NestedOn(WorkbenchRate, dump_only=True,
|
workbench = NestedOn(WorkbenchRate, dump_only=True,
|
||||||
description=m.AggregateRate.workbench_id.comment)
|
description=m.AggregateRate.workbench_id.comment)
|
||||||
manual = NestedOn(ManualRate,
|
manual = NestedOn(ManualRate,
|
||||||
|
@ -176,6 +188,7 @@ class AggregateRate(Rate):
|
||||||
|
|
||||||
|
|
||||||
class Price(EventWithOneDevice):
|
class Price(EventWithOneDevice):
|
||||||
|
__doc__ = m.Price.__doc__
|
||||||
currency = EnumField(Currency, required=True, description=m.Price.currency.comment)
|
currency = EnumField(Currency, required=True, description=m.Price.currency.comment)
|
||||||
price = Decimal(places=m.Price.SCALE,
|
price = Decimal(places=m.Price.SCALE,
|
||||||
rounding=m.Price.ROUND,
|
rounding=m.Price.ROUND,
|
||||||
|
@ -187,6 +200,8 @@ class Price(EventWithOneDevice):
|
||||||
|
|
||||||
|
|
||||||
class EreusePrice(Price):
|
class EreusePrice(Price):
|
||||||
|
__doc__ = m.EreusePrice.__doc__
|
||||||
|
|
||||||
class Service(MarshmallowSchema):
|
class Service(MarshmallowSchema):
|
||||||
class Type(MarshmallowSchema):
|
class Type(MarshmallowSchema):
|
||||||
amount = Float()
|
amount = Float()
|
||||||
|
@ -202,6 +217,7 @@ class EreusePrice(Price):
|
||||||
|
|
||||||
|
|
||||||
class Install(EventWithOneDevice):
|
class Install(EventWithOneDevice):
|
||||||
|
__doc__ = m.Install.__doc__
|
||||||
name = SanitizedStr(validate=Length(min=4, max=STR_BIG_SIZE),
|
name = SanitizedStr(validate=Length(min=4, max=STR_BIG_SIZE),
|
||||||
required=True,
|
required=True,
|
||||||
description='The name of the OS installed.')
|
description='The name of the OS installed.')
|
||||||
|
@ -210,6 +226,7 @@ class Install(EventWithOneDevice):
|
||||||
|
|
||||||
|
|
||||||
class Snapshot(EventWithOneDevice):
|
class Snapshot(EventWithOneDevice):
|
||||||
|
__doc__ = m.Snapshot.__doc__
|
||||||
"""
|
"""
|
||||||
The Snapshot updates the state of the device with information about
|
The Snapshot updates the state of the device with information about
|
||||||
its components and events performed at them.
|
its components and events performed at them.
|
||||||
|
@ -229,7 +246,7 @@ class Snapshot(EventWithOneDevice):
|
||||||
'the async Snapshot.')
|
'the async Snapshot.')
|
||||||
|
|
||||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS)
|
elapsed = TimeDelta(precision=TimeDelta.SECONDS)
|
||||||
components = NestedOn(Component,
|
components = NestedOn(s_device.Component,
|
||||||
many=True,
|
many=True,
|
||||||
description='A list of components that are inside of the device'
|
description='A list of components that are inside of the device'
|
||||||
'at the moment of this Snapshot.'
|
'at the moment of this Snapshot.'
|
||||||
|
@ -274,10 +291,12 @@ class Snapshot(EventWithOneDevice):
|
||||||
|
|
||||||
|
|
||||||
class Test(EventWithOneDevice):
|
class Test(EventWithOneDevice):
|
||||||
|
__doc__ = m.Test.__doc__
|
||||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||||
|
|
||||||
|
|
||||||
class TestDataStorage(Test):
|
class TestDataStorage(Test):
|
||||||
|
__doc__ = m.TestDataStorage.__doc__
|
||||||
length = EnumField(TestDataStorageLength, required=True)
|
length = EnumField(TestDataStorageLength, required=True)
|
||||||
status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True)
|
status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True)
|
||||||
lifetime = TimeDelta(precision=TimeDelta.HOURS)
|
lifetime = TimeDelta(precision=TimeDelta.HOURS)
|
||||||
|
@ -292,55 +311,59 @@ class TestDataStorage(Test):
|
||||||
|
|
||||||
|
|
||||||
class StressTest(Test):
|
class StressTest(Test):
|
||||||
pass
|
__doc__ = m.StressTest.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Benchmark(EventWithOneDevice):
|
class Benchmark(EventWithOneDevice):
|
||||||
|
__doc__ = m.Benchmark.__doc__
|
||||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||||
|
|
||||||
|
|
||||||
class BenchmarkDataStorage(Benchmark):
|
class BenchmarkDataStorage(Benchmark):
|
||||||
|
__doc__ = m.BenchmarkDataStorage.__doc__
|
||||||
read_speed = Float(required=True, data_key='readSpeed')
|
read_speed = Float(required=True, data_key='readSpeed')
|
||||||
write_speed = Float(required=True, data_key='writeSpeed')
|
write_speed = Float(required=True, data_key='writeSpeed')
|
||||||
|
|
||||||
|
|
||||||
class BenchmarkWithRate(Benchmark):
|
class BenchmarkWithRate(Benchmark):
|
||||||
|
__doc__ = m.BenchmarkWithRate.__doc__
|
||||||
rate = Float(required=True)
|
rate = Float(required=True)
|
||||||
|
|
||||||
|
|
||||||
class BenchmarkProcessor(BenchmarkWithRate):
|
class BenchmarkProcessor(BenchmarkWithRate):
|
||||||
pass
|
__doc__ = m.BenchmarkProcessor.__doc__
|
||||||
|
|
||||||
|
|
||||||
class BenchmarkProcessorSysbench(BenchmarkProcessor):
|
class BenchmarkProcessorSysbench(BenchmarkProcessor):
|
||||||
pass
|
__doc__ = m.BenchmarkProcessorSysbench.__doc__
|
||||||
|
|
||||||
|
|
||||||
class BenchmarkRamSysbench(BenchmarkWithRate):
|
class BenchmarkRamSysbench(BenchmarkWithRate):
|
||||||
pass
|
__doc__ = m.BenchmarkRamSysbench.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ToRepair(EventWithMultipleDevices):
|
class ToRepair(EventWithMultipleDevices):
|
||||||
pass
|
__doc__ = m.ToRepair.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Repair(EventWithMultipleDevices):
|
class Repair(EventWithMultipleDevices):
|
||||||
pass
|
__doc__ = m.Repair.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ReadyToUse(EventWithMultipleDevices):
|
class ReadyToUse(EventWithMultipleDevices):
|
||||||
pass
|
__doc__ = m.ReadyToUse.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ToPrepare(EventWithMultipleDevices):
|
class ToPrepare(EventWithMultipleDevices):
|
||||||
pass
|
__doc__ = m.ToPrepare.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Prepare(EventWithMultipleDevices):
|
class Prepare(EventWithMultipleDevices):
|
||||||
pass
|
__doc__ = m.Prepare.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Live(EventWithOneDevice):
|
class Live(EventWithOneDevice):
|
||||||
|
__doc__ = m.Live.__doc__
|
||||||
ip = IP(dump_only=True)
|
ip = IP(dump_only=True)
|
||||||
subdivision_confidence = Integer(dump_only=True, data_key='subdivisionConfidence')
|
subdivision_confidence = Integer(dump_only=True, data_key='subdivisionConfidence')
|
||||||
subdivision = EnumField(Subdivision, dump_only=True)
|
subdivision = EnumField(Subdivision, dump_only=True)
|
||||||
|
@ -353,60 +376,63 @@ class Live(EventWithOneDevice):
|
||||||
|
|
||||||
|
|
||||||
class Organize(EventWithMultipleDevices):
|
class Organize(EventWithMultipleDevices):
|
||||||
pass
|
__doc__ = m.Organize.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Reserve(Organize):
|
class Reserve(Organize):
|
||||||
pass
|
__doc__ = m.Reserve.__doc__
|
||||||
|
|
||||||
|
|
||||||
class CancelReservation(Organize):
|
class CancelReservation(Organize):
|
||||||
pass
|
__doc__ = m.CancelReservation.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Trade(EventWithMultipleDevices):
|
class Trade(EventWithMultipleDevices):
|
||||||
|
__doc__ = m.Trade.__doc__
|
||||||
shipping_date = DateTime(data_key='shippingDate')
|
shipping_date = DateTime(data_key='shippingDate')
|
||||||
invoice_number = SanitizedStr(validate=Length(max=STR_SIZE), data_key='invoiceNumber')
|
invoice_number = SanitizedStr(validate=Length(max=STR_SIZE), data_key='invoiceNumber')
|
||||||
price = NestedOn(Price)
|
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)
|
confirms = NestedOn(Organize)
|
||||||
|
|
||||||
|
|
||||||
class Sell(Trade):
|
class Sell(Trade):
|
||||||
pass
|
__doc__ = m.Sell.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Donate(Trade):
|
class Donate(Trade):
|
||||||
pass
|
__doc__ = m.Donate.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Rent(Trade):
|
class Rent(Trade):
|
||||||
pass
|
__doc__ = m.Rent.__doc__
|
||||||
|
|
||||||
|
|
||||||
class CancelTrade(Trade):
|
class CancelTrade(Trade):
|
||||||
pass
|
__doc__ = m.CancelTrade.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ToDisposeProduct(Trade):
|
class ToDisposeProduct(Trade):
|
||||||
pass
|
__doc__ = m.ToDisposeProduct.__doc__
|
||||||
|
|
||||||
|
|
||||||
class DisposeProduct(Trade):
|
class DisposeProduct(Trade):
|
||||||
pass
|
__doc__ = m.DisposeProduct.__doc__
|
||||||
|
|
||||||
|
|
||||||
class Receive(EventWithMultipleDevices):
|
class Receive(EventWithMultipleDevices):
|
||||||
|
__doc__ = m.Receive.__doc__
|
||||||
role = EnumField(ReceiverRole)
|
role = EnumField(ReceiverRole)
|
||||||
|
|
||||||
|
|
||||||
class Migrate(EventWithMultipleDevices):
|
class Migrate(EventWithMultipleDevices):
|
||||||
|
__doc__ = m.Migrate.__doc__
|
||||||
other = URL()
|
other = URL()
|
||||||
|
|
||||||
|
|
||||||
class MigrateTo(Migrate):
|
class MigrateTo(Migrate):
|
||||||
pass
|
__doc__ = m.MigrateTo.__doc__
|
||||||
|
|
||||||
|
|
||||||
class MigrateFrom(Migrate):
|
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 teal.marshmallow import SanitizedStr, URL
|
||||||
|
|
||||||
from ereuse_devicehub.marshmallow import NestedOn
|
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.lot import models as m
|
||||||
from ereuse_devicehub.resources.models import STR_SIZE
|
from ereuse_devicehub.resources.models import STR_SIZE
|
||||||
from ereuse_devicehub.resources.schemas import Thing
|
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)
|
name = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
|
||||||
description = SanitizedStr(description=m.Lot.description.comment)
|
description = SanitizedStr(description=m.Lot.description.comment)
|
||||||
closed = f.Boolean(missing=False, description=m.Lot.closed.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)
|
children = NestedOn('Lot', many=True, dump_only=True)
|
||||||
parents = 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__)
|
url = URL(dump_only=True, description=m.Lot.url.__doc__)
|
||||||
|
|
|
@ -9,21 +9,26 @@ STR_XSM_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
class Thing(db.Model):
|
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
|
__abstract__ = True
|
||||||
# todo make updated to auto-update
|
|
||||||
updated = db.Column(db.TIMESTAMP(timezone=True),
|
updated = db.Column(db.TIMESTAMP(timezone=True),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
index=True,
|
index=True,
|
||||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
server_default=db.text('CURRENT_TIMESTAMP'))
|
||||||
updated.comment = """
|
updated.comment = """
|
||||||
When this was last changed.
|
The last time Devicehub recorded a change for this thing.
|
||||||
"""
|
"""
|
||||||
created = db.Column(db.TIMESTAMP(timezone=True),
|
created = db.Column(db.TIMESTAMP(timezone=True),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
index=True,
|
index=True,
|
||||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
server_default=db.text('CURRENT_TIMESTAMP'))
|
||||||
created.comment = """
|
created.comment = """
|
||||||
When Devicehub created this.
|
When Devicehub created this.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from marshmallow import post_load
|
from marshmallow import post_load
|
||||||
from marshmallow.fields import DateTime, List, String
|
from marshmallow.fields import DateTime, List, String
|
||||||
|
from marshmallow.schema import SchemaMeta
|
||||||
from teal.marshmallow import URL
|
from teal.marshmallow import URL
|
||||||
from teal.resource import Schema
|
from teal.resource import Schema
|
||||||
|
|
||||||
|
@ -18,10 +20,59 @@ class UnitCodes(Enum):
|
||||||
kgm = 'KGM'
|
kgm = 'KGM'
|
||||||
m = 'MTR'
|
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):
|
class Thing(Schema):
|
||||||
type = String(description='Only required when it is nested.')
|
type = String(description=_type_description)
|
||||||
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
|
same_as = List(URL(dump_only=True),
|
||||||
|
dump_only=True,
|
||||||
|
data_key='sameAs')
|
||||||
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment)
|
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment)
|
||||||
created = DateTime('iso', dump_only=True, description=m.Thing.created.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 import Response, current_app as app, g, redirect, request
|
||||||
from flask_sqlalchemy import Pagination
|
from flask_sqlalchemy import Pagination
|
||||||
from teal.marshmallow import ValidationError
|
from teal.marshmallow import ValidationError
|
||||||
|
@ -22,7 +19,6 @@ class TagView(View):
|
||||||
res = self._post_one()
|
res = self._post_one()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@teal.cache.cache(datetime.timedelta(minutes=1))
|
|
||||||
def find(self, args: dict):
|
def find(self, args: dict):
|
||||||
tags = Tag.query.filter(Tag.is_printable_q()) \
|
tags = Tag.query.filter(Tag.is_printable_q()) \
|
||||||
.order_by(Tag.created.desc()) \
|
.order_by(Tag.created.desc()) \
|
||||||
|
|
Reference in New Issue