Merge branch 'rate' into devel

This commit is contained in:
Xavier Bustamante Talavera 2019-07-01 11:31:18 +02:00
commit c92d6b3282
46 changed files with 935 additions and 823 deletions

View File

@ -41,8 +41,7 @@ def get_version(ctx, param, value):
@click.group(cls=DevicehubGroup, @click.group(cls=DevicehubGroup,
context_settings=Devicehub.cli_context_settings, context_settings=Devicehub.cli_context_settings,
add_version_option=False, add_version_option=False,
help=""" help="""Manages the Devicehub of the inventory {}.
Manages the Devicehub of the inventory {}.
Use 'export dhi=xx' to set the inventory that this CLI Use 'export dhi=xx' to set the inventory that this CLI
manages. For example 'export dhi=db1' and then executing manages. For example 'export dhi=db1' and then executing

View File

@ -121,8 +121,7 @@ class Client(TealClient):
class UserClient(Client): class UserClient(Client):
""" """A client that identifies all of its requests with a specific user.
A client that identifies all of its requests with a specific user.
It will automatically perform login on the first request. It will automatically perform login on the first request.
""" """

View File

@ -26,8 +26,7 @@ class DevicehubConfig(Config):
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str] PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
SQLALCHEMY_DATABASE_URI = 'postgresql://dhub:ereuse@localhost/devicehub' # type: str SQLALCHEMY_DATABASE_URI = 'postgresql://dhub:ereuse@localhost/devicehub' # type: str
MIN_WORKBENCH = StrictVersion('11.0a1') # type: StrictVersion MIN_WORKBENCH = StrictVersion('11.0a1') # type: StrictVersion
""" """The minimum version of ereuse.org workbench that this devicehub
the minimum version of ereuse.org workbench that this devicehub
accepts. we recommend not changing this value. accepts. we recommend not changing this value.
""" """
API_DOC_CONFIG_TITLE = 'Devicehub' API_DOC_CONFIG_TITLE = 'Devicehub'
@ -42,6 +41,4 @@ class DevicehubConfig(Config):
PRICE_SOFTWARE = PriceSoftware.Ereuse PRICE_SOFTWARE = PriceSoftware.Ereuse
PRICE_VERSION = StrictVersion('1.0') PRICE_VERSION = StrictVersion('1.0')
PRICE_CURRENCY = Currency.EUR PRICE_CURRENCY = Currency.EUR
""" """Official versions."""
Official versions
"""

View File

@ -27,8 +27,7 @@ class DhSession(SchemaSession):
class SQLAlchemy(SchemaSQLAlchemy): class SQLAlchemy(SchemaSQLAlchemy):
""" """Superuser must create the required extensions in the public
Superuser must create the required extensions in the public
schema of the database, as it is in the `search_path` schema of the database, as it is in the `search_path`
defined in teal. defined in teal.
""" """

View File

@ -1,5 +1,4 @@
""" """This file contains all actions can apply to a device and is sorted according
This file contains all actions can apply to a device and is sorted according
to a structure based on: to a structure based on:
* Generic Actions * Generic Actions
@ -72,34 +71,29 @@ class Action(Thing):
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) type = Column(Unicode, nullable=False)
name = Column(CIText(), default='', nullable=False) name = Column(CIText(), default='', nullable=False)
name.comment = """ name.comment = """A name or title for the action. Used when searching
A name or title for the action. Used when searching for actions. for actions.
""" """
severity = Column(teal.db.IntEnum(Severity), default=Severity.Info, nullable=False) severity = Column(teal.db.IntEnum(Severity), default=Severity.Info, nullable=False)
severity.comment = Severity.__doc__ severity.comment = Severity.__doc__
closed = Column(Boolean, default=True, nullable=False) closed = Column(Boolean, default=True, nullable=False)
closed.comment = """ closed.comment = """Whether the author has finished the action.
Whether the author has finished the action. After this is set to True, no modifications are allowed.
After this is set to True, no modifications are allowed. By default actions are closed when performed.
By default actions are closed when performed.
""" """
description = Column(Unicode, default='', nullable=False) description = Column(Unicode, default='', nullable=False)
description.comment = """ description.comment = """A comment about the action."""
A comment about the action.
"""
start_time = Column(db.TIMESTAMP(timezone=True)) start_time = Column(db.TIMESTAMP(timezone=True))
start_time.comment = """ start_time.comment = """When the action starts. For some actions like
When the action starts. For some actions like reservations reservations the time when they are available, for others like renting
the time when they are available, for others like renting when the renting starts.
when the renting starts.
""" """
end_time = Column(db.TIMESTAMP(timezone=True)) end_time = Column(db.TIMESTAMP(timezone=True))
end_time.comment = """ end_time.comment = """When the action ends. For some actions like reservations
When the action ends. For some actions like reservations the time when they expire, for others like renting
the time when they expire, for others like renting the time the end rents. For punctual actions it is the time
the time the end rents. For punctual actions it is the time they are performed; it differs with ``created`` in which
they are performed; it differs with ``created`` in which created is the where the system received the action.
created is the where the system received the action.
""" """
snapshot_id = Column(UUID(as_uuid=True), ForeignKey('snapshot.id', snapshot_id = Column(UUID(as_uuid=True), ForeignKey('snapshot.id',
@ -120,8 +114,7 @@ class Action(Thing):
author = relationship(User, author = relationship(User,
backref=backref('authored_actions', lazy=True, collection_class=set), backref=backref('authored_actions', lazy=True, collection_class=set),
primaryjoin=author_id == User.id) primaryjoin=author_id == User.id)
author_id.comment = """ author_id.comment = """The user that recorded this action in the system.
The user that recorded this action in the system.
This does not necessarily has to be the person that produced This does not necessarily has to be the person that produced
the action in the real world. For that purpose see the action in the real world. For that purpose see
@ -136,8 +129,8 @@ class Action(Thing):
agent = relationship(Agent, agent = relationship(Agent,
backref=backref('actions_agent', lazy=True, **_sorted_actions), backref=backref('actions_agent', lazy=True, **_sorted_actions),
primaryjoin=agent_id == Agent.id) primaryjoin=agent_id == Agent.id)
agent_id.comment = """ agent_id.comment = """The direct performer or driver of the action.
The direct performer or driver of the action. e.g. John wrote a book. e.g. John wrote a book.
It can differ with the user that registered the action in the It can differ with the user that registered the action in the
system, which can be in their behalf. system, which can be in their behalf.
@ -148,8 +141,7 @@ class Action(Thing):
secondary=lambda: ActionComponent.__table__, secondary=lambda: ActionComponent.__table__,
order_by=lambda: Component.id, order_by=lambda: Component.id,
collection_class=OrderedSet) collection_class=OrderedSet)
components.comment = """ components.comment = """The components that are affected by the action.
The components that are affected by the action.
When performing actions to parent devices their components are When performing actions to parent devices their components are
affected too. affected too.
@ -165,9 +157,8 @@ class Action(Thing):
parent = relationship(Computer, parent = relationship(Computer,
backref=backref('actions_parent', lazy=True, **_sorted_actions), backref=backref('actions_parent', lazy=True, **_sorted_actions),
primaryjoin=parent_id == Computer.id) primaryjoin=parent_id == Computer.id)
parent_id.comment = """ parent_id.comment = """For actions that are performed to components,
For actions that are performed to components, the device parent the device parent at that time.
at that time.
For example: for a ``EraseBasic`` performed on a data storage, this For example: for a ``EraseBasic`` performed on a data storage, this
would point to the computer that contained this data storage, if any. would point to the computer that contained this data storage, if any.
@ -197,8 +188,7 @@ class Action(Thing):
# noinspection PyMethodParameters # noinspection PyMethodParameters
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -276,8 +266,7 @@ class ActionWithOneDevice(JoinedTableMixin, Action):
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -365,7 +354,7 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@property @property
def certificate(self): def certificate(self):
"""The URL of this erasure certificate.""" """The URL of this erasure certificate."""
# todo will this url_for_resoure work for other resources? # todo will this url_for_resource work for other resources?
return urlutils.URL(url_for_resource('Document', item_id=self.id)) return urlutils.URL(url_for_resource('Document', item_id=self.id))
def __str__(self) -> str: def __str__(self) -> str:
@ -430,8 +419,7 @@ class Step(db.Model):
# noinspection PyMethodParameters # noinspection PyMethodParameters
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -545,9 +533,8 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
version = Column(StrictVersionType(STR_SM_SIZE), nullable=False) version = Column(StrictVersionType(STR_SM_SIZE), nullable=False)
software = Column(DBEnum(SnapshotSoftware), nullable=False) software = Column(DBEnum(SnapshotSoftware), nullable=False)
elapsed = Column(Interval) elapsed = Column(Interval)
elapsed.comment = """ elapsed.comment = """For Snapshots made with Workbench, the total amount
For Snapshots made with Workbench, the total amount of time of time it took to complete.
it took to complete.
""" """
def __str__(self) -> str: def __str__(self) -> str:
@ -578,8 +565,7 @@ class Benchmark(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -656,8 +642,7 @@ class Test(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -684,6 +669,11 @@ class MeasureBattery(TestMixin, Test):
Operative Systems keep a record of several aspects of a battery. Operative Systems keep a record of several aspects of a battery.
This is a sample of those. This is a sample of those.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether the health are Dead, Overheat or OverVoltage.
* :attr:`Severity.Warning`: whether the health are UnspecifiedValue or Cold.
""" """
size = db.Column(db.Integer, nullable=False) size = db.Column(db.Integer, nullable=False)
size.comment = """Maximum battery capacity, in mAh.""" size.comment = """Maximum battery capacity, in mAh."""
@ -712,7 +702,7 @@ class TestDataStorage(TestMixin, Test):
Failing and warning conditions are as follows: Failing and warning conditions are as follows:
* :attr:`Severity.Error`: if the SMART test failed. * :attr:`Severity.Error`: whether the SMART test failed.
* :attr:`Severity.Warning`: if there is a significant chance for * :attr:`Severity.Warning`: if there is a significant chance for
the data storage to fail in the following year. the data storage to fail in the following year.
""" """
@ -765,6 +755,11 @@ class TestDataStorage(TestMixin, Test):
class StressTest(TestMixin, Test): class StressTest(TestMixin, Test):
"""The act of stressing (putting to the maximum capacity) """The act of stressing (putting to the maximum capacity)
a device for an amount of minutes. a device for an amount of minutes.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether failed StressTest.
* :attr:`Severity.Warning`: if stress test are less than 5 minutes.
""" """
elapsed = Column(Interval, nullable=False) elapsed = Column(Interval, nullable=False)
@ -780,7 +775,13 @@ class StressTest(TestMixin, Test):
class TestAudio(TestMixin, Test): class TestAudio(TestMixin, Test):
"""The act of checking the audio aspects of the device.""" """The act of checking the audio aspects of the device.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether speaker or microphone variables fail.
* :attr:`Severity.Warning`: .
"""
_speaker = Column('speaker', Boolean) _speaker = Column('speaker', Boolean)
_speaker.comment = """Whether the speaker works as expected.""" _speaker.comment = """Whether the speaker works as expected."""
_microphone = Column('microphone', Boolean) _microphone = Column('microphone', Boolean)
@ -824,9 +825,9 @@ class TestCamera(TestMixin, Test):
Failing and warning conditions are as follows: Failing and warning conditions are as follows:
* :attr:`Severity.Error`: if the camera cannot turn on or * :attr:`Severity.Error`: whether the camera cannot turn on or
has significant visual problems. has significant visual problems.
* :attr:`Severity.Warning`: if there are small visual problems * :attr:`Severity.Warning`: whether there are small visual problems
with the camera (like dust) that it still allows it to be used. with the camera (like dust) that it still allows it to be used.
""" """
@ -856,7 +857,7 @@ class TestDisplayHinge(TestMixin, Test):
Failing and warning conditions are as follows: Failing and warning conditions are as follows:
* :attr:`Severity.Error`: if the laptop does not stay open * :attr:`Severity.Error`: whether the laptop does not stay open
or closed at desired angles. From R2 Provision 6 pag.22. or closed at desired angles. From R2 Provision 6 pag.22.
""" """
@ -872,7 +873,13 @@ class TestPowerAdapter(TestMixin, Test):
class TestBios(TestMixin, Test): class TestBios(TestMixin, Test):
"""Tests the working condition and grades the usability of the BIOS.""" """Tests the working condition and grades the usability of the BIOS.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether Bios beeps or access range is D or E.
* :attr:`Severity.Warning`: whether access range is B or C.
"""
beeps_power_on = Column(Boolean) beeps_power_on = Column(Boolean)
beeps_power_on.comment = """Whether there are no beeps or error beeps_power_on.comment = """Whether there are no beeps or error
codes when booting up. codes when booting up.
@ -884,7 +891,8 @@ class TestBios(TestMixin, Test):
This is used as an usability measure for accessing and modifying This is used as an usability measure for accessing and modifying
a bios, specially as something as important as modifying the boot a bios, specially as something as important as modifying the boot
menu.""" menu.
"""
class VisualTest(TestMixin, Test): class VisualTest(TestMixin, Test):
@ -893,7 +901,16 @@ class VisualTest(TestMixin, Test):
Reference R2 provision 6 Templates Ready for Resale Checklist (Desktop) Reference R2 provision 6 Templates Ready for Resale Checklist (Desktop)
https://sustainableelectronics.org/sites/default/files/6.c.2%20Desktop%20R2-Ready%20for%20Resale%20Checklist.docx https://sustainableelectronics.org/sites/default/files/6.c.2%20Desktop%20R2-Ready%20for%20Resale%20Checklist.docx
Physical condition grade Physical condition grade.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether appearance range is less than B or
functionality range is less than B.
* :attr:`Severity.Warning`: whether appearance range is B or A and
functionality range is B.
* :attr:`Severity.Info`: whether appearance range is B or A and
functionality range is A.
""" """
appearance_range = Column(DBEnum(AppearanceRange), nullable=False) appearance_range = Column(DBEnum(AppearanceRange), nullable=False)
appearance_range.comment = AppearanceRange.__doc__ appearance_range.comment = AppearanceRange.__doc__
@ -916,7 +933,6 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
* Appearance (A). Visual evaluation, surface deterioration. * Appearance (A). Visual evaluation, surface deterioration.
* Performance (Q). Components characteristics and components benchmarks. * Performance (Q). Components characteristics and components benchmarks.
""" """
# todo jn: explain in each comment what the rate considers.
N = 2 N = 2
"""The number of significant digits for rates. """The number of significant digits for rates.
Values are rounded and stored to it. Values are rounded and stored to it.
@ -929,11 +945,11 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
_appearance = Column('appearance', _appearance = Column('appearance',
Float(decimal_return_scale=N), Float(decimal_return_scale=N),
check_range('appearance', *R_NEGATIVE)) check_range('appearance', *R_NEGATIVE))
_appearance.comment = """""" _appearance.comment = """Subjective value representing aesthetic aspects."""
_functionality = Column('functionality', _functionality = Column('functionality',
Float(decimal_return_scale=N), Float(decimal_return_scale=N),
check_range('functionality', *R_NEGATIVE)) check_range('functionality', *R_NEGATIVE))
_functionality.comment = """""" _functionality.comment = """Subjective value representing usage aspects."""
@property @property
def rating(self): def rating(self):
@ -966,8 +982,7 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -993,10 +1008,9 @@ class RateMixin:
class RateComputer(RateMixin, Rate): class RateComputer(RateMixin, Rate):
""" """The act of rating a computer type devices.
The act of rating a computer type devices.
It's the starting point for calculating the rate. It's the starting point for calculating the rate.
Algorithm explained in v1.0 file Algorithm explained in v1.0 file.
""" """
_processor = Column('processor', _processor = Column('processor',
Float(decimal_return_scale=Rate.N), Float(decimal_return_scale=Rate.N),
@ -1063,9 +1077,7 @@ class RateComputer(RateMixin, Rate):
@classmethod @classmethod
def compute(cls, device): def compute(cls, device):
""" """The act of compute general computer rate."""
The act of compute general computer rate
"""
from ereuse_devicehub.resources.action.rate.v1_0 import rate_algorithm from ereuse_devicehub.resources.action.rate.v1_0 import rate_algorithm
rate = rate_algorithm.compute(device) rate = rate_algorithm.compute(device)
price = None price = None
@ -1075,6 +1087,7 @@ class RateComputer(RateMixin, Rate):
class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice): class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice):
# TODO rewrite Class comment change AggregateRate..
"""The act of setting a trading price for the device. """The act of setting a trading price for the device.
This does not imply that the device is ultimately traded for that This does not imply that the device is ultimately traded for that
@ -1101,7 +1114,8 @@ class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice):
version.comment = """The version of the software, or None.""" version.comment = """The version of the software, or None."""
rating_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id)) rating_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id))
rating_id.comment = """The Rate used to auto-compute rating_id.comment = """The Rate used to auto-compute
this price, if it has not been set manually.""" this price, if it has not been set manually.
"""
rating = relationship(Rate, rating = relationship(Rate,
backref=backref('price', backref=backref('price',
lazy=True, lazy=True,
@ -1125,8 +1139,7 @@ class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -1216,9 +1229,8 @@ class EreusePrice(Price):
@orm.reconstructor @orm.reconstructor
def _compute(self): def _compute(self):
""" """Calculates eReuse.org prices when initializing the instance
Calculates eReuse.org prices when initializing the from the price and other properties.
instance from the price and other properties.
""" """
self.refurbisher = self._service(self.Service.REFURBISHER) self.refurbisher = self._service(self.Service.REFURBISHER)
self.retailer = self._service(self.Service.RETAILER) self.retailer = self._service(self.Service.RETAILER)
@ -1279,7 +1291,7 @@ class Prepare(ActionWithMultipleDevices):
class Live(JoinedWithOneDeviceMixin, ActionWithOneDevice): class Live(JoinedWithOneDeviceMixin, ActionWithOneDevice):
"""A keep-alive from a device connected to the Internet with """A keep-alive from a device connected to the Internet with
information about its state (in the form of a ``Snapshot`` action) information about its state (in the form of a ``Snapshot`` action)
and usage statistics. and usage statistics.
""" """
ip = Column(IP, nullable=False, ip = Column(IP, nullable=False,
comment='The IP where the live was triggered.') comment='The IP where the live was triggered.')
@ -1335,41 +1347,34 @@ class Trade(JoinedTableMixin, ActionWithMultipleDevices):
extend `Schema's Trade <http://schema.org/TradeAction>`_. extend `Schema's Trade <http://schema.org/TradeAction>`_.
""" """
shipping_date = Column(db.TIMESTAMP(timezone=True)) shipping_date = Column(db.TIMESTAMP(timezone=True))
shipping_date.comment = """ shipping_date.comment = """When are the devices going to be ready
When are the devices going to be ready for shipping? for shipping?
""" """
invoice_number = Column(CIText()) invoice_number = Column(CIText())
invoice_number.comment = """ invoice_number.comment = """The id of the invoice so they can be linked."""
The id of the invoice so they can be linked.
"""
price_id = Column(UUID(as_uuid=True), ForeignKey(Price.id)) price_id = Column(UUID(as_uuid=True), ForeignKey(Price.id))
price = relationship(Price, price = relationship(Price,
backref=backref('trade', lazy=True, uselist=False), backref=backref('trade', lazy=True, uselist=False),
primaryjoin=price_id == Price.id) primaryjoin=price_id == Price.id)
price_id.comment = """ price_id.comment = """The price set for this trade.
The price set for this trade. If no price is set it is supposed that the trade was
not payed, usual in donations.
If no price is set it is supposed that the trade was
not payed, usual in donations.
""" """
to_id = Column(UUID(as_uuid=True), ForeignKey(Agent.id), nullable=False) to_id = Column(UUID(as_uuid=True), ForeignKey(Agent.id), nullable=False)
# todo compute the org # todo compute the org
to = relationship(Agent, to = relationship(Agent,
backref=backref('actions_to', lazy=True, **_sorted_actions), backref=backref('actions_to', lazy=True, **_sorted_actions),
primaryjoin=to_id == Agent.id) primaryjoin=to_id == Agent.id)
to_comment = """ to_comment = """The agent that gets the device due this deal."""
The agent that gets the device due this deal.
"""
confirms_id = Column(UUID(as_uuid=True), ForeignKey(Organize.id)) confirms_id = Column(UUID(as_uuid=True), ForeignKey(Organize.id))
confirms = relationship(Organize, confirms = relationship(Organize,
backref=backref('confirmation', lazy=True, uselist=False), backref=backref('confirmation', lazy=True, uselist=False),
primaryjoin=confirms_id == Organize.id) primaryjoin=confirms_id == Organize.id)
confirms_id.comment = """ confirms_id.comment = """An organize action that this association confirms.
An organize action that this association confirms.
For example, a ``Sell`` or ``Rent``
For example, a ``Sell`` or ``Rent`` can confirm a ``Reserve`` action.
can confirm a ``Reserve`` action. """
"""
class Sell(Trade): class Sell(Trade):
@ -1482,8 +1487,7 @@ def actions_not_for_components(target: Action, value: Device, old_value, initiat
@event.listens_for(ActionWithOneDevice.device, Events.set.__name__, propagate=True) @event.listens_for(ActionWithOneDevice.device, Events.set.__name__, propagate=True)
def update_components_action_one(target: ActionWithOneDevice, device: Device, __, ___): def update_components_action_one(target: ActionWithOneDevice, device: Device, __, ___):
""" """Syncs the :attr:`.Action.components` with the components in
Syncs the :attr:`.Action.components` with the components in
:attr:`ereuse_devicehub.resources.device.models.Computer.components`. :attr:`ereuse_devicehub.resources.device.models.Computer.components`.
""" """
# For Add and Remove, ``components`` have different meanings # For Add and Remove, ``components`` have different meanings
@ -1500,8 +1504,7 @@ def update_components_action_one(target: ActionWithOneDevice, device: Device, __
@event.listens_for(ActionWithMultipleDevices.devices, Events.append.__name__, propagate=True) @event.listens_for(ActionWithMultipleDevices.devices, Events.append.__name__, propagate=True)
def update_components_action_multiple(target: ActionWithMultipleDevices, def update_components_action_multiple(target: ActionWithMultipleDevices,
value: Union[Set[Device], Device], _): value: Union[Set[Device], Device], _):
""" """Syncs the :attr:`.Action.components` with the components in
Syncs the :attr:`.Action.components` with the components in
:attr:`ereuse_devicehub.resources.device.models.Computer.components`. :attr:`ereuse_devicehub.resources.device.models.Computer.components`.
""" """
target.components.clear() target.components.clear()
@ -1513,8 +1516,7 @@ def update_components_action_multiple(target: ActionWithMultipleDevices,
@event.listens_for(ActionWithMultipleDevices.devices, Events.remove.__name__, propagate=True) @event.listens_for(ActionWithMultipleDevices.devices, Events.remove.__name__, propagate=True)
def remove_components_action_multiple(target: ActionWithMultipleDevices, device: Device, __): def remove_components_action_multiple(target: ActionWithMultipleDevices, device: Device, __):
""" """Syncs the :attr:`.Action.components` with the components in
Syncs the :attr:`.Action.components` with the components in
:attr:`ereuse_devicehub.resources.device.models.Computer.components`. :attr:`ereuse_devicehub.resources.device.models.Computer.components`.
""" """
target.components.clear() target.components.clear()
@ -1528,9 +1530,7 @@ def remove_components_action_multiple(target: ActionWithMultipleDevices, device:
@event.listens_for(Install.device, Events.set.__name__, propagate=True) @event.listens_for(Install.device, Events.set.__name__, propagate=True)
@event.listens_for(Benchmark.device, Events.set.__name__, propagate=True) @event.listens_for(Benchmark.device, Events.set.__name__, propagate=True)
def update_parent(target: Union[EraseBasic, Test, Install], device: Device, _, __): def update_parent(target: Union[EraseBasic, Test, Install], device: Device, _, __):
""" """Syncs the :attr:`Action.parent` with the parent of the device."""
Syncs the :attr:`Action.parent` with the parent of the device.
"""
target.parent = None target.parent = None
if isinstance(device, Component): if isinstance(device, Component):
target.parent = device.parent target.parent = device.parent

View File

@ -1,22 +1,23 @@
import math
from typing import Iterable from typing import Iterable
import math
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
class BaseRate: class BaseRate:
"""growing exponential from this value""" """Growing exponential from this value."""
CEXP = 0 CEXP = 0
"""growing lineal starting on this value""" """Growing lineal starting on this value."""
CLIN = 242 CLIN = 242
"""growing logarithmic starting on this value""" """Growing logarithmic starting on this value."""
CLOG = 0.5 CLOG = 0.5
"""Processor has 50% of weight over total score, used in harmonic mean""" """Processor has 50% of weight over total score, used in harmonic mean."""
PROCESSOR_WEIGHT = 0.5 PROCESSOR_WEIGHT = 0.5
"""Storage has 20% of weight over total score, used in harmonic mean""" """Storage has 20% of weight over total score, used in harmonic mean."""
DATA_STORAGE_WEIGHT = 0.2 DATA_STORAGE_WEIGHT = 0.2
"""Ram has 30% of weight over total score, used in harmonic mean""" """Ram has 30% of weight over total score, used in harmonic mean."""
RAM_WEIGHT = 0.3 RAM_WEIGHT = 0.3
def compute(self, device: Device): def compute(self, device: Device):
@ -43,9 +44,7 @@ class BaseRate:
return sum(weights) / sum(char / rate for char, rate in zip(weights, rates)) return sum(weights) / sum(char / rate for char, rate in zip(weights, rates))
def harmonic_mean_rates(self, rate_processor, rate_storage, rate_ram): def harmonic_mean_rates(self, rate_processor, rate_storage, rate_ram):
""" """Merging components using harmonic formula."""
Merging components
"""
total_weights = self.PROCESSOR_WEIGHT + self.DATA_STORAGE_WEIGHT + self.RAM_WEIGHT total_weights = self.PROCESSOR_WEIGHT + self.DATA_STORAGE_WEIGHT + self.RAM_WEIGHT
total_rate = self.PROCESSOR_WEIGHT / rate_processor \ total_rate = self.PROCESSOR_WEIGHT / rate_processor \
+ self.DATA_STORAGE_WEIGHT / rate_storage \ + self.DATA_STORAGE_WEIGHT / rate_storage \

View File

@ -13,14 +13,15 @@ class RateAlgorithm(BaseRate):
"""The algorithm that generates the Rate v1.0. """The algorithm that generates the Rate v1.0.
Rate v1.0 rates only computers, counting their processor, ram, Rate v1.0 rates only computers, counting their processor, ram,
data storage, appearance, and functionality. This rate is only data storage, appearance and functionality. This rate is only
triggered by a Snapshot from Workbench that has a VisualTest. triggered by a Snapshot from Workbench that has a VisualTest.
The algorithm is as follows: The algorithm is as follows:
1. Specialized subclasses of :class:`BaseRate` compute a rating 1. Specialized subclasses of :class:`BaseRate` compute a rating
for each component. To perform this, each class normalizes first for each component. To perform this, each class normalizes first
the characteristics and benchmarks of the components between the characteristics and benchmarks of the components between
0 and 1, and then they merge the values to a resulting score. 0 and 1, and then they merge the values with specific formulas for
each components to get a resulting score.
The classes are: The classes are:
* :class:`ProcessorRate`, using cores, speed, and ``BenchmarkProcessor``. * :class:`ProcessorRate`, using cores, speed, and ``BenchmarkProcessor``.
@ -103,9 +104,7 @@ class RateAlgorithm(BaseRate):
class ProcessorRate(BaseRate): class ProcessorRate(BaseRate):
""" """Calculate a ProcessorRate of all Processor devices."""
Calculate a ProcessorRate of all Processor devices
"""
# processor.xMin, processor.xMax # processor.xMin, processor.xMax
PROCESSOR_NORM = 3196.17, 17503.81 PROCESSOR_NORM = 3196.17, 17503.81
@ -115,10 +114,10 @@ class ProcessorRate(BaseRate):
DEFAULT_SCORE = 4000 DEFAULT_SCORE = 4000
def compute(self, processor: Processor): def compute(self, processor: Processor):
""" Compute processor rate """Compute processor rate
We assume always exists a Benchmark Processor We assume always exists a Benchmark Processor.
Obs: cores and speed are possible NULL value Obs: cores and speed are possible NULL value
:return: result is a rate (score) of Processor characteristics :return: result is a rate (score) of Processor characteristics
""" """
cores = processor.cores or self.DEFAULT_CORES cores = processor.cores or self.DEFAULT_CORES
speed = processor.speed or self.DEFAULT_SPEED speed = processor.speed or self.DEFAULT_SPEED
@ -146,9 +145,7 @@ class ProcessorRate(BaseRate):
class RamRate(BaseRate): class RamRate(BaseRate):
""" """Calculate a RamRate of all RamModule devices."""
Calculate a RamRate of all RamModule devices
"""
# ram.size.xMin; ram.size.xMax # ram.size.xMin; ram.size.xMax
SIZE_NORM = 256, 8192 SIZE_NORM = 256, 8192
RAM_SPEED_NORM = 133, 1333 RAM_SPEED_NORM = 133, 1333
@ -158,8 +155,7 @@ class RamRate(BaseRate):
RAM_WEIGHTS = 0.7, 0.3 RAM_WEIGHTS = 0.7, 0.3
def compute(self, ram_devices: Iterable[RamModule]): def compute(self, ram_devices: Iterable[RamModule]):
""" """If ram speed or ram size, we assume default values before declared.
If ram speed or ram size, we assume default values before declared
:return: result is a rate (score) of all RamModule components :return: result is a rate (score) of all RamModule components
""" """
size = 0.0 size = 0.0
@ -204,9 +200,7 @@ class RamRate(BaseRate):
class DataStorageRate(BaseRate): class DataStorageRate(BaseRate):
""" """Calculate the rate of all DataStorage devices."""
Calculate the rate of all DataStorage devices
"""
# drive.size.xMin; drive.size.xMax # drive.size.xMin; drive.size.xMax
SIZE_NORM = 4, 265000 SIZE_NORM = 4, 265000
READ_SPEED_NORM = 2.7, 109.5 READ_SPEED_NORM = 2.7, 109.5
@ -215,8 +209,7 @@ class DataStorageRate(BaseRate):
DATA_STORAGE_WEIGHTS = 0.5, 0.25, 0.25 DATA_STORAGE_WEIGHTS = 0.5, 0.25, 0.25
def compute(self, data_storage_devices: Iterable[DataStorage]): def compute(self, data_storage_devices: Iterable[DataStorage]):
""" """Obs: size != NULL and 0 value & read_speed and write_speed != NULL
Obs: size != NULL and 0 value & read_speed and write_speed != NULL
:return: result is a rate (score) of all DataStorage devices :return: result is a rate (score) of all DataStorage devices
""" """
size = 0 size = 0

View File

@ -46,8 +46,7 @@ class ActionView(View):
return self.schema.jsonify(action) return self.schema.jsonify(action)
def snapshot(self, snapshot_json: dict, resource_def): def snapshot(self, snapshot_json: dict, resource_def):
""" """Performs a Snapshot.
Performs a Snapshot.
See `Snapshot` section in docs for more info. See `Snapshot` section in docs for more info.
""" """

View File

@ -29,18 +29,13 @@ class Agent(Thing):
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) type = Column(Unicode, nullable=False)
name = Column(CIText()) name = Column(CIText())
name.comment = """ name.comment = """The name of the organization or person."""
The name of the organization or person.
"""
tax_id = Column(Unicode(length=STR_SM_SIZE), check_lower('tax_id')) tax_id = Column(Unicode(length=STR_SM_SIZE), check_lower('tax_id'))
tax_id.comment = """ tax_id.comment = """The Tax / Fiscal ID of the organization,
The Tax / Fiscal ID of the organization, e.g. the TIN in the US or the CIF/NIF in Spain.
e.g. the TIN in the US or the CIF/NIF in Spain.
""" """
country = Column(DBEnum(enums.Country)) country = Column(DBEnum(enums.Country))
country.comment = """ country.comment = """Country issuing the tax_id number."""
Country issuing the tax_id number.
"""
telephone = Column(PhoneNumberType()) telephone = Column(PhoneNumberType())
email = Column(EmailType, unique=True) email = Column(EmailType, unique=True)
@ -52,8 +47,7 @@ class Agent(Thing):
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -137,8 +131,7 @@ class Membership(Thing):
class Person(Individual): class Person(Individual):
""" """A person in the system. There can be several persons pointing to
A person in the system. There can be several persons pointing to
a real. a real.
""" """
pass pass

View File

@ -44,18 +44,15 @@ class Device(Thing):
(it is a recursive relationship). (it is a recursive relationship).
""" """
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. Used only
The identifier of the device for this database. Used only internally for software; users should not use this.
internally for software; users should not use this.
""" """
type = Column(Unicode(STR_SM_SIZE), nullable=False) type = Column(Unicode(STR_SM_SIZE), nullable=False)
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
The Hardware ID (HID) is the unique ID traceability systems systems use to ID a device globally. This field is auto-generated
use to ID a device globally. This field is auto-generated from Devicehub using literal identifiers from the device,
from Devicehub using literal identifiers from the device, so it can re-generated *offline*.
so it can re-generated *offline*.
""" + HID_CONVERSION_DOC """ + HID_CONVERSION_DOC
model = Column(Unicode, check_lower('model')) model = Column(Unicode, check_lower('model'))
model.comment = """The model of the device in lower case. model.comment = """The model of the device in lower case.
@ -84,21 +81,13 @@ class Device(Thing):
version = db.Column(db.CIText()) version = db.Column(db.CIText())
version.comment = """The version code this device, like v1 or A001.""" version.comment = """The version code this device, like v1 or A001."""
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."""
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 in meters.
"""
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 in meters.
"""
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 in meters.
"""
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.DateTime) production_date = Column(db.DateTime)
@ -146,8 +135,7 @@ class Device(Thing):
@property @property
def actions(self) -> list: def actions(self) -> list:
""" """All the actions where the device participated, including:
All the actions where the device participated, including:
1. Actions performed directly to the device. 1. Actions performed directly to the device.
2. Actions performed to a component. 2. Actions performed to a component.
@ -177,8 +165,7 @@ class Device(Thing):
@property @property
def physical_properties(self) -> Dict[str, object or None]: def physical_properties(self) -> Dict[str, object or None]:
""" """Fields that describe the physical properties of a device.
Fields that describe the physical properties of a device.
:return A generator where each value is a tuple with tho fields: :return A generator where each value is a tuple with tho fields:
- Column. - Column.
@ -266,8 +253,7 @@ class Device(Thing):
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
""" """Defines inheritance.
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/ From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html extensions/declarative/api.html
@ -314,24 +300,20 @@ class Device(Thing):
class DisplayMixin: class DisplayMixin:
"""Base class for the Display Component and the Monitor Device.""" """Base class for the Display Component and the Monitor Device."""
size = Column(Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=False) size = Column(Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=False)
size.comment = """ size.comment = """The size of the monitor in inches."""
The size of the monitor in inches.
"""
technology = Column(DBEnum(DisplayTech)) technology = Column(DBEnum(DisplayTech))
technology.comment = """ technology.comment = """The technology the monitor uses to display
The technology the monitor uses to display the image. the image.
""" """
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000), resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000),
nullable=False) nullable=False)
resolution_width.comment = """ resolution_width.comment = """The maximum horizontal resolution the
The maximum horizontal resolution the monitor can natively support monitor can natively support in pixels.
in pixels.
""" """
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000), resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000),
nullable=False) nullable=False)
resolution_height.comment = """ resolution_height.comment = """The maximum vertical resolution the
The maximum vertical resolution the monitor can natively support monitor can natively support in pixels.
in pixels.
""" """
refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000)) refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000))
contrast_ratio = Column(SmallInteger, check_range('contrast_ratio', 100, 100000)) contrast_ratio = Column(SmallInteger, check_range('contrast_ratio', 100, 100000))
@ -467,7 +449,8 @@ class Desktop(Computer):
class Laptop(Computer): class Laptop(Computer):
layout = Column(DBEnum(Layouts)) layout = Column(DBEnum(Layouts))
layout.comment = """Layout of a built-in keyboard of the computer, layout.comment = """Layout of a built-in keyboard of the computer,
if any.""" if any.
"""
class Server(Computer): class Server(Computer):
@ -549,11 +532,11 @@ class Component(Device):
) )
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component': def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
""" """Gets a component that:
Gets a component that:
- has the same parent. * has the same parent.
- Doesn't generate HID. * Doesn't generate HID.
- Has same physical properties. * Has same physical properties.
:param parent: :param parent:
:param blacklist: A set of components to not to consider :param blacklist: A set of components to not to consider
when looking for similar ones. when looking for similar ones.
@ -580,17 +563,13 @@ class JoinedComponentTableMixin:
class GraphicCard(JoinedComponentTableMixin, Component): class GraphicCard(JoinedComponentTableMixin, Component):
memory = Column(SmallInteger, check_range('memory', min=1, max=10000)) memory = Column(SmallInteger, check_range('memory', min=1, max=10000))
memory.comment = """ memory.comment = """The amount of memory of the Graphic Card in MB."""
The amount of memory of the Graphic Card in MB.
"""
class DataStorage(JoinedComponentTableMixin, Component): class DataStorage(JoinedComponentTableMixin, Component):
"""A device that stores information.""" """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.
"""
interface = Column(DBEnum(DataStorageInterface)) interface = Column(DBEnum(DataStorageInterface))
@property @property
@ -623,9 +602,7 @@ class SolidStateDrive(DataStorage):
class Motherboard(JoinedComponentTableMixin, Component): class Motherboard(JoinedComponentTableMixin, Component):
slots = Column(SmallInteger, check_range('slots', min=0)) slots = Column(SmallInteger, check_range('slots', min=0))
slots.comment = """ slots.comment = """PCI slots the motherboard has."""
PCI slots the motherboard has.
"""
usb = Column(SmallInteger, check_range('usb', min=0)) usb = Column(SmallInteger, check_range('usb', min=0))
firewire = Column(SmallInteger, check_range('firewire', min=0)) firewire = Column(SmallInteger, check_range('firewire', min=0))
serial = Column(SmallInteger, check_range('serial', min=0)) serial = Column(SmallInteger, check_range('serial', min=0))
@ -638,13 +615,11 @@ class Motherboard(JoinedComponentTableMixin, Component):
class NetworkMixin: class NetworkMixin:
speed = Column(SmallInteger, check_range('speed', min=10, max=10000)) speed = Column(SmallInteger, check_range('speed', min=10, max=10000))
speed.comment = """ speed.comment = """The maximum speed this network adapter can handle,
The maximum speed this network adapter can handle, in mbps. in mbps.
""" """
wireless = Column(Boolean, nullable=False, default=False) wireless = Column(Boolean, nullable=False, default=False)
wireless.comment = """ wireless.comment = """Whether it is a wireless interface."""
Whether it is a wireless interface.
"""
def __format__(self, format_spec): def __format__(self, format_spec):
v = super().__format__(format_spec) v = super().__format__(format_spec)
@ -685,8 +660,7 @@ 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 part, like laptops, displays but that it is not their main part, like laptops,
mobiles, smart-watches, and so on; excluding ``ComputerMonitor`` mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
and ``TelevisionSet``. and ``TelevisionSet``.

