Merge remote-tracking branch 'origin/master' into reports

This commit is contained in:
JNadeu 2018-10-17 12:31:31 +02:00
commit c4129a2a1a
22 changed files with 616 additions and 107 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)
if s.get('uuid', None) == 'ec23c11b-80b6-42cd-ac5c-73ba7acddbc4':
sample_pc = s['device']['id']
else:
pcs.add(s['device']['id']) 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

View File

@ -187,7 +187,9 @@ class Device(Thing):
if 't' in format_spec: if 't' in format_spec:
v += '{0.t} {0.model}'.format(self) v += '{0.t} {0.model}'.format(self)
if 's' in format_spec: if 's' in format_spec:
v += '({0.manufacturer}) S/N {0.serial_number}'.format(self) v += '({0.manufacturer})'.format(self)
if self.serial_number:
v += ' S/N ' + self.serial_number.upper()
return v return v
@ -272,7 +274,9 @@ class Computer(Device):
if 't' in format_spec: if 't' in format_spec:
v += '{0.chassis} {0.model}'.format(self) v += '{0.chassis} {0.model}'.format(self)
elif 's' in format_spec: elif 's' in format_spec:
v += '({0.manufacturer}) S/N {0.serial_number}'.format(self) v += '({0.manufacturer})'.format(self)
if self.serial_number:
v += ' S/N ' + self.serial_number.upper()
return v return v

View File

@ -1,5 +1,7 @@
from enum import Enum from enum import Enum
import inflection
from ereuse_devicehub.resources.event import models as e from ereuse_devicehub.resources.event import models as e
@ -9,6 +11,9 @@ class State(Enum):
"""Events participating in this state.""" """Events participating in this state."""
return (s.value for s in cls) return (s.value for s in cls)
def __str__(self):
return inflection.humanize(inflection.underscore(self.name))
class Trading(State): class Trading(State):
Reserved = e.Reserve Reserved = e.Reserve

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 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

