Add MakeAvailable and Repair; add device.image; add templating file, change layout for project LOT

This commit is contained in:
Xavier Bustamante Talavera 2019-07-07 21:36:09 +02:00
parent f8ec8fc882
commit ce961a1bed
19 changed files with 350 additions and 286 deletions

View file

@ -23,8 +23,8 @@ state Physical {
ToBeRepaired --> Repaired : Repair ToBeRepaired --> Repaired : Repair
Repaired -> Preparing : ToPrepare Repaired -> Preparing : ToPrepare
Preparing --> Prepared : Prepare Preparing --> Prepared : Prepare
Prepared --> ReadyToBeUsed : ReadyToUse Prepared --> Ready : ReadyToUse
ReadyToBeUsed --> InUse : Live Ready --> InUse : Live
InUse -> InUse : Live InUse -> InUse : Live
state DisposeWaste state DisposeWaste
state Recover state Recover

View file

@ -44,5 +44,5 @@ Physical
:cvar Repaired: The device has been repaired. :cvar Repaired: The device has been repaired.
:cvar Preparing: The device is going to be or being prepared. :cvar Preparing: The device is going to be or being prepared.
:cvar Prepared: The device has been prepared. :cvar Prepared: The device has been prepared.
:cvar ReadyToBeUsed: The device is in working conditions. :cvar Ready: The device is in working conditions.
:cvar InUse: The device is being reported to be in active use. :cvar InUse: The device is being reported to be in active use.

View file

@ -18,11 +18,13 @@ from ereuse_devicehub.db import db
from ereuse_devicehub.dummy.dummy import Dummy from ereuse_devicehub.dummy.dummy import Dummy
from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.device.search import DeviceSearch
from ereuse_devicehub.resources.inventory import Inventory, InventoryDef from ereuse_devicehub.resources.inventory import Inventory, InventoryDef
from ereuse_devicehub.templating import Environment
class Devicehub(Teal): class Devicehub(Teal):
test_client_class = Client test_client_class = Client
Dummy = Dummy Dummy = Dummy
jinja_environment = Environment
def __init__(self, def __init__(self,
inventory: str, inventory: str,

View file

@ -104,7 +104,7 @@ class Dummy:
# Perform generic actions # Perform generic actions
for pc, model in zip(pcs, for pc, model in zip(pcs,
{m.ToRepair, m.Repair, m.ToPrepare, m.Available, m.ToPrepare, {m.ToRepair, m.Repair, m.ToPrepare, m.Ready, m.ToPrepare,
m.Prepare}): m.Prepare}):
user.post({'type': model.t, 'devices': [pc]}, res=m.Action) user.post({'type': model.t, 'devices': [pc]}, res=m.Action)
@ -144,7 +144,7 @@ class Dummy:
user.post({'type': m.ToPrepare.t, 'devices': [sample_pc]}, res=m.Action) user.post({'type': m.ToPrepare.t, 'devices': [sample_pc]}, res=m.Action)
user.post({'type': m.Prepare.t, 'devices': [sample_pc]}, res=m.Action) user.post({'type': m.Prepare.t, 'devices': [sample_pc]}, res=m.Action)
user.post({'type': m.Available.t, 'devices': [sample_pc]}, res=m.Action) user.post({'type': m.Ready.t, 'devices': [sample_pc]}, res=m.Action)
user.post({'type': m.Price.t, 'device': sample_pc, 'currency': 'EUR', 'price': 85}, user.post({'type': m.Price.t, 'device': sample_pc, 'currency': 'EUR', 'price': 85},
res=m.Action) res=m.Action)
# todo test reserve # todo test reserve

View file

@ -188,9 +188,9 @@ class RepairDef(ActionDef):
SCHEMA = schemas.Repair SCHEMA = schemas.Repair
class Available(ActionDef): class ReadyDef(ActionDef):
VIEW = None VIEW = None
SCHEMA = schemas.Available SCHEMA = schemas.Ready
class ToPrepareDef(ActionDef): class ToPrepareDef(ActionDef):
@ -233,6 +233,11 @@ class RentDef(ActionDef):
SCHEMA = schemas.Rent SCHEMA = schemas.Rent
class MakeAvailable(ActionDef):
VIEW = None
SCHEMA = schemas.MakeAvailable
class CancelTradeDef(ActionDef): class CancelTradeDef(ActionDef):
VIEW = None VIEW = None
SCHEMA = schemas.CancelTrade SCHEMA = schemas.CancelTrade

View file

@ -1256,7 +1256,7 @@ class Repair(ActionWithMultipleDevices):
""" """
class Available(ActionWithMultipleDevices): class Ready(ActionWithMultipleDevices):
"""The device is ready to be used. """The device is ready to be used.
This involves greater preparation from the ``Prepare`` action, This involves greater preparation from the ``Prepare`` action,
@ -1420,6 +1420,11 @@ class DisposeProduct(Trade):
# ``RecyclingCenter``. # ``RecyclingCenter``.
class MakeAvailable(ActionWithMultipleDevices):
"""The act of setting willingness for trading."""
pass
class Receive(JoinedTableMixin, ActionWithMultipleDevices): class Receive(JoinedTableMixin, ActionWithMultipleDevices):
"""The act of physically taking delivery of a device. """The act of physically taking delivery of a device.

View file

@ -434,7 +434,7 @@ class Repair(ActionWithMultipleDevices):
pass pass
class ReadyToUse(ActionWithMultipleDevices): class Ready(ActionWithMultipleDevices):
pass pass
@ -505,6 +505,10 @@ class Rent(Trade):
pass pass
class MakeAvailable(ActionWithMultipleDevices):
pass
class CancelTrade(Trade): class CancelTrade(Trade):
pass pass

View file

@ -47,7 +47,11 @@ class ActionWithOneDevice(Action):
class ActionWithMultipleDevices(Action): class ActionWithMultipleDevices(Action):
__doc__ = m.ActionWithMultipleDevices.__doc__ __doc__ = m.ActionWithMultipleDevices.__doc__
devices = NestedOn(s_device.Device, many=True, only_query='id', collection_class=OrderedSet) devices = NestedOn(s_device.Device,
many=True,
required=True, # todo test ensuring len(devices) >= 1
only_query='id',
collection_class=OrderedSet)
class Add(ActionWithOneDevice): class Add(ActionWithOneDevice):
@ -347,8 +351,8 @@ class Repair(ActionWithMultipleDevices):
__doc__ = m.Repair.__doc__ __doc__ = m.Repair.__doc__
class Available(ActionWithMultipleDevices): class Ready(ActionWithMultipleDevices):
__doc__ = m.Available.__doc__ __doc__ = m.Ready.__doc__
class ToPrepare(ActionWithMultipleDevices): class ToPrepare(ActionWithMultipleDevices):
@ -405,6 +409,10 @@ class Rent(Trade):
__doc__ = m.Rent.__doc__ __doc__ = m.Rent.__doc__
class MakeAvailable(ActionWithMultipleDevices):
__doc__ = m.MakeAvailable.__doc__
class CancelTrade(Trade): class CancelTrade(Trade):
__doc__ = m.CancelTrade.__doc__ __doc__ = m.CancelTrade.__doc__

View file

@ -302,9 +302,9 @@ class Mixer(CookingDef):
SCHEMA = schemas.Mixer SCHEMA = schemas.Mixer
class DrillDef(DeviceDef): class DIYAndGardeningDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Drill SCHEMA = schemas.DIYAndGardening
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, 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, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
@ -313,7 +313,12 @@ class DrillDef(DeviceDef):
url_prefix, subdomain, url_defaults, root_path, cli_commands) url_prefix, subdomain, url_defaults, root_path, cli_commands)
class PackOfScrewdriversDef(DeviceDef): class DrillDef(DIYAndGardeningDef):
VIEW = None
SCHEMA = schemas.Drill
class PackOfScrewdriversDef(DIYAndGardeningDef):
VIEW = None VIEW = None
SCHEMA = schemas.PackOfScrewdrivers SCHEMA = schemas.PackOfScrewdrivers
@ -324,21 +329,31 @@ class PackOfScrewdriversDef(DeviceDef):
url_prefix, subdomain, url_defaults, root_path, cli_commands) url_prefix, subdomain, url_defaults, root_path, cli_commands)
class DehumidifierDef(DeviceDef): class HomeDef(DeviceDef):
VIEW = None
SCHEMA = schemas.Home
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 DehumidifierDef(HomeDef):
VIEW = None VIEW = None
SCHEMA = schemas.Dehumidifier SCHEMA = schemas.Dehumidifier
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 StairsDef(HomeDef):
class StairsDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Stairs SCHEMA = schemas.Stairs
class RecreationDef(DeviceDef):
VIEW = None
SCHEMA = schemas.Recreation
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, 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, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
@ -346,27 +361,15 @@ class StairsDef(DeviceDef):
url_prefix, subdomain, url_defaults, root_path, cli_commands) url_prefix, subdomain, url_defaults, root_path, cli_commands)
class BikeDef(DeviceDef): class BikeDef(RecreationDef):
VIEW = None VIEW = None
SCHEMA = schemas.Bike SCHEMA = schemas.Bike
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 RacketDef(RecreationDef):
class RacketDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Racket SCHEMA = schemas.Racket
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 ManufacturerDef(Resource): class ManufacturerDef(Resource):
VIEW = ManufacturerView VIEW = ManufacturerView

