Add str() to many events; small bugfixes

This commit is contained in:
Xavier Bustamante Talavera 2018-10-16 08:46:55 +02:00
parent 47f5bf69ae
commit b391950d97
15 changed files with 576 additions and 88 deletions

View File

@ -35,7 +35,9 @@ call the new file ``app.py``.
Create a PostgreSQL database called *devicehub* by running Create a PostgreSQL database called *devicehub* by running
[create-db](examples/create-db.sh): [create-db](examples/create-db.sh):
- In Debian 9: `sudo su - postgres; examples/create-db.sh devicehub` - In a Debian 9 terminal, execute the following two commands:
1. `sudo su - postgres`.
2. `bash examples/create-db.sh devicehub`.
- In MacOS: `examples/create-db.sh devicehub`. - In MacOS: `examples/create-db.sh devicehub`.
Create the tables in the database by executing in the same directory Create the tables in the database by executing in the same directory

View File

@ -58,16 +58,28 @@ class Dummy:
'-o', org_id '-o', org_id
], ],
catch_exceptions=False) catch_exceptions=False)
# create tag for pc-laudem
runner.invoke(args=[
'create-tag', 'tagA',
'-p', 'https://t.devicetag.io',
'-s', 'tagA-secondary'
],
catch_exceptions=False)
files = tuple(Path(__file__).parent.joinpath('files').iterdir()) files = tuple(Path(__file__).parent.joinpath('files').iterdir())
print('done.') print('done.')
sample_pc = None # We treat this one as a special sample for demonstrations
pcs = set() # type: Set[int] pcs = set() # type: Set[int]
with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar: with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar:
for path in bar: for path in bar:
with path.open() as f: with path.open() as f:
snapshot = yaml.load(f) snapshot = yaml.load(f)
s, _ = user.post(res=m.Snapshot, data=snapshot) s, _ = user.post(res=m.Snapshot, data=snapshot)
pcs.add(s['device']['id']) if s.get('uuid', None) == 'ec23c11b-80b6-42cd-ac5c-73ba7acddbc4':
sample_pc = s['device']['id']
else:
pcs.add(s['device']['id'])
assert sample_pc
print('PC sample is', sample_pc)
# Link tags and eTags # Link tags and eTags
for tag, pc in zip((self.TAGS[1], self.TAGS[2], self.ET[0][0], self.ET[1][1]), pcs): for tag, pc in zip((self.TAGS[1], self.TAGS[2], self.ET[0][0], self.ET[1][1]), pcs):
user.put({}, res=Tag, item='{}/device/{}'.format(tag, pc), status=204) user.put({}, res=Tag, item='{}/device/{}'.format(tag, pc), status=204)
@ -105,9 +117,29 @@ class Dummy:
assert len(inventory['items']) assert len(inventory['items'])
i, _ = user.get(res=Device, query=[('search', 'intel')]) i, _ = user.get(res=Device, query=[('search', 'intel')])
assert len(i['items']) == 10
i, _ = user.get(res=Device, query=[('search', 'pc')])
assert len(i['items']) == 11 assert len(i['items']) == 11
i, _ = user.get(res=Device, query=[('search', 'pc')])
assert len(i['items']) == 12
# Let's create a set of events for the pc device
# Make device Ready
user.post({'type': m.ToPrepare.t, 'devices': [sample_pc]}, res=m.Event)
user.post({'type': m.Prepare.t, 'devices': [sample_pc]}, res=m.Event)
user.post({'type': m.ReadyToUse.t, 'devices': [sample_pc]}, res=m.Event)
user.post({'type': m.Price.t, 'device': sample_pc, 'currency': 'EUR', 'price': 85},
res=m.Event)
# todo test reserve
user.post( # Sell device
{
'type': m.Sell.t,
'to': user.user['individuals'][0]['id'],
'devices': [sample_pc]
},
res=m.Event)
# todo Receive
# For netbook: to preapre -> torepair -> to dispose -> disposed
print('⭐ Done.') print('⭐ Done.')
def user_client(self, email: str, password: str): def user_client(self, email: str, password: str):