@ -10,17 +10,69 @@
crossorigin="anonymous"> crossorigin="anonymous">
<title>Devicehub | {{ device.__format__('t') }}</title> <title>Devicehub | {{ device.__format__('t') }}</title>
</head> </head>
<body class="container"> <body>
<nav class="navbar navbar-default" style="background-color: gainsboro; margin: 0 !important">
<div class="container-fluid">
<a href="https://www.ereuse.org/" target="_blank">
<img alt="Brand"
class="center-block"
style="height: 4em; padding-bottom: 0.1em"
src="{{ url_for('Device.static', filename='ereuse-logo.svg') }}">
</a>
</div>
</nav>
<div class="jumbotron">
<img class="center-block"
style="height: 13em; padding-bottom: 0.1em"
src="{{ url_for('Device.static', filename='magrama.svg') }}">
</div>
<div 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>
</div>
<div class="container">
<h2 class='text-center'>
This is your {{ device.t }}.
</h2>
<p class="text-center">
{% if device.trading %}
{{ device.trading }}
{% endif %}
{% if device.trading and device.physical %}
and
{% endif %}
{% if device.physical %}
{{ device.physical }}
{% endif %}
</p>
<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>
<p>
If your device comes with the following tag
<img class="img-responsive center-block" style="width: 12em;"
src="{{ url_for('Device.static', filename='photochromic-alone.svg') }}">
it means it has been refurbished by an eReuse.org
certified organization.
</p>
<p>
The tag is special illuminate it with the torch of
your phone for 6 seconds and it will react like in
the following image:
<img class="img-responsive center-block" style="width: 30em;"
src="{{ url_for('Device.static', filename='photochromic-tag-web.svg') }}">
This is proof that this device is genuine.
</p>
</article>
<article class="col-md-6">
<h3>These are the specifications</h3>
<div class="table-responsive">
<table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
@ -28,69 +80,142 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% if device.processor_model %}
<tr> <tr>
<td> <td>
<details> CPU {{ device.processor_model }}
<summary>CPU {{ device.processor_model }}</summary> </td>
{{ macros.component_type(device.components, 'Processor') }} <td>
</details> {% if device.rate %}
{{ device.rate.processor_range }}
({{ device.rate.processor }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.ram_size %}
<tr>
<td>
RAM {{ device.ram_size // 1000 }} GB
{{ macros.component_type(device.components, 'RamModule') }}
</td>
<td>
{% if device.rate %}
{{ device.rate.ram_range }}
({{ device.rate.ram }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.data_storage_size %}
<tr>
<td>
Data Storage {{ device.data_storage_size // 1000 }} GB
{{ macros.component_type(device.components, 'SolidStateDrive') }}
{{ macros.component_type(device.components, 'HardDrive') }}
</td>
<td>
{% if device.rate %}
{{ device.rate.data_storage_range }}
({{ device.rate.data_storage }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.graphic_card_model %}
<tr>
<td>
Graphics {{ device.graphic_card_model }}
{{ macros.component_type(device.components, 'GraphicCard') }}
</td> </td>
<td></td> <td></td>
</tr> </tr>
{% endif %}
{% if device.network_speeds %}
<tr> <tr>
<td> <td>
<details> Network
<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] %} {% if device.network_speeds[0] %}
Ethernet of {{ device.network_speeds[0] }} Mbps Ethernet
{% if device.network_speeds[0] != None %}
max. {{ device.network_speeds[0] }} Mbps
{% endif %}
{% endif %} {% endif %}
{% if device.network_speeds[0] and device.network_speeds[1] %} {% if device.network_speeds[0] and device.network_speeds[1] %}
+ +
{% endif %} {% endif %}
{% if device.network_speeds[1] %} {% if device.network_speeds[1] %}
WiFi of {{ device.network_speeds[1] }} Mbps WiFi
{% if device.network_speeds[1] != None %}
max. {{ device.network_speeds[1] }} Mbps
{% endif %}
{% endif %} {% endif %}
</summary>
{{ macros.component_type(device.components, 'NetworkAdapter') }} {{ macros.component_type(device.components, 'NetworkAdapter') }}
</details>
</td> </td>
<td></td> <td></td>
</tr> </tr>
{% endif %}
{% if device.rate %}
<tr class="active">
<td class="text-right">
Total rate
</td>
<td>
{{ device.rate.rating_range }}
({{ device.rate.rating }})
</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> </tbody>
</table> </table>
</div>
<h3>This is the traceability log of your device</h3>
<div class="text-right">
<small>Latest one.</small>
</div>
<ol>
{% for event in device.events|reverse %}
<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>Oldest one.</small>
</div>
</article> </article>
<aside class="col-md-6"> </div>
<h2>Check the validity of the device</h2>
<p>Use the flashlight to scan...</p>
</aside>
</div> </div>
</body> </body>

View File

@ -1,10 +1,10 @@
{% macro component_type(components, type) %} {% macro component_type(components, type) %}
<ul class="list-unstyled"> <ul>
{% 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):
@ -222,7 +245,7 @@ class ComputerChassis(Enum):
Virtual = 'Non-physical device' Virtual = 'Non-physical device'
def __format__(self, format_spec): def __format__(self, format_spec):
return self.value.lower() return inflection.humanize(inflection.underscore(self.value))
class ReceiverRole(Enum): class ReceiverRole(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,6 +388,7 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
@property @property
def rating_range(self) -> RatingRange: def rating_range(self) -> RatingRange:
if self.rating:
return RatingRange.from_score(self.rating) return RatingRange.from_score(self.rating)
@declared_attr @declared_attr
@ -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

@ -1,6 +1,6 @@
from flask import Response, current_app as app, request from flask import Response, current_app as app, redirect, request
from teal.marshmallow import ValidationError from teal.marshmallow import ValidationError
from teal.resource import View from teal.resource import View, url_for_resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
@ -27,6 +27,8 @@ class TagDeviceView(View):
tag = Tag.from_an_id(id).one() # type: Tag tag = Tag.from_an_id(id).one() # type: Tag
if not tag.device: if not tag.device:
raise TagNotLinked(tag.id) raise TagNotLinked(tag.id)
if not request.authorization:
return redirect(location=url_for_resource(Device, tag.device.id))
return app.resources[Device.t].schema.jsonify(tag.device) return app.resources[Device.t].schema.jsonify(tag.device)
# noinspection PyMethodOverriding # noinspection PyMethodOverriding
@ -55,6 +57,8 @@ def get_device_from_tag(id: str):
""" """
# todo this could be more efficient by Device.query... join with tag # todo this could be more efficient by Device.query... join with tag
device = Tag.query.filter_by(id=id).one().device device = Tag.query.filter_by(id=id).one().device
if not request.authorization:
return redirect(location=url_for_resource(Device, device.id))
if device is None: if device is None:
raise TagNotLinked(id) raise TagNotLinked(id)
return app.resources[Device.t].schema.jsonify(device) return app.resources[Device.t].schema.jsonify(device)

View File

@ -25,7 +25,7 @@ requests==2.19.1
requests-mock==1.5.2 requests-mock==1.5.2
SQLAlchemy==1.2.11 SQLAlchemy==1.2.11
SQLAlchemy-Utils==0.33.3 SQLAlchemy-Utils==0.33.3
teal==0.2.0a24 teal==0.2.0a25
webargs==4.0.0 webargs==4.0.0
Werkzeug==0.14.1 Werkzeug==0.14.1
sqlalchemy-citext==1.3.post0 sqlalchemy-citext==1.3.post0

View File

@ -34,7 +34,7 @@ setup(
long_description=long_description, long_description=long_description,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
install_requires=[ install_requires=[
'teal>=0.2.0a24', # teal always first 'teal>=0.2.0a25', # teal always first
'click', 'click',
'click-spinner', 'click-spinner',
'ereuse-utils[Naming]>=0.4b9', 'ereuse-utils[Naming]>=0.4b9',

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

@ -506,8 +506,8 @@ def test_device_properties_format(app: Devicehub, user: UserClient):
with app.app_context(): with app.app_context():
pc = Laptop.query.one() # type: Laptop pc = Laptop.query.one() # type: Laptop
assert format(pc) == 'Laptop 1: model 1000h, S/N 94oaaq021116' assert format(pc) == 'Laptop 1: model 1000h, S/N 94oaaq021116'
assert format(pc, 't') == 'netbook 1000h' assert format(pc, 't') == 'Netbook 1000h'
assert format(pc, 's') == '(asustek computer inc.) S/N 94oaaq021116' assert format(pc, 's') == '(asustek computer inc.) S/N 94OAAQ021116'
assert pc.ram_size == 1024 assert pc.ram_size == 1024
assert pc.data_storage_size == 152627 assert pc.data_storage_size == 152627
assert pc.graphic_card_model == 'mobile 945gse express integrated graphics controller' assert pc.graphic_card_model == 'mobile 945gse express integrated graphics controller'
@ -516,18 +516,18 @@ def test_device_properties_format(app: Devicehub, user: UserClient):
assert format(net) == 'NetworkAdapter 2: model ar8121/ar8113/ar8114 ' \ assert format(net) == 'NetworkAdapter 2: model ar8121/ar8113/ar8114 ' \
'gigabit or fast ethernet, S/N 00:24:8c:7f:cf:2d' 'gigabit or fast ethernet, S/N 00:24:8c:7f:cf:2d'
assert format(net, 't') == 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet' assert format(net, 't') == 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet'
assert format(net, 's') == '(qualcomm atheros) S/N 00:24:8c:7f:cf:2d 100 Mbps' assert format(net, 's') == '(qualcomm atheros) S/N 00:24:8C:7F:CF:2D 100 Mbps'
hdd = next(c for c in pc.components if isinstance(c, DataStorage)) hdd = next(c for c in pc.components if isinstance(c, DataStorage))
assert format(hdd) == 'HardDrive 7: model st9160310as, S/N 5sv4tqa6' assert format(hdd) == 'HardDrive 7: model st9160310as, S/N 5sv4tqa6'
assert format(hdd, 't') == 'HardDrive st9160310as' assert format(hdd, 't') == 'HardDrive st9160310as'
assert format(hdd, 's') == '(seagate) S/N 5sv4tqa6 152 GB' assert format(hdd, 's') == '(seagate) S/N 5SV4TQA6 152 GB'
def test_device_public(user: UserClient, client: Client): def test_device_public(user: UserClient, client: Client):
s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot) s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
html, _ = client.get(res=Device, item=s['device']['id'], accept=ANY) html, _ = client.get(res=Device, item=s['device']['id'], accept=ANY)
assert 'intel atom cpu n270 @ 1.60ghz' in html assert 'intel atom cpu n270 @ 1.60ghz' in html
assert 'S/N 00:24:8c:7f:cf:2d 100 Mbps' in html assert 'S/N 00:24:8C:7F:CF:2D 100 Mbps' in html
@pytest.mark.xfail(reason='Functionality not yet developed.') @pytest.mark.xfail(reason='Functionality not yet developed.')

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