Merge branch 'rate' into devel
|
@ -41,8 +41,7 @@ def get_version(ctx, param, value):
|
|||
@click.group(cls=DevicehubGroup,
|
||||
context_settings=Devicehub.cli_context_settings,
|
||||
add_version_option=False,
|
||||
help="""
|
||||
Manages the Devicehub of the inventory {}.
|
||||
help="""Manages the Devicehub of the inventory {}.
|
||||
|
||||
Use 'export dhi=xx' to set the inventory that this CLI
|
||||
manages. For example 'export dhi=db1' and then executing
|
||||
|
|
|
@ -121,8 +121,7 @@ class Client(TealClient):
|
|||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
|
|
@ -26,8 +26,7 @@ class DevicehubConfig(Config):
|
|||
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
|
||||
SQLALCHEMY_DATABASE_URI = 'postgresql://dhub:ereuse@localhost/devicehub' # type: str
|
||||
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.
|
||||
"""
|
||||
API_DOC_CONFIG_TITLE = 'Devicehub'
|
||||
|
@ -42,6 +41,4 @@ class DevicehubConfig(Config):
|
|||
PRICE_SOFTWARE = PriceSoftware.Ereuse
|
||||
PRICE_VERSION = StrictVersion('1.0')
|
||||
PRICE_CURRENCY = Currency.EUR
|
||||
"""
|
||||
Official versions
|
||||
"""
|
||||
"""Official versions."""
|
||||
|
|
|
@ -27,8 +27,7 @@ class DhSession(SchemaSession):
|
|||
|
||||
|
||||
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`
|
||||
defined in teal.
|
||||
"""
|
||||
|
|
|
@ -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:
|
||||
|
||||
* Generic Actions
|
||||
|
@ -72,30 +71,25 @@ class Action(Thing):
|
|||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||
type = Column(Unicode, nullable=False)
|
||||
name = Column(CIText(), default='', nullable=False)
|
||||
name.comment = """
|
||||
A name or title for the action. Used when searching for actions.
|
||||
name.comment = """A name or title for the action. Used when searching
|
||||
for actions.
|
||||
"""
|
||||
severity = Column(teal.db.IntEnum(Severity), default=Severity.Info, nullable=False)
|
||||
severity.comment = Severity.__doc__
|
||||
closed = Column(Boolean, default=True, nullable=False)
|
||||
closed.comment = """
|
||||
Whether the author has finished the action.
|
||||
closed.comment = """Whether the author has finished the action.
|
||||
After this is set to True, no modifications are allowed.
|
||||
By default actions are closed when performed.
|
||||
"""
|
||||
description = Column(Unicode, default='', nullable=False)
|
||||
description.comment = """
|
||||
A comment about the action.
|
||||
"""
|
||||
description.comment = """A comment about the action."""
|
||||
start_time = Column(db.TIMESTAMP(timezone=True))
|
||||
start_time.comment = """
|
||||
When the action starts. For some actions like reservations
|
||||
the time when they are available, for others like renting
|
||||
start_time.comment = """When the action starts. For some actions like
|
||||
reservations the time when they are available, for others like renting
|
||||
when the renting starts.
|
||||
"""
|
||||
end_time = Column(db.TIMESTAMP(timezone=True))
|
||||
end_time.comment = """
|
||||
When the action ends. For some actions like reservations
|
||||
end_time.comment = """When the action ends. For some actions like reservations
|
||||
the time when they expire, for others like renting
|
||||
the time the end rents. For punctual actions it is the time
|
||||
they are performed; it differs with ``created`` in which
|
||||
|
@ -120,8 +114,7 @@ class Action(Thing):
|
|||
author = relationship(User,
|
||||
backref=backref('authored_actions', lazy=True, collection_class=set),
|
||||
primaryjoin=author_id == User.id)
|
||||
author_id.comment = """
|
||||
The user that recorded this action in the system.
|
||||
author_id.comment = """The user that recorded this action in the system.
|
||||
|
||||
This does not necessarily has to be the person that produced
|
||||
the action in the real world. For that purpose see
|
||||
|
@ -136,8 +129,8 @@ class Action(Thing):
|
|||
agent = relationship(Agent,
|
||||
backref=backref('actions_agent', lazy=True, **_sorted_actions),
|
||||
primaryjoin=agent_id == Agent.id)
|
||||
agent_id.comment = """
|
||||
The direct performer or driver of the action. e.g. John wrote a book.
|
||||
agent_id.comment = """The direct performer or driver of the action.
|
||||
e.g. John wrote a book.
|
||||
|
||||
It can differ with the user that registered the action in the
|
||||
system, which can be in their behalf.
|
||||
|
@ -148,8 +141,7 @@ class Action(Thing):
|
|||
secondary=lambda: ActionComponent.__table__,
|
||||
order_by=lambda: Component.id,
|
||||
collection_class=OrderedSet)
|
||||
components.comment = """
|
||||
The components that are affected by the action.
|
||||
components.comment = """The components that are affected by the action.
|
||||
|
||||
When performing actions to parent devices their components are
|
||||
affected too.
|
||||
|
@ -165,9 +157,8 @@ class Action(Thing):
|
|||
parent = relationship(Computer,
|
||||
backref=backref('actions_parent', lazy=True, **_sorted_actions),
|
||||
primaryjoin=parent_id == Computer.id)
|
||||
parent_id.comment = """
|
||||
For actions that are performed to components, the device parent
|
||||
at that time.
|
||||
parent_id.comment = """For actions that are performed to components,
|
||||
the device parent at that time.
|
||||
|
||||
For example: for a ``EraseBasic`` performed on a data storage, this
|
||||
would point to the computer that contained this data storage, if any.
|
||||
|
@ -197,8 +188,7 @@ class Action(Thing):
|
|||
# noinspection PyMethodParameters
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -276,8 +266,7 @@ class ActionWithOneDevice(JoinedTableMixin, Action):
|
|||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -365,7 +354,7 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
@property
|
||||
def certificate(self):
|
||||
"""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))
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
@ -430,8 +419,7 @@ class Step(db.Model):
|
|||
# noinspection PyMethodParameters
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -545,9 +533,8 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
version = Column(StrictVersionType(STR_SM_SIZE), nullable=False)
|
||||
software = Column(DBEnum(SnapshotSoftware), nullable=False)
|
||||
elapsed = Column(Interval)
|
||||
elapsed.comment = """
|
||||
For Snapshots made with Workbench, the total amount of time
|
||||
it took to complete.
|
||||
elapsed.comment = """For Snapshots made with Workbench, the total amount
|
||||
of time it took to complete.
|
||||
"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
@ -578,8 +565,7 @@ class Benchmark(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -656,8 +642,7 @@ class Test(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -684,6 +669,11 @@ class MeasureBattery(TestMixin, Test):
|
|||
|
||||
Operative Systems keep a record of several aspects of a battery.
|
||||
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.comment = """Maximum battery capacity, in mAh."""
|
||||
|
@ -712,7 +702,7 @@ class TestDataStorage(TestMixin, Test):
|
|||
|
||||
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
|
||||
the data storage to fail in the following year.
|
||||
"""
|
||||
|
@ -765,6 +755,11 @@ class TestDataStorage(TestMixin, Test):
|
|||
class StressTest(TestMixin, Test):
|
||||
"""The act of stressing (putting to the maximum capacity)
|
||||
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)
|
||||
|
||||
|
@ -780,7 +775,13 @@ class StressTest(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.comment = """Whether the speaker works as expected."""
|
||||
_microphone = Column('microphone', Boolean)
|
||||
|
@ -824,9 +825,9 @@ class TestCamera(TestMixin, Test):
|
|||
|
||||
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.
|
||||
* :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.
|
||||
"""
|
||||
|
||||
|
@ -856,7 +857,7 @@ class TestDisplayHinge(TestMixin, Test):
|
|||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
@ -872,7 +873,13 @@ class TestPowerAdapter(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.comment = """Whether there are no beeps or error
|
||||
codes when booting up.
|
||||
|
@ -884,7 +891,8 @@ class TestBios(TestMixin, Test):
|
|||
|
||||
This is used as an usability measure for accessing and modifying
|
||||
a bios, specially as something as important as modifying the boot
|
||||
menu."""
|
||||
menu.
|
||||
"""
|
||||
|
||||
|
||||
class VisualTest(TestMixin, Test):
|
||||
|
@ -893,7 +901,16 @@ class VisualTest(TestMixin, Test):
|
|||
|
||||
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
|
||||
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.comment = AppearanceRange.__doc__
|
||||
|
@ -916,7 +933,6 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
* Appearance (A). Visual evaluation, surface deterioration.
|
||||
* Performance (Q). Components characteristics and components benchmarks.
|
||||
"""
|
||||
# todo jn: explain in each comment what the rate considers.
|
||||
N = 2
|
||||
"""The number of significant digits for rates.
|
||||
Values are rounded and stored to it.
|
||||
|
@ -929,11 +945,11 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
_appearance = Column('appearance',
|
||||
Float(decimal_return_scale=N),
|
||||
check_range('appearance', *R_NEGATIVE))
|
||||
_appearance.comment = """"""
|
||||
_appearance.comment = """Subjective value representing aesthetic aspects."""
|
||||
_functionality = Column('functionality',
|
||||
Float(decimal_return_scale=N),
|
||||
check_range('functionality', *R_NEGATIVE))
|
||||
_functionality.comment = """"""
|
||||
_functionality.comment = """Subjective value representing usage aspects."""
|
||||
|
||||
@property
|
||||
def rating(self):
|
||||
|
@ -966,8 +982,7 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -993,10 +1008,9 @@ class RateMixin:
|
|||
|
||||
|
||||
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.
|
||||
Algorithm explained in v1.0 file
|
||||
Algorithm explained in v1.0 file.
|
||||
"""
|
||||
_processor = Column('processor',
|
||||
Float(decimal_return_scale=Rate.N),
|
||||
|
@ -1063,9 +1077,7 @@ class RateComputer(RateMixin, Rate):
|
|||
|
||||
@classmethod
|
||||
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
|
||||
rate = rate_algorithm.compute(device)
|
||||
price = None
|
||||
|
@ -1075,6 +1087,7 @@ class RateComputer(RateMixin, Rate):
|
|||
|
||||
|
||||
class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
||||
# TODO rewrite Class comment change AggregateRate..
|
||||
"""The act of setting a trading price for the device.
|
||||
|
||||
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."""
|
||||
rating_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id))
|
||||
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,
|
||||
backref=backref('price',
|
||||
lazy=True,
|
||||
|
@ -1125,8 +1139,7 @@ class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -1216,9 +1229,8 @@ class EreusePrice(Price):
|
|||
|
||||
@orm.reconstructor
|
||||
def _compute(self):
|
||||
"""
|
||||
Calculates eReuse.org prices when initializing the
|
||||
instance from the price and other properties.
|
||||
"""Calculates eReuse.org prices when initializing the instance
|
||||
from the price and other properties.
|
||||
"""
|
||||
self.refurbisher = self._service(self.Service.REFURBISHER)
|
||||
self.retailer = self._service(self.Service.RETAILER)
|
||||
|
@ -1335,20 +1347,16 @@ class Trade(JoinedTableMixin, ActionWithMultipleDevices):
|
|||
extend `Schema's Trade <http://schema.org/TradeAction>`_.
|
||||
"""
|
||||
shipping_date = Column(db.TIMESTAMP(timezone=True))
|
||||
shipping_date.comment = """
|
||||
When are the devices going to be ready for shipping?
|
||||
shipping_date.comment = """When are the devices going to be ready
|
||||
for shipping?
|
||||
"""
|
||||
invoice_number = Column(CIText())
|
||||
invoice_number.comment = """
|
||||
The id of the invoice so they can be linked.
|
||||
"""
|
||||
invoice_number.comment = """The id of the invoice so they can be linked."""
|
||||
price_id = Column(UUID(as_uuid=True), ForeignKey(Price.id))
|
||||
price = relationship(Price,
|
||||
backref=backref('trade', lazy=True, uselist=False),
|
||||
primaryjoin=price_id == Price.id)
|
||||
price_id.comment = """
|
||||
The price set for this trade.
|
||||
|
||||
price_id.comment = """The price set for this trade.
|
||||
If no price is set it is supposed that the trade was
|
||||
not payed, usual in donations.
|
||||
"""
|
||||
|
@ -1357,15 +1365,12 @@ class Trade(JoinedTableMixin, ActionWithMultipleDevices):
|
|||
to = relationship(Agent,
|
||||
backref=backref('actions_to', lazy=True, **_sorted_actions),
|
||||
primaryjoin=to_id == Agent.id)
|
||||
to_comment = """
|
||||
The agent that gets the device due this deal.
|
||||
"""
|
||||
to_comment = """The agent that gets the device due this deal."""
|
||||
confirms_id = Column(UUID(as_uuid=True), ForeignKey(Organize.id))
|
||||
confirms = relationship(Organize,
|
||||
backref=backref('confirmation', lazy=True, uselist=False),
|
||||
primaryjoin=confirms_id == Organize.id)
|
||||
confirms_id.comment = """
|
||||
An organize action that this association confirms.
|
||||
confirms_id.comment = """An organize action that this association confirms.
|
||||
|
||||
For example, a ``Sell`` or ``Rent``
|
||||
can confirm a ``Reserve`` action.
|
||||
|
@ -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)
|
||||
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`.
|
||||
"""
|
||||
# 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)
|
||||
def update_components_action_multiple(target: ActionWithMultipleDevices,
|
||||
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`.
|
||||
"""
|
||||
target.components.clear()
|
||||
|
@ -1513,8 +1516,7 @@ def update_components_action_multiple(target: ActionWithMultipleDevices,
|
|||
|
||||
@event.listens_for(ActionWithMultipleDevices.devices, Events.remove.__name__, propagate=True)
|
||||
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`.
|
||||
"""
|
||||
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(Benchmark.device, Events.set.__name__, propagate=True)
|
||||
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
|
||||
if isinstance(device, Component):
|
||||
target.parent = device.parent
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import math
|
||||
from typing import Iterable
|
||||
|
||||
import math
|
||||
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
|
||||
|
||||
class BaseRate:
|
||||
"""growing exponential from this value"""
|
||||
"""Growing exponential from this value."""
|
||||
CEXP = 0
|
||||
"""growing lineal starting on this value"""
|
||||
"""Growing lineal starting on this value."""
|
||||
CLIN = 242
|
||||
"""growing logarithmic starting on this value"""
|
||||
"""Growing logarithmic starting on this value."""
|
||||
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
|
||||
"""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
|
||||
"""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
|
||||
|
||||
def compute(self, device: Device):
|
||||
|
@ -43,9 +44,7 @@ class BaseRate:
|
|||
return sum(weights) / sum(char / rate for char, rate in zip(weights, rates))
|
||||
|
||||
def harmonic_mean_rates(self, rate_processor, rate_storage, rate_ram):
|
||||
"""
|
||||
Merging components
|
||||
"""
|
||||
"""Merging components using harmonic formula."""
|
||||
total_weights = self.PROCESSOR_WEIGHT + self.DATA_STORAGE_WEIGHT + self.RAM_WEIGHT
|
||||
total_rate = self.PROCESSOR_WEIGHT / rate_processor \
|
||||
+ self.DATA_STORAGE_WEIGHT / rate_storage \
|
||||
|
|
|
@ -13,14 +13,15 @@ class RateAlgorithm(BaseRate):
|
|||
"""The algorithm that generates the Rate v1.0.
|
||||
|
||||
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.
|
||||
The algorithm is as follows:
|
||||
|
||||
1. Specialized subclasses of :class:`BaseRate` compute a rating
|
||||
for each component. To perform this, each class normalizes first
|
||||
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:
|
||||
|
||||
* :class:`ProcessorRate`, using cores, speed, and ``BenchmarkProcessor``.
|
||||
|
@ -103,9 +104,7 @@ class RateAlgorithm(BaseRate):
|
|||
|
||||
|
||||
class ProcessorRate(BaseRate):
|
||||
"""
|
||||
Calculate a ProcessorRate of all Processor devices
|
||||
"""
|
||||
"""Calculate a ProcessorRate of all Processor devices."""
|
||||
# processor.xMin, processor.xMax
|
||||
PROCESSOR_NORM = 3196.17, 17503.81
|
||||
|
||||
|
@ -115,8 +114,8 @@ class ProcessorRate(BaseRate):
|
|||
DEFAULT_SCORE = 4000
|
||||
|
||||
def compute(self, processor: Processor):
|
||||
""" Compute processor rate
|
||||
We assume always exists a Benchmark Processor
|
||||
"""Compute processor rate
|
||||
We assume always exists a Benchmark Processor.
|
||||
Obs: cores and speed are possible NULL value
|
||||
:return: result is a rate (score) of Processor characteristics
|
||||
"""
|
||||
|
@ -146,9 +145,7 @@ class ProcessorRate(BaseRate):
|
|||
|
||||
|
||||
class RamRate(BaseRate):
|
||||
"""
|
||||
Calculate a RamRate of all RamModule devices
|
||||
"""
|
||||
"""Calculate a RamRate of all RamModule devices."""
|
||||
# ram.size.xMin; ram.size.xMax
|
||||
SIZE_NORM = 256, 8192
|
||||
RAM_SPEED_NORM = 133, 1333
|
||||
|
@ -158,8 +155,7 @@ class RamRate(BaseRate):
|
|||
RAM_WEIGHTS = 0.7, 0.3
|
||||
|
||||
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
|
||||
"""
|
||||
size = 0.0
|
||||
|
@ -204,9 +200,7 @@ class RamRate(BaseRate):
|
|||
|
||||
|
||||
class DataStorageRate(BaseRate):
|
||||
"""
|
||||
Calculate the rate of all DataStorage devices
|
||||
"""
|
||||
"""Calculate the rate of all DataStorage devices."""
|
||||
# drive.size.xMin; drive.size.xMax
|
||||
SIZE_NORM = 4, 265000
|
||||
READ_SPEED_NORM = 2.7, 109.5
|
||||
|
@ -215,8 +209,7 @@ class DataStorageRate(BaseRate):
|
|||
DATA_STORAGE_WEIGHTS = 0.5, 0.25, 0.25
|
||||
|
||||
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
|
||||
"""
|
||||
size = 0
|
||||
|
|
|
@ -46,8 +46,7 @@ class ActionView(View):
|
|||
return self.schema.jsonify(action)
|
||||
|
||||
def snapshot(self, snapshot_json: dict, resource_def):
|
||||
"""
|
||||
Performs a Snapshot.
|
||||
"""Performs a Snapshot.
|
||||
|
||||
See `Snapshot` section in docs for more info.
|
||||
"""
|
||||
|
|
|
@ -29,18 +29,13 @@ class Agent(Thing):
|
|||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||
type = Column(Unicode, nullable=False)
|
||||
name = Column(CIText())
|
||||
name.comment = """
|
||||
The name of the organization or person.
|
||||
"""
|
||||
name.comment = """The name of the organization or person."""
|
||||
tax_id = Column(Unicode(length=STR_SM_SIZE), check_lower('tax_id'))
|
||||
tax_id.comment = """
|
||||
The Tax / Fiscal ID of the organization,
|
||||
tax_id.comment = """The Tax / Fiscal ID of the organization,
|
||||
e.g. the TIN in the US or the CIF/NIF in Spain.
|
||||
"""
|
||||
country = Column(DBEnum(enums.Country))
|
||||
country.comment = """
|
||||
Country issuing the tax_id number.
|
||||
"""
|
||||
country.comment = """Country issuing the tax_id number."""
|
||||
telephone = Column(PhoneNumberType())
|
||||
email = Column(EmailType, unique=True)
|
||||
|
||||
|
@ -52,8 +47,7 @@ class Agent(Thing):
|
|||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -137,8 +131,7 @@ class Membership(Thing):
|
|||
|
||||
|
||||
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.
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -44,18 +44,15 @@ class Device(Thing):
|
|||
(it is a recursive relationship).
|
||||
"""
|
||||
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
||||
id.comment = """
|
||||
The identifier of the device for this database. Used only
|
||||
id.comment = """The identifier of the device for this database. Used only
|
||||
internally for software; users should not use this.
|
||||
"""
|
||||
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||
hid = Column(Unicode(), check_lower('hid'), unique=True)
|
||||
hid.comment = """
|
||||
The Hardware ID (HID) is the unique ID traceability systems
|
||||
use to ID a device globally. This field is auto-generated
|
||||
hid.comment = """The Hardware ID (HID) is the unique ID traceability
|
||||
systems use to ID a device globally. This field is auto-generated
|
||||
from Devicehub using literal identifiers from the device,
|
||||
so it can re-generated *offline*.
|
||||
|
||||
""" + HID_CONVERSION_DOC
|
||||
model = Column(Unicode, check_lower('model'))
|
||||
model.comment = """The model of the device in lower case.
|
||||
|
@ -84,21 +81,13 @@ class Device(Thing):
|
|||
version = db.Column(db.CIText())
|
||||
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.comment = """
|
||||
The weight of the device.
|
||||
"""
|
||||
weight.comment = """The weight of the device."""
|
||||
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
|
||||
width.comment = """
|
||||
The width of the device in meters.
|
||||
"""
|
||||
width.comment = """The width of the device in meters."""
|
||||
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
|
||||
height.comment = """
|
||||
The height of the device in meters.
|
||||
"""
|
||||
height.comment = """The height of the device in meters."""
|
||||
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
|
||||
depth.comment = """
|
||||
The depth of the device in meters.
|
||||
"""
|
||||
depth.comment = """The depth of the device in meters."""
|
||||
color = Column(ColorType)
|
||||
color.comment = """The predominant color of the device."""
|
||||
production_date = Column(db.DateTime)
|
||||
|
@ -146,8 +135,7 @@ class Device(Thing):
|
|||
|
||||
@property
|
||||
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.
|
||||
2. Actions performed to a component.
|
||||
|
@ -177,8 +165,7 @@ class Device(Thing):
|
|||
|
||||
@property
|
||||
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:
|
||||
- Column.
|
||||
|
@ -266,8 +253,7 @@ class Device(Thing):
|
|||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
Defines inheritance.
|
||||
"""Defines inheritance.
|
||||
|
||||
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||
extensions/declarative/api.html
|
||||
|
@ -314,24 +300,20 @@ class Device(Thing):
|
|||
class DisplayMixin:
|
||||
"""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.comment = """
|
||||
The size of the monitor in inches.
|
||||
"""
|
||||
size.comment = """The size of the monitor in inches."""
|
||||
technology = Column(DBEnum(DisplayTech))
|
||||
technology.comment = """
|
||||
The technology the monitor uses to display the image.
|
||||
technology.comment = """The technology the monitor uses to display
|
||||
the image.
|
||||
"""
|
||||
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000),
|
||||
nullable=False)
|
||||
resolution_width.comment = """
|
||||
The maximum horizontal resolution the monitor can natively support
|
||||
in pixels.
|
||||
resolution_width.comment = """The maximum horizontal resolution the
|
||||
monitor can natively support in pixels.
|
||||
"""
|
||||
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000),
|
||||
nullable=False)
|
||||
resolution_height.comment = """
|
||||
The maximum vertical resolution the monitor can natively support
|
||||
in pixels.
|
||||
resolution_height.comment = """The maximum vertical resolution the
|
||||
monitor can natively support in pixels.
|
||||
"""
|
||||
refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000))
|
||||
contrast_ratio = Column(SmallInteger, check_range('contrast_ratio', 100, 100000))
|
||||
|
@ -467,7 +449,8 @@ class Desktop(Computer):
|
|||
class Laptop(Computer):
|
||||
layout = Column(DBEnum(Layouts))
|
||||
layout.comment = """Layout of a built-in keyboard of the computer,
|
||||
if any."""
|
||||
if any.
|
||||
"""
|
||||
|
||||
|
||||
class Server(Computer):
|
||||
|
@ -549,11 +532,11 @@ class Component(Device):
|
|||
)
|
||||
|
||||
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
|
||||
"""
|
||||
Gets a component that:
|
||||
- has the same parent.
|
||||
- Doesn't generate HID.
|
||||
- Has same physical properties.
|
||||
"""Gets a component that:
|
||||
|
||||
* has the same parent.
|
||||
* Doesn't generate HID.
|
||||
* Has same physical properties.
|
||||
:param parent:
|
||||
:param blacklist: A set of components to not to consider
|
||||
when looking for similar ones.
|
||||
|
@ -580,17 +563,13 @@ class JoinedComponentTableMixin:
|
|||
|
||||
class GraphicCard(JoinedComponentTableMixin, Component):
|
||||
memory = Column(SmallInteger, check_range('memory', min=1, max=10000))
|
||||
memory.comment = """
|
||||
The amount of memory of the Graphic Card in MB.
|
||||
"""
|
||||
memory.comment = """The amount of memory of the Graphic Card in MB."""
|
||||
|
||||
|
||||
class DataStorage(JoinedComponentTableMixin, Component):
|
||||
"""A device that stores information."""
|
||||
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
||||
size.comment = """
|
||||
The size of the data-storage in MB.
|
||||
"""
|
||||
size.comment = """The size of the data-storage in MB."""
|
||||
interface = Column(DBEnum(DataStorageInterface))
|
||||
|
||||
@property
|
||||
|
@ -623,9 +602,7 @@ class SolidStateDrive(DataStorage):
|
|||
|
||||
class Motherboard(JoinedComponentTableMixin, Component):
|
||||
slots = Column(SmallInteger, check_range('slots', min=0))
|
||||
slots.comment = """
|
||||
PCI slots the motherboard has.
|
||||
"""
|
||||
slots.comment = """PCI slots the motherboard has."""
|
||||
usb = Column(SmallInteger, check_range('usb', min=0))
|
||||
firewire = Column(SmallInteger, check_range('firewire', min=0))
|
||||
serial = Column(SmallInteger, check_range('serial', min=0))
|
||||
|
@ -638,13 +615,11 @@ class Motherboard(JoinedComponentTableMixin, Component):
|
|||
|
||||
class NetworkMixin:
|
||||
speed = Column(SmallInteger, check_range('speed', min=10, max=10000))
|
||||
speed.comment = """
|
||||
The maximum speed this network adapter can handle, in mbps.
|
||||
speed.comment = """The maximum speed this network adapter can handle,
|
||||
in mbps.
|
||||
"""
|
||||
wireless = Column(Boolean, nullable=False, default=False)
|
||||
wireless.comment = """
|
||||
Whether it is a wireless interface.
|
||||
"""
|
||||
wireless.comment = """Whether it is a wireless interface."""
|
||||
|
||||
def __format__(self, format_spec):
|
||||
v = super().__format__(format_spec)
|
||||
|
@ -685,8 +660,7 @@ class SoundCard(JoinedComponentTableMixin, 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,
|
||||
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
|
||||
and ``TelevisionSet``.
|
||||
|
|
|
@ -20,8 +20,7 @@ class State(Enum):
|
|||
|
||||
|
||||
class Trading(State):
|
||||
"""
|
||||
Trading states.
|
||||
"""Trading states.
|
||||
|
||||
:cvar Reserved: The device has been reserved.
|
||||
:cvar Cancelled: The device has been cancelled.
|
||||
|
@ -44,8 +43,7 @@ class Trading(State):
|
|||
|
||||
|
||||
class Physical(State):
|
||||
"""
|
||||
Physical states.
|
||||
"""Physical states.
|
||||
|
||||
:cvar ToBeRepaired: The device has been selected for reparation.
|
||||
:cvar Repaired: The device has been repaired.
|
||||
|
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
|
@ -1,3 +1,17 @@
|
|||
<?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">
|
||||
<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 |
|
@ -1,3 +1,34 @@
|
|||
<?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">
|
||||
<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 |
|
@ -23,8 +23,7 @@ class Sync:
|
|||
def run(self,
|
||||
device: Device,
|
||||
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
|
||||
and updates / inserts them as necessary.
|
||||
|
@ -79,8 +78,7 @@ class Sync:
|
|||
component: Component,
|
||||
blacklist: Set[int],
|
||||
parent: Computer):
|
||||
"""
|
||||
Synchronizes one component to the DB.
|
||||
"""Synchronizes one component to the DB.
|
||||
|
||||
This method is a specialization of :meth:`.execute_register`
|
||||
but for components that are inside parents.
|
||||
|
@ -125,8 +123,7 @@ class Sync:
|
|||
return db_component, is_new
|
||||
|
||||
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
|
||||
or one of the tags, and...
|
||||
|
@ -205,8 +202,7 @@ class Sync:
|
|||
|
||||
@staticmethod
|
||||
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.
|
||||
"""
|
||||
|
@ -217,8 +213,7 @@ class Sync:
|
|||
@staticmethod
|
||||
def add_remove(device: Computer,
|
||||
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).
|
||||
|
||||
:param device: A device which ``components`` attribute contains
|
||||
|
|
|
@ -78,8 +78,7 @@ class DeviceView(View):
|
|||
page = f.Integer(validate=v.Range(min=1), missing=1)
|
||||
|
||||
def get(self, id):
|
||||
"""
|
||||
Devices view
|
||||
"""Devices view
|
||||
---
|
||||
description: Gets a device or multiple devices.
|
||||
parameters:
|
||||
|
|
|
@ -56,9 +56,7 @@ class DeviceRow(OrderedDict):
|
|||
self.components()
|
||||
|
||||
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)
|
||||
# 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
|
||||
|
@ -75,8 +73,8 @@ class DeviceRow(OrderedDict):
|
|||
i += 1
|
||||
|
||||
def fill_component(self, type, i, component=None):
|
||||
"""
|
||||
Function to put specific information of components in OrderedDict (csv)
|
||||
"""Function to put specific information of components
|
||||
in OrderedDict (csv)
|
||||
:param type: type of component
|
||||
:param component: device.components
|
||||
"""
|
||||
|
@ -85,11 +83,13 @@ class DeviceRow(OrderedDict):
|
|||
self['{} {} Model'.format(type, i)] = component.serial_number if component else ''
|
||||
self['{} {} Serial Number'.format(type, i)] = component.serial_number if component else ''
|
||||
|
||||
""" Particular fields for component GraphicCard """
|
||||
"""Particular fields for component GraphicCard."""
|
||||
if isinstance(component, d.GraphicCard):
|
||||
self['{} {} Memory (MB)'.format(type, i)] = component.memory
|
||||
|
||||
""" Particular fields for component DataStorage.t -> (HardDrive, SolidStateDrive) """
|
||||
"""Particular fields for component DataStorage.t ->
|
||||
(HardDrive, SolidStateDrive)
|
||||
"""
|
||||
if isinstance(component, d.DataStorage):
|
||||
self['{} {} Size (MB)'.format(type, i)] = component.size
|
||||
self['{} {} Privacy'.format(type, i)] = component.privacy
|
||||
|
@ -109,16 +109,14 @@ class DeviceRow(OrderedDict):
|
|||
except:
|
||||
self['{} {} Writing speed'.format(type, i)] = ''
|
||||
|
||||
""" Particular fields for component Processor """
|
||||
"""Particular fields for component Processor."""
|
||||
if isinstance(component, d.Processor):
|
||||
self['{} {} Number of cores'.format(type, i)] = component.cores
|
||||
self['{} {} Speed (GHz)'.format(type, i)] = component.speed
|
||||
|
||||
""" Particular fields for component RamModule """
|
||||
"""Particular fields for component RamModule."""
|
||||
if isinstance(component, d.RamModule):
|
||||
self['{} {} Size (MB)'.format(type, i)] = component.size
|
||||
self['{} {} Speed (MHz)'.format(type, i)] = component.speed
|
||||
|
||||
# todo add Display size, ...
|
||||
# todo add NetworkAdapter speedLink?
|
||||
# todo add some ComputerAccessories
|
||||
# todo add Display, NetworkAdapter, etc...
|
||||
|
|
|
@ -110,11 +110,7 @@ class DevicesDocumentView(DeviceView):
|
|||
return self.generate_post_csv(query)
|
||||
|
||||
def generate_post_csv(self, query):
|
||||
"""
|
||||
Get device query and put information in csv format
|
||||
:param query:
|
||||
:return:
|
||||
"""
|
||||
"""Get device query and put information in csv format."""
|
||||
data = StringIO()
|
||||
cw = csv.writer(data)
|
||||
first = True
|
||||
|
|
|
@ -24,16 +24,13 @@ class Lot(Thing):
|
|||
description = db.Column(CIText())
|
||||
description.comment = """A comment about the lot."""
|
||||
closed = db.Column(db.Boolean, default=False, nullable=False)
|
||||
closed.comment = """
|
||||
A closed lot cannot be modified anymore.
|
||||
"""
|
||||
closed.comment = """A closed lot cannot be modified anymore."""
|
||||
devices = db.relationship(Device,
|
||||
backref=db.backref('lots', lazy=True, collection_class=set),
|
||||
secondary=lambda: LotDevice.__table__,
|
||||
lazy=True,
|
||||
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
|
||||
descendant lots.
|
||||
|
@ -67,8 +64,7 @@ class Lot(Thing):
|
|||
|
||||
def __init__(self, name: str, closed: bool = closed.default.arg,
|
||||
description: str = None) -> None:
|
||||
"""
|
||||
Initializes a lot
|
||||
"""Initializes a lot
|
||||
:param name:
|
||||
:param closed:
|
||||
"""
|
||||
|
@ -173,9 +169,7 @@ class LotDevice(db.Model):
|
|||
nullable=False,
|
||||
default=lambda: g.user.id)
|
||||
author = db.relationship(User, primaryjoin=author_id == User.id)
|
||||
author_id.comment = """
|
||||
The user that put the device in the lot.
|
||||
"""
|
||||
author_id.comment = """The user that put the device in the lot."""
|
||||
|
||||
|
||||
class Path(db.Model):
|
||||
|
@ -191,9 +185,7 @@ class Path(db.Model):
|
|||
primaryjoin=Lot.id == lot_id)
|
||||
path = db.Column(LtreeType, nullable=False)
|
||||
created = db.Column(db.TIMESTAMP(timezone=True), server_default=db.text('CURRENT_TIMESTAMP'))
|
||||
created.comment = """
|
||||
When Devicehub created this.
|
||||
"""
|
||||
created.comment = """When Devicehub created this."""
|
||||
|
||||
__table_args__ = (
|
||||
# dag.delete_edge needs to disable internally/temporarily the unique constraint
|
||||
|
|
|
@ -23,8 +23,7 @@ class LotFormat(Enum):
|
|||
|
||||
class LotView(View):
|
||||
class FindArgs(MarshmallowSchema):
|
||||
"""
|
||||
Allowed arguments for the ``find``
|
||||
"""Allowed arguments for the ``find``
|
||||
method (GET collection) endpoint
|
||||
"""
|
||||
format = EnumField(LotFormat, missing=None)
|
||||
|
@ -56,8 +55,7 @@ class LotView(View):
|
|||
|
||||
@teal.cache.cache(datetime.timedelta(minutes=5))
|
||||
def find(self, args: dict):
|
||||
"""
|
||||
Gets lots.
|
||||
"""Gets lots.
|
||||
|
||||
By passing the value `UiTree` in the parameter `format`
|
||||
of the query you get a recursive nested suited for ui-tree::
|
||||
|
|
|
@ -20,16 +20,14 @@ class Thing(db.Model):
|
|||
nullable=False,
|
||||
index=True,
|
||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
||||
updated.comment = """
|
||||
The last time Devicehub recorded a change for this thing.
|
||||
updated.comment = """The last time Devicehub recorded a change for
|
||||
this thing.
|
||||
"""
|
||||
created = db.Column(db.TIMESTAMP(timezone=True),
|
||||
nullable=False,
|
||||
index=True,
|
||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
||||
created.comment = """
|
||||
When Devicehub created this.
|
||||
"""
|
||||
created.comment = """When Devicehub created this."""
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
# We need to set 'created' before sqlalchemy inits the class
|
||||
|
|
|
@ -39,8 +39,8 @@ class Tag(Thing):
|
|||
collection_class=set)
|
||||
"""The organization that issued the tag."""
|
||||
provider = Column(URL())
|
||||
provider.comment = """
|
||||
The tag provider URL. If None, the provider is this Devicehub.
|
||||
provider.comment = """The tag provider URL. If None, the provider is
|
||||
this Devicehub.
|
||||
"""
|
||||
device_id = Column(BigInteger,
|
||||
# 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)
|
||||
"""The device linked to this tag."""
|
||||
secondary = Column(db.CIText(), index=True)
|
||||
secondary.comment = """
|
||||
A secondary identifier for this tag. It has the same
|
||||
constraints as the main one. Only needed in special cases.
|
||||
secondary.comment = """A secondary identifier for this tag.
|
||||
It has the same constraints as the main one. Only needed in special cases.
|
||||
"""
|
||||
|
||||
__table_args__ = (
|
||||
|
@ -116,7 +115,7 @@ class Tag(Thing):
|
|||
|
||||
@classmethod
|
||||
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()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
|
@ -50,7 +50,7 @@ class TagView(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):
|
||||
"""Gets the device from the tag."""
|
||||
|
@ -78,8 +78,7 @@ class TagDeviceView(View):
|
|||
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
@ -28,8 +28,7 @@ class User(Thing):
|
|||
# todo set restriction that user has, at least, one active db
|
||||
|
||||
def __init__(self, email, password=None, inventories=None) -> None:
|
||||
"""
|
||||
Creates an user.
|
||||
"""Creates an user.
|
||||
:param email:
|
||||
:param password:
|
||||
:param inventories: A set of Inventory where the user has
|
||||
|
|
|
@ -29,8 +29,7 @@ class User(Thing):
|
|||
load_only=(),
|
||||
dump_only=(),
|
||||
partial=False):
|
||||
"""
|
||||
Instantiates the User.
|
||||
"""Instantiates the User.
|
||||
|
||||
By default we exclude token from both load/dump
|
||||
so they are not taken / set in normal usage by mistake.
|
||||
|
|
|
@ -5,11 +5,11 @@ device:
|
|||
model: d1ml
|
||||
manufacturer: d1mr
|
||||
components:
|
||||
- type: GraphicCard
|
||||
- type: GraphicCard
|
||||
serial_number: gc1s
|
||||
model: gc1ml
|
||||
manufacturer: gc1mr
|
||||
- type: RamModule
|
||||
- type: RamModule
|
||||
serial_number: rm1s
|
||||
model: rm1ml
|
||||
manufacturer: rm1mr
|
||||
|
|
|
@ -102,8 +102,7 @@ def test_membership_repeating_id():
|
|||
|
||||
@pytest.mark.usefixtures(app_context.__name__)
|
||||
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
|
||||
:meth:`ereuse_devicehub.resources.user.Organization.get_default_org`.
|
||||
"""
|
||||
|
|
|
@ -31,9 +31,7 @@ from tests.conftest import file
|
|||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
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',
|
||||
manufacturer='p1ma',
|
||||
serial_number='p1s',
|
||||
|
@ -182,8 +180,7 @@ def test_add_remove():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
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.
|
||||
"""
|
||||
s = conftest.file('pc-components.db')
|
||||
|
@ -200,8 +197,7 @@ def test_sync_run_components_empty():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
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.
|
||||
"""
|
||||
s = conftest.file('pc-components.db')
|
||||
|
@ -218,10 +214,7 @@ def test_sync_run_components_none():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_sync_execute_register_desktop_new_desktop_no_tag():
|
||||
"""
|
||||
Syncs a new d.Desktop with HID and without a tag, creating it.
|
||||
:return:
|
||||
"""
|
||||
"""Syncs a new d.Desktop with HID and without a tag, creating it."""
|
||||
# Case 1: device does not exist on DB
|
||||
pc = d.Desktop(**conftest.file('pc-components.db')['device'])
|
||||
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__)
|
||||
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'])
|
||||
db.session.add(pc)
|
||||
db.session.commit()
|
||||
|
@ -246,8 +237,7 @@ def test_sync_execute_register_desktop_existing_no_tag():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
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.
|
||||
"""
|
||||
|
@ -260,8 +250,7 @@ def test_sync_execute_register_desktop_no_hid_no_tag():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
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.
|
||||
"""
|
||||
|
@ -279,8 +268,7 @@ def test_sync_execute_register_desktop_tag_not_linked():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
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
|
||||
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__)
|
||||
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.
|
||||
|
||||
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__)
|
||||
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.
|
||||
(If it has HID it validates both HID and tag point at the same
|
||||
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__)
|
||||
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.
|
||||
"""
|
||||
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__)
|
||||
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.
|
||||
|
||||
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')
|
||||
def test_manufacturer_enforced():
|
||||
"""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):
|
||||
|
|
|
@ -56,8 +56,7 @@ def test_device_sort():
|
|||
|
||||
@pytest.fixture()
|
||||
def device_query_dummy(app: Devicehub):
|
||||
"""
|
||||
3 computers, where:
|
||||
"""3 computers, where:
|
||||
|
||||
1. s1 Desktop with a Processor
|
||||
2. s2 Desktop with an SSD
|
||||
|
|
|
@ -28,7 +28,7 @@ def test_dispatcher_default(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
|
||||
# to a well-known endpoint for the app.
|
||||
# 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):
|
||||
"""Users special endpoint returns an app"""
|
||||
"""Users special endpoint returns an app."""
|
||||
# For now returns the first app, as all apps
|
||||
# can answer {}/users/login
|
||||
app = dispatcher({'SCRIPT_NAME:': '/', 'PATH_INFO': '/users/'}, noop)
|
||||
|
|
|
@ -22,8 +22,7 @@ from tests.conftest import create_user, file
|
|||
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_author():
|
||||
"""
|
||||
Checks the default created author.
|
||||
"""Checks the default created author.
|
||||
|
||||
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():
|
||||
"""Tests the several ways of creating eReuse Price, emulating
|
||||
from an AggregateRate and ensuring that the different Range
|
||||
return correct results."""
|
||||
return correct results.
|
||||
"""
|
||||
# important to check Range.low no returning warranty2
|
||||
# Range.verylow not returning nothing
|
||||
|
||||
|
|
|
@ -13,8 +13,7 @@ from ereuse_devicehub.resources.inventory import Inventory
|
|||
from ereuse_devicehub.resources.user import User
|
||||
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).
|
||||
"""
|
||||
|
||||
|
|
|
@ -9,8 +9,7 @@ from ereuse_devicehub.resources.enums import ComputerChassis
|
|||
from ereuse_devicehub.resources.lot.models import Lot, LotDevice
|
||||
from tests import conftest
|
||||
|
||||
"""
|
||||
In case of error, debug with:
|
||||
"""In case of error, debug with:
|
||||
|
||||
try:
|
||||
with db.session.begin_nested():
|
||||
|
@ -67,7 +66,7 @@ def test_lot_model_children():
|
|||
|
||||
|
||||
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)
|
||||
assert l['name'] == 'foo'
|
||||
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):
|
||||
"""Tests adding children lots to a lot through the view and
|
||||
GETting the results."""
|
||||
GETting the results.
|
||||
"""
|
||||
parent, _ = user.post(({'name': 'Parent'}), res=Lot)
|
||||
child, _ = user.post(({'name': 'Child'}), res=Lot)
|
||||
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):
|
||||
"""Tests adding a device to a lot using POST and
|
||||
removing it with DELETE."""
|
||||
removing it with DELETE.
|
||||
"""
|
||||
# todo check with components
|
||||
with app.app_context():
|
||||
device = Desktop(serial_number='foo',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from decimal import Decimal
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
import math
|
||||
import pytest
|
||||
|
||||
from ereuse_devicehub.client import UserClient
|
||||
|
@ -96,8 +95,7 @@ def test_price_from_rate():
|
|||
|
||||
|
||||
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
|
||||
2. Snapshot software aren't Workbench
|
||||
3. Device type are not Computer
|
||||
|
@ -138,8 +136,8 @@ def test_multiple_rates(user: UserClient):
|
|||
ensuring that the tests / benchmarks...
|
||||
from the first rate do not contaminate the second rate.
|
||||
|
||||
This ensures that rates only takes the last version of actions
|
||||
and components (in case device has new components, for example).
|
||||
This ensures that rates only takes all the correct actions
|
||||
and components rates in case device have new tests/benchmarks.
|
||||
"""
|
||||
pc = Desktop(chassis=ComputerChassis.Tower)
|
||||
hdd = HardDrive(size=476940)
|
||||
|
@ -172,15 +170,13 @@ def test_multiple_rates(user: UserClient):
|
|||
|
||||
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))
|
||||
ssd = SolidStateDrive(size=476940)
|
||||
ssd.actions_one.add(BenchmarkDataStorage(read_speed=222, write_speed=111))
|
||||
pc.components |= {
|
||||
hdd,
|
||||
ssd,
|
||||
RamModule(size=2048, speed=1067),
|
||||
RamModule(size=2048, speed=1067),
|
||||
cpu
|
||||
}
|
||||
|
||||
# Add test visual with functionality and appearance range
|
||||
|
@ -190,13 +186,13 @@ def test_multiple_rates(user: UserClient):
|
|||
|
||||
rate2, price2 = RateComputer.compute(pc)
|
||||
|
||||
assert rate2.data_storage == 4.27
|
||||
assert rate2.processor == 3.61
|
||||
assert rate2.ram == 4.12
|
||||
assert rate2.data_storage == 4.3
|
||||
assert rate2.processor == 3.78
|
||||
assert rate2.ram == 3.95
|
||||
|
||||
assert rate2.appearance == 0
|
||||
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')
|
||||
|
|
|
@ -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:
|
||||
-DataStorage
|
||||
-RamModule
|
||||
-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
|
||||
|
||||
|
@ -31,9 +29,8 @@ from tests import conftest
|
|||
|
||||
|
||||
def test_rate_data_storage_rate():
|
||||
"""
|
||||
Test to check if compute data storage rate have same value than previous score version;
|
||||
id = pc_1193, pc_1201, pc_79, pc_798
|
||||
"""Test to check if compute data storage rate have same value than
|
||||
previous score version.
|
||||
"""
|
||||
|
||||
hdd_1969 = HardDrive(size=476940)
|
||||
|
@ -67,10 +64,8 @@ def test_rate_data_storage_rate():
|
|||
|
||||
|
||||
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;
|
||||
id = pc_2992
|
||||
"""
|
||||
|
||||
hdd_null = HardDrive(size=None)
|
||||
|
@ -81,9 +76,7 @@ def test_rate_data_storage_size_is_null():
|
|||
|
||||
|
||||
def test_rate_no_data_storage():
|
||||
"""
|
||||
Test without data storage devices
|
||||
"""
|
||||
"""Test without data storage devices."""
|
||||
|
||||
hdd_null = HardDrive()
|
||||
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():
|
||||
"""
|
||||
Test to check if compute ram rate have same value than previous score version
|
||||
only with 1 RamModule; id = pc_1201
|
||||
"""Test to check if compute ram rate have same value than previous
|
||||
score version only with 1 RamModule.
|
||||
"""
|
||||
|
||||
ram1 = RamModule(size=2048, speed=1333)
|
||||
|
@ -105,9 +97,8 @@ def test_rate_ram_rate():
|
|||
|
||||
|
||||
def test_rate_ram_rate_2modules():
|
||||
"""
|
||||
Test to check if compute ram rate have same value than previous score version
|
||||
with 2 RamModule; id = pc_1193
|
||||
"""Test to check if compute ram rate have same value than previous
|
||||
score version with 2 RamModule.
|
||||
"""
|
||||
|
||||
ram1 = RamModule(size=4096, speed=1600)
|
||||
|
@ -119,9 +110,8 @@ def test_rate_ram_rate_2modules():
|
|||
|
||||
|
||||
def test_rate_ram_rate_4modules():
|
||||
"""
|
||||
Test to check if compute ram rate have same value than previous score version
|
||||
with 2 RamModule; id = pc_79
|
||||
"""Test to check if compute ram rate have same value than previous
|
||||
score version with 2 RamModule.
|
||||
"""
|
||||
|
||||
ram1 = RamModule(size=512, speed=667)
|
||||
|
@ -135,8 +125,8 @@ def test_rate_ram_rate_4modules():
|
|||
|
||||
|
||||
def test_rate_ram_module_size_is_0():
|
||||
"""
|
||||
Test where input data RamModule.size = 0; is like no RamModule has been detected; id = pc_798
|
||||
"""Test where input data RamModule.size = 0; is like no RamModule
|
||||
has been detected.
|
||||
"""
|
||||
|
||||
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():
|
||||
"""
|
||||
Test where RamModule.speed is NULL (not detected) but has size.
|
||||
Pc ID = 795(1542), 745(1535), 804(1549)
|
||||
"""
|
||||
"""Test where RamModule.speed is NULL (not detected) but has size."""
|
||||
|
||||
ram0 = RamModule(size=2048, speed=None)
|
||||
|
||||
|
@ -165,9 +152,7 @@ def test_rate_ram_speed_is_null():
|
|||
|
||||
|
||||
def test_rate_no_ram_module():
|
||||
"""
|
||||
Test without RamModule
|
||||
"""
|
||||
"""Test without RamModule."""
|
||||
ram0 = RamModule()
|
||||
|
||||
ram_rate = RamRate().compute([ram0])
|
||||
|
@ -175,9 +160,8 @@ def test_rate_no_ram_module():
|
|||
|
||||
|
||||
def test_rate_processor_rate():
|
||||
"""
|
||||
Test to check if compute processor rate have same value than previous score version
|
||||
only with 1 core; id = 79
|
||||
"""Test to check if compute processor rate have same value than previous
|
||||
score version only with 1 core.
|
||||
"""
|
||||
|
||||
cpu = Processor(cores=1, speed=1.6)
|
||||
|
@ -190,9 +174,8 @@ def test_rate_processor_rate():
|
|||
|
||||
|
||||
def test_rate_processor_rate_2cores():
|
||||
"""
|
||||
Test to check if compute processor rate have same value than previous score version
|
||||
with 2 cores; id = pc_1193, pc_1201
|
||||
"""Test to check if compute processor rate have same value than previous
|
||||
score version with 2 cores.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# TODO JN if delete processor default score for benchmark_cpu
|
||||
|
||||
|
||||
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.actions_one.add(BenchmarkProcessor(rate=0))
|
||||
|
||||
|
@ -227,9 +205,7 @@ def test_rate_processor_with_null_cores():
|
|||
|
||||
|
||||
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.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)
|
||||
|
||||
|
||||
# TODO JN add price asserts in rate computers??
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_rate_computer_1193():
|
||||
"""
|
||||
Test rate computer characteristics:
|
||||
"""Test rate computer characteristics:
|
||||
- 2 module ram
|
||||
- processor with 2 cores
|
||||
|
||||
|
@ -297,8 +269,7 @@ def test_rate_computer_1193():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_rate_computer_1201():
|
||||
"""
|
||||
Test rate computer characteristics:
|
||||
"""Test rate computer characteristics:
|
||||
- only 1 module ram
|
||||
- processor 2 cores
|
||||
|
||||
|
@ -349,8 +320,7 @@ def test_rate_computer_1201():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_rate_computer_multiple_ram_module():
|
||||
"""
|
||||
Test rate computer characteristics:
|
||||
"""Test rate computer characteristics:
|
||||
- only 1 module ram
|
||||
- processor 2 cores
|
||||
|
||||
|
@ -408,8 +378,7 @@ def test_rate_computer_multiple_ram_module():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_rate_computer_one_ram_module():
|
||||
"""
|
||||
Test rate computer characteristics:
|
||||
"""Test rate computer characteristics:
|
||||
- only 1 module ram
|
||||
- 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')
|
||||
def test_rate_computer_with_data_storage_without_benchmark():
|
||||
"""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.
|
||||
"""
|
||||
|
|
|
@ -3,8 +3,6 @@ from datetime import datetime
|
|||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from ereuse_devicehub.client import UserClient
|
||||
from ereuse_devicehub.resources.action.models import Snapshot
|
||||
from ereuse_devicehub.resources.documents import documents
|
||||
|
@ -12,9 +10,7 @@ from tests.conftest import file
|
|||
|
||||
|
||||
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)
|
||||
csv_str, _ = user.get(res=documents.DocumentDef.t,
|
||||
item='devices/',
|
||||
|
@ -41,9 +37,7 @@ def test_export_basic_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)
|
||||
csv_str, _ = user.get(res=documents.DocumentDef.t,
|
||||
item='devices/',
|
||||
|
@ -71,8 +65,8 @@ def test_export_full_snapshot(user: UserClient):
|
|||
|
||||
|
||||
def test_export_empty(user: UserClient):
|
||||
"""
|
||||
Test to check works correctly exporting csv without any information (no snapshot)
|
||||
"""Test to check works correctly exporting csv without any information,
|
||||
export a placeholder device.
|
||||
"""
|
||||
csv_str, _ = user.get(res=documents.DocumentDef.t,
|
||||
accept='text/csv',
|
||||
|
@ -85,9 +79,7 @@ def test_export_empty(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)
|
||||
csv_str, _ = user.get(res=documents.DocumentDef.t,
|
||||
item='devices/',
|
||||
|
@ -111,9 +103,7 @@ def test_export_computer_monitor(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)
|
||||
csv_str, _ = user.get(res=documents.DocumentDef.t,
|
||||
item='devices/',
|
||||
|
@ -136,8 +126,7 @@ def test_export_keyboard(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..)
|
||||
"""
|
||||
# Open fixture csv and transform to list
|
||||
|
|
|
@ -26,8 +26,7 @@ from tests.conftest import file
|
|||
|
||||
@pytest.mark.usefixtures('auth_app_context')
|
||||
def test_snapshot_model():
|
||||
"""
|
||||
Tests creating a Snapshot with its relationships ensuring correct
|
||||
"""Tests creating a Snapshot with its relationships ensuring correct
|
||||
DB mapping.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Tests the post snapshot endpoint (validation, etc), data correctness,
|
||||
"""Tests the post snapshot endpoint (validation, etc), data correctness,
|
||||
and relationship correctness.
|
||||
"""
|
||||
# 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):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
|
@ -212,8 +209,7 @@ def test_snapshot_component_add_remove(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.
|
||||
"""
|
||||
# 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')
|
||||
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')
|
||||
snapshot_and_check(user, s, action_types=('RateMonitor',))
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
|
||||
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')
|
||||
snapshot = snapshot_and_check(user, s, action_types=('VisualTest',))
|
||||
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')
|
||||
def test_snapshot_components_none():
|
||||
"""
|
||||
Tests that a snapshot without components does not
|
||||
remove them from the computer.
|
||||
"""Tests that a snapshot without components does not remove them
|
||||
from the computer.
|
||||
"""
|
||||
|
||||
|
||||
# TODO JN is really necessary in which cases??
|
||||
@pytest.mark.xfail(reason='Test not developed')
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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]):
|
||||
"""
|
||||
Asserts that the components in components1 are
|
||||
similar than the components in components2.
|
||||
"""Asserts that the components in components1 are similar than
|
||||
the components in components2.
|
||||
"""
|
||||
assert len(components1) == len(components2)
|
||||
key = itemgetter('serialNumber')
|
||||
|
@ -447,8 +435,7 @@ def snapshot_and_check(user: UserClient,
|
|||
input_snapshot: dict,
|
||||
action_types: Tuple[str, ...] = tuple(),
|
||||
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
|
||||
order as described in the passed-in ``action_types``.
|
||||
|
|
|
@ -80,8 +80,7 @@ def test_tag_post(app: Devicehub, 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.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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
|
||||
it should raise an exception.
|
||||
"""
|
||||
|
|
|
@ -18,8 +18,7 @@ from tests.conftest import app_context, create_user
|
|||
|
||||
@pytest.mark.usefixtures(app_context.__name__)
|
||||
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.
|
||||
"""
|
||||
|
@ -63,8 +62,7 @@ def test_hash_password():
|
|||
|
||||
|
||||
def test_login_success(client: Client, app: Devicehub):
|
||||
"""
|
||||
Tests successfully performing login.
|
||||
"""Tests successfully performing login.
|
||||
This checks that:
|
||||
|
||||
- User is returned.
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
"""
|
||||
Tests that emulates the behaviour of a WorkbenchServer.
|
||||
"""
|
||||
"""Tests that emulates the behaviour of a WorkbenchServer."""
|
||||
import json
|
||||
import math
|
||||
import pathlib
|
||||
|
||||
import math
|
||||
import pytest
|
||||
|
||||
from ereuse_devicehub.client import UserClient
|
||||
|
@ -17,8 +15,7 @@ from tests.conftest import file
|
|||
|
||||
|
||||
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
|
||||
in the docs.
|
||||
"""
|
||||
|
@ -72,8 +69,7 @@ def test_workbench_server_condensed(user: UserClient):
|
|||
|
||||
@pytest.mark.xfail(reason='Functionality not yet developed.')
|
||||
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/
|
||||
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):
|
||||
"""
|
||||
Checks the values of the device, components,
|
||||
"""Checks the values of the device, components,
|
||||
actions and their relationships of a real pc.
|
||||
"""
|
||||
s = file('real-eee-1001pxd.snapshot.11')
|
||||
|
|