View file

@ -79,14 +79,14 @@ class Device(Thing):
generation = db.Column(db.SmallInteger, check_range('generation', 0)) generation = db.Column(db.SmallInteger, check_range('generation', 0))
generation.comment = """The generation of the device.""" generation.comment = """The generation of the device."""
version = db.Column(db.CIText()) version = db.Column(db.CIText())
version.comment = """The version code this device, like v1 or A001.""" version.comment = """The version code of this device, like v1 or A001."""
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5)) weight = Column(Float(decimal_return_scale=4), check_range('weight', 0.1, 5))
weight.comment = """The weight of the device.""" weight.comment = """The weight of the device in Kg."""
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5)) width = Column(Float(decimal_return_scale=4), check_range('width', 0.1, 5))
width.comment = """The width of the device in meters.""" width.comment = """The width of the device in meters."""
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5)) height = Column(Float(decimal_return_scale=4), check_range('height', 0.1, 5))
height.comment = """The height of the device in meters.""" height.comment = """The height of the device in meters."""
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5)) depth = Column(Float(decimal_return_scale=4), check_range('depth', 0.1, 5))
depth.comment = """The depth of the device in meters.""" depth.comment = """The depth of the device in meters."""
color = Column(ColorType) color = Column(ColorType)
color.comment = """The predominant color of the device.""" color.comment = """The predominant color of the device."""
@ -101,6 +101,8 @@ class Device(Thing):
sku.comment = """The Stock Keeping Unit (SKU), i.e. a sku.comment = """The Stock Keeping Unit (SKU), i.e. a
merchant-specific identifier for a product or service. merchant-specific identifier for a product or service.
""" """
image = db.Column(db.URL)
image.comment = "An image of the device."
_NON_PHYSICAL_PROPS = { _NON_PHYSICAL_PROPS = {
'id', 'id',
@ -120,7 +122,8 @@ class Device(Thing):
'production_date', 'production_date',
'variant', 'variant',
'version', 'version',
'sku' 'sku',
'image'
} }
__table_args__ = ( __table_args__ = (
@ -167,7 +170,7 @@ class Device(Thing):
def physical_properties(self) -> Dict[str, object or None]: def physical_properties(self) -> Dict[str, object or None]:
"""Fields that describe the physical properties of a device. """Fields that describe the physical properties of a device.
:return A generator where each value is a tuple with tho fields: :return A dictionary:
- Column. - Column.
- Actual value of the column or None. - Actual value of the column or None.
""" """
@ -291,9 +294,13 @@ 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})'.format(self) superclass = self.__class__.mro()[1]
if not isinstance(self, Device) and superclass != Device:
assert issubclass(superclass, Thing)
v += superclass.__name__ + ' '
v += '{0.manufacturer}'.format(self)
if self.serial_number: if self.serial_number:
v += ' S/N ' + self.serial_number.upper() v += ' ' + self.serial_number.upper()
return v return v
@ -787,7 +794,11 @@ class Mixer(Cooking):
pass pass
class Drill(Device): class DIYAndGardening(Device):
pass
class Drill(DIYAndGardening):
max_drill_bit_size = db.Column(db.SmallInteger) max_drill_bit_size = db.Column(db.SmallInteger)
@ -795,21 +806,29 @@ class PackOfScrewdrivers(Device):
pass pass
class Dehumidifier(Device): class Home(Device):
pass
class Dehumidifier(Home):
size = db.Column(db.SmallInteger) size = db.Column(db.SmallInteger)
size.comment = """The capacity in Liters.""" size.comment = """The capacity in Liters."""
class Stairs(Device): class Stairs(Home):
max_allowed_weight = db.Column(db.Integer) max_allowed_weight = db.Column(db.Integer)
class Bike(Device): class Recreation(Device):
pass
class Bike(Recreation):
wheel_size = db.Column(db.SmallInteger) wheel_size = db.Column(db.SmallInteger)
gears = db.Column(db.SmallInteger) gears = db.Column(db.SmallInteger)
class Racket(Device): class Racket(Recreation):
pass pass