View File

@ -0,0 +1,146 @@
{
"closed": true,
"components": [
{
"events": [],
"manufacturer": "Intel Corporation",
"model": "82567LM-3 Gigabit Network Connection",
"serialNumber": "00:23:7d:49:5e:31",
"speed": 1000,
"type": "NetworkAdapter",
"wireless": false
},
{
"events": [],
"manufacturer": "Intel Corporation",
"model": "82801JD/DO HD Audio Controller",
"serialNumber": null,
"type": "SoundCard"
},
{
"events": [],
"format": "DIMM",
"interface": "DDR2",
"manufacturer": null,
"model": "HYMP125U64CP8-S6",
"serialNumber": null,
"size": 2048,
"speed": 800.0,
"type": "RamModule"
},
{
"address": 64,
"cores": 2,
"events": [
{
"elapsed": 0,
"rate": 11970.54,
"type": "BenchmarkProcessor"
},
{
"elapsed": 20,
"rate": 19.6233,
"type": "BenchmarkProcessorSysbench"
}
],
"manufacturer": "Intel Corp.",
"model": "Intel Core2 Duo CPU E8400 @ 3.00GHz",
"serialNumber": null,
"speed": 3.0,
"threads": 2,
"type": "Processor"
},
{
"events": [
{
"elapsed": 16,
"readSpeed": 76.8,
"type": "BenchmarkDataStorage",
"writeSpeed": 21.1
},
{
"assessment": true,
"currentPendingSectorCount": 0,
"elapsed": 134,
"error": false,
"length": "Short",
"lifetime": 19549,
"offlineUncorrectable": 0,
"powerCycleCount": 3354,
"reallocatedSectorCount": 33,
"reportedUncorrectableErrors": 0,
"status": "Completed without error",
"type": "TestDataStorage"
}
],
"interface": "ATA",
"manufacturer": "Seagate",
"model": "ST3160815AS",
"serialNumber": "6RX7AWEZ",
"size": 152627,
"type": "HardDrive"
},
{
"events": [],
"manufacturer": "Intel Corporation",
"memory": 256.0,
"model": "4 Series Chipset Integrated Graphics Controller",
"serialNumber": null,
"type": "GraphicCard"
},
{
"events": [],
"firewire": 0,
"manufacturer": "Hewlett-Packard",
"model": "3031h",
"pcmcia": 0,
"serial": 0,
"serialNumber": "CZC901381R",
"slots": 0,
"type": "Motherboard",
"usb": 8
}
],
"device": {
"chassis": "Tower",
"events": [
{
"elapsed": 60,
"error": false,
"type": "StressTest"
},
{
"elapsed": 6,
"rate": 5.7674,
"type": "BenchmarkRamSysbench"
},
{
"appearanceRange": "A",
"biosRange": "A",
"functionalityRange": "A",
"type": "WorkbenchRate"
}
],
"manufacturer": "Hewlett-Packard",
"model": "HP Compaq dc7900 Small Form Factor",
"serialNumber": "CZC901381R",
"tags": [
{
"id": "tagA-secondary",
"type": "Tag"
}
],
"type": "Desktop"
},
"elapsed": 238,
"endTime": "2018-10-15T13:59:37.431309+00:00",
"expectedEvents": [
"Benchmark",
"TestDataStorage",
"StressTest"
],
"software": "Workbench",
"type": "Snapshot",
"uuid": "ec23c11b-80b6-42cd-ac5c-73ba7acddbc4",
"version": "11.0a6"
}

View File