View File

@ -20,8 +20,7 @@ class State(Enum):
class Trading(State): class Trading(State):
""" """Trading states.
Trading states.
:cvar Reserved: The device has been reserved. :cvar Reserved: The device has been reserved.
:cvar Cancelled: The device has been cancelled. :cvar Cancelled: The device has been cancelled.
@ -44,8 +43,7 @@ class Trading(State):
class Physical(State): class Physical(State):
""" """Physical states.
Physical states.
:cvar ToBeRepaired: The device has been selected for reparation. :cvar ToBeRepaired: The device has been selected for reparation.
:cvar Repaired: The device has been repaired. :cvar Repaired: The device has been repaired.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,3 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC <?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC
"-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 46 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"><rect id="Photochromic-alone" serif:id="Photochromic alone" x="0" y="0" width="45.84" height="45.84" style="fill:none;"/><path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:#fff;"/><clipPath id="_clip1"><path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath><g clip-path="url(#_clip1)"><clipPath id="_clip2"><rect x="4.275" y="4.975" width="36.85" height="36.85"/></clipPath><g clip-path="url(#_clip2)"><g id="Artboard1"><rect x="4.275" y="4.975" width="36.71" height="36.701" style="fill:none;"/><g id="Logo-01"><path d="M6.749,38.053c4.02,-13.193 12.218,-7.047 23.011,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.606 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;"/><path d="M36.496,29.706l2.816,5.333l-6.526,4.015c0,0 -1.033,0.738 -1.598,-0.147c-0.565,-0.887 -1.553,-2.671 -2.028,-3.741c-0.364,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-miterlimit:1.41421;"/><path d="M39.031,33.89l-2.012,-3.698l1.953,-1.153l2.012,3.699l-1.953,1.152Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-miterlimit:1.41421;"/></g></g></g></g><path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:none;stroke:#333;stroke-width:0.1px;stroke-linejoin:miter;stroke-miterlimit:11;"/></svg> <svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 46 46"
version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"><rect id="Photochromic-alone" serif:id="Photochromic alone" x="0" y="0" width="45.84" height="45.84" style="fill:none;"/>
<path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:#fff;"/>
<clipPath id="_clip1"><path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath>
<g clip-path="url(#_clip1)"><clipPath id="_clip2"><rect x="4.275" y="4.975" width="36.85" height="36.85"/></clipPath>
<g clip-path="url(#_clip2)"><g id="Artboard1"><rect x="4.275" y="4.975" width="36.71" height="36.701" style="fill:none;"/><g
id="Logo-01"><path d="M6.749,38.053c4.02,-13.193 12.218,-7.047 23.011,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.606 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;"/>
<path d="M36.496,29.706l2.816,5.333l-6.526,4.015c0,0 -1.033,0.738 -1.598,-0.147c-0.565,-0.887 -1.553,-2.671 -2.028,-3.741c-0.364,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-miterlimit:1.41421;"/>
<path d="M39.031,33.89l-2.012,-3.698l1.953,-1.153l2.012,3.699l-1.953,1.152Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-miterlimit:1.41421;"/></g></g></g></g>
<path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:none;stroke:#333;stroke-width:0.1px;stroke-linejoin:miter;stroke-miterlimit:11;"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,3 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC <?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC
"-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 124 44" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-miterlimit:11;"><rect id="Photochromic-Tag-web" serif:id="Photochromic Tag web" x="0" y="0" width="123.413" height="43.733" style="fill:none;"/><clipPath id="_clip1"><rect x="0" y="0" width="123.413" height="43.733"/></clipPath><g clip-path="url(#_clip1)"><g><path d="M44.175,28.681l0,-4.925l24.55,0l0,-2.496l10.513,4.959l-10.513,4.958l0,-2.496l-24.55,0Z" style="fill:#333;stroke:#333;stroke-width:0.85px;"/><text x="49.508px" y="19.727px" style="font-family:'Lato-Regular', 'Lato', sans-serif;font-size:24.107px;fill:#231f20;">6s</text></g><path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:#fff;"/><clipPath id="_clip2"><path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath><g clip-path="url(#_clip2)"><clipPath id="_clip3"><rect x="2.835" y="2.835" width="36.85" height="36.85"/></clipPath><g clip-path="url(#_clip3)"><g id="Artboard1"><rect x="2.835" y="2.835" width="36.71" height="36.701" style="fill:none;"/><g id="Logo-01"><path d="M5.309,35.913c4.02,-13.194 12.218,-7.047 23.011,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.605 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;stroke-linejoin:round;stroke-miterlimit:10;"/><path d="M35.056,27.566l2.816,5.333l-6.526,4.014c0,0 -1.033,0.739 -1.598,-0.147c-0.565,-0.886 -1.553,-2.67 -2.028,-3.74c-0.364,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/><path d="M37.591,31.75l-2.012,-3.699l1.953,-1.152l2.012,3.699l-1.953,1.152Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/></g></g></g></g><path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:none;stroke:#333;stroke-width:0.1px;"/><path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:#fff;"/><clipPath id="_clip4"><path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath><g clip-path="url(#_clip4)"><clipPath id="_clip5"><rect x="83.32" y="2.835" width="36.85" height="36.85"/></clipPath><g clip-path="url(#_clip5)"><g id="Artboard11" serif:id="Artboard1"><rect x="83.32" y="2.835" width="36.71" height="36.701" style="fill:none;"/><path d="M114.217,23.7c0.896,-2.192 1.142,-4.517 0.713,-6.743c-1.347,-6.998 -8.906,-11.33 -16.87,-9.668c-7.965,1.662 -13.337,8.693 -11.99,15.691c0.428,2.225 1.516,4.273 3.153,5.936l24.994,-5.216Z" style="fill:#e5f20d;"/><g id="Logo-011" serif:id="Logo-01"><path d="M85.795,35.92c4.019,-13.193 12.217,-7.047 23.01,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.606 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;stroke-linejoin:round;stroke-miterlimit:10;"/><path d="M115.541,27.573l2.816,5.333l-6.526,4.015c0,0 -1.033,0.738 -1.597,-0.147c-0.565,-0.887 -1.553,-2.671 -2.029,-3.741c-0.363,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/><path d="M118.077,31.757l-2.013,-3.698l1.953,-1.152l2.013,3.698l-1.953,1.152Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/></g></g></g></g><path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:none;stroke:#333;stroke-width:0.1px;"/></g></svg> <svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 124 44"
version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-miterlimit:11;"><rect id="Photochromic-Tag-web" serif:id="Photochromic Tag web" x="0" y="0" width="123.413" height="43.733" style="fill:none;"/>
<clipPath id="_clip1"><rect x="0" y="0" width="123.413" height="43.733"/></clipPath>
<g clip-path="url(#_clip1)"><g><path d="M44.175,28.681l0,-4.925l24.55,0l0,-2.496l10.513,4.959l-10.513,4.958l0,-2.496l-24.55,0Z" style="fill:#333;stroke:#333;stroke-width:0.85px;"/><text
x="49.508px" y="19.727px"
style="font-family:'Lato-Regular', 'Lato', sans-serif;font-size:24.107px;fill:#231f20;">6s</text></g>
<path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:#fff;"/>
<clipPath id="_clip2"><path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath>
<g clip-path="url(#_clip2)"><clipPath id="_clip3"><rect x="2.835" y="2.835" width="36.85" height="36.85"/></clipPath>
<g clip-path="url(#_clip3)"><g id="Artboard1"><rect x="2.835" y="2.835" width="36.71" height="36.701" style="fill:none;"/><g
id="Logo-01"><path d="M5.309,35.913c4.02,-13.194 12.218,-7.047 23.011,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.605 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d="M35.056,27.566l2.816,5.333l-6.526,4.014c0,0 -1.033,0.739 -1.598,-0.147c-0.565,-0.886 -1.553,-2.67 -2.028,-3.74c-0.364,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/>
<path d="M37.591,31.75l-2.012,-3.699l1.953,-1.152l2.012,3.699l-1.953,1.152Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/></g></g></g></g>
<path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:none;stroke:#333;stroke-width:0.1px;"/>
<path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:#fff;"/>
<clipPath id="_clip4"><path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath>
<g clip-path="url(#_clip4)"><clipPath id="_clip5"><rect x="83.32" y="2.835" width="36.85" height="36.85"/></clipPath>
<g clip-path="url(#_clip5)"><g id="Artboard11" serif:id="Artboard1"><rect x="83.32" y="2.835" width="36.71" height="36.701" style="fill:none;"/><path
d="M114.217,23.7c0.896,-2.192 1.142,-4.517 0.713,-6.743c-1.347,-6.998 -8.906,-11.33 -16.87,-9.668c-7.965,1.662 -13.337,8.693 -11.99,15.691c0.428,2.225 1.516,4.273 3.153,5.936l24.994,-5.216Z"
style="fill:#e5f20d;"/><g id="Logo-011" serif:id="Logo-01"><path d="M85.795,35.92c4.019,-13.193 12.217,-7.047 23.01,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.606 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d="M115.541,27.573l2.816,5.333l-6.526,4.015c0,0 -1.033,0.738 -1.597,-0.147c-0.565,-0.887 -1.553,-2.671 -2.029,-3.741c-0.363,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/>
<path d="M118.077,31.757l-2.013,-3.698l1.953,-1.152l2.013,3.698l-1.953,1.152Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/></g></g></g></g>
<path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:none;stroke:#333;stroke-width:0.1px;"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -23,8 +23,7 @@ class Sync:
def run(self, def run(self,
device: Device, device: Device,
components: Iterable[Component] or None) -> (Device, OrderedSet): components: Iterable[Component] or None) -> (Device, OrderedSet):
""" """Synchronizes the device and components with the database.
Synchronizes the device and components with the database.
Identifies if the device and components exist in the database Identifies if the device and components exist in the database
and updates / inserts them as necessary. and updates / inserts them as necessary.
@ -79,8 +78,7 @@ class Sync:
component: Component, component: Component,
blacklist: Set[int], blacklist: Set[int],
parent: Computer): parent: Computer):
""" """Synchronizes one component to the DB.
Synchronizes one component to the DB.
This method is a specialization of :meth:`.execute_register` This method is a specialization of :meth:`.execute_register`
but for components that are inside parents. but for components that are inside parents.
@ -125,8 +123,7 @@ class Sync:
return db_component, is_new return db_component, is_new
def execute_register(self, device: Device) -> Device: def execute_register(self, device: Device) -> Device:
""" """Synchronizes one device to the DB.
Synchronizes one device to the DB.
This method tries to get an existing device using the HID This method tries to get an existing device using the HID
or one of the tags, and... or one of the tags, and...
@ -205,8 +202,7 @@ class Sync:
@staticmethod @staticmethod
def merge(device: Device, db_device: Device): def merge(device: Device, db_device: Device):
""" """Copies the physical properties of the device to the db_device.
Copies the physical properties of the device to the db_device.
This method mutates db_device. This method mutates db_device.
""" """
@ -217,8 +213,7 @@ class Sync:
@staticmethod @staticmethod
def add_remove(device: Computer, def add_remove(device: Computer,
components: Set[Component]) -> OrderedSet: components: Set[Component]) -> OrderedSet:
""" """Generates the Add and Remove actions (but doesn't add them to
Generates the Add and Remove actions (but doesn't add them to
session). session).
:param device: A device which ``components`` attribute contains :param device: A device which ``components`` attribute contains