View file

@ -45,6 +45,7 @@ class Device(Thing):
version = ... # type: Column version = ... # type: Column
variant = ... # type: Column variant = ... # type: Column
sku = ... # type: Column sku = ... # type: Column
image = ... #type: Column
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
@ -70,6 +71,7 @@ class Device(Thing):
self.version = ... # type: Optional[str] self.version = ... # type: Optional[str]
self.variant = ... # type: Optional[str] self.variant = ... # type: Optional[str]
self.sku = ... # type: Optional[str] self.sku = ... # type: Optional[str]
self.image = ... # type: Optional[urlutils.URL]
@property @property
def actions(self) -> List[e.Action]: def actions(self) -> List[e.Action]:

View file

@ -60,8 +60,9 @@ class Device(Thing):
many=True, many=True,
dump_only=True, dump_only=True,
description=m.Device.working.__doc__) description=m.Device.working.__doc__)
variant = SanitizedStr(description=m.Device.variant) variant = SanitizedStr(description=m.Device.variant.comment)
sku = SanitizedStr(description=m.Device.sku) sku = SanitizedStr(description=m.Device.sku.comment)
image = URL(description=m.Device.image.comment)
@pre_load @pre_load
def from_actions_to_actions_one(self, data: dict): def from_actions_to_actions_one(self, data: dict):
@ -413,26 +414,38 @@ class Mixer(Cooking):
__doc__ = m.Mixer.__doc__ __doc__ = m.Mixer.__doc__
class Drill(Device): class DIYAndGardening(Device):
pass
class Drill(DIYAndGardening):
max_drill_bit_size = Integer(data_key='maxDrillBitSize') max_drill_bit_size = Integer(data_key='maxDrillBitSize')
class PackOfScrewdrivers(Device): class PackOfScrewdrivers(DIYAndGardening):
size = Integer() size = Integer()
class Dehumidifier(Device): class Home(Device):
pass
class Dehumidifier(Home):
size = Integer() size = Integer()
class Stairs(Device): class Stairs(Home):
max_allowed_weight = Integer(data_key='maxAllowedWeight') max_allowed_weight = Integer(data_key='maxAllowedWeight')
class Bike(Device): class Recreation(Device):
pass
class Bike(Recreation):
wheel_size = Integer(data_key='wheelSize') wheel_size = Integer(data_key='wheelSize')
gears = Integer() gears = Integer()
class Racket(Device): class Racket(Recreation):
pass pass