@ -14,7 +14,8 @@ class DeviceDef(Resource):
AUTH = False # We manage this at each view AUTH = False # We manage this at each view
def __init__(self, app, def __init__(self, app,
import_name=__name__, static_folder=None, import_name=__name__,
static_folder='static',
static_url_path=None, static_url_path=None,
template_folder='templates', template_folder='templates',
url_prefix=None, url_prefix=None,
@ -30,6 +31,12 @@ class ComputerDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Computer SCHEMA = schemas.Computer
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class DesktopDef(ComputerDef): class DesktopDef(ComputerDef):
VIEW = None VIEW = None
@ -50,6 +57,12 @@ class MonitorDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Monitor SCHEMA = schemas.Monitor
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class ComputerMonitorDef(MonitorDef): class ComputerMonitorDef(MonitorDef):
VIEW = None VIEW = None
@ -65,6 +78,12 @@ class MobileDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Mobile SCHEMA = schemas.Mobile
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class SmartphoneDef(MobileDef): class SmartphoneDef(MobileDef):
VIEW = None VIEW = None
@ -85,6 +104,12 @@ class ComponentDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Component SCHEMA = schemas.Component
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
class GraphicCardDef(ComponentDef): class GraphicCardDef(ComponentDef):
VIEW = None VIEW = None

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1,3 @@
<?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>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,3 @@
<?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>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -13,85 +13,205 @@
<body class="container"> <body class="container">
<div class="page-header"> <div class="page-header">
<h1>{{ device.__format__('t') }} <h1>{{ device.__format__('t') }}<br>
<small>{{ device.__format__('s') }}</small> <small>{{ device.__format__('s') }}</small>
</h1> </h1>
</div> </div>
<h2 class='text-center'>
This is your {{ device.t }}.
</h2>
<div class="row"> <div class="row">
<article class="col-md-6"> <article class="col-md-6">
<table class="table"> <h3>You can verify the originality of your device.</h3>
<thead> <p>
<tr> If your device comes with the following tag
<th></th> </p>
<th>Range</th> <img class="img-responsive center-block" style="width: 12em;"
</tr> src="{{ url_for('Device.static', filename='photochromic-alone.svg') }}">
</thead> <p>
<tbody> It means it has been refurbished by an eReuse.org
<tr> certificated organization.
<td> </p>
<details> <p>
<summary>CPU {{ device.processor_model }}</summary> The tag is special illuminate it with the torch of
{{ macros.component_type(device.components, 'Processor') }} your phone for 6 seconds and it will react like in
</details> the following image:
</td> </p>
<td></td> <img class="img-responsive center-block" style="width: 30em;"
</tr> src="{{ url_for('Device.static', filename='photochromic-tag-web.svg') }}">
<tr>
<td>
<details>
<summary>RAM {{ device.ram_size // 1000 }} GB</summary>
{{ macros.component_type(device.components, 'RamModule') }}
</details>
</td>
<td>//range//</td>
</tr>
<tr>
<td>
<details>
<summary>Data Storage {{ device.data_storage_size // 1000 }} GB</summary>
{{ macros.component_type(device.components, 'SolidStateDrive') }}
{{ macros.component_type(device.components, 'HardDrive') }}
</details>
</td>
<td>//range//</td>
</tr>
<tr>
<td>
<details>
<summary>Graphics {{ device.graphic_card_model }}</summary>
{{ macros.component_type(device.components, 'GraphicCard') }}
</details>
</td>
<td>//range//</td>
</tr>
<tr>
<td>
<details>
<summary>Network
{% if device.network_speeds[0] %}
Ethernet of {{ device.network_speeds[0] }} Mbps
{% endif %}
{% if device.network_speeds[0] and device.network_speeds[1] %}
+
{% endif %}
{% if device.network_speeds[1] %}
WiFi of {{ device.network_speeds[1] }} Mbps
{% endif %}
</summary>
{{ macros.component_type(device.components, 'NetworkAdapter') }}
</details>
</td>
<td></td>
</tr>
</tbody>
</table>
</article> </article>
<aside class="col-md-6"> <article class="col-md-6">
<h2>Check the validity of the device</h2> <h3>These are the specifications</h3>
<p>Use the flashlight to scan...</p> <ul class="list-unstyled">
</aside> <li>
</div> <ul class="list-inline">
{% if device.trading %}
<li>
{{ device.trading.name }}
</li>
{% endif %}
{% if device.physical %}
<li>
{{ device.physical.name }}
</li>
{% endif %}
</ul>
</li>
{% if device.physical_possessor %}
<li>
Physical possessor:
{{ device.physical_possessor.name }},
{{ device.physical_possessor.country.value }}
</li>
{% endif %}
</ul>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th></th>
<th>Range</th>
</tr>
</thead>
<tbody>
{% if device.processor_model %}
<tr>
<td>
<details>
<summary>CPU {{ device.processor_model }}</summary>
{{ macros.component_type(device.components, 'Processor') }}
</details>
</td>
<td>{{ device.rate.processor_range if device.rate }}</td>
</tr>
{% endif %}
{% if device.ram_size %}
<tr>
<td>
<details>
<summary>RAM {{ device.ram_size // 1000 }} GB</summary>
{{ macros.component_type(device.components, 'RamModule') }}
</details>
</td>
<td>{{ device.rate.ram_range if device.rate }}</td>
</tr>
{% endif %}
{% if device.data_storage_size %}
<tr>
<td>
<details>
<summary>Data Storage {{ device.data_storage_size // 1000 }} GB
</summary>
{{ macros.component_type(device.components, 'SolidStateDrive') }}
{{ macros.component_type(device.components, 'HardDrive') }}
</details>
</td>
<td>{{ device.rate.data_storage_range if device.rate }}</td>
</tr>
{% endif %}
{% if device.graphic_card_model %}
<tr>
<td>
<details>
<summary>Graphics {{ device.graphic_card_model }}</summary>
{{ macros.component_type(device.components, 'GraphicCard') }}
</details>
</td>
<td></td>
</tr>
{% endif %}
{% if device.network_speeds %}
<tr>
<td>
<details>
<summary>Network
{% if device.network_speeds[0] %}
Ethernet
{% if device.network_speeds[0] != None %}
max. {{ device.network_speeds[0] }} Mbps
{% endif %}
{% endif %}
{% if device.network_speeds[0] and device.network_speeds[1] %}
+
{% endif %}
{% if device.network_speeds[1] %}
WiFi
{% if device.network_speeds[1] != None %}
max. {{ device.network_speeds[1] }} Mbps
{% endif %}
{% endif %}
</summary>
{{ macros.component_type(device.components, 'NetworkAdapter') }}
</details>
</td>
<td></td>
</tr>
{% endif %}
{% if device.rate %}
<tr class="active">
<td class="text-right">
Total rate
</td>
<td>
{{ device.rate.rating_range }}
</td>
</tr>
{% endif %}
{% if device.rate and device.rate.price %}
<tr class="active">
<td class="text-right">
Algorithm price
</td>
<td>
{{ device.rate.price }}
</td>
</tr>
{% endif %}
{% if device.price %}
<tr class="active">
<td class="text-right">
Actual price
</td>
<td>
{{ device.price }}
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<h3>This is the traceability log of your device</h3>
<div class="text-right">
<small>Oldest one.</small>
</div>
<ol>
{% for event in device.events %}
<li>
<strong>
{{ event.type }}
</strong>
{{ event }}
<br>
<div class="text-muted">
<small>
{{ event._date_str }}
</small>
</div>
</li>
{% endfor %}
</ol>
<div class="text-right">
<small>Latest one.</small>
</div>
</article>
</div>
<img class="img-responsive center-block" style="max-width: 30em;"
src="{{ url_for('Device.static', filename='ereuse-logo.svg') }}">
</body> </body>
</html> </html>

View File

@ -2,9 +2,9 @@
<ul class="list-unstyled"> <ul class="list-unstyled">
{% for c in components if c.t == type %} {% for c in components if c.t == type %}
<li> <li>
<strong>{{ c.__format__('t') }}</strong> {{ c.__format__('t') }}
<p> <p>
<small>{{ c.__format__('s') }}</small> <small class="text-muted">{{ c.__format__('s') }}</small>
</p> </p>
</li> </li>
{% endfor %} {% endfor %}

View File

@ -2,6 +2,8 @@ from distutils.version import StrictVersion
from enum import Enum, IntEnum, unique from enum import Enum, IntEnum, unique
from typing import Union from typing import Union
import inflection
@unique @unique
class SnapshotSoftware(Enum): class SnapshotSoftware(Enum):
@ -11,6 +13,9 @@ class SnapshotSoftware(Enum):
Web = 'Web' Web = 'Web'
DesktopApp = 'DesktopApp' DesktopApp = 'DesktopApp'
def __str__(self):
return self.name
@unique @unique
class RatingSoftware(Enum): class RatingSoftware(Enum):
@ -25,6 +30,9 @@ class RatingSoftware(Enum):
""" """
EMarket = 'EMarket' EMarket = 'EMarket'
def __str__(self):
return self.name
RATE_POSITIVE = 0, 10 RATE_POSITIVE = 0, 10
RATE_NEGATIVE = -3, 5 RATE_NEGATIVE = -3, 5
@ -55,6 +63,12 @@ class RatingRange(IntEnum):
else: else:
return cls.HIGH return cls.HIGH
def __str__(self):
return inflection.humanize(self.name)
def __format__(self, format_spec):
return str(self)
@unique @unique
class PriceSoftware(Enum): class PriceSoftware(Enum):
@ -81,6 +95,9 @@ class AppearanceRange(Enum):
D = 'D. Is acceptable (visual damage in visible parts, not screens)' D = 'D. Is acceptable (visual damage in visible parts, not screens)'
E = 'E. Is unacceptable (considerable visual damage that can affect usage)' E = 'E. Is unacceptable (considerable visual damage that can affect usage)'
def __str__(self):
return self.name
@unique @unique
class FunctionalityRange(Enum): class FunctionalityRange(Enum):
@ -91,6 +108,9 @@ class FunctionalityRange(Enum):
C = 'C. A non-important button (or similar) doesn\'t work; screen has multiple scratches in edges' C = 'C. A non-important button (or similar) doesn\'t work; screen has multiple scratches in edges'
D = 'D. Multiple buttons don\'t work; screen has visual damage resulting in uncomfortable usage' D = 'D. Multiple buttons don\'t work; screen has visual damage resulting in uncomfortable usage'
def __str__(self):
return self.name
@unique @unique
class Bios(Enum): class Bios(Enum):
@ -101,6 +121,9 @@ class Bios(Enum):
D = 'D. Like B or C, but you had to unlock the BIOS (i.e. by removing the battery)' D = 'D. Like B or C, but you had to unlock the BIOS (i.e. by removing the battery)'
E = 'E. The device could not be booted through the network.' E = 'E. The device could not be booted through the network.'
def __str__(self):
return self.name
@unique @unique
class Orientation(Enum): class Orientation(Enum):

View File

@ -5,6 +5,7 @@ from distutils.version import StrictVersion
from typing import Set, Union from typing import Set, Union
from uuid import uuid4 from uuid import uuid4
import inflection
from boltons import urlutils from boltons import urlutils
from citext import CIText from citext import CIText
from flask import current_app as app, g from flask import current_app as app, g
@ -196,6 +197,17 @@ class Event(Thing):
raise ValidationError('The event cannot start after it finished.') raise ValidationError('The event cannot start after it finished.')
return start_time return start_time
@property
def _err_str(self):
return '❌ Error.' if self.error else ''
@property
def _date_str(self):
return '{:%c}'.format(self.end_time or self.created)
def __str__(self) -> str:
return '{}'.format(self._err_str)
class EventComponent(db.Model): class EventComponent(db.Model):
device_id = Column(BigInteger, ForeignKey(Component.id), primary_key=True) device_id = Column(BigInteger, ForeignKey(Component.id), primary_key=True)
@ -284,6 +296,11 @@ class EraseBasic(JoinedWithOneDeviceMixin, EventWithOneDevice):
only writing zeros. only writing zeros.
""" """
# todo return erasure properties like num steps, if it is british...
def __str__(self) -> str:
return '{} on {}.'.format(self._err_str, self.end_time)
class EraseSectors(EraseBasic): class EraseSectors(EraseBasic):
pass pass
@ -340,6 +357,9 @@ class Snapshot(JoinedWithOneDeviceMixin, EventWithOneDevice):
""" """
expected_events = Column(ArrayOfEnum(DBEnum(SnapshotExpectedEvents))) expected_events = Column(ArrayOfEnum(DBEnum(SnapshotExpectedEvents)))
def __str__(self) -> str:
return '{}. {} version {}.'.format(self._err_str, self.software, self.version)
class Install(JoinedWithOneDeviceMixin, EventWithOneDevice): class Install(JoinedWithOneDeviceMixin, EventWithOneDevice):
elapsed = Column(Interval, nullable=False) elapsed = Column(Interval, nullable=False)
@ -368,7 +388,8 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
@property @property
def rating_range(self) -> RatingRange: def rating_range(self) -> RatingRange:
return RatingRange.from_score(self.rating) if self.rating:
return RatingRange.from_score(self.rating)
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
@ -384,6 +405,9 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
args[POLYMORPHIC_ON] = cls.type args[POLYMORPHIC_ON] = cls.type
return args return args
def __str__(self) -> str:
return '{} ({} v.{})'.format(self.rating_range, self.software, self.version)
class IndividualRate(Rate): class IndividualRate(Rate):
pass pass
@ -400,6 +424,12 @@ class ManualRate(IndividualRate):
functionality_range = Column(DBEnum(FunctionalityRange)) functionality_range = Column(DBEnum(FunctionalityRange))
functionality_range.comment = FunctionalityRange.__doc__ functionality_range.comment = FunctionalityRange.__doc__
def __str__(self) -> str:
return super().__str__() + '. Appearance {} and functionality {}'.format(
self.appearance_range,
self.functionality_range
)
class WorkbenchRate(ManualRate): class WorkbenchRate(ManualRate):
id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id), primary_key=True) id = Column(UUID(as_uuid=True), ForeignKey(ManualRate.id), primary_key=True)
@ -425,6 +455,26 @@ class WorkbenchRate(ManualRate):
from ereuse_devicehub.resources.event.rate.main import main from ereuse_devicehub.resources.event.rate.main import main
return main(self, **app.config.get_namespace('WORKBENCH_RATE_')) return main(self, **app.config.get_namespace('WORKBENCH_RATE_'))
@property
def data_storage_range(self):
if self.data_storage:
return RatingRange.from_score(self.data_storage)
@property
def ram_range(self):
if self.ram:
return RatingRange.from_score(self.ram)
@property
def processor_range(self):
if self.processor:
return RatingRange.from_score(self.processor)
@property
def graphic_card_range(self):
if self.graphic_card:
return RatingRange.from_score(self.graphic_card)
class AggregateRate(Rate): class AggregateRate(Rate):
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True) id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
@ -474,6 +524,22 @@ class AggregateRate(Rate):
def graphic_card(self): def graphic_card(self):
return self.workbench.graphic_card return self.workbench.graphic_card
@property
def data_storage_range(self):
return self.workbench.data_storage_range
@property
def ram_range(self):
return self.workbench.ram_range
@property
def processor_range(self):
return self.workbench.processor_range
@property
def graphic_card_range(self):
return self.workbench.graphic_card_range
@property @property
def bios(self): def bios(self):
return self.workbench.bios return self.workbench.bios
@ -530,10 +596,11 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
uselist=False), uselist=False),
primaryjoin=AggregateRate.id == rating_id) primaryjoin=AggregateRate.id == rating_id)
def __init__(self, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
if 'price' in kwargs: if 'price' in kwargs:
assert isinstance(kwargs['price'], Decimal), 'Price must be a Decimal' assert isinstance(kwargs['price'], Decimal), 'Price must be a Decimal'
super().__init__(currency=kwargs.pop('currency', app.config['PRICE_CURRENCY']), **kwargs) super().__init__(currency=kwargs.pop('currency', app.config['PRICE_CURRENCY']), *args,
**kwargs)
@classmethod @classmethod
def to_price(cls, value: Union[Decimal, float], rounding=ROUND) -> Decimal: def to_price(cls, value: Union[Decimal, float], rounding=ROUND) -> Decimal:
@ -543,6 +610,23 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
# equation from marshmallow.fields.Decimal # equation from marshmallow.fields.Decimal
return value.quantize(Decimal((0, (1,), -cls.SCALE)), rounding=rounding) return value.quantize(Decimal((0, (1,), -cls.SCALE)), rounding=rounding)
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
#sqlalchemy.ext.declarative.declared_attr>`_
"""
args = {POLYMORPHIC_ID: cls.t}
if cls.t == 'Price':
args[POLYMORPHIC_ON] = cls.type
return args
def __str__(self) -> str:
return '{0:0.2f} {1}'.format(self.price, self.currency)
class EreusePrice(Price): class EreusePrice(Price):
"""A Price class that auto-computes its amount by""" """A Price class that auto-computes its amount by"""
@ -661,11 +745,14 @@ class TestDataStorage(Test):
offline_uncorrectable = Column(SmallInteger) offline_uncorrectable = Column(SmallInteger)
remaining_lifetime_percentage = Column(SmallInteger) remaining_lifetime_percentage = Column(SmallInteger)
def __str__(self) -> str:
return '{}. Lifetime of {:.1f} years'.format(inflection.humanize(self.status),
self.lifetime.days / 365)
# todo remove lifetime / passed_lifetime as I think they are the same # todo remove lifetime / passed_lifetime as I think they are the same
class StressTest(Test): class StressTest(Test):
pass
@validates('elapsed') @validates('elapsed')
def is_minute_and_bigger_than_1_minute(self, _, value: timedelta): def is_minute_and_bigger_than_1_minute(self, _, value: timedelta):
@ -674,6 +761,9 @@ class StressTest(Test):
assert seconds >= 60 assert seconds >= 60
return value return value
def __str__(self) -> str:
return '{}. Computing for {}'.format(self._err_str, self.elapsed)
class Benchmark(JoinedWithOneDeviceMixin, EventWithOneDevice): class Benchmark(JoinedWithOneDeviceMixin, EventWithOneDevice):
elapsed = Column(Interval) elapsed = Column(Interval)
@ -698,11 +788,17 @@ class BenchmarkDataStorage(Benchmark):
read_speed = Column(Float(decimal_return_scale=2), nullable=False) read_speed = Column(Float(decimal_return_scale=2), nullable=False)
write_speed = Column(Float(decimal_return_scale=2), nullable=False) write_speed = Column(Float(decimal_return_scale=2), nullable=False)
def __str__(self) -> str:
return 'Read: {} MB/s, write: {} MB/s'.format(self.read_speed, self.write_speed)
class BenchmarkWithRate(Benchmark): class BenchmarkWithRate(Benchmark):
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True) id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
rate = Column(SmallInteger, nullable=False) rate = Column(SmallInteger, nullable=False)
def __str__(self) -> str:
return '{} points'.format(self.rate)
class BenchmarkProcessor(BenchmarkWithRate): class BenchmarkProcessor(BenchmarkWithRate):
pass pass

View File

@ -65,6 +65,10 @@ class Event(Thing):
def url(self) -> urlutils.URL: def url(self) -> urlutils.URL:
pass pass
@property
def _err_str(self):
pass
class EventWithOneDevice(Event): class EventWithOneDevice(Event):
@ -256,6 +260,22 @@ class WorkbenchRate(ManualRate):
def ratings(self) -> Set[Rate]: def ratings(self) -> Set[Rate]:
pass pass
@property
def data_storage_range(self):
pass
@property
def ram_range(self):
pass
@property
def processor_range(self):
pass
@property
def graphic_card_range(self):
pass
class Price(EventWithOneDevice): class Price(EventWithOneDevice):
SCALE = ... SCALE = ...

View File

@ -12,8 +12,8 @@ from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.agent.schemas import Agent from ereuse_devicehub.resources.agent.schemas import Agent
from ereuse_devicehub.resources.device.schemas import Component, Computer, Device from ereuse_devicehub.resources.device.schemas import Component, Computer, Device
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \ from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
PriceSoftware, RATE_POSITIVE, RatingSoftware, ReceiverRole, SnapshotExpectedEvents, \ PriceSoftware, RATE_POSITIVE, RatingRange, RatingSoftware, ReceiverRole, \
SnapshotSoftware, TestDataStorageLength SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
from ereuse_devicehub.resources.event import models as m from ereuse_devicehub.resources.event import models as m
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.schemas import Thing
@ -107,6 +107,7 @@ class Rate(EventWithOneDevice):
description=m.Rate.version.comment) description=m.Rate.version.comment)
appearance = Integer(validate=Range(-3, 5), dump_only=True) appearance = Integer(validate=Range(-3, 5), dump_only=True)
functionality = Integer(validate=Range(-3, 5), dump_only=True) functionality = Integer(validate=Range(-3, 5), dump_only=True)
rating_range = EnumField(RatingRange, dump_only=True, data_key='ratingRange')
class IndividualRate(Rate): class IndividualRate(Rate):
@ -134,6 +135,10 @@ class WorkbenchRate(ManualRate):
bios_range = EnumField(Bios, bios_range = EnumField(Bios,
description=m.WorkbenchRate.bios_range.comment, description=m.WorkbenchRate.bios_range.comment,
data_key='biosRange') data_key='biosRange')
data_storage_range = EnumField(RatingRange, dump_only=True, data_key='dataStorageRange')
ram_range = EnumField(RatingRange, dump_only=True, data_key='ramRange')
processor_range = EnumField(RatingRange, dump_only=True, data_key='processorRange')
graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange')
class AggregateRate(Rate): class AggregateRate(Rate):
@ -159,6 +164,10 @@ class AggregateRate(Rate):
data_key='functionalityRange', data_key='functionalityRange',
description=m.ManualRate.functionality_range.comment) description=m.ManualRate.functionality_range.comment)
labelling = Boolean(description=m.ManualRate.labelling.comment) labelling = Boolean(description=m.ManualRate.labelling.comment)
data_storage_range = EnumField(RatingRange, dump_only=True, data_key='dataStorageRange')
ram_range = EnumField(RatingRange, dump_only=True, data_key='ramRange')
processor_range = EnumField(RatingRange, dump_only=True, data_key='processorRange')
graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange')
class Price(EventWithOneDevice): class Price(EventWithOneDevice):
@ -265,7 +274,7 @@ class Test(EventWithOneDevice):
class TestDataStorage(Test): class TestDataStorage(Test):
length = EnumField(TestDataStorageLength, required=True) length = EnumField(TestDataStorageLength, required=True)
status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True) status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True)
lifetime = TimeDelta(precision=TimeDelta.DAYS) lifetime = TimeDelta(precision=TimeDelta.HOURS)
assessment = Boolean() assessment = Boolean()
reallocated_sector_count = Integer(data_key='reallocatedSectorCount') reallocated_sector_count = Integer(data_key='reallocatedSectorCount')
power_cycle_count = Integer(data_key='powerCycleCount') power_cycle_count = Integer(data_key='powerCycleCount')

View File

@ -28,7 +28,8 @@ def test_api_docs(client: Client):
'/manufacturers/', '/manufacturers/',
'/lots/{id}/children', '/lots/{id}/children',
'/lots/{id}/devices', '/lots/{id}/devices',
'/tags/{tag_id}/device/{device_id}' '/tags/{tag_id}/device/{device_id}',
'/devices/static/{filename}'
} }
assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'} assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'}
assert docs['components']['securitySchemes']['bearerAuth'] == { assert docs['components']['securitySchemes']['bearerAuth'] == {

View File

@ -283,6 +283,11 @@ def test_price_custom():
assert c['price']['id'] == p['id'] assert c['price']['id'] == p['id']
@pytest.mark.xfail(reson='Develop test')
def test_price_custom_client():
"""As test_price_custom but creating the price through the API."""
@pytest.mark.xfail(reson='Develop test') @pytest.mark.xfail(reson='Develop test')
def test_ereuse_price(): def test_ereuse_price():
"""Tests the several ways of creating eReuse Price, emulating """Tests the several ways of creating eReuse Price, emulating