View File

@ -2,222 +2,222 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css" <link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css"
rel="stylesheet" rel="stylesheet"
integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT" integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT"
crossorigin="anonymous"> crossorigin="anonymous">
<title>Devicehub | {{ device.__format__('t') }}</title> <title>Devicehub | {{ device.__format__('t') }}</title>
</head> </head>
<body> <body>
<nav class="navbar navbar-default" style="background-color: gainsboro; margin: 0 !important"> <nav class="navbar navbar-default" style="background-color: gainsboro; margin: 0 !important">
<div class="container-fluid"> <div class="container-fluid">
<a href="https://www.ereuse.org/" target="_blank"> <a href="https://www.ereuse.org/" target="_blank">
<img alt="Brand" <img alt="Brand"
class="center-block" class="center-block"
style="height: 4em; padding-bottom: 0.1em" style="height: 4em; padding-bottom: 0.1em"
src="{{ url_for('Device.static', filename='ereuse-logo.svg') }}"> src="{{ url_for('Device.static', filename='ereuse-logo.svg') }}">
</a> </a>
</div> </div>
</nav> </nav>
<div class="jumbotron"> <div class="jumbotron">
<img class="center-block" <img class="center-block"
style="height: 13em; padding-bottom: 0.1em" style="height: 13em; padding-bottom: 0.1em"
src="{{ url_for('Device.static', filename='magrama.svg') }}"> src="{{ url_for('Device.static', filename='magrama.svg') }}">
</div> </div>
<div class="container"> <div class="container">
<div class="page-header"> <div class="page-header">
<h1>{{ device.__format__('t') }}<br> <h1>{{ device.__format__('t') }}<br>
<small>{{ device.__format__('s') }}</small> <small>{{ device.__format__('s') }}</small>
</h1> </h1>
</div> </div>
</div> </div>
<div class="container"> <div class="container">
<h2 class='text-center'> <h2 class='text-center'>
This is your {{ device.t }}. This is your {{ device.t }}.
</h2> </h2>
<p class="text-center"> <p class="text-center">
{% if device.trading %} {% if device.trading %}
{{ device.trading }} {{ device.trading }}
{% endif %} {% endif %}
{% if device.trading and device.physical %} {% if device.trading and device.physical %}
and and
{% endif %} {% endif %}
{% if device.physical %} {% if device.physical %}
{{ device.physical }} {{ device.physical }}
{% endif %} {% endif %}
</p> </p>
<div class="row"> <div class="row">
<article class="col-md-6"> <article class="col-md-6">
<h3>You can verify the originality of your device.</h3> <h3>You can verify the originality of your device.</h3>
<p> <p>
If your device comes with the following tag If your device comes with the following tag
<img class="img-responsive center-block" style="width: 12em;" <img class="img-responsive center-block" style="width: 12em;"
src="{{ url_for('Device.static', filename='photochromic-alone.svg') }}"> src="{{ url_for('Device.static', filename='photochromic-alone.svg') }}">
it means it has been refurbished by an eReuse.org it means it has been refurbished by an eReuse.org
certified organization. certified organization.
</p> </p>
<p> <p>
The tag is special illuminate it with the torch of The tag is special illuminate it with the torch of
your phone for 6 seconds and it will react like in your phone for 6 seconds and it will react like in
the following image: the following image:
<img class="img-responsive center-block" style="width: 30em;" <img class="img-responsive center-block" style="width: 30em;"
src="{{ url_for('Device.static', filename='photochromic-tag-web.svg') }}"> src="{{ url_for('Device.static', filename='photochromic-tag-web.svg') }}">
This is proof that this device is genuine. This is proof that this device is genuine.
</p> </p>
</article> </article>
<article class="col-md-6"> <article class="col-md-6">
<h3>These are the specifications</h3> <h3>These are the specifications</h3>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th>Range</th> <th>Range</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% if device.processor_model %} {% if device.processor_model %}
<tr> <tr>
<td> <td>
CPU {{ device.processor_model }} CPU {{ device.processor_model }}
</td> </td>
<td> <td>
{% if device.rate %} {% if device.rate %}
{{ device.rate.processor_range }} {{ device.rate.processor_range }}
({{ device.rate.processor }}) ({{ device.rate.processor }})
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.ram_size %} {% if device.ram_size %}
<tr> <tr>
<td> <td>
RAM {{ device.ram_size // 1000 }} GB RAM {{ device.ram_size // 1000 }} GB
{{ macros.component_type(device.components, 'RamModule') }} {{ macros.component_type(device.components, 'RamModule') }}
</td> </td>
<td> <td>
{% if device.rate %} {% if device.rate %}
{{ device.rate.ram_range }} {{ device.rate.ram_range }}
({{ device.rate.ram }}) ({{ device.rate.ram }})
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.data_storage_size %} {% if device.data_storage_size %}
<tr> <tr>
<td> <td>
Data Storage {{ device.data_storage_size // 1000 }} GB Data Storage {{ device.data_storage_size // 1000 }} GB
{{ macros.component_type(device.components, 'SolidStateDrive') }} {{ macros.component_type(device.components, 'SolidStateDrive') }}
{{ macros.component_type(device.components, 'HardDrive') }} {{ macros.component_type(device.components, 'HardDrive') }}
</td> </td>
<td> <td>
{% if device.rate %} {% if device.rate %}
{{ device.rate.data_storage_range }} {{ device.rate.data_storage_range }}
({{ device.rate.data_storage }}) ({{ device.rate.data_storage }})
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.graphic_card_model %} {% if device.graphic_card_model %}
<tr> <tr>
<td> <td>
Graphics {{ device.graphic_card_model }} Graphics {{ device.graphic_card_model }}
{{ macros.component_type(device.components, 'GraphicCard') }} {{ macros.component_type(device.components, 'GraphicCard') }}
</td> </td>
<td></td> <td></td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.network_speeds %} {% if device.network_speeds %}
<tr> <tr>
<td> <td>
Network Network
{% if device.network_speeds[0] %} {% if device.network_speeds[0] %}
Ethernet Ethernet
{% if device.network_speeds[0] != None %} {% if device.network_speeds[0] != None %}
max. {{ device.network_speeds[0] }} Mbps max. {{ device.network_speeds[0] }} Mbps
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if device.network_speeds[0] and device.network_speeds[1] %} {% if device.network_speeds[0] and device.network_speeds[1] %}
+ +
{% endif %} {% endif %}
{% if device.network_speeds[1] %} {% if device.network_speeds[1] %}
WiFi WiFi
{% if device.network_speeds[1] != None %} {% if device.network_speeds[1] != None %}
max. {{ device.network_speeds[1] }} Mbps max. {{ device.network_speeds[1] }} Mbps
{% endif %} {% endif %}
{% endif %} {% endif %}
{{ macros.component_type(device.components, 'NetworkAdapter') }} {{ macros.component_type(device.components, 'NetworkAdapter') }}
</td> </td>
<td></td> <td></td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.rate %} {% if device.rate %}
<tr class="active"> <tr class="active">
<td class="text-right"> <td class="text-right">
Total rate Total rate
</td> </td>
<td> <td>
{{ device.rate.rating_range }} {{ device.rate.rating_range }}
({{ device.rate.rating }}) ({{ device.rate.rating }})
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.rate and device.rate.price %} {% if device.rate and device.rate.price %}
<tr class="active"> <tr class="active">
<td class="text-right"> <td class="text-right">
Algorithm price Algorithm price
</td> </td>
<td> <td>
{{ device.rate.price }} {{ device.rate.price }}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.price %} {% if device.price %}
<tr class="active"> <tr class="active">
<td class="text-right"> <td class="text-right">
Actual price Actual price
</td> </td>
<td> <td>
{{ device.price }} {{ device.price }}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
</div>
<h3>This is the traceability log of your device</h3>
<div class="text-right">
<small>Latest one.</small>
</div>
<ol>
{% for action in device.actions|reverse %}
<li>
<strong>
{{ action.type }}
</strong>
{{ action }}
<br>
<div class="text-muted">
<small>
{{ action._date_str }}
</small>
</div> </div>
{% if action.certificate %} <h3>This is the traceability log of your device</h3>
<a href="{{ action.certificate.to_text() }}">See the certificate</a> <div class="text-right">
{% endif %} <small>Latest one.</small>
</li> </div>
{% endfor %} <ol>
</ol> {% for action in device.actions|reverse %}
<div class="text-right"> <li>
<small>Oldest one.</small> <strong>
</div> {{ action.type }}
</article> </strong>
</div>
{{ action }}
<br>
<div class="text-muted">
<small>
{{ action._date_str }}
</small>
</div>
{% if action.certificate %}
<a href="{{ action.certificate.to_text() }}">See the certificate</a>
{% endif %}
</li>
{% endfor %}
</ol>
<div class="text-right">
<small>Oldest one.</small>
</div>
</article>
</div>
</div> </div>
</body> </body>