View file

@ -40,6 +40,7 @@ class Trading(State):
# todo add Pay = e.Pay # todo add Pay = e.Pay
ToBeDisposed = e.ToDisposeProduct ToBeDisposed = e.ToDisposeProduct
ProductDisposed = e.DisposeProduct ProductDisposed = e.DisposeProduct
Available = e.MakeAvailable
class Physical(State): class Physical(State):
@ -49,12 +50,12 @@ class Physical(State):
:cvar Repaired: The device has been repaired. :cvar Repaired: The device has been repaired.
:cvar Preparing: The device is going to be or being prepared. :cvar Preparing: The device is going to be or being prepared.
:cvar Prepared: The device has been prepared. :cvar Prepared: The device has been prepared.
:cvar ReadyToBeUsed: The device is in working conditions. :cvar Ready: The device is in working conditions.
:cvar InUse: The device is being reported to be in active use. :cvar InUse: The device is being reported to be in active use.
""" """
ToBeRepaired = e.ToRepair ToBeRepaired = e.ToRepair
Repaired = e.Repair Repaired = e.Repair
Preparing = e.ToPrepare Preparing = e.ToPrepare
Prepared = e.Prepare Prepared = e.Prepare
ReadyToBeUsed = e.Available Ready = e.Ready
InUse = e.Live InUse = e.Live

View file

@ -8,6 +8,7 @@
rel="stylesheet" rel="stylesheet"
integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT" integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT"
crossorigin="anonymous"> crossorigin="anonymous">
<script src="https://use.fontawesome.com/7553aecc27.js"></script>
<title>Devicehub | {{ device.__format__('t') }}</title> <title>Devicehub | {{ device.__format__('t') }}</title>
</head> </head>
<body> <body>
@ -22,55 +23,39 @@
</a> </a>
</div> </div>
</nav> </nav>
<div class="jumbotron"> <div class="container-fluid">
<img class="center-block" <div class="row">
style="height: 13em; padding-bottom: 0.1em" <div class="page-header col-md-6 col-md-offset-3">
src="{{ url_for('Device.static', filename='magrama.svg') }}">
</div>
<div class="container">
<div class="page-header">
<h1>{{ device.__format__('t') }}<br> <h1>{{ device.__format__('t') }}<br>
<small>{{ device.__format__('s') }}</small> <small>{{ device.__format__('s') }}</small>
</h1> </h1>
</div> </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"> <div class="col-md-3">
<h3>You can verify the originality of your device.</h3> {% if device.image %}
<p> <a href="{{ device.image.to_text() }}" class="thumbnail" target="_blank">
If your device comes with the following tag <img src="{{ device.image.to_text() }}"
<img class="img-responsive center-block" style="width: 12em;" alt="Cykel.JPG">
src="{{ url_for('Device.static', filename='photochromic-alone.svg') }}"> </a>
it means it has been refurbished by an eReuse.org {% endif %}
certified organization. </div>
</p> <div class="col-md-6">
<p> {% if device.trading == states.Trading.Available %}
The tag is special illuminate it with the torch of <div class="alert alert-success">
your phone for 6 seconds and it will react like in <i class="fa fa-check-circle"></i> {{ device.trading }}
the following image: </div>
<img class="img-responsive center-block" style="width: 30em;" {% else %}
src="{{ url_for('Device.static', filename='photochromic-tag-web.svg') }}"> <div class="alert alert-warning">
This is proof that this device is genuine. <i class="fa fa-exclamation-circle"></i> Not available.
</p> </div>
</article> {% endif %}
<article class="col-md-6"> <ul>
<h3>These are the specifications</h3> {% for key, value in device.physical_properties.items() %}
<li>{{ key }}: {{ value }}
{% endfor %}
</ul>
{% if isinstance(device, d.Computer) %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
@ -189,7 +174,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<h3>This is the traceability log of your device</h3> <h4>Public traceability log of the device</h4>
<div class="text-right"> <div class="text-right">
<small>Latest one.</small> <small>Latest one.</small>
</div> </div>
@ -216,9 +201,9 @@
<div class="text-right"> <div class="text-right">
<small>Oldest one.</small> <small>Oldest one.</small>
</div> </div>
</article> {% endif %}
</div>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -14,6 +14,7 @@ from ereuse_devicehub.db import db
from ereuse_devicehub.query import SearchQueryParser, things_response from ereuse_devicehub.query import SearchQueryParser, things_response
from ereuse_devicehub.resources import search from ereuse_devicehub.resources import search
from ereuse_devicehub.resources.action import models as actions from ereuse_devicehub.resources.action import models as actions
from ereuse_devicehub.resources.device import states
from ereuse_devicehub.resources.device.models import Device, Manufacturer from ereuse_devicehub.resources.device.models import Device, Manufacturer
from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.device.search import DeviceSearch
from ereuse_devicehub.resources.lot.models import LotDeviceDescendants from ereuse_devicehub.resources.lot.models import LotDeviceDescendants
@ -101,7 +102,7 @@ class DeviceView(View):
def one_public(self, id: int): def one_public(self, id: int):
device = Device.query.filter_by(id=id).one() device = Device.query.filter_by(id=id).one()
return render_template('devices/layout.html', device=device) return render_template('devices/layout.html', device=device, states=states)
@auth.Auth.requires_auth @auth.Auth.requires_auth
def one_private(self, id: int): def one_private(self, id: int):

View file

@ -0,0 +1,13 @@
import flask.templating
import ereuse_devicehub.resources.device.models
class Environment(flask.templating.Environment):
"""As flask's environment but with some globals set"""
def __init__(self, app, **options):
super().__init__(app, **options)
self.globals[isinstance.__name__] = isinstance
self.globals[issubclass.__name__] = issubclass
self.globals['d'] = ereuse_devicehub.resources.device.models

View file

@ -42,4 +42,4 @@ def test_api_docs(client: Client):
'scheme': 'basic', 'scheme': 'basic',
'name': 'Authorization' 'name': 'Authorization'
} }
assert len(docs['definitions']) == 110 assert len(docs['definitions']) == 114

View file

@ -468,18 +468,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 00:24:8C:7F:CF:2D 100 Mbps'
hdd = next(c for c in pc.components if isinstance(c, d.DataStorage)) hdd = next(c for c in pc.components if isinstance(c, d.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 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=d.Device, item=s['device']['id'], accept=ANY) html, _ = client.get(res=d.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 '00:24:8C:7F:CF:2D 100 Mbps' in html
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)

View file

@ -1,7 +1,7 @@
import ipaddress import ipaddress
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from typing import Tuple from typing import Tuple, Type
import pytest import pytest
from flask import current_app as app, g from flask import current_app as app, g
@ -168,7 +168,7 @@ def test_update_components_action_multiple():
hdd = HardDrive(serial_number='foo', manufacturer='bar', model='foo-bar') hdd = HardDrive(serial_number='foo', manufacturer='bar', model='foo-bar')
computer.components.add(hdd) computer.components.add(hdd)
ready = models.Available() ready = models.Ready()
assert not ready.devices assert not ready.devices
assert not ready.components assert not ready.components
@ -214,7 +214,7 @@ def test_update_parent():
(models.ToRepair, states.Physical.ToBeRepaired), (models.ToRepair, states.Physical.ToBeRepaired),
(models.Repair, states.Physical.Repaired), (models.Repair, states.Physical.Repaired),
(models.ToPrepare, states.Physical.Preparing), (models.ToPrepare, states.Physical.Preparing),
(models.Available, states.Physical.ReadyToBeUsed), (models.Ready, states.Physical.Ready),
(models.Prepare, states.Physical.Prepared) (models.Prepare, states.Physical.Prepared)
])) ]))
def test_generic_action(action_model_state: Tuple[models.Action, states.Trading], def test_generic_action(action_model_state: Tuple[models.Action, states.Trading],
@ -268,24 +268,27 @@ def test_reserve_and_cancel(user: UserClient):
@pytest.mark.parametrize('action_model_state', @pytest.mark.parametrize('action_model_state',
(pytest.param(ams, id=ams[0].__class__.__name__) (pytest.param(ams, id=ams[0].__name__)
for ams in [ for ams in [
(models.MakeAvailable, states.Trading.Available),
(models.Sell, states.Trading.Sold), (models.Sell, states.Trading.Sold),
(models.Donate, states.Trading.Donated), (models.Donate, states.Trading.Donated),
(models.Rent, states.Trading.Renting), (models.Rent, states.Trading.Renting),
(models.DisposeProduct, states.Trading.ProductDisposed) (models.DisposeProduct, states.Trading.ProductDisposed)
])) ]))
def test_trade(action_model_state: Tuple[models.Action, states.Trading], user: UserClient): def test_trade(action_model_state: Tuple[Type[models.Action], states.Trading], user: UserClient):
"""Tests POSTing all Trade actions.""" """Tests POSTing all Trade actions."""
# todo missing None states.Trading for after cancelling renting, for example
action_model, state = action_model_state action_model, state = action_model_state
snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot) snapshot, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
action = { action = {
'type': action_model.t, 'type': action_model.t,
'devices': [snapshot['device']['id']], 'devices': [snapshot['device']['id']]
'to': user.user['individuals'][0]['id'],
'shippingDate': '2018-06-29T12:28:54',
'invoiceNumber': 'ABC'
} }
if issubclass(action_model, models.Trade):
action['to'] = user.user['individuals'][0]['id']
action['shippingDate'] = '2018-06-29T12:28:54'
action['invoiceNumber'] = 'ABC'
action, _ = user.post(action, res=models.Action) action, _ = user.post(action, res=models.Action)
assert action['devices'][0]['id'] == snapshot['device']['id'] assert action['devices'][0]['id'] == snapshot['device']['id']
device, _ = user.get(res=Device, item=snapshot['device']['id']) device, _ = user.get(res=Device, item=snapshot['device']['id'])