Merge remote-tracking branch 'origin/master' into reports
This commit is contained in:
commit
c4129a2a1a
|
@ -35,7 +35,9 @@ call the new file ``app.py``.
|
|||
Create a PostgreSQL database called *devicehub* by running
|
||||
[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`.
|
||||
|
||||
Create the tables in the database by executing in the same directory
|
||||
|
|
|
@ -58,16 +58,28 @@ class Dummy:
|
|||
'-o', org_id
|
||||
],
|
||||
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())
|
||||
print('done.')
|
||||
sample_pc = None # We treat this one as a special sample for demonstrations
|
||||
pcs = set() # type: Set[int]
|
||||
with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar:
|
||||
for path in bar:
|
||||
with path.open() as f:
|
||||
snapshot = yaml.load(f)
|
||||
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
|
||||
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)
|
||||
|
@ -105,9 +117,29 @@ class Dummy:
|
|||
assert len(inventory['items'])
|
||||
|
||||
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
|
||||
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.')
|
||||
|
||||
def user_client(self, email: str, password: str):
|
||||
|
|
146
ereuse_devicehub/dummy/files/pc-laudem.snapshot.11.yaml
Normal file
146
ereuse_devicehub/dummy/files/pc-laudem.snapshot.11.yaml
Normal 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"
|
||||
}
|
|
@ -14,7 +14,8 @@ class DeviceDef(Resource):
|
|||
AUTH = False # We manage this at each view
|
||||
|
||||
def __init__(self, app,
|
||||
import_name=__name__, static_folder=None,
|
||||
import_name=__name__,
|
||||
static_folder='static',
|
||||
static_url_path=None,
|
||||
template_folder='templates',
|
||||
url_prefix=None,
|
||||
|
@ -30,6 +31,12 @@ class ComputerDef(DeviceDef):
|
|||
VIEW = None
|
||||
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):
|
||||
VIEW = None
|
||||
|
@ -50,6 +57,12 @@ class MonitorDef(DeviceDef):
|
|||
VIEW = None
|
||||
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):
|
||||
VIEW = None
|
||||
|
@ -65,6 +78,12 @@ class MobileDef(DeviceDef):
|
|||
VIEW = None
|
||||
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):
|
||||
VIEW = None
|
||||
|
@ -85,6 +104,12 @@ class ComponentDef(DeviceDef):
|
|||
VIEW = None
|
||||
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):
|
||||
VIEW = None
|
||||
|
|
|
@ -187,7 +187,9 @@ class Device(Thing):
|
|||
if 't' in format_spec:
|
||||
v += '{0.t} {0.model}'.format(self)
|
||||
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
|
||||
|
||||
|
||||
|
@ -272,7 +274,9 @@ class Computer(Device):
|
|||
if 't' in format_spec:
|
||||
v += '{0.chassis} {0.model}'.format(self)
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from enum import Enum
|
||||
|
||||
import inflection
|
||||
|
||||
from ereuse_devicehub.resources.event import models as e
|
||||
|
||||
|
||||
|
@ -9,6 +11,9 @@ class State(Enum):
|
|||
"""Events participating in this state."""
|
||||
return (s.value for s in cls)
|
||||
|
||||
def __str__(self):
|
||||
return inflection.humanize(inflection.underscore(self.name))
|
||||
|
||||
|
||||
class Trading(State):
|
||||
Reserved = e.Reserve
|
||||
|
|
3
ereuse_devicehub/resources/device/static/ereuse-logo.svg
Normal file
3
ereuse_devicehub/resources/device/static/ereuse-logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.4 KiB |
3
ereuse_devicehub/resources/device/static/magrama.svg
Normal file
3
ereuse_devicehub/resources/device/static/magrama.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 22 KiB |
|
@ -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 |
|
@ -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 |
|
@ -10,87 +10,212 @@
|
|||
crossorigin="anonymous">
|
||||
<title>Devicehub | {{ device.__format__('t') }}</title>
|
||||
</head>
|
||||
<body class="container">
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>{{ device.__format__('t') }}
|
||||
<small>{{ device.__format__('s') }}</small>
|
||||
</h1>
|
||||
<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">
|
||||
<h1>{{ device.__format__('t') }}<br>
|
||||
<small>{{ device.__format__('s') }}</small>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h2 class='text-center'>
|
||||
This is your {{ device.t }}.
|
||||
</h2>
|
||||
|
||||
<div class="row">
|
||||
<article class="col-md-6">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Range</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<details>
|
||||
<summary>CPU – {{ device.processor_model }}</summary>
|
||||
{{ macros.component_type(device.components, 'Processor') }}
|
||||
</details>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<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>
|
||||
<aside class="col-md-6">
|
||||
<h2>Check the validity of the device</h2>
|
||||
<p>Use the flashlight to scan...</p>
|
||||
</aside>
|
||||
<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">
|
||||
<article class="col-md-6">
|
||||
<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>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Range</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if device.processor_model %}
|
||||
<tr>
|
||||
<td>
|
||||
CPU – {{ device.processor_model }}
|
||||
</td>
|
||||
<td>
|
||||
{% 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>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if device.network_speeds %}
|
||||
<tr>
|
||||
<td>
|
||||
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 %}
|
||||
{{ macros.component_type(device.components, 'NetworkAdapter') }}
|
||||
</td>
|
||||
<td></td>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{% macro component_type(components, type) %}
|
||||
<ul class="list-unstyled">
|
||||
<ul>
|
||||
{% for c in components if c.t == type %}
|
||||
<li>
|
||||
<strong>{{ c.__format__('t') }}</strong>
|
||||
{{ c.__format__('t') }}
|
||||
<p>
|
||||
<small>{{ c.__format__('s') }}</small>
|
||||
<small class="text-muted">{{ c.__format__('s') }}</small>
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
|
@ -2,6 +2,8 @@ from distutils.version import StrictVersion
|
|||
from enum import Enum, IntEnum, unique
|
||||
from typing import Union
|
||||
|
||||
import inflection
|
||||
|
||||
|
||||
@unique
|
||||
class SnapshotSoftware(Enum):
|
||||
|
@ -11,6 +13,9 @@ class SnapshotSoftware(Enum):
|
|||
Web = 'Web'
|
||||
DesktopApp = 'DesktopApp'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@unique
|
||||
class RatingSoftware(Enum):
|
||||
|
@ -25,6 +30,9 @@ class RatingSoftware(Enum):
|
|||
"""
|
||||
EMarket = 'EMarket'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
RATE_POSITIVE = 0, 10
|
||||
RATE_NEGATIVE = -3, 5
|
||||
|
@ -55,6 +63,12 @@ class RatingRange(IntEnum):
|
|||
else:
|
||||
return cls.HIGH
|
||||
|
||||
def __str__(self):
|
||||
return inflection.humanize(self.name)
|
||||
|
||||
def __format__(self, format_spec):
|
||||
return str(self)
|
||||
|
||||
|
||||
@unique
|
||||
class PriceSoftware(Enum):
|
||||
|
@ -81,6 +95,9 @@ class AppearanceRange(Enum):
|
|||
D = 'D. Is acceptable (visual damage in visible parts, not screens)'
|
||||
E = 'E. Is unacceptable (considerable visual damage that can affect usage)'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@unique
|
||||
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'
|
||||
D = 'D. Multiple buttons don\'t work; screen has visual damage resulting in uncomfortable usage'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@unique
|
||||
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)'
|
||||
E = 'E. The device could not be booted through the network.'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
@unique
|
||||
class Orientation(Enum):
|
||||
|
@ -222,7 +245,7 @@ class ComputerChassis(Enum):
|
|||
Virtual = 'Non-physical device'
|
||||
|
||||
def __format__(self, format_spec):
|
||||
return self.value.lower()
|
||||
return inflection.humanize(inflection.underscore(self.value))
|
||||
|
||||
|
||||
class ReceiverRole(Enum):
|
||||
|
|
|
@ -5,6 +5,7 @@ from distutils.version import StrictVersion
|
|||
from typing import Set, Union
|
||||
from uuid import uuid4
|
||||
|
||||
import inflection
|
||||
from boltons import urlutils
|
||||
from citext import CIText
|
||||
from flask import current_app as app, g
|
||||
|
@ -196,6 +197,17 @@ class Event(Thing):
|
|||
raise ValidationError('The event cannot start after it finished.')
|
||||
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):
|
||||
device_id = Column(BigInteger, ForeignKey(Component.id), primary_key=True)
|
||||
|
@ -284,6 +296,11 @@ class EraseBasic(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
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):
|
||||
pass
|
||||
|
@ -340,6 +357,9 @@ class Snapshot(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
"""
|
||||
expected_events = Column(ArrayOfEnum(DBEnum(SnapshotExpectedEvents)))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{}. {} version {}.'.format(self._err_str, self.software, self.version)
|
||||
|
||||
|
||||
class Install(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
elapsed = Column(Interval, nullable=False)
|
||||
|
@ -368,7 +388,8 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
|
||||
@property
|
||||
def rating_range(self) -> RatingRange:
|
||||
return RatingRange.from_score(self.rating)
|
||||
if self.rating:
|
||||
return RatingRange.from_score(self.rating)
|
||||
|
||||
@declared_attr
|
||||
def __mapper_args__(cls):
|
||||
|
@ -384,6 +405,9 @@ class Rate(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
args[POLYMORPHIC_ON] = cls.type
|
||||
return args
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{} ({} v.{})'.format(self.rating_range, self.software, self.version)
|
||||
|
||||
|
||||
class IndividualRate(Rate):
|
||||
pass
|
||||
|
@ -400,6 +424,12 @@ class ManualRate(IndividualRate):
|
|||
functionality_range = Column(DBEnum(FunctionalityRange))
|
||||
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):
|
||||
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
|
||||
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):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||
|
@ -474,6 +524,22 @@ class AggregateRate(Rate):
|
|||
def graphic_card(self):
|
||||
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
|
||||
def bios(self):
|
||||
return self.workbench.bios
|
||||
|
@ -530,10 +596,11 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
uselist=False),
|
||||
primaryjoin=AggregateRate.id == rating_id)
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
if 'price' in kwargs:
|
||||
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
|
||||
def to_price(cls, value: Union[Decimal, float], rounding=ROUND) -> Decimal:
|
||||
|
@ -543,6 +610,23 @@ class Price(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
# equation from marshmallow.fields.Decimal
|
||||
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):
|
||||
"""A Price class that auto-computes its amount by"""
|
||||
|
@ -661,11 +745,14 @@ class TestDataStorage(Test):
|
|||
offline_uncorrectable = 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
|
||||
|
||||
|
||||
class StressTest(Test):
|
||||
pass
|
||||
|
||||
@validates('elapsed')
|
||||
def is_minute_and_bigger_than_1_minute(self, _, value: timedelta):
|
||||
|
@ -674,6 +761,9 @@ class StressTest(Test):
|
|||
assert seconds >= 60
|
||||
return value
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{}. Computing for {}'.format(self._err_str, self.elapsed)
|
||||
|
||||
|
||||
class Benchmark(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
||||
elapsed = Column(Interval)
|
||||
|
@ -698,11 +788,17 @@ class BenchmarkDataStorage(Benchmark):
|
|||
read_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):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
|
||||
rate = Column(SmallInteger, nullable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{} points'.format(self.rate)
|
||||
|
||||
|
||||
class BenchmarkProcessor(BenchmarkWithRate):
|
||||
pass
|
||||
|
|
|
@ -65,6 +65,10 @@ class Event(Thing):
|
|||
def url(self) -> urlutils.URL:
|
||||
pass
|
||||
|
||||
@property
|
||||
def _err_str(self):
|
||||
pass
|
||||
|
||||
|
||||
class EventWithOneDevice(Event):
|
||||
|
||||
|
@ -256,6 +260,22 @@ class WorkbenchRate(ManualRate):
|
|||
def ratings(self) -> Set[Rate]:
|
||||
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):
|
||||
SCALE = ...
|
||||
|
|
|
@ -12,8 +12,8 @@ from ereuse_devicehub.marshmallow import NestedOn
|
|||
from ereuse_devicehub.resources.agent.schemas import Agent
|
||||
from ereuse_devicehub.resources.device.schemas import Component, Computer, Device
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
||||
PriceSoftware, RATE_POSITIVE, RatingSoftware, ReceiverRole, SnapshotExpectedEvents, \
|
||||
SnapshotSoftware, TestDataStorageLength
|
||||
PriceSoftware, RATE_POSITIVE, RatingRange, RatingSoftware, ReceiverRole, \
|
||||
SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
|
||||
from ereuse_devicehub.resources.event import models as m
|
||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
|
@ -107,6 +107,7 @@ class Rate(EventWithOneDevice):
|
|||
description=m.Rate.version.comment)
|
||||
appearance = 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):
|
||||
|
@ -134,6 +135,10 @@ class WorkbenchRate(ManualRate):
|
|||
bios_range = EnumField(Bios,
|
||||
description=m.WorkbenchRate.bios_range.comment,
|
||||
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):
|
||||
|
@ -159,6 +164,10 @@ class AggregateRate(Rate):
|
|||
data_key='functionalityRange',
|
||||
description=m.ManualRate.functionality_range.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):
|
||||
|
@ -265,7 +274,7 @@ class Test(EventWithOneDevice):
|
|||
class TestDataStorage(Test):
|
||||
length = EnumField(TestDataStorageLength, 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()
|
||||
reallocated_sector_count = Integer(data_key='reallocatedSectorCount')
|
||||
power_cycle_count = Integer(data_key='powerCycleCount')
|
||||
|
|
|
@ -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.resource import View
|
||||
from teal.resource import View, url_for_resource
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
|
@ -27,6 +27,8 @@ class TagDeviceView(View):
|
|||
tag = Tag.from_an_id(id).one() # type: Tag
|
||||
if not tag.device:
|
||||
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)
|
||||
|
||||
# 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
|
||||
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:
|
||||
raise TagNotLinked(id)
|
||||
return app.resources[Device.t].schema.jsonify(device)
|
||||
|
|
|
@ -25,7 +25,7 @@ requests==2.19.1
|
|||
requests-mock==1.5.2
|
||||
SQLAlchemy==1.2.11
|
||||
SQLAlchemy-Utils==0.33.3
|
||||
teal==0.2.0a24
|
||||
teal==0.2.0a25
|
||||
webargs==4.0.0
|
||||
Werkzeug==0.14.1
|
||||
sqlalchemy-citext==1.3.post0
|
||||
|
|
2
setup.py
2
setup.py
|
@ -34,7 +34,7 @@ setup(
|
|||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
install_requires=[
|
||||
'teal>=0.2.0a24', # teal always first
|
||||
'teal>=0.2.0a25', # teal always first
|
||||
'click',
|
||||
'click-spinner',
|
||||
'ereuse-utils[Naming]>=0.4b9',
|
||||
|
|
|
@ -28,7 +28,8 @@ def test_api_docs(client: Client):
|
|||
'/manufacturers/',
|
||||
'/lots/{id}/children',
|
||||
'/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['components']['securitySchemes']['bearerAuth'] == {
|
||||
|
|
|
@ -506,8 +506,8 @@ def test_device_properties_format(app: Devicehub, user: UserClient):
|
|||
with app.app_context():
|
||||
pc = Laptop.query.one() # type: Laptop
|
||||
assert format(pc) == 'Laptop 1: model 1000h, S/N 94oaaq021116'
|
||||
assert format(pc, 't') == 'netbook 1000h'
|
||||
assert format(pc, 's') == '(asustek computer inc.) S/N 94oaaq021116'
|
||||
assert format(pc, 't') == 'Netbook 1000h'
|
||||
assert format(pc, 's') == '(asustek computer inc.) S/N 94OAAQ021116'
|
||||
assert pc.ram_size == 1024
|
||||
assert pc.data_storage_size == 152627
|
||||
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 ' \
|
||||
'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, '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))
|
||||
assert format(hdd) == 'HardDrive 7: model st9160310as, S/N 5sv4tqa6'
|
||||
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):
|
||||
s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
|
||||
html, _ = client.get(res=Device, item=s['device']['id'], accept=ANY)
|
||||
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.')
|
||||
|
|
|
@ -283,6 +283,11 @@ def test_price_custom():
|
|||
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')
|
||||
def test_ereuse_price():
|
||||
"""Tests the several ways of creating eReuse Price, emulating
|
||||
|
|
Reference in a new issue