View File

@ -1,18 +1,18 @@
{% macro component_type(components, type) %} {% macro component_type(components, type) %}
<ul> <ul>
{% for c in components if c.t == type %} {% for c in components if c.t == type %}
<li> <li>
{{ c.__format__('t') }} {{ c.__format__('t') }}
<p> <p>
<small class="text-muted">{{ c.__format__('s') }}</small> <small class="text-muted">{{ c.__format__('s') }}</small>
</p> </p>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endmacro %} {% endmacro %}
{% macro rate(range) %} {% macro rate(range) %}
<span class="label label-primary"> <span class="label label-primary">
{{ range }} {{ range }}
</span> </span>
{% endmacro %} {% endmacro %}

View File

@ -78,8 +78,7 @@ class DeviceView(View):
page = f.Integer(validate=v.Range(min=1), missing=1) page = f.Integer(validate=v.Range(min=1), missing=1)
def get(self, id): def get(self, id):
""" """Devices view
Devices view
--- ---
description: Gets a device or multiple devices. description: Gets a device or multiple devices.
parameters: parameters:

View File

@ -56,9 +56,7 @@ class DeviceRow(OrderedDict):
self.components() self.components()
def components(self): def components(self):
""" """Function to get all components information of a device."""
Function to get all components information of a device
"""
assert isinstance(self.device, d.Computer) assert isinstance(self.device, d.Computer)
# todo put an input specific order (non alphabetic) & where are a list of types components # todo put an input specific order (non alphabetic) & where are a list of types components
for type in sorted(current_app.resources[d.Component.t].subresources_types): # type: str for type in sorted(current_app.resources[d.Component.t].subresources_types): # type: str
@ -75,8 +73,8 @@ class DeviceRow(OrderedDict):
i += 1 i += 1
def fill_component(self, type, i, component=None): def fill_component(self, type, i, component=None):
""" """Function to put specific information of components
Function to put specific information of components in OrderedDict (csv) in OrderedDict (csv)
:param type: type of component :param type: type of component
:param component: device.components :param component: device.components
""" """
@ -85,11 +83,13 @@ class DeviceRow(OrderedDict):
self['{} {} Model'.format(type, i)] = component.serial_number if component else '' self['{} {} Model'.format(type, i)] = component.serial_number if component else ''
self['{} {} Serial Number'.format(type, i)] = component.serial_number if component else '' self['{} {} Serial Number'.format(type, i)] = component.serial_number if component else ''
""" Particular fields for component GraphicCard """ """Particular fields for component GraphicCard."""
if isinstance(component, d.GraphicCard): if isinstance(component, d.GraphicCard):
self['{} {} Memory (MB)'.format(type, i)] = component.memory self['{} {} Memory (MB)'.format(type, i)] = component.memory
""" Particular fields for component DataStorage.t -> (HardDrive, SolidStateDrive) """ """Particular fields for component DataStorage.t ->
(HardDrive, SolidStateDrive)
"""
if isinstance(component, d.DataStorage): if isinstance(component, d.DataStorage):
self['{} {} Size (MB)'.format(type, i)] = component.size self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Privacy'.format(type, i)] = component.privacy self['{} {} Privacy'.format(type, i)] = component.privacy
@ -109,16 +109,14 @@ class DeviceRow(OrderedDict):
except: except:
self['{} {} Writing speed'.format(type, i)] = '' self['{} {} Writing speed'.format(type, i)] = ''
""" Particular fields for component Processor """ """Particular fields for component Processor."""
if isinstance(component, d.Processor): if isinstance(component, d.Processor):
self['{} {} Number of cores'.format(type, i)] = component.cores self['{} {} Number of cores'.format(type, i)] = component.cores
self['{} {} Speed (GHz)'.format(type, i)] = component.speed self['{} {} Speed (GHz)'.format(type, i)] = component.speed
""" Particular fields for component RamModule """ """Particular fields for component RamModule."""
if isinstance(component, d.RamModule): if isinstance(component, d.RamModule):
self['{} {} Size (MB)'.format(type, i)] = component.size self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Speed (MHz)'.format(type, i)] = component.speed self['{} {} Speed (MHz)'.format(type, i)] = component.speed
# todo add Display size, ... # todo add Display, NetworkAdapter, etc...
# todo add NetworkAdapter speedLink?
# todo add some ComputerAccessories

