Merge pull request #242 from eReuse/feature/add-column-type-in-devices-list
add icons types of devices in list of devices
This commit is contained in:
commit
a5bc694d86
|
@ -1,20 +1,30 @@
|
||||||
import pathlib
|
|
||||||
import copy
|
import copy
|
||||||
|
import pathlib
|
||||||
import time
|
import time
|
||||||
from flask import g
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set
|
||||||
from flask_sqlalchemy import event
|
|
||||||
|
|
||||||
from boltons import urlutils
|
from boltons import urlutils
|
||||||
from citext import CIText
|
from citext import CIText
|
||||||
from ereuse_utils.naming import HID_CONVERSION_DOC, Naming
|
from ereuse_utils.naming import HID_CONVERSION_DOC, Naming
|
||||||
|
from flask import g
|
||||||
|
from flask_sqlalchemy import event
|
||||||
from more_itertools import unique_everseen
|
from more_itertools import unique_everseen
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
from sqlalchemy import BigInteger, Boolean, Column
|
||||||
Sequence, SmallInteger, Unicode, inspect, text
|
from sqlalchemy import Enum as DBEnum
|
||||||
|
from sqlalchemy import (
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
Sequence,
|
||||||
|
SmallInteger,
|
||||||
|
Unicode,
|
||||||
|
inspect,
|
||||||
|
text,
|
||||||
|
)
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.ext.hybrid import hybrid_property
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
|
@ -22,19 +32,41 @@ from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
from sqlalchemy_utils import ColorType
|
from sqlalchemy_utils import ColorType
|
||||||
from stdnum import imei, meid
|
from stdnum import imei, meid
|
||||||
from teal.db import CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, URL, \
|
from teal.db import (
|
||||||
check_lower, check_range, IntEnum
|
CASCADE_DEL,
|
||||||
|
POLYMORPHIC_ID,
|
||||||
|
POLYMORPHIC_ON,
|
||||||
|
URL,
|
||||||
|
IntEnum,
|
||||||
|
ResourceNotFound,
|
||||||
|
check_lower,
|
||||||
|
check_range,
|
||||||
|
)
|
||||||
from teal.enums import Layouts
|
from teal.enums import Layouts
|
||||||
from teal.marshmallow import ValidationError
|
from teal.marshmallow import ValidationError
|
||||||
from teal.resource import url_for_resource
|
from teal.resource import url_for_resource
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.utils import hashcode
|
|
||||||
from ereuse_devicehub.resources.enums import BatteryTechnology, CameraFacing, ComputerChassis, \
|
|
||||||
DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity, TransferState
|
|
||||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing, listener_reset_field_updated_in_actual_time
|
|
||||||
from ereuse_devicehub.resources.user.models import User
|
|
||||||
from ereuse_devicehub.resources.device.metrics import Metrics
|
from ereuse_devicehub.resources.device.metrics import Metrics
|
||||||
|
from ereuse_devicehub.resources.enums import (
|
||||||
|
BatteryTechnology,
|
||||||
|
CameraFacing,
|
||||||
|
ComputerChassis,
|
||||||
|
DataStorageInterface,
|
||||||
|
DisplayTech,
|
||||||
|
PrinterTechnology,
|
||||||
|
RamFormat,
|
||||||
|
RamInterface,
|
||||||
|
Severity,
|
||||||
|
TransferState,
|
||||||
|
)
|
||||||
|
from ereuse_devicehub.resources.models import (
|
||||||
|
STR_SM_SIZE,
|
||||||
|
Thing,
|
||||||
|
listener_reset_field_updated_in_actual_time,
|
||||||
|
)
|
||||||
|
from ereuse_devicehub.resources.user.models import User
|
||||||
|
from ereuse_devicehub.resources.utils import hashcode
|
||||||
|
|
||||||
|
|
||||||
def create_code(context):
|
def create_code(context):
|
||||||
|
@ -58,17 +90,21 @@ class Device(Thing):
|
||||||
Devices can contain ``Components``, which are just a type of device
|
Devices can contain ``Components``, which are just a type of device
|
||||||
(it is a recursive relationship).
|
(it is a recursive relationship).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
||||||
id.comment = """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.
|
internally for software; users should not use this.
|
||||||
"""
|
"""
|
||||||
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||||
hid = Column(Unicode(), check_lower('hid'), unique=False)
|
hid = Column(Unicode(), check_lower('hid'), unique=False)
|
||||||
hid.comment = """The Hardware ID (HID) is the ID traceability
|
hid.comment = (
|
||||||
|
"""The Hardware ID (HID) is the ID traceability
|
||||||
systems use to ID a device globally. This field is auto-generated
|
systems use to ID a device globally. This field is auto-generated
|
||||||
from Devicehub using literal identifiers from the device,
|
from Devicehub using literal identifiers from the device,
|
||||||
so it can re-generated *offline*.
|
so it can re-generated *offline*.
|
||||||
""" + HID_CONVERSION_DOC
|
"""
|
||||||
|
+ HID_CONVERSION_DOC
|
||||||
|
)
|
||||||
model = Column(Unicode(), check_lower('model'))
|
model = Column(Unicode(), check_lower('model'))
|
||||||
model.comment = """The model of the device in lower case.
|
model.comment = """The model of the device in lower case.
|
||||||
|
|
||||||
|
@ -118,14 +154,18 @@ class Device(Thing):
|
||||||
image = db.Column(db.URL)
|
image = db.Column(db.URL)
|
||||||
image.comment = "An image of the device."
|
image.comment = "An image of the device."
|
||||||
|
|
||||||
owner_id = db.Column(UUID(as_uuid=True),
|
owner_id = db.Column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
db.ForeignKey(User.id),
|
db.ForeignKey(User.id),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
default=lambda: g.user.id)
|
default=lambda: g.user.id,
|
||||||
|
)
|
||||||
owner = db.relationship(User, primaryjoin=owner_id == User.id)
|
owner = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||||
allocated = db.Column(Boolean, default=False)
|
allocated = db.Column(Boolean, default=False)
|
||||||
allocated.comment = "device is allocated or not."
|
allocated.comment = "device is allocated or not."
|
||||||
devicehub_id = db.Column(db.CIText(), nullable=True, unique=True, default=create_code)
|
devicehub_id = db.Column(
|
||||||
|
db.CIText(), nullable=True, unique=True, default=create_code
|
||||||
|
)
|
||||||
devicehub_id.comment = "device have a unique code."
|
devicehub_id.comment = "device have a unique code."
|
||||||
active = db.Column(Boolean, default=True)
|
active = db.Column(Boolean, default=True)
|
||||||
|
|
||||||
|
@ -152,12 +192,12 @@ class Device(Thing):
|
||||||
'image',
|
'image',
|
||||||
'allocated',
|
'allocated',
|
||||||
'devicehub_id',
|
'devicehub_id',
|
||||||
'active'
|
'active',
|
||||||
}
|
}
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
db.Index('device_id', id, postgresql_using='hash'),
|
db.Index('device_id', id, postgresql_using='hash'),
|
||||||
db.Index('type_index', type, postgresql_using='hash')
|
db.Index('type_index', type, postgresql_using='hash'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, **kw) -> None:
|
def __init__(self, **kw) -> None:
|
||||||
|
@ -187,7 +227,9 @@ class Device(Thing):
|
||||||
for ac in actions_one:
|
for ac in actions_one:
|
||||||
ac.real_created = ac.created
|
ac.real_created = ac.created
|
||||||
|
|
||||||
return sorted(chain(actions_multiple, actions_one), key=lambda x: x.real_created)
|
return sorted(
|
||||||
|
chain(actions_multiple, actions_one), key=lambda x: x.real_created
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def problems(self):
|
def problems(self):
|
||||||
|
@ -196,8 +238,9 @@ class Device(Thing):
|
||||||
There can be up to 3 actions: current Snapshot,
|
There can be up to 3 actions: current Snapshot,
|
||||||
current Physical action, current Trading action.
|
current Physical action, current Trading action.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.device import states
|
|
||||||
from ereuse_devicehub.resources.action.models import Snapshot
|
from ereuse_devicehub.resources.action.models import Snapshot
|
||||||
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
actions = set()
|
actions = set()
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
actions.add(self.last_action_of(Snapshot))
|
actions.add(self.last_action_of(Snapshot))
|
||||||
|
@ -217,11 +260,13 @@ class Device(Thing):
|
||||||
"""
|
"""
|
||||||
# todo ensure to remove materialized values when start using them
|
# todo ensure to remove materialized values when start using them
|
||||||
# todo or self.__table__.columns if inspect fails
|
# todo or self.__table__.columns if inspect fails
|
||||||
return {c.key: getattr(self, c.key, None)
|
return {
|
||||||
|
c.key: getattr(self, c.key, None)
|
||||||
for c in inspect(self.__class__).attrs
|
for c in inspect(self.__class__).attrs
|
||||||
if isinstance(c, ColumnProperty)
|
if isinstance(c, ColumnProperty)
|
||||||
and not getattr(c, 'foreign_keys', None)
|
and not getattr(c, 'foreign_keys', None)
|
||||||
and c.key not in self._NON_PHYSICAL_PROPS}
|
and c.key not in self._NON_PHYSICAL_PROPS
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def public_properties(self) -> Dict[str, object or None]:
|
def public_properties(self) -> Dict[str, object or None]:
|
||||||
|
@ -234,11 +279,13 @@ class Device(Thing):
|
||||||
"""
|
"""
|
||||||
non_public = ['amount', 'transfer_state', 'receiver_id']
|
non_public = ['amount', 'transfer_state', 'receiver_id']
|
||||||
hide_properties = list(self._NON_PHYSICAL_PROPS) + non_public
|
hide_properties = list(self._NON_PHYSICAL_PROPS) + non_public
|
||||||
return {c.key: getattr(self, c.key, None)
|
return {
|
||||||
|
c.key: getattr(self, c.key, None)
|
||||||
for c in inspect(self.__class__).attrs
|
for c in inspect(self.__class__).attrs
|
||||||
if isinstance(c, ColumnProperty)
|
if isinstance(c, ColumnProperty)
|
||||||
and not getattr(c, 'foreign_keys', None)
|
and not getattr(c, 'foreign_keys', None)
|
||||||
and c.key not in hide_properties}
|
and c.key not in hide_properties
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def public_actions(self) -> List[object]:
|
def public_actions(self) -> List[object]:
|
||||||
|
@ -260,6 +307,7 @@ class Device(Thing):
|
||||||
"""The last Rate of the device."""
|
"""The last Rate of the device."""
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
from ereuse_devicehub.resources.action.models import Rate
|
from ereuse_devicehub.resources.action.models import Rate
|
||||||
|
|
||||||
return self.last_action_of(Rate)
|
return self.last_action_of(Rate)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -268,12 +316,14 @@ class Device(Thing):
|
||||||
ever been set."""
|
ever been set."""
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
from ereuse_devicehub.resources.action.models import Price
|
from ereuse_devicehub.resources.action.models import Price
|
||||||
|
|
||||||
return self.last_action_of(Price)
|
return self.last_action_of(Price)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_action_trading(self):
|
def last_action_trading(self):
|
||||||
"""which is the last action trading"""
|
"""which is the last action trading"""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
return self.last_action_of(*states.Trading.actions())
|
return self.last_action_of(*states.Trading.actions())
|
||||||
|
|
||||||
|
@ -287,6 +337,7 @@ class Device(Thing):
|
||||||
- Management
|
- Management
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
return self.last_action_of(*states.Status.actions())
|
return self.last_action_of(*states.Status.actions())
|
||||||
|
|
||||||
|
@ -300,6 +351,7 @@ class Device(Thing):
|
||||||
- Management
|
- Management
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
status_actions = [ac.t for ac in states.Status.actions()]
|
status_actions = [ac.t for ac in states.Status.actions()]
|
||||||
history = []
|
history = []
|
||||||
for ac in self.actions:
|
for ac in self.actions:
|
||||||
|
@ -329,13 +381,15 @@ class Device(Thing):
|
||||||
if not hasattr(lot, 'trade'):
|
if not hasattr(lot, 'trade'):
|
||||||
return
|
return
|
||||||
|
|
||||||
Status = {0: 'Trade',
|
Status = {
|
||||||
|
0: 'Trade',
|
||||||
1: 'Confirm',
|
1: 'Confirm',
|
||||||
2: 'NeedConfirmation',
|
2: 'NeedConfirmation',
|
||||||
3: 'TradeConfirmed',
|
3: 'TradeConfirmed',
|
||||||
4: 'Revoke',
|
4: 'Revoke',
|
||||||
5: 'NeedConfirmRevoke',
|
5: 'NeedConfirmRevoke',
|
||||||
6: 'RevokeConfirmed'}
|
6: 'RevokeConfirmed',
|
||||||
|
}
|
||||||
|
|
||||||
trade = lot.trade
|
trade = lot.trade
|
||||||
user_from = trade.user_from
|
user_from = trade.user_from
|
||||||
|
@ -408,6 +462,7 @@ class Device(Thing):
|
||||||
"""If the actual trading state is an revoke action, this property show
|
"""If the actual trading state is an revoke action, this property show
|
||||||
the id of that revoke"""
|
the id of that revoke"""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
action = self.last_action_of(*states.Trading.actions())
|
action = self.last_action_of(*states.Trading.actions())
|
||||||
if action.type == 'Revoke':
|
if action.type == 'Revoke':
|
||||||
|
@ -417,6 +472,7 @@ class Device(Thing):
|
||||||
def physical(self):
|
def physical(self):
|
||||||
"""The actual physical state, None otherwise."""
|
"""The actual physical state, None otherwise."""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
action = self.last_action_of(*states.Physical.actions())
|
action = self.last_action_of(*states.Physical.actions())
|
||||||
return states.Physical(action.__class__)
|
return states.Physical(action.__class__)
|
||||||
|
@ -425,6 +481,7 @@ class Device(Thing):
|
||||||
def traking(self):
|
def traking(self):
|
||||||
"""The actual traking state, None otherwise."""
|
"""The actual traking state, None otherwise."""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
action = self.last_action_of(*states.Traking.actions())
|
action = self.last_action_of(*states.Traking.actions())
|
||||||
return states.Traking(action.__class__)
|
return states.Traking(action.__class__)
|
||||||
|
@ -433,6 +490,7 @@ class Device(Thing):
|
||||||
def usage(self):
|
def usage(self):
|
||||||
"""The actual usage state, None otherwise."""
|
"""The actual usage state, None otherwise."""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
action = self.last_action_of(*states.Usage.actions())
|
action = self.last_action_of(*states.Usage.actions())
|
||||||
return states.Usage(action.__class__)
|
return states.Usage(action.__class__)
|
||||||
|
@ -470,8 +528,11 @@ class Device(Thing):
|
||||||
test has been executed.
|
test has been executed.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.action.models import Test
|
from ereuse_devicehub.resources.action.models import Test
|
||||||
current_tests = unique_everseen((e for e in reversed(self.actions) if isinstance(e, Test)),
|
|
||||||
key=attrgetter('type')) # last test of each type
|
current_tests = unique_everseen(
|
||||||
|
(e for e in reversed(self.actions) if isinstance(e, Test)),
|
||||||
|
key=attrgetter('type'),
|
||||||
|
) # last test of each type
|
||||||
return self._warning_actions(current_tests)
|
return self._warning_actions(current_tests)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -496,7 +557,9 @@ class Device(Thing):
|
||||||
|
|
||||||
def set_hid(self):
|
def set_hid(self):
|
||||||
with suppress(TypeError):
|
with suppress(TypeError):
|
||||||
self.hid = Naming.hid(self.type, self.manufacturer, self.model, self.serial_number)
|
self.hid = Naming.hid(
|
||||||
|
self.type, self.manufacturer, self.model, self.serial_number
|
||||||
|
)
|
||||||
|
|
||||||
def last_action_of(self, *types):
|
def last_action_of(self, *types):
|
||||||
"""Gets the last action of the given types.
|
"""Gets the last action of the given types.
|
||||||
|
@ -509,7 +572,9 @@ class Device(Thing):
|
||||||
actions.sort(key=lambda x: x.created)
|
actions.sort(key=lambda x: x.created)
|
||||||
return next(e for e in reversed(actions) if isinstance(e, types))
|
return next(e for e in reversed(actions) if isinstance(e, types))
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise LookupError('{!r} does not contain actions of types {}.'.format(self, types))
|
raise LookupError(
|
||||||
|
'{!r} does not contain actions of types {}.'.format(self, types)
|
||||||
|
)
|
||||||
|
|
||||||
def which_user_put_this_device_in_trace(self):
|
def which_user_put_this_device_in_trace(self):
|
||||||
"""which is the user than put this device in this trade"""
|
"""which is the user than put this device in this trade"""
|
||||||
|
@ -546,6 +611,32 @@ class Device(Thing):
|
||||||
metrics = Metrics(device=self)
|
metrics = Metrics(device=self)
|
||||||
return metrics.get_metrics()
|
return metrics.get_metrics()
|
||||||
|
|
||||||
|
def get_type_logo(self):
|
||||||
|
# This is used for see one logo of type of device in the frontend
|
||||||
|
types = {
|
||||||
|
"Desktop": "bi bi-file-post-fill",
|
||||||
|
"Laptop": "bi bi-laptop",
|
||||||
|
"Server": "bi bi-server",
|
||||||
|
"Processor": "bi bi-cpu",
|
||||||
|
"RamModule": "bi bi-list",
|
||||||
|
"Motherboard": "bi bi-cpu-fill",
|
||||||
|
"NetworkAdapter": "bi bi-hdd-network",
|
||||||
|
"GraphicCard": "bi bi-brush",
|
||||||
|
"SoundCard": "bi bi-volume-up-fill",
|
||||||
|
"Monitor": "bi bi-display",
|
||||||
|
"Display": "bi bi-display",
|
||||||
|
"ComputerMonitor": "bi bi-display",
|
||||||
|
"TelevisionSet": "bi bi-easel",
|
||||||
|
"TV": "bi bi-easel",
|
||||||
|
"Projector": "bi bi-camera-video",
|
||||||
|
"Tablet": "bi bi-tablet-landscape",
|
||||||
|
"Smartphone": "bi bi-phone",
|
||||||
|
"Cellphone": "bi bi-telephone",
|
||||||
|
"HardDrive": "bi bi-hdd-stack",
|
||||||
|
"SolidStateDrive": "bi bi-hdd",
|
||||||
|
}
|
||||||
|
return types.get(self.type, '')
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.id < other.id
|
return self.id < other.id
|
||||||
|
|
||||||
|
@ -571,19 +662,24 @@ class Device(Thing):
|
||||||
|
|
||||||
class DisplayMixin:
|
class DisplayMixin:
|
||||||
"""Base class for the Display Component and the Monitor Device."""
|
"""Base class for the Display Component and the Monitor Device."""
|
||||||
size = Column(Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=True)
|
|
||||||
|
size = Column(
|
||||||
|
Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=True
|
||||||
|
)
|
||||||
size.comment = """The size of the monitor in inches."""
|
size.comment = """The size of the monitor in inches."""
|
||||||
technology = Column(DBEnum(DisplayTech))
|
technology = Column(DBEnum(DisplayTech))
|
||||||
technology.comment = """The technology the monitor uses to display
|
technology.comment = """The technology the monitor uses to display
|
||||||
the image.
|
the image.
|
||||||
"""
|
"""
|
||||||
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000),
|
resolution_width = Column(
|
||||||
nullable=True)
|
SmallInteger, check_range('resolution_width', 10, 20000), nullable=True
|
||||||
|
)
|
||||||
resolution_width.comment = """The maximum horizontal resolution the
|
resolution_width.comment = """The maximum horizontal resolution the
|
||||||
monitor can natively support in pixels.
|
monitor can natively support in pixels.
|
||||||
"""
|
"""
|
||||||
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000),
|
resolution_height = Column(
|
||||||
nullable=True)
|
SmallInteger, check_range('resolution_height', 10, 20000), nullable=True
|
||||||
|
)
|
||||||
resolution_height.comment = """The maximum vertical resolution the
|
resolution_height.comment = """The maximum vertical resolution the
|
||||||
monitor can natively support in pixels.
|
monitor can natively support in pixels.
|
||||||
"""
|
"""
|
||||||
|
@ -622,8 +718,12 @@ class DisplayMixin:
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if self.size:
|
if self.size:
|
||||||
return '{0.t} {0.serial_number} {0.size}in ({0.aspect_ratio}) {0.technology}'.format(self)
|
return '{0.t} {0.serial_number} {0.size}in ({0.aspect_ratio}) {0.technology}'.format(
|
||||||
return '{0.t} {0.serial_number} 0in ({0.aspect_ratio}) {0.technology}'.format(self)
|
self
|
||||||
|
)
|
||||||
|
return '{0.t} {0.serial_number} 0in ({0.aspect_ratio}) {0.technology}'.format(
|
||||||
|
self
|
||||||
|
)
|
||||||
|
|
||||||
def __format__(self, format_spec: str) -> str:
|
def __format__(self, format_spec: str) -> str:
|
||||||
v = ''
|
v = ''
|
||||||
|
@ -645,6 +745,7 @@ class Computer(Device):
|
||||||
Computer is broadly extended by ``Desktop``, ``Laptop``, and
|
Computer is broadly extended by ``Desktop``, ``Laptop``, and
|
||||||
``Server``. The property ``chassis`` defines it more granularly.
|
``Server``. The property ``chassis`` defines it more granularly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
chassis = Column(DBEnum(ComputerChassis), nullable=True)
|
chassis = Column(DBEnum(ComputerChassis), nullable=True)
|
||||||
chassis.comment = """The physical form of the computer.
|
chassis.comment = """The physical form of the computer.
|
||||||
|
@ -652,16 +753,18 @@ class Computer(Device):
|
||||||
It is a subset of the Linux definition of DMI / DMI decode.
|
It is a subset of the Linux definition of DMI / DMI decode.
|
||||||
"""
|
"""
|
||||||
amount = Column(Integer, check_range('amount', min=0, max=100), default=0)
|
amount = Column(Integer, check_range('amount', min=0, max=100), default=0)
|
||||||
owner_id = db.Column(UUID(as_uuid=True),
|
owner_id = db.Column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
db.ForeignKey(User.id),
|
db.ForeignKey(User.id),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
default=lambda: g.user.id)
|
default=lambda: g.user.id,
|
||||||
|
)
|
||||||
# author = db.relationship(User, primaryjoin=owner_id == User.id)
|
# author = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||||
transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False)
|
transfer_state = db.Column(
|
||||||
|
IntEnum(TransferState), default=TransferState.Initial, nullable=False
|
||||||
|
)
|
||||||
transfer_state.comment = TransferState.__doc__
|
transfer_state.comment = TransferState.__doc__
|
||||||
receiver_id = db.Column(UUID(as_uuid=True),
|
receiver_id = db.Column(UUID(as_uuid=True), db.ForeignKey(User.id), nullable=True)
|
||||||
db.ForeignKey(User.id),
|
|
||||||
nullable=True)
|
|
||||||
receiver = db.relationship(User, primaryjoin=receiver_id == User.id)
|
receiver = db.relationship(User, primaryjoin=receiver_id == User.id)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
@ -684,22 +787,30 @@ class Computer(Device):
|
||||||
@property
|
@property
|
||||||
def ram_size(self) -> int:
|
def ram_size(self) -> int:
|
||||||
"""The total of RAM memory the computer has."""
|
"""The total of RAM memory the computer has."""
|
||||||
return sum(ram.size or 0 for ram in self.components if isinstance(ram, RamModule))
|
return sum(
|
||||||
|
ram.size or 0 for ram in self.components if isinstance(ram, RamModule)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data_storage_size(self) -> int:
|
def data_storage_size(self) -> int:
|
||||||
"""The total of data storage the computer has."""
|
"""The total of data storage the computer has."""
|
||||||
return sum(ds.size or 0 for ds in self.components if isinstance(ds, DataStorage))
|
return sum(
|
||||||
|
ds.size or 0 for ds in self.components if isinstance(ds, DataStorage)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def processor_model(self) -> str:
|
def processor_model(self) -> str:
|
||||||
"""The model of one of the processors of the computer."""
|
"""The model of one of the processors of the computer."""
|
||||||
return next((p.model for p in self.components if isinstance(p, Processor)), None)
|
return next(
|
||||||
|
(p.model for p in self.components if isinstance(p, Processor)), None
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def graphic_card_model(self) -> str:
|
def graphic_card_model(self) -> str:
|
||||||
"""The model of one of the graphic cards of the computer."""
|
"""The model of one of the graphic cards of the computer."""
|
||||||
return next((p.model for p in self.components if isinstance(p, GraphicCard)), None)
|
return next(
|
||||||
|
(p.model for p in self.components if isinstance(p, GraphicCard)), None
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network_speeds(self) -> List[int]:
|
def network_speeds(self) -> List[int]:
|
||||||
|
@ -724,16 +835,18 @@ class Computer(Device):
|
||||||
it is not None.
|
it is not None.
|
||||||
"""
|
"""
|
||||||
return set(
|
return set(
|
||||||
privacy for privacy in
|
privacy
|
||||||
(hdd.privacy for hdd in self.components if isinstance(hdd, DataStorage))
|
for privacy in (
|
||||||
|
hdd.privacy for hdd in self.components if isinstance(hdd, DataStorage)
|
||||||
|
)
|
||||||
if privacy
|
if privacy
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def external_document_erasure(self):
|
def external_document_erasure(self):
|
||||||
"""Returns the external ``DataStorage`` proof of erasure.
|
"""Returns the external ``DataStorage`` proof of erasure."""
|
||||||
"""
|
|
||||||
from ereuse_devicehub.resources.action.models import DataWipe
|
from ereuse_devicehub.resources.action.models import DataWipe
|
||||||
|
|
||||||
urls = set()
|
urls = set()
|
||||||
try:
|
try:
|
||||||
ev = self.last_action_of(DataWipe)
|
ev = self.last_action_of(DataWipe)
|
||||||
|
@ -756,8 +869,11 @@ class Computer(Device):
|
||||||
if not self.hid:
|
if not self.hid:
|
||||||
return
|
return
|
||||||
components = self.components if components_snap is None else components_snap
|
components = self.components if components_snap is None else components_snap
|
||||||
macs_network = [c.serial_number for c in components
|
macs_network = [
|
||||||
if c.type == 'NetworkAdapter' and c.serial_number is not None]
|
c.serial_number
|
||||||
|
for c in components
|
||||||
|
if c.type == 'NetworkAdapter' and c.serial_number is not None
|
||||||
|
]
|
||||||
macs_network.sort()
|
macs_network.sort()
|
||||||
mac = macs_network[0] if macs_network else ''
|
mac = macs_network[0] if macs_network else ''
|
||||||
if not mac or mac in self.hid:
|
if not mac or mac in self.hid:
|
||||||
|
@ -823,9 +939,13 @@ class Mobile(Device):
|
||||||
"""
|
"""
|
||||||
ram_size = db.Column(db.Integer, check_range('ram_size', min=128, max=36000))
|
ram_size = db.Column(db.Integer, check_range('ram_size', min=128, max=36000))
|
||||||
ram_size.comment = """The total of RAM of the device in MB."""
|
ram_size.comment = """The total of RAM of the device in MB."""
|
||||||
data_storage_size = db.Column(db.Integer, check_range('data_storage_size', 0, 10 ** 8))
|
data_storage_size = db.Column(
|
||||||
|
db.Integer, check_range('data_storage_size', 0, 10**8)
|
||||||
|
)
|
||||||
data_storage_size.comment = """The total of data storage of the device in MB"""
|
data_storage_size.comment = """The total of data storage of the device in MB"""
|
||||||
display_size = db.Column(db.Float(decimal_return_scale=1), check_range('display_size', min=0.1, max=30.0))
|
display_size = db.Column(
|
||||||
|
db.Float(decimal_return_scale=1), check_range('display_size', min=0.1, max=30.0)
|
||||||
|
)
|
||||||
display_size.comment = """The total size of the device screen"""
|
display_size.comment = """The total size of the device screen"""
|
||||||
|
|
||||||
@validates('imei')
|
@validates('imei')
|
||||||
|
@ -855,21 +975,24 @@ class Cellphone(Mobile):
|
||||||
|
|
||||||
class Component(Device):
|
class Component(Device):
|
||||||
"""A device that can be inside another device."""
|
"""A device that can be inside another device."""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
parent_id = Column(BigInteger, ForeignKey(Computer.id))
|
parent_id = Column(BigInteger, ForeignKey(Computer.id))
|
||||||
parent = relationship(Computer,
|
parent = relationship(
|
||||||
backref=backref('components',
|
Computer,
|
||||||
|
backref=backref(
|
||||||
|
'components',
|
||||||
lazy=True,
|
lazy=True,
|
||||||
cascade=CASCADE_DEL,
|
cascade=CASCADE_DEL,
|
||||||
order_by=lambda: Component.id,
|
order_by=lambda: Component.id,
|
||||||
collection_class=OrderedSet),
|
collection_class=OrderedSet,
|
||||||
primaryjoin=parent_id == Computer.id)
|
),
|
||||||
|
primaryjoin=parent_id == Computer.id,
|
||||||
__table_args__ = (
|
|
||||||
db.Index('parent_index', parent_id, postgresql_using='hash'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
__table_args__ = (db.Index('parent_index', parent_id, postgresql_using='hash'),)
|
||||||
|
|
||||||
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
|
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
|
||||||
"""Gets a component that:
|
"""Gets a component that:
|
||||||
|
|
||||||
|
@ -881,11 +1004,16 @@ class Component(Device):
|
||||||
when looking for similar ones.
|
when looking for similar ones.
|
||||||
"""
|
"""
|
||||||
assert self.hid is None, 'Don\'t use this method with a component that has HID'
|
assert self.hid is None, 'Don\'t use this method with a component that has HID'
|
||||||
component = self.__class__.query \
|
component = (
|
||||||
.filter_by(parent=parent, hid=None, owner_id=self.owner_id,
|
self.__class__.query.filter_by(
|
||||||
**self.physical_properties) \
|
parent=parent,
|
||||||
.filter(~Component.id.in_(blacklist)) \
|
hid=None,
|
||||||
|
owner_id=self.owner_id,
|
||||||
|
**self.physical_properties,
|
||||||
|
)
|
||||||
|
.filter(~Component.id.in_(blacklist))
|
||||||
.first()
|
.first()
|
||||||
|
)
|
||||||
if not component:
|
if not component:
|
||||||
raise ResourceNotFound(self.type)
|
raise ResourceNotFound(self.type)
|
||||||
return component
|
return component
|
||||||
|
@ -908,6 +1036,7 @@ class GraphicCard(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
class DataStorage(JoinedComponentTableMixin, Component):
|
class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
"""A device that stores information."""
|
"""A device that stores information."""
|
||||||
|
|
||||||
size = Column(Integer, check_range('size', min=1, max=10**8))
|
size = Column(Integer, check_range('size', min=1, max=10**8))
|
||||||
size.comment = """The size of the data-storage in MB."""
|
size.comment = """The size of the data-storage in MB."""
|
||||||
interface = Column(DBEnum(DataStorageInterface))
|
interface = Column(DBEnum(DataStorageInterface))
|
||||||
|
@ -919,6 +1048,7 @@ class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
This is, the last erasure performed to the data storage.
|
This is, the last erasure performed to the data storage.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.action.models import EraseBasic
|
from ereuse_devicehub.resources.action.models import EraseBasic
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ev = self.last_action_of(EraseBasic)
|
ev = self.last_action_of(EraseBasic)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
|
@ -933,9 +1063,9 @@ class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def external_document_erasure(self):
|
def external_document_erasure(self):
|
||||||
"""Returns the external ``DataStorage`` proof of erasure.
|
"""Returns the external ``DataStorage`` proof of erasure."""
|
||||||
"""
|
|
||||||
from ereuse_devicehub.resources.action.models import DataWipe
|
from ereuse_devicehub.resources.action.models import DataWipe
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ev = self.last_action_of(DataWipe)
|
ev = self.last_action_of(DataWipe)
|
||||||
return ev.document.url.to_text()
|
return ev.document.url.to_text()
|
||||||
|
@ -985,6 +1115,7 @@ class NetworkAdapter(JoinedComponentTableMixin, NetworkMixin, Component):
|
||||||
|
|
||||||
class Processor(JoinedComponentTableMixin, Component):
|
class Processor(JoinedComponentTableMixin, Component):
|
||||||
"""The CPU."""
|
"""The CPU."""
|
||||||
|
|
||||||
speed = Column(Float, check_range('speed', 0.1, 15))
|
speed = Column(Float, check_range('speed', 0.1, 15))
|
||||||
speed.comment = """The regular CPU speed."""
|
speed.comment = """The regular CPU speed."""
|
||||||
cores = Column(SmallInteger, check_range('cores', 1, 10))
|
cores = Column(SmallInteger, check_range('cores', 1, 10))
|
||||||
|
@ -999,6 +1130,7 @@ class Processor(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
class RamModule(JoinedComponentTableMixin, Component):
|
class RamModule(JoinedComponentTableMixin, Component):
|
||||||
"""A stick of RAM."""
|
"""A stick of RAM."""
|
||||||
|
|
||||||
size = Column(SmallInteger, check_range('size', min=128, max=17000))
|
size = Column(SmallInteger, check_range('size', min=128, max=17000))
|
||||||
size.comment = """The capacity of the RAM stick."""
|
size.comment = """The capacity of the RAM stick."""
|
||||||
speed = Column(SmallInteger, check_range('speed', min=100, max=10000))
|
speed = Column(SmallInteger, check_range('speed', min=100, max=10000))
|
||||||
|
@ -1016,6 +1148,7 @@ class Display(JoinedComponentTableMixin, DisplayMixin, Component):
|
||||||
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
|
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
|
||||||
and ``TelevisionSet``.
|
and ``TelevisionSet``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1033,12 +1166,14 @@ class Battery(JoinedComponentTableMixin, Component):
|
||||||
def capacity(self) -> float:
|
def capacity(self) -> float:
|
||||||
"""The quantity of"""
|
"""The quantity of"""
|
||||||
from ereuse_devicehub.resources.action.models import MeasureBattery
|
from ereuse_devicehub.resources.action.models import MeasureBattery
|
||||||
|
|
||||||
real_size = self.last_action_of(MeasureBattery).size
|
real_size = self.last_action_of(MeasureBattery).size
|
||||||
return real_size / self.size if real_size and self.size else None
|
return real_size / self.size if real_size and self.size else None
|
||||||
|
|
||||||
|
|
||||||
class Camera(Component):
|
class Camera(Component):
|
||||||
"""The camera of a device."""
|
"""The camera of a device."""
|
||||||
|
|
||||||
focal_length = db.Column(db.SmallInteger)
|
focal_length = db.Column(db.SmallInteger)
|
||||||
video_height = db.Column(db.SmallInteger)
|
video_height = db.Column(db.SmallInteger)
|
||||||
video_width = db.Column(db.Integer)
|
video_width = db.Column(db.Integer)
|
||||||
|
@ -1051,6 +1186,7 @@ class Camera(Component):
|
||||||
|
|
||||||
class ComputerAccessory(Device):
|
class ComputerAccessory(Device):
|
||||||
"""Computer peripherals and similar accessories."""
|
"""Computer peripherals and similar accessories."""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1073,6 +1209,7 @@ class MemoryCardReader(ComputerAccessory):
|
||||||
|
|
||||||
class Networking(NetworkMixin, Device):
|
class Networking(NetworkMixin, Device):
|
||||||
"""Routers, switches, hubs..."""
|
"""Routers, switches, hubs..."""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1118,6 +1255,7 @@ class Microphone(Sound):
|
||||||
|
|
||||||
class Video(Device):
|
class Video(Device):
|
||||||
"""Devices related to video treatment."""
|
"""Devices related to video treatment."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1131,6 +1269,7 @@ class Videoconference(Video):
|
||||||
|
|
||||||
class Cooking(Device):
|
class Cooking(Device):
|
||||||
"""Cooking devices."""
|
"""Cooking devices."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1182,6 +1321,7 @@ class Manufacturer(db.Model):
|
||||||
Ideally users should use the names from this list when submitting
|
Ideally users should use the names from this list when submitting
|
||||||
devices.
|
devices.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = db.Column(CIText(), primary_key=True)
|
name = db.Column(CIText(), primary_key=True)
|
||||||
name.comment = """The normalized name of the manufacturer."""
|
name.comment = """The normalized name of the manufacturer."""
|
||||||
url = db.Column(URL(), unique=True)
|
url = db.Column(URL(), unique=True)
|
||||||
|
@ -1192,7 +1332,7 @@ class Manufacturer(db.Model):
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
|
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
|
||||||
db.Index('name_index', text('name gin_trgm_ops'), postgresql_using='gin'),
|
db.Index('name_index', text('name gin_trgm_ops'), postgresql_using='gin'),
|
||||||
{'schema': 'common'}
|
{'schema': 'common'},
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1202,10 +1342,7 @@ class Manufacturer(db.Model):
|
||||||
#: Dialect used to write the CSV
|
#: Dialect used to write the CSV
|
||||||
|
|
||||||
with pathlib.Path(__file__).parent.joinpath('manufacturers.csv').open() as f:
|
with pathlib.Path(__file__).parent.joinpath('manufacturers.csv').open() as f:
|
||||||
cursor.copy_expert(
|
cursor.copy_expert('COPY common.manufacturer FROM STDIN (FORMAT csv)', f)
|
||||||
'COPY common.manufacturer FROM STDIN (FORMAT csv)',
|
|
||||||
f
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
listener_reset_field_updated_in_actual_time(Device)
|
listener_reset_field_updated_in_actual_time(Device)
|
||||||
|
@ -1217,6 +1354,7 @@ def create_code_tag(mapper, connection, device):
|
||||||
this tag is the same of devicehub_id.
|
this tag is the same of devicehub_id.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
|
||||||
if isinstance(device, Computer):
|
if isinstance(device, Computer):
|
||||||
tag = Tag(device_id=device.id, id=device.devicehub_id)
|
tag = Tag(device_id=device.id, id=device.devicehub_id)
|
||||||
db.session.add(tag)
|
db.session.add(tag)
|
||||||
|
|
|
@ -336,6 +336,9 @@
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
{% if dev.get_type_logo() %}
|
||||||
|
<i class="{{ dev.get_type_logo() }}" title="{{ dev.type }}"></i>
|
||||||
|
{% endif %}
|
||||||
<a href="{{ url_for('inventory.device_details', id=dev.devicehub_id)}}">
|
<a href="{{ url_for('inventory.device_details', id=dev.devicehub_id)}}">
|
||||||
{{ dev.verbose_name }}
|
{{ dev.verbose_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
Reference in New Issue