View File

@ -110,11 +110,7 @@ class DevicesDocumentView(DeviceView):
return self.generate_post_csv(query) return self.generate_post_csv(query)
def generate_post_csv(self, query): def generate_post_csv(self, query):
""" """Get device query and put information in csv format."""
Get device query and put information in csv format
:param query:
:return:
"""
data = StringIO() data = StringIO()
cw = csv.writer(data) cw = csv.writer(data)
first = True first = True

View File

@ -1,95 +1,95 @@
{% extends "documents/layout.html" %} {% extends "documents/layout.html" %}
{% block body %} {% block body %}
<div> <div>
<h2>Resumé</h2> <h2>Resumé</h2>
<table class="table table-bordered"> <table class="table table-bordered">
<thead> <thead>
<tr> <tr>
<th>S/N</th> <th>S/N</th>
<th>Tags</th> <th>Tags</th>
<th>S/N Data Storage</th> <th>S/N Data Storage</th>
<th>Type of erasure</th> <th>Type of erasure</th>
<th>Result</th> <th>Result</th>
<th>Date</th> <th>Date</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for erasure in erasures %} {% for erasure in erasures %}
<tr> <tr>
{% if erasure.parent.serial_number %} {% if erasure.parent.serial_number %}
<td> <td>
{{ erasure.parent.serial_number.upper() }} {{ erasure.parent.serial_number.upper() }}
</td> </td>
{% else %} {% else %}
<td></td> <td></td>
{% endif %} {% endif %}
<td> <td>
{{ erasure.parent.tags.__format__('') }} {{ erasure.parent.tags.__format__('') }}
</td> </td>
<td> <td>
{{ erasure.device.serial_number.upper() }} {{ erasure.device.serial_number.upper() }}
</td> </td>
<td> <td>
{{ erasure.type }} {{ erasure.type }}
</td> </td>
<td> <td>
{{ erasure.severity }} {{ erasure.severity }}
</td> </td>
<td> <td>
{{ erasure.date_str }} {{ erasure.date_str }}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="page-break row"> <div class="page-break row">
<h2>Details</h2> <h2>Details</h2>
{% for erasure in erasures %} {% for erasure in erasures %}
<div class="col-md-6 no-page-break"> <div class="col-md-6 no-page-break">
<h4>{{ erasure.device.__format__('t') }}</h4> <h4>{{ erasure.device.__format__('t') }}</h4>
<dl>
<dt>Data storage:</dt>
<dd>{{ erasure.device.__format__('ts') }}</dd>
<dt>Computer:</dt>
<dd>{{ erasure.parent.__format__('ts') }}</dd>
<dt>Tags:</dt>
<dd>{{ erasure.parent.tags }}</dd>
<dt>Erasure:</dt>
<dd>{{ erasure.__format__('ts') }}</dd>
{% if erasure.steps %}
<dt>Erasure steps:</dt>
<dd>
<ol>
{% for step in erasure.steps %}
<li>{{ step.__format__('') }}</li>
{% endfor %}
</ol>
</dd>
{% endif %}
</dl>
</div>
{% endfor %}
</div>
<div class="no-page-break">
<h2>Glossary</h2>
<dl> <dl>
<dt>Data storage:</dt> <dt>Erase Basic</dt>
<dd>{{ erasure.device.__format__('ts') }}</dd>
<dt>Computer:</dt>
<dd>{{ erasure.parent.__format__('ts') }}</dd>
<dt>Tags:</dt>
<dd>{{ erasure.parent.tags }}</dd>
<dt>Erasure:</dt>
<dd>{{ erasure.__format__('ts') }}</dd>
{% if erasure.steps %}
<dt>Erasure steps:</dt>
<dd> <dd>
<ol> A software-based fast non-100%-secured way of erasing data storage,
{% for step in erasure.steps %} using <a href="https://en.wikipedia.org/wiki/Shred_(Unix)">shred</a>.
<li>{{ step.__format__('') }}</li> </dd>
{% endfor %} <dt>Erase Sectors</dt>
</ol> <dd>
A secured-way of erasing data storages, checking sector-by-sector
the erasure, using <a href="https://en.wikipedia.org/wiki/Badblocks">badblocks</a>.
</dd> </dd>
{% endif %}
</dl> </dl>
</div> </div>
{% endfor %} <div class="no-print">
</div> <a href="{{ url_pdf }}">Click here to download the PDF.</a>
<div class="no-page-break"> </div>
<h2>Glossary</h2> <div class="print-only">
<dl> <a href="{{ url_web }}">Verify on-line the integrity of this document</a>
<dt>Erase Basic</dt> </div>
<dd>
A software-based fast non-100%-secured way of erasing data storage,
using <a href="https://en.wikipedia.org/wiki/Shred_(Unix)">shred</a>.
</dd>
<dt>Erase Sectors</dt>
<dd>
A secured-way of erasing data storages, checking sector-by-sector
the erasure, using <a href="https://en.wikipedia.org/wiki/Badblocks">badblocks</a>.
</dd>
</dl>
</div>
<div class="no-print">
<a href="{{ url_pdf }}">Click here to download the PDF.</a>
</div>
<div class="print-only">
<a href="{{ url_web }}">Verify on-line the integrity of this document</a>
</div>
{% endblock %} {% endblock %}

View File

@ -2,25 +2,25 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css" <link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css"
rel="stylesheet" rel="stylesheet"
integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT" integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT"
crossorigin="anonymous"> crossorigin="anonymous">
<link rel="stylesheet" <link rel="stylesheet"
type="text/css" type="text/css"
href="{{ url_for('Document.static', filename='print.css') }}"> href="{{ url_for('Document.static', filename='print.css') }}">
<title>Devicehub | {{ title }}</title> <title>Devicehub | {{ title }}</title>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<header class="page-header"> <header class="page-header">
<h1> {{ title }}</h1> <h1> {{ title }}</h1>
</header> </header>
</div> </div>
{% block body %}{% endblock %} {% block body %}{% endblock %}
</div> </div>
</body> </body>
</html> </html>

View File

@ -24,16 +24,13 @@ class Lot(Thing):
description = db.Column(CIText()) description = db.Column(CIText())
description.comment = """A comment about the lot.""" description.comment = """A comment about the lot."""
closed = db.Column(db.Boolean, default=False, nullable=False) closed = db.Column(db.Boolean, default=False, nullable=False)
closed.comment = """ closed.comment = """A closed lot cannot be modified anymore."""
A closed lot cannot be modified anymore.
"""
devices = db.relationship(Device, devices = db.relationship(Device,
backref=db.backref('lots', lazy=True, collection_class=set), backref=db.backref('lots', lazy=True, collection_class=set),
secondary=lambda: LotDevice.__table__, secondary=lambda: LotDevice.__table__,
lazy=True, lazy=True,
collection_class=set) collection_class=set)
""" """The **children** devices that the lot has.
The **children** devices that the lot has.
Note that the lot can have more devices, if they are inside Note that the lot can have more devices, if they are inside
descendant lots. descendant lots.
@ -67,8 +64,7 @@ class Lot(Thing):
def __init__(self, name: str, closed: bool = closed.default.arg, def __init__(self, name: str, closed: bool = closed.default.arg,
description: str = None) -> None: description: str = None) -> None:
""" """Initializes a lot
Initializes a lot
:param name: :param name:
:param closed: :param closed:
""" """
@ -173,9 +169,7 @@ class LotDevice(db.Model):
nullable=False, nullable=False,
default=lambda: g.user.id) default=lambda: g.user.id)
author = db.relationship(User, primaryjoin=author_id == User.id) author = db.relationship(User, primaryjoin=author_id == User.id)
author_id.comment = """ author_id.comment = """The user that put the device in the lot."""
The user that put the device in the lot.
"""
class Path(db.Model): class Path(db.Model):
@ -191,9 +185,7 @@ class Path(db.Model):
primaryjoin=Lot.id == lot_id) primaryjoin=Lot.id == lot_id)
path = db.Column(LtreeType, nullable=False) path = db.Column(LtreeType, nullable=False)
created = db.Column(db.TIMESTAMP(timezone=True), server_default=db.text('CURRENT_TIMESTAMP')) created = db.Column(db.TIMESTAMP(timezone=True), server_default=db.text('CURRENT_TIMESTAMP'))
created.comment = """ created.comment = """When Devicehub created this."""
When Devicehub created this.
"""
__table_args__ = ( __table_args__ = (
# dag.delete_edge needs to disable internally/temporarily the unique constraint # dag.delete_edge needs to disable internally/temporarily the unique constraint

View File

@ -23,8 +23,7 @@ class LotFormat(Enum):
class LotView(View): class LotView(View):
class FindArgs(MarshmallowSchema): class FindArgs(MarshmallowSchema):
""" """Allowed arguments for the ``find``
Allowed arguments for the ``find``
method (GET collection) endpoint method (GET collection) endpoint
""" """
format = EnumField(LotFormat, missing=None) format = EnumField(LotFormat, missing=None)
@ -56,8 +55,7 @@ class LotView(View):
@teal.cache.cache(datetime.timedelta(minutes=5)) @teal.cache.cache(datetime.timedelta(minutes=5))
def find(self, args: dict): def find(self, args: dict):
""" """Gets lots.
Gets lots.
By passing the value `UiTree` in the parameter `format` By passing the value `UiTree` in the parameter `format`
of the query you get a recursive nested suited for ui-tree:: of the query you get a recursive nested suited for ui-tree::

View File

@ -20,16 +20,14 @@ class Thing(db.Model):
nullable=False, nullable=False,
index=True, index=True,
server_default=db.text('CURRENT_TIMESTAMP')) server_default=db.text('CURRENT_TIMESTAMP'))
updated.comment = """ updated.comment = """The last time Devicehub recorded a change for
The last time Devicehub recorded a change for this thing. 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:
# We need to set 'created' before sqlalchemy inits the class # We need to set 'created' before sqlalchemy inits the class

View File

@ -39,8 +39,8 @@ class Tag(Thing):
collection_class=set) collection_class=set)
"""The organization that issued the tag.""" """The organization that issued the tag."""
provider = Column(URL()) provider = Column(URL())
provider.comment = """ provider.comment = """The tag provider URL. If None, the provider is
The tag provider URL. If None, the provider is this Devicehub. this Devicehub.
""" """
device_id = Column(BigInteger, device_id = Column(BigInteger,
# We don't want to delete the tag on device deletion, only set to null # We don't want to delete the tag on device deletion, only set to null
@ -50,9 +50,8 @@ class Tag(Thing):
primaryjoin=Device.id == device_id) primaryjoin=Device.id == device_id)
"""The device linked to this tag.""" """The device linked to this tag."""
secondary = Column(db.CIText(), index=True) secondary = Column(db.CIText(), index=True)
secondary.comment = """ secondary.comment = """A secondary identifier for this tag.
A secondary identifier for this tag. It has the same It has the same constraints as the main one. Only needed in special cases.
constraints as the main one. Only needed in special cases.
""" """
__table_args__ = ( __table_args__ = (
@ -116,7 +115,7 @@ class Tag(Thing):
@classmethod @classmethod
def is_printable_q(cls): def is_printable_q(cls):
"""Return a SQLAlchemy filter expression for printable queries""" """Return a SQLAlchemy filter expression for printable queries."""
return cls.org_id == Organization.get_default_org_id() return cls.org_id == Organization.get_default_org_id()
def __repr__(self) -> str: def __repr__(self) -> str:

View File

@ -50,7 +50,7 @@ class TagView(View):
class TagDeviceView(View): class TagDeviceView(View):
"""Endpoints to work with the device of the tag; /tags/23/device""" """Endpoints to work with the device of the tag; /tags/23/device."""
def one(self, id): def one(self, id):
"""Gets the device from the tag.""" """Gets the device from the tag."""
@ -78,8 +78,7 @@ class TagDeviceView(View):
def get_device_from_tag(id: str): def get_device_from_tag(id: str):
""" """Gets the device by passing a tag id.
Gets the device by passing a tag id.
Example: /tags/23/device. Example: /tags/23/device.

View File

@ -28,8 +28,7 @@ class User(Thing):
# todo set restriction that user has, at least, one active db # todo set restriction that user has, at least, one active db
def __init__(self, email, password=None, inventories=None) -> None: def __init__(self, email, password=None, inventories=None) -> None:
""" """Creates an user.
Creates an user.
:param email: :param email:
:param password: :param password:
:param inventories: A set of Inventory where the user has :param inventories: A set of Inventory where the user has

View File

@ -29,8 +29,7 @@ class User(Thing):
load_only=(), load_only=(),
dump_only=(), dump_only=(),
partial=False): partial=False):
""" """Instantiates the User.
Instantiates the User.
By default we exclude token from both load/dump By default we exclude token from both load/dump
so they are not taken / set in normal usage by mistake. so they are not taken / set in normal usage by mistake.

View File

@ -5,11 +5,11 @@ device:
model: d1ml model: d1ml
manufacturer: d1mr manufacturer: d1mr
components: components:
- type: GraphicCard - type: GraphicCard
serial_number: gc1s serial_number: gc1s
model: gc1ml model: gc1ml
manufacturer: gc1mr manufacturer: gc1mr
- type: RamModule - type: RamModule
serial_number: rm1s serial_number: rm1s
model: rm1ml model: rm1ml
manufacturer: rm1mr manufacturer: rm1mr

View File

@ -32,8 +32,8 @@ components:
passedLifetime: 16947, powerCycleCount: 1694, reallocatedSectorCount: 0, reportedUncorrectableErrors: 0, passedLifetime: 16947, powerCycleCount: 1694, reallocatedSectorCount: 0, reportedUncorrectableErrors: 0,
status: Completed without error, type: Short offline} status: Completed without error, type: Short offline}
type: HDD type: HDD
- { '@type': GraphicCard, manufacturer: Intel Corporation, memory: 256.0, model: 4 - { '@type': GraphicCard, manufacturer: Intel Corporation, memory: 256.0, model: 4
Series Chipset Integrated Graphics Controller, serialNumber: null} Series Chipset Integrated Graphics Controller, serialNumber: null}
- '@type': Motherboard - '@type': Motherboard
connectors: {firewire: 0, pcmcia: 0, serial: 1, usb: 8} connectors: {firewire: 0, pcmcia: 0, serial: 1, usb: 8}
manufacturer: LENOVO manufacturer: LENOVO
@ -41,22 +41,22 @@ components:
serialNumber: null serialNumber: null
totalSlots: 0 totalSlots: 0
usedSlots: 2 usedSlots: 2
- { '@type': NetworkAdapter, manufacturer: Intel Corporation, model: 82567LM-3 Gigabit - { '@type': NetworkAdapter, manufacturer: Intel Corporation, model: 82567LM-3 Gigabit
Network Connection, serialNumber: '00:21:86:2c:5e:d6', speed: 1000} Network Connection, serialNumber: '00:21:86:2c:5e:d6', speed: 1000}
- { '@type': SoundCard, manufacturer: Intel Corporation, model: 82801JD/DO HD Audio - { '@type': SoundCard, manufacturer: Intel Corporation, model: 82801JD/DO HD Audio
Controller, serialNumber: null} Controller, serialNumber: null}
condition: condition:
appearance: {general: B} appearance: {general: B}
functionality: {general: A} functionality: {general: A}
date: '2018-05-09T10:32:15' date: '2018-05-09T10:32:15'
debug: debug:
capabilities: { dmi-2.5: DMI version 2.5, smbios-2.5: SMBIOS version 2.5, smp: Symmetric capabilities: { dmi-2.5: DMI version 2.5, smbios-2.5: SMBIOS version 2.5, smp: Symmetric
Multi-Processing, smp-1.4: SMP specification v1.4} Multi-Processing, smp-1.4: SMP specification v1.4}
children: children:
- children: - children:
- capabilities: { acpi: ACPI, biosbootspecification: BIOS boot specification, cdboot: Booting - capabilities: { acpi: ACPI, biosbootspecification: BIOS boot specification, cdboot: Booting
from CD-ROM/DVD, edd: Enhanced Disk Drive extensions, escd: ESCD, ls120boot: Booting from CD-ROM/DVD, edd: Enhanced Disk Drive extensions, escd: ESCD, ls120boot: Booting
from LS-120, pci: PCI bus, pnp: Plug-and-Play, shadowing: BIOS shadowing, from LS-120, pci: PCI bus, pnp: Plug-and-Play, shadowing: BIOS shadowing,
smartbattery: Smart battery, upgrade: BIOS EEPROM can be upgraded, usb: USB smartbattery: Smart battery, upgrade: BIOS EEPROM can be upgraded, usb: USB
legacy emulation} legacy emulation}
capacity: 4128768 capacity: 4128768
@ -71,9 +71,9 @@ debug:
vendor: LENOVO vendor: LENOVO
version: 5CKT48AUS version: 5CKT48AUS
- businfo: cpu@0 - businfo: cpu@0
capabilities: { acpi: thermal control (ACPI), aperfmperf: true, apic: on-chip capabilities: { acpi: thermal control (ACPI), aperfmperf: true, apic: on-chip
advanced programmable interrupt controller (APIC), arch_perfmon: true, boot: boot advanced programmable interrupt controller (APIC), arch_perfmon: true, boot: boot
processor, bts: true, clflush: true, cmov: conditional move instruction, processor, bts: true, clflush: true, cmov: conditional move instruction,
constant_tsc: true, cpufreq: CPU Frequency scaling, cx16: true, cx8: compare constant_tsc: true, cpufreq: CPU Frequency scaling, cx16: true, cx8: compare
and exchange 8-byte, de: debugging extensions, ds_cpl: true, dtes64: true, and exchange 8-byte, de: debugging extensions, ds_cpl: true, dtes64: true,
dtherm: true, dts: debug trace and EMON store MSRs, eagerfpu: true, est: true, dtherm: true, dts: debug trace and EMON store MSRs, eagerfpu: true, est: true,
@ -149,16 +149,16 @@ debug:
version: 6.7.10 version: 6.7.10
width: 64 width: 64
- children: - children:
- { claimed: true, class: memory, clock: 1067000000, description: DIMM DDR2 Synchronous - { claimed: true, class: memory, clock: 1067000000, description: DIMM DDR2 Synchronous
1067 MHz (0.9 ns), handle: 'DMI:001F', id: 'bank:0', physid: '0', product: '000000000000000000000000000000000000', 1067 MHz (0.9 ns), handle: 'DMI:001F', id: 'bank:0', physid: '0', product: '000000000000000000000000000000000000',
serial: '00000000', size: 2147483648, slot: J6G1, units: bytes, vendor: Unknown, serial: '00000000', size: 2147483648, slot: J6G1, units: bytes, vendor: Unknown,
width: 40960} width: 40960}
- {claimed: true, class: memory, clock: 1067000000, description: 'DIMM DDR2 - {claimed: true, class: memory, clock: 1067000000, description: 'DIMM DDR2
Synchronous 1067 MHz (0.9 ns) [empty]', handle: 'DMI:0020', id: 'bank:1', Synchronous 1067 MHz (0.9 ns) [empty]', handle: 'DMI:0020', id: 'bank:1',
physid: '1', product: 012345678901234567890123456789012345, serial: '01234567', physid: '1', product: 012345678901234567890123456789012345, serial: '01234567',
slot: J6G2, vendor: 48spaces} slot: J6G2, vendor: 48spaces}
- { claimed: true, class: memory, clock: 1067000000, description: DIMM DDR2 Synchronous - { claimed: true, class: memory, clock: 1067000000, description: DIMM DDR2 Synchronous
1067 MHz (0.9 ns), handle: 'DMI:0021', id: 'bank:2', physid: '2', product: '000000000000000000000000000000000000', 1067 MHz (0.9 ns), handle: 'DMI:0021', id: 'bank:2', physid: '2', product: '000000000000000000000000000000000000',
serial: '00000000', size: 2147483648, slot: J6H1, units: bytes, vendor: Unknown, serial: '00000000', size: 2147483648, slot: J6H1, units: bytes, vendor: Unknown,
width: 41984} width: 41984}
- {claimed: true, class: memory, clock: 1067000000, description: 'DIMM DDR2 - {claimed: true, class: memory, clock: 1067000000, description: 'DIMM DDR2
@ -205,7 +205,7 @@ debug:
- businfo: pci@0000:00:00.0 - businfo: pci@0000:00:00.0
children: children:
- businfo: pci@0000:00:02.0 - businfo: pci@0000:00:02.0
capabilities: { bus_master: bus mastering, cap_list: PCI capabilities listing, capabilities: { bus_master: bus mastering, cap_list: PCI capabilities listing,
msi: Message Signalled Interrupts, pm: Power Management, rom: extension msi: Message Signalled Interrupts, pm: Power Management, rom: extension
ROM, vga_controller: true} ROM, vga_controller: true}
claimed: true claimed: true
@ -265,8 +265,8 @@ debug:
version: '03' version: '03'
width: 32 width: 32
- businfo: pci@0000:00:03.3 - businfo: pci@0000:00:03.3
capabilities: { '16550': true, bus_master: bus mastering, cap_list: PCI capabilities capabilities: { '16550': true, bus_master: bus mastering, cap_list: PCI capabilities
listing, msi: Message Signalled Interrupts, pm: Power Management} listing, msi: Message Signalled Interrupts, pm: Power Management}
claimed: true claimed: true
class: communication class: communication
clock: 66000000 clock: 66000000
@ -280,8 +280,8 @@ debug:
version: '03' version: '03'
width: 32 width: 32
- businfo: pci@0000:00:19.0 - businfo: pci@0000:00:19.0
capabilities: { 1000bt-fd: 1Gbit/s (full duplex), 100bt: 100Mbit/s, 100bt-fd: 100Mbit/s capabilities: { 1000bt-fd: 1Gbit/s (full duplex), 100bt: 100Mbit/s, 100bt-fd: 100Mbit/s
(full duplex), 10bt: 10Mbit/s, 10bt-fd: 10Mbit/s (full duplex), autonegotiation: Auto-negotiation, (full duplex), 10bt: 10Mbit/s, 10bt-fd: 10Mbit/s (full duplex), autonegotiation: Auto-negotiation,
bus_master: bus mastering, cap_list: PCI capabilities listing, ethernet: true, bus_master: bus mastering, cap_list: PCI capabilities listing, ethernet: true,
msi: Message Signalled Interrupts, physical: Physical interface, pm: Power msi: Message Signalled Interrupts, physical: Physical interface, pm: Power
Management, tp: twisted pair} Management, tp: twisted pair}
@ -575,8 +575,8 @@ debug:
version: '02' version: '02'
width: 32 width: 32
- businfo: pci@0000:00:1f.2 - businfo: pci@0000:00:1f.2
capabilities: { ahci_1.0: true, bus_master: bus mastering, cap_list: PCI capabilities capabilities: { ahci_1.0: true, bus_master: bus mastering, cap_list: PCI capabilities
listing, msi: Message Signalled Interrupts, pm: Power Management, storage: true} listing, msi: Message Signalled Interrupts, pm: Power Management, storage: true}
claimed: true claimed: true
class: storage class: storage
clock: 66000000 clock: 66000000
@ -620,7 +620,7 @@ debug:
table} table}
children: children:
- businfo: scsi@0:0.0.0,1 - businfo: scsi@0:0.0.0,1
capabilities: { dir_nlink: directories with 65000+ subdirs, ext2: EXT2/EXT3, capabilities: { dir_nlink: directories with 65000+ subdirs, ext2: EXT2/EXT3,
ext4: true, extended_attributes: Extended Attributes, extents: extent-based ext4: true, extended_attributes: Extended Attributes, extents: extent-based
allocation, huge_files: 16TB+ files, initialized: initialized volume, allocation, huge_files: 16TB+ files, initialized: initialized volume,
journaled: true, large_files: 4GB+ files, primary: Primary partition} journaled: true, large_files: 4GB+ files, primary: Primary partition}
@ -672,8 +672,8 @@ debug:
- capabilities: {emulated: Emulated device} - capabilities: {emulated: Emulated device}
children: children:
- businfo: scsi@1:0.0.0 - businfo: scsi@1:0.0.0
capabilities: { audio: Audio CD playback, cd-r: CD-R burning, cd-rw: CD-RW capabilities: { audio: Audio CD playback, cd-r: CD-R burning, cd-rw: CD-RW
burning, dvd: DVD playback, dvd-r: DVD-R burning, dvd-ram: DVD-RAM burning, burning, dvd: DVD playback, dvd-r: DVD-R burning, dvd-ram: DVD-RAM burning,
removable: support is removable} removable: support is removable}
claimed: true claimed: true
class: disk class: disk

View File

@ -102,8 +102,7 @@ def test_membership_repeating_id():
@pytest.mark.usefixtures(app_context.__name__) @pytest.mark.usefixtures(app_context.__name__)
def test_default_org_exists(config: DevicehubConfig): def test_default_org_exists(config: DevicehubConfig):
""" """Ensures that the default organization is created on app
Ensures that the default organization is created on app
initialization and that is accessible for the method initialization and that is accessible for the method
:meth:`ereuse_devicehub.resources.user.Organization.get_default_org`. :meth:`ereuse_devicehub.resources.user.Organization.get_default_org`.
""" """

View File

@ -31,9 +31,7 @@ from tests.conftest import file
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_device_model(): def test_device_model():
""" """Tests that the correctness of the device model and its relationships."""
Tests that the correctness of the device model and its relationships.
"""
pc = d.Desktop(model='p1mo', pc = d.Desktop(model='p1mo',
manufacturer='p1ma', manufacturer='p1ma',
serial_number='p1s', serial_number='p1s',
@ -182,8 +180,7 @@ def test_add_remove():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_run_components_empty(): def test_sync_run_components_empty():
""" """Syncs a device that has an empty components list. The system should
Syncs a device that has an empty components list. The system should
remove all the components from the device. remove all the components from the device.
""" """
s = conftest.file('pc-components.db') s = conftest.file('pc-components.db')
@ -200,8 +197,7 @@ def test_sync_run_components_empty():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_run_components_none(): def test_sync_run_components_none():
""" """Syncs a device that has a None components. The system should
Syncs a device that has a None components. The system should
keep all the components from the device. keep all the components from the device.
""" """
s = conftest.file('pc-components.db') s = conftest.file('pc-components.db')
@ -218,10 +214,7 @@ def test_sync_run_components_none():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_new_desktop_no_tag(): def test_sync_execute_register_desktop_new_desktop_no_tag():
""" """Syncs a new d.Desktop with HID and without a tag, creating it."""
Syncs a new d.Desktop with HID and without a tag, creating it.
:return:
"""
# Case 1: device does not exist on DB # Case 1: device does not exist on DB
pc = d.Desktop(**conftest.file('pc-components.db')['device']) pc = d.Desktop(**conftest.file('pc-components.db')['device'])
db_pc = Sync().execute_register(pc) db_pc = Sync().execute_register(pc)
@ -230,9 +223,7 @@ def test_sync_execute_register_desktop_new_desktop_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_existing_no_tag(): def test_sync_execute_register_desktop_existing_no_tag():
""" """Syncs an existing d.Desktop with HID and without a tag."""
Syncs an existing d.Desktop with HID and without a tag.
"""
pc = d.Desktop(**conftest.file('pc-components.db')['device']) pc = d.Desktop(**conftest.file('pc-components.db')['device'])
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()
@ -246,8 +237,7 @@ def test_sync_execute_register_desktop_existing_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_no_hid_no_tag(): def test_sync_execute_register_desktop_no_hid_no_tag():
""" """Syncs a d.Desktop without HID and no tag.
Syncs a d.Desktop without HID and no tag.
This should fail as we don't have a way to identify it. This should fail as we don't have a way to identify it.
""" """
@ -260,8 +250,7 @@ def test_sync_execute_register_desktop_no_hid_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_tag_not_linked(): def test_sync_execute_register_desktop_tag_not_linked():
""" """Syncs a new d.Desktop with HID and a non-linked tag.
Syncs a new d.Desktop with HID and a non-linked tag.
It is OK if the tag was not linked, it will be linked in this process. It is OK if the tag was not linked, it will be linked in this process.
""" """
@ -279,8 +268,7 @@ def test_sync_execute_register_desktop_tag_not_linked():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str): def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
""" """Validates registering a d.Desktop without HID and a non-linked tag.
Validates registering a d.Desktop without HID and a non-linked tag.
In this case it is ok still, as the non-linked tag proves that In this case it is ok still, as the non-linked tag proves that
the d.Desktop was not existing before (otherwise the tag would the d.Desktop was not existing before (otherwise the tag would
@ -302,8 +290,7 @@ def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_tag_does_not_exist(): def test_sync_execute_register_tag_does_not_exist():
""" """Ensures not being able to register if the tag does not exist,
Ensures not being able to register if the tag does not exist,
even if the device has HID or it existed before. even if the device has HID or it existed before.
Tags have to be created before trying to link them through a Snapshot. Tags have to be created before trying to link them through a Snapshot.
@ -315,8 +302,7 @@ def test_sync_execute_register_tag_does_not_exist():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_tag_linked_same_device(): def test_sync_execute_register_tag_linked_same_device():
""" """If the tag is linked to the device, regardless if it has HID,
If the tag is linked to the device, regardless if it has HID,
the system should match the device through the tag. the system should match the device through the tag.
(If it has HID it validates both HID and tag point at the same (If it has HID it validates both HID and tag point at the same
device, this his checked in ). device, this his checked in ).
@ -336,8 +322,7 @@ def test_sync_execute_register_tag_linked_same_device():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags(): def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags():
""" """Checks that sync raises an error if finds that at least two passed-in
Checks that sync raises an error if finds that at least two passed-in
tags are not linked to the same device. tags are not linked to the same device.
""" """
pc1 = d.Desktop(**conftest.file('pc-components.db')['device']) pc1 = d.Desktop(**conftest.file('pc-components.db')['device'])
@ -358,8 +343,7 @@ def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags():
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_mismatch_between_tags_and_hid(): def test_sync_execute_register_mismatch_between_tags_and_hid():
""" """Checks that sync raises an error if it finds that the HID does
Checks that sync raises an error if it finds that the HID does
not point at the same device as the tag does. not point at the same device as the tag does.
In this case we set HID -> pc1 but tag -> pc2 In this case we set HID -> pc1 but tag -> pc2
@ -465,7 +449,8 @@ def test_manufacturer(user: UserClient):
@pytest.mark.xfail(reason='Develop functionality') @pytest.mark.xfail(reason='Develop functionality')
def test_manufacturer_enforced(): def test_manufacturer_enforced():
"""Ensures that non-computer devices can submit only """Ensures that non-computer devices can submit only
manufacturers from the Manufacturer table.""" manufacturers from the Manufacturer table.
"""
def test_device_properties_format(app: Devicehub, user: UserClient): def test_device_properties_format(app: Devicehub, user: UserClient):

View File

@ -56,8 +56,7 @@ def test_device_sort():
@pytest.fixture() @pytest.fixture()
def device_query_dummy(app: Devicehub): def device_query_dummy(app: Devicehub):
""" """3 computers, where:
3 computers, where:
1. s1 Desktop with a Processor 1. s1 Desktop with a Processor
2. s2 Desktop with an SSD 2. s2 Desktop with an SSD

View File

@ -28,7 +28,7 @@ def test_dispatcher_default(dispatcher: PathDispatcher):
def test_dispatcher_return_app(dispatcher: PathDispatcher): def test_dispatcher_return_app(dispatcher: PathDispatcher):
"""The dispatcher returns the correct app for the URL""" """The dispatcher returns the correct app for the URL."""
# Note that the dispatcher does not check if the URL points # Note that the dispatcher does not check if the URL points
# to a well-known endpoint for the app. # to a well-known endpoint for the app.
# Only if can route it to an app. And then the app checks # Only if can route it to an app. And then the app checks
@ -39,7 +39,7 @@ def test_dispatcher_return_app(dispatcher: PathDispatcher):
def test_dispatcher_users(dispatcher: PathDispatcher): def test_dispatcher_users(dispatcher: PathDispatcher):
"""Users special endpoint returns an app""" """Users special endpoint returns an app."""
# For now returns the first app, as all apps # For now returns the first app, as all apps
# can answer {}/users/login # can answer {}/users/login
app = dispatcher({'SCRIPT_NAME:': '/', 'PATH_INFO': '/users/'}, noop) app = dispatcher({'SCRIPT_NAME:': '/', 'PATH_INFO': '/users/'}, noop)

View File

@ -22,8 +22,7 @@ from tests.conftest import create_user, file
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_author(): def test_author():
""" """Checks the default created author.
Checks the default created author.
Note that the author can be accessed after inserting the row. Note that the author can be accessed after inserting the row.
""" """
@ -341,7 +340,8 @@ def test_price_custom_client(user: UserClient):
def test_ereuse_price(): def test_ereuse_price():
"""Tests the several ways of creating eReuse Price, emulating """Tests the several ways of creating eReuse Price, emulating
from an AggregateRate and ensuring that the different Range from an AggregateRate and ensuring that the different Range
return correct results.""" return correct results.
"""
# important to check Range.low no returning warranty2 # important to check Range.low no returning warranty2
# Range.verylow not returning nothing # Range.verylow not returning nothing

View File

@ -13,8 +13,7 @@ from ereuse_devicehub.resources.inventory import Inventory
from ereuse_devicehub.resources.user import User from ereuse_devicehub.resources.user import User
from tests.conftest import TestConfig from tests.conftest import TestConfig
""" """Tests the management of inventories in a multi-inventory environment
Tests the management of inventories in a multi-inventory environment
(several Devicehub instances that point at different schemas). (several Devicehub instances that point at different schemas).
""" """

View File

@ -9,8 +9,7 @@ from ereuse_devicehub.resources.enums import ComputerChassis
from ereuse_devicehub.resources.lot.models import Lot, LotDevice from ereuse_devicehub.resources.lot.models import Lot, LotDevice
from tests import conftest from tests import conftest
""" """In case of error, debug with:
In case of error, debug with:
try: try:
with db.session.begin_nested(): with db.session.begin_nested():
@ -67,7 +66,7 @@ def test_lot_model_children():
def test_lot_modify_patch_endpoint_and_delete(user: UserClient): def test_lot_modify_patch_endpoint_and_delete(user: UserClient):
"""Creates and modifies lot properties through the endpoint""" """Creates and modifies lot properties through the endpoint."""
l, _ = user.post({'name': 'foo', 'description': 'baz'}, res=Lot) l, _ = user.post({'name': 'foo', 'description': 'baz'}, res=Lot)
assert l['name'] == 'foo' assert l['name'] == 'foo'
assert l['description'] == 'baz' assert l['description'] == 'baz'
@ -310,7 +309,8 @@ def test_post_get_lot(user: UserClient):
def test_lot_post_add_children_view_ui_tree_normal(user: UserClient): def test_lot_post_add_children_view_ui_tree_normal(user: UserClient):
"""Tests adding children lots to a lot through the view and """Tests adding children lots to a lot through the view and
GETting the results.""" GETting the results.
"""
parent, _ = user.post(({'name': 'Parent'}), res=Lot) parent, _ = user.post(({'name': 'Parent'}), res=Lot)
child, _ = user.post(({'name': 'Child'}), res=Lot) child, _ = user.post(({'name': 'Child'}), res=Lot)
parent, _ = user.post({}, parent, _ = user.post({},
@ -347,7 +347,8 @@ def test_lot_post_add_children_view_ui_tree_normal(user: UserClient):
def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient): def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient):
"""Tests adding a device to a lot using POST and """Tests adding a device to a lot using POST and
removing it with DELETE.""" removing it with DELETE.
"""
# todo check with components # todo check with components
with app.app_context(): with app.app_context():
device = Desktop(serial_number='foo', device = Desktop(serial_number='foo',

View File

@ -1,7 +1,6 @@
from decimal import Decimal from decimal import Decimal
from distutils.version import StrictVersion from distutils.version import StrictVersion
import math
import pytest import pytest
from ereuse_devicehub.client import UserClient from ereuse_devicehub.client import UserClient
@ -96,8 +95,7 @@ def test_price_from_rate():
def test_when_rate_must_not_compute(user: UserClient): def test_when_rate_must_not_compute(user: UserClient):
""" """Test to check if rate is computed in case of should not be calculated:
Test to check if rate is computed in case of should not be calculated:
1. Snapshot haven't visual test 1. Snapshot haven't visual test
2. Snapshot software aren't Workbench 2. Snapshot software aren't Workbench
3. Device type are not Computer 3. Device type are not Computer
@ -138,8 +136,8 @@ def test_multiple_rates(user: UserClient):
ensuring that the tests / benchmarks... ensuring that the tests / benchmarks...
from the first rate do not contaminate the second rate. from the first rate do not contaminate the second rate.
This ensures that rates only takes the last version of actions This ensures that rates only takes all the correct actions
and components (in case device has new components, for example). and components rates in case device have new tests/benchmarks.
""" """
pc = Desktop(chassis=ComputerChassis.Tower) pc = Desktop(chassis=ComputerChassis.Tower)
hdd = HardDrive(size=476940) hdd = HardDrive(size=476940)
@ -172,15 +170,13 @@ def test_multiple_rates(user: UserClient):
assert price1.price == Decimal('92.4001') assert price1.price == Decimal('92.4001')
hdd = SolidStateDrive(size=476940)
hdd.actions_one.add(BenchmarkDataStorage(read_speed=222, write_speed=169))
cpu = Processor(cores=1, speed=3.0)
cpu.actions_one.add(BenchmarkProcessor(rate=16069.44)) cpu.actions_one.add(BenchmarkProcessor(rate=16069.44))
ssd = SolidStateDrive(size=476940)
ssd.actions_one.add(BenchmarkDataStorage(read_speed=222, write_speed=111))
pc.components |= { pc.components |= {
hdd, ssd,
RamModule(size=2048, speed=1067), RamModule(size=2048, speed=1067),
RamModule(size=2048, speed=1067), RamModule(size=2048, speed=1067),
cpu
} }
# Add test visual with functionality and appearance range # Add test visual with functionality and appearance range
@ -190,13 +186,13 @@ def test_multiple_rates(user: UserClient):
rate2, price2 = RateComputer.compute(pc) rate2, price2 = RateComputer.compute(pc)
assert rate2.data_storage == 4.27 assert rate2.data_storage == 4.3
assert rate2.processor == 3.61 assert rate2.processor == 3.78
assert rate2.ram == 4.12 assert rate2.ram == 3.95
assert rate2.appearance == 0 assert rate2.appearance == 0
assert rate2.functionality == -0.5 assert rate2.functionality == -0.5
assert rate2.rating == 3.37 assert rate2.rating == 3.43
assert rate2.price.price == Decimal('67.4001') assert price2.price == Decimal('68.6001')

View File

@ -1,15 +1,13 @@
""" """This file test all corner cases when compute score v1.0.
This file test all corner cases when compute score v1.0.
First test to compute rate for every component in isolation.
First test to compute rate for every component in isolation
todo rewrite some corner cases using range(min,max) characteristics
in devices/schemas
Components in Score v1: Components in Score v1:
-DataStorage -DataStorage
-RamModule -RamModule
-Processor -Processor
Then some test compute rate with all components that use the a1lgorithm Then some test compute rate with all components that use the algorithm
Excluded cases in tests Excluded cases in tests
@ -31,9 +29,8 @@ from tests import conftest
def test_rate_data_storage_rate(): def test_rate_data_storage_rate():
""" """Test to check if compute data storage rate have same value than
Test to check if compute data storage rate have same value than previous score version; previous score version.
id = pc_1193, pc_1201, pc_79, pc_798
""" """
hdd_1969 = HardDrive(size=476940) hdd_1969 = HardDrive(size=476940)
@ -67,10 +64,8 @@ def test_rate_data_storage_rate():
def test_rate_data_storage_size_is_null(): def test_rate_data_storage_size_is_null():
""" """Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0,
Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0,
BenchmarkDataStorage.write_speed = 0 is like no DataStorage has been detected; BenchmarkDataStorage.write_speed = 0 is like no DataStorage has been detected;
id = pc_2992
""" """
hdd_null = HardDrive(size=None) hdd_null = HardDrive(size=None)
@ -81,9 +76,7 @@ def test_rate_data_storage_size_is_null():
def test_rate_no_data_storage(): def test_rate_no_data_storage():
""" """Test without data storage devices."""
Test without data storage devices
"""
hdd_null = HardDrive() hdd_null = HardDrive()
hdd_null.actions_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0)) hdd_null.actions_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0))
@ -92,9 +85,8 @@ def test_rate_no_data_storage():
def test_rate_ram_rate(): def test_rate_ram_rate():
""" """Test to check if compute ram rate have same value than previous
Test to check if compute ram rate have same value than previous score version score version only with 1 RamModule.
only with 1 RamModule; id = pc_1201
""" """
ram1 = RamModule(size=2048, speed=1333) ram1 = RamModule(size=2048, speed=1333)
@ -105,9 +97,8 @@ def test_rate_ram_rate():
def test_rate_ram_rate_2modules(): def test_rate_ram_rate_2modules():
""" """Test to check if compute ram rate have same value than previous
Test to check if compute ram rate have same value than previous score version score version with 2 RamModule.
with 2 RamModule; id = pc_1193
""" """
ram1 = RamModule(size=4096, speed=1600) ram1 = RamModule(size=4096, speed=1600)
@ -119,9 +110,8 @@ def test_rate_ram_rate_2modules():
def test_rate_ram_rate_4modules(): def test_rate_ram_rate_4modules():
""" """Test to check if compute ram rate have same value than previous
Test to check if compute ram rate have same value than previous score version score version with 2 RamModule.
with 2 RamModule; id = pc_79
""" """
ram1 = RamModule(size=512, speed=667) ram1 = RamModule(size=512, speed=667)
@ -135,8 +125,8 @@ def test_rate_ram_rate_4modules():
def test_rate_ram_module_size_is_0(): def test_rate_ram_module_size_is_0():
""" """Test where input data RamModule.size = 0; is like no RamModule
Test where input data RamModule.size = 0; is like no RamModule has been detected; id = pc_798 has been detected.
""" """
ram0 = RamModule(size=0, speed=888) ram0 = RamModule(size=0, speed=888)
@ -146,10 +136,7 @@ def test_rate_ram_module_size_is_0():
def test_rate_ram_speed_is_null(): def test_rate_ram_speed_is_null():
""" """Test where RamModule.speed is NULL (not detected) but has size."""
Test where RamModule.speed is NULL (not detected) but has size.
Pc ID = 795(1542), 745(1535), 804(1549)
"""
ram0 = RamModule(size=2048, speed=None) ram0 = RamModule(size=2048, speed=None)
@ -165,9 +152,7 @@ def test_rate_ram_speed_is_null():
def test_rate_no_ram_module(): def test_rate_no_ram_module():
""" """Test without RamModule."""
Test without RamModule
"""
ram0 = RamModule() ram0 = RamModule()
ram_rate = RamRate().compute([ram0]) ram_rate = RamRate().compute([ram0])
@ -175,9 +160,8 @@ def test_rate_no_ram_module():
def test_rate_processor_rate(): def test_rate_processor_rate():
""" """Test to check if compute processor rate have same value than previous
Test to check if compute processor rate have same value than previous score version score version only with 1 core.
only with 1 core; id = 79
""" """
cpu = Processor(cores=1, speed=1.6) cpu = Processor(cores=1, speed=1.6)
@ -190,9 +174,8 @@ def test_rate_processor_rate():
def test_rate_processor_rate_2cores(): def test_rate_processor_rate_2cores():
""" """Test to check if compute processor rate have same value than previous
Test to check if compute processor rate have same value than previous score version score version with 2 cores.
with 2 cores; id = pc_1193, pc_1201
""" """
cpu = Processor(cores=2, speed=3.4) cpu = Processor(cores=2, speed=3.4)
@ -211,13 +194,8 @@ def test_rate_processor_rate_2cores():
assert math.isclose(processor_rate, 3.93, rel_tol=0.002) assert math.isclose(processor_rate, 3.93, rel_tol=0.002)
# TODO JN if delete processor default score for benchmark_cpu
def test_rate_processor_with_null_cores(): def test_rate_processor_with_null_cores():
""" """Test with processor device have null number of cores."""
Test with processor device have null number of cores
"""
cpu = Processor(cores=None, speed=3.3) cpu = Processor(cores=None, speed=3.3)
cpu.actions_one.add(BenchmarkProcessor(rate=0)) cpu.actions_one.add(BenchmarkProcessor(rate=0))
@ -227,9 +205,7 @@ def test_rate_processor_with_null_cores():
def test_rate_processor_with_null_speed(): def test_rate_processor_with_null_speed():
""" """Test with processor device have null speed value."""
Test with processor device have null speed value
"""
cpu = Processor(cores=1, speed=None) cpu = Processor(cores=1, speed=None)
cpu.actions_one.add(BenchmarkProcessor(rate=0)) cpu.actions_one.add(BenchmarkProcessor(rate=0))
@ -238,13 +214,9 @@ def test_rate_processor_with_null_speed():
assert math.isclose(processor_rate, 1.06, rel_tol=0.001) assert math.isclose(processor_rate, 1.06, rel_tol=0.001)
# TODO JN add price asserts in rate computers??
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_1193(): def test_rate_computer_1193():
""" """Test rate computer characteristics:
Test rate computer characteristics:
- 2 module ram - 2 module ram
- processor with 2 cores - processor with 2 cores
@ -297,8 +269,7 @@ def test_rate_computer_1193():
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_1201(): def test_rate_computer_1201():
""" """Test rate computer characteristics:
Test rate computer characteristics:
- only 1 module ram - only 1 module ram
- processor 2 cores - processor 2 cores
@ -349,8 +320,7 @@ def test_rate_computer_1201():
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_multiple_ram_module(): def test_rate_computer_multiple_ram_module():
""" """Test rate computer characteristics:
Test rate computer characteristics:
- only 1 module ram - only 1 module ram
- processor 2 cores - processor 2 cores
@ -408,8 +378,7 @@ def test_rate_computer_multiple_ram_module():
@pytest.mark.usefixtures(conftest.auth_app_context.__name__) @pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_one_ram_module(): def test_rate_computer_one_ram_module():
""" """Test rate computer characteristics:
Test rate computer characteristics:
- only 1 module ram - only 1 module ram
- processor 2 cores - processor 2 cores
@ -463,4 +432,5 @@ def test_rate_computer_one_ram_module():
@pytest.mark.xfail(reason='Data Storage rate actually requires a DSSBenchmark') @pytest.mark.xfail(reason='Data Storage rate actually requires a DSSBenchmark')
def test_rate_computer_with_data_storage_without_benchmark(): def test_rate_computer_with_data_storage_without_benchmark():
"""For example if the data storage was introduced manually """For example if the data storage was introduced manually
or comes from an old version without benchmark.""" or comes from an old version without benchmark.
"""

View File

@ -3,8 +3,6 @@ from datetime import datetime
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
import pytest
from ereuse_devicehub.client import UserClient from ereuse_devicehub.client import UserClient
from ereuse_devicehub.resources.action.models import Snapshot from ereuse_devicehub.resources.action.models import Snapshot
from ereuse_devicehub.resources.documents import documents from ereuse_devicehub.resources.documents import documents
@ -12,9 +10,7 @@ from tests.conftest import file
def test_export_basic_snapshot(user: UserClient): def test_export_basic_snapshot(user: UserClient):
""" """Test export device information in a csv file."""
Test export device information in a csv file
"""
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot) snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t, csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/', item='devices/',
@ -41,9 +37,7 @@ def test_export_basic_snapshot(user: UserClient):
def test_export_full_snapshot(user: UserClient): def test_export_full_snapshot(user: UserClient):
""" """Test a export device with all information and a lot of components."""
Test a export device with all information and a lot of components
"""
snapshot, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot) snapshot, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t, csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/', item='devices/',
@ -71,8 +65,8 @@ def test_export_full_snapshot(user: UserClient):
def test_export_empty(user: UserClient): def test_export_empty(user: UserClient):
""" """Test to check works correctly exporting csv without any information,
Test to check works correctly exporting csv without any information (no snapshot) export a placeholder device.
""" """
csv_str, _ = user.get(res=documents.DocumentDef.t, csv_str, _ = user.get(res=documents.DocumentDef.t,
accept='text/csv', accept='text/csv',
@ -85,9 +79,7 @@ def test_export_empty(user: UserClient):
def test_export_computer_monitor(user: UserClient): def test_export_computer_monitor(user: UserClient):
""" """Test a export device type computer monitor."""
Test a export device type computer monitor
"""
snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot) snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t, csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/', item='devices/',
@ -111,9 +103,7 @@ def test_export_computer_monitor(user: UserClient):
def test_export_keyboard(user: UserClient): def test_export_keyboard(user: UserClient):
""" """Test a export device type keyboard."""
Test a export device type keyboard
"""
snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot) snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t, csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/', item='devices/',
@ -136,8 +126,7 @@ def test_export_keyboard(user: UserClient):
def test_export_multiple_different_devices(user: UserClient): def test_export_multiple_different_devices(user: UserClient):
""" """Test function 'Export' of multiple different device types (like
Test function 'Export' of multiple different device types (like
computers, keyboards, monitors, etc..) computers, keyboards, monitors, etc..)
""" """
# Open fixture csv and transform to list # Open fixture csv and transform to list

View File

@ -26,8 +26,7 @@ from tests.conftest import file
@pytest.mark.usefixtures('auth_app_context') @pytest.mark.usefixtures('auth_app_context')
def test_snapshot_model(): def test_snapshot_model():
""" """Tests creating a Snapshot with its relationships ensuring correct
Tests creating a Snapshot with its relationships ensuring correct
DB mapping. DB mapping.
""" """
device = m.Desktop(serial_number='a1', chassis=ComputerChassis.Tower) device = m.Desktop(serial_number='a1', chassis=ComputerChassis.Tower)
@ -62,8 +61,7 @@ def test_snapshot_schema(app: Devicehub):
def test_snapshot_post(user: UserClient): def test_snapshot_post(user: UserClient):
""" """Tests the post snapshot endpoint (validation, etc), data correctness,
Tests the post snapshot endpoint (validation, etc), data correctness,
and relationship correctness. and relationship correctness.
""" """
# TODO add all action_types to check, how to add correctly?? # TODO add all action_types to check, how to add correctly??
@ -98,8 +96,7 @@ def test_snapshot_post(user: UserClient):
def test_snapshot_component_add_remove(user: UserClient): def test_snapshot_component_add_remove(user: UserClient):
""" """Tests adding and removing components and some don't generate HID.
Tests adding and removing components and some don't generate HID.
All computers generate HID. All computers generate HID.
""" """
@ -212,8 +209,7 @@ def test_snapshot_component_add_remove(user: UserClient):
def _test_snapshot_computer_no_hid(user: UserClient): def _test_snapshot_computer_no_hid(user: UserClient):
""" """Tests inserting a computer that doesn't generate a HID, neither
Tests inserting a computer that doesn't generate a HID, neither
some of its components. some of its components.
""" """
# PC with 2 components. PC doesn't have HID and neither 1st component # PC with 2 components. PC doesn't have HID and neither 1st component
@ -384,18 +380,14 @@ def test_test_data_storage(user: UserClient):
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it') @pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
def test_snapshot_computer_monitor(user: UserClient): def test_snapshot_computer_monitor(user: UserClient):
""" """Tests that a snapshot of computer monitor device create correctly."""
Tests that a snapshot of computer monitor device create correctly.
"""
s = file('computer-monitor.snapshot') s = file('computer-monitor.snapshot')
snapshot_and_check(user, s, action_types=('RateMonitor',)) snapshot_and_check(user, s, action_types=('RateMonitor',))
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it') @pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
def test_snapshot_mobile_smartphone_imei_manual_rate(user: UserClient): def test_snapshot_mobile_smartphone_imei_manual_rate(user: UserClient):
""" """Tests that a snapshot of smartphone device is creat correctly."""
Tests that a snapshot of smartphone device is creat correctly.
"""
s = file('smartphone.snapshot') s = file('smartphone.snapshot')
snapshot = snapshot_and_check(user, s, action_types=('VisualTest',)) snapshot = snapshot_and_check(user, s, action_types=('VisualTest',))
mobile, _ = user.get(res=m.Device, item=snapshot['device']['id']) mobile, _ = user.get(res=m.Device, item=snapshot['device']['id'])
@ -404,24 +396,21 @@ def test_snapshot_mobile_smartphone_imei_manual_rate(user: UserClient):
@pytest.mark.xfail(reason='Test not developed') @pytest.mark.xfail(reason='Test not developed')
def test_snapshot_components_none(): def test_snapshot_components_none():
""" """Tests that a snapshot without components does not remove them
Tests that a snapshot without components does not from the computer.
remove them from the computer.
""" """
# TODO JN is really necessary in which cases?? # TODO JN is really necessary in which cases??
@pytest.mark.xfail(reason='Test not developed') @pytest.mark.xfail(reason='Test not developed')
def test_snapshot_components_empty(): def test_snapshot_components_empty():
""" """Tests that a snapshot whose components are an empty list remove
Tests that a snapshot whose components are an empty list remove
all its components. all its components.
""" """
def assert_similar_device(device1: dict, device2: dict): def assert_similar_device(device1: dict, device2: dict):
""" """Like :class:`ereuse_devicehub.resources.device.models.Device.
Like :class:`ereuse_devicehub.resources.device.models.Device.
is_similar()` but adapted for testing. is_similar()` but adapted for testing.
""" """
assert isinstance(device1, dict) and device1 assert isinstance(device1, dict) and device1
@ -431,9 +420,8 @@ def assert_similar_device(device1: dict, device2: dict):
def assert_similar_components(components1: List[dict], components2: List[dict]): def assert_similar_components(components1: List[dict], components2: List[dict]):
""" """Asserts that the components in components1 are similar than
Asserts that the components in components1 are the components in components2.
similar than the components in components2.
""" """
assert len(components1) == len(components2) assert len(components1) == len(components2)
key = itemgetter('serialNumber') key = itemgetter('serialNumber')
@ -447,8 +435,7 @@ def snapshot_and_check(user: UserClient,
input_snapshot: dict, input_snapshot: dict,
action_types: Tuple[str, ...] = tuple(), action_types: Tuple[str, ...] = tuple(),
perform_second_snapshot=True) -> dict: perform_second_snapshot=True) -> dict:
""" """Performs a Snapshot and then checks if the result is ok:
Performs a Snapshot and then checks if the result is ok:
- There have been performed the types of actions and in the same - There have been performed the types of actions and in the same
order as described in the passed-in ``action_types``. order as described in the passed-in ``action_types``.

View File

@ -80,8 +80,7 @@ def test_tag_post(app: Devicehub, user: UserClient):
def test_tag_post_etag(user: UserClient): def test_tag_post_etag(user: UserClient):
""" """Ensures users cannot create eReuse.org tags through POST;
Ensures users cannot create eReuse.org tags through POST;
only terminal. only terminal.
""" """
user.post({'id': 'FO-123456'}, res=Tag, status=CannotCreateETag) user.post({'id': 'FO-123456'}, res=Tag, status=CannotCreateETag)
@ -121,8 +120,7 @@ def test_tag_get_device_from_tag_endpoint_no_tag(user: UserClient):
def test_tag_get_device_from_tag_endpoint_multiple_tags(app: Devicehub, user: UserClient): def test_tag_get_device_from_tag_endpoint_multiple_tags(app: Devicehub, user: UserClient):
""" """As above, but when there are two tags with the same ID, the
As above, but when there are two tags with the same ID, the
system should not return any of both (to be deterministic) so system should not return any of both (to be deterministic) so
it should raise an exception. it should raise an exception.
""" """

View File

@ -18,8 +18,7 @@ from tests.conftest import app_context, create_user
@pytest.mark.usefixtures(app_context.__name__) @pytest.mark.usefixtures(app_context.__name__)
def test_create_user_method_with_agent(app: Devicehub): def test_create_user_method_with_agent(app: Devicehub):
""" """Tests creating an user through the main method.
Tests creating an user through the main method.
This method checks that the token is correct, too. This method checks that the token is correct, too.
""" """
@ -63,8 +62,7 @@ def test_hash_password():
def test_login_success(client: Client, app: Devicehub): def test_login_success(client: Client, app: Devicehub):
""" """Tests successfully performing login.
Tests successfully performing login.
This checks that: This checks that:
- User is returned. - User is returned.

View File

@ -1,10 +1,8 @@
""" """Tests that emulates the behaviour of a WorkbenchServer."""
Tests that emulates the behaviour of a WorkbenchServer.
"""
import json import json
import math
import pathlib import pathlib
import math
import pytest import pytest
from ereuse_devicehub.client import UserClient from ereuse_devicehub.client import UserClient
@ -17,8 +15,7 @@ from tests.conftest import file
def test_workbench_server_condensed(user: UserClient): def test_workbench_server_condensed(user: UserClient):
""" """As :def:`.test_workbench_server_phases` but all the actions
As :def:`.test_workbench_server_phases` but all the actions
condensed in only one big ``Snapshot`` file, as described condensed in only one big ``Snapshot`` file, as described
in the docs. in the docs.
""" """
@ -72,8 +69,7 @@ def test_workbench_server_condensed(user: UserClient):
@pytest.mark.xfail(reason='Functionality not yet developed.') @pytest.mark.xfail(reason='Functionality not yet developed.')
def test_workbench_server_phases(user: UserClient): def test_workbench_server_phases(user: UserClient):
""" """Tests the phases described in the docs section `Snapshots from
Tests the phases described in the docs section `Snapshots from
Workbench <http://devicehub.ereuse.org/ Workbench <http://devicehub.ereuse.org/
actions.html#snapshots-from-workbench>`_. actions.html#snapshots-from-workbench>`_.
""" """
@ -170,8 +166,7 @@ def test_real_toshiba_11(user: UserClient):
def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient): def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
""" """Checks the values of the device, components,
Checks the values of the device, components,
actions and their relationships of a real pc. actions and their relationships of a real pc.
""" """
s = file('real-eee-1001pxd.snapshot.11') s = file('real-eee-1001pxd.snapshot.11')