Merge remote-tracking branch 'origin/testing' into feature/33-export-lot-description

# Conflicts:
#	ereuse_devicehub/resources/documents/documents.py
#	tests/test_documents.py
This commit is contained in:
nad 2020-08-18 19:31:43 +02:00
commit 1fccea2bcc
40 changed files with 1277 additions and 543 deletions

View File

@ -9,8 +9,8 @@ import ereuse_utils.cli
from ereuse_utils.session import DevicehubClient from ereuse_utils.session import DevicehubClient
from flask.globals import _app_ctx_stack, g from flask.globals import _app_ctx_stack, g
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from teal.teal import Teal
from teal.db import SchemaSQLAlchemy from teal.db import SchemaSQLAlchemy
from teal.teal import Teal
from ereuse_devicehub.auth import Auth from ereuse_devicehub.auth import Auth
from ereuse_devicehub.client import Client, UserClient from ereuse_devicehub.client import Client, UserClient
@ -19,7 +19,6 @@ 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.resources.user import User
from ereuse_devicehub.templating import Environment from ereuse_devicehub.templating import Environment
@ -117,7 +116,6 @@ class Devicehub(Teal):
self.db.session.commit() self.db.session.commit()
print('done.') print('done.')
def _init_db(self, exclude_schema=None) -> bool: def _init_db(self, exclude_schema=None) -> bool:
if exclude_schema: if exclude_schema:
assert isinstance(self.db, SchemaSQLAlchemy) assert isinstance(self.db, SchemaSQLAlchemy)

View File

@ -101,7 +101,7 @@ class Dummy:
# Make one hdd ErasePhysical # Make one hdd ErasePhysical
hdd = next(hdd for hdd in s['components'] if hdd['type'] == 'HardDrive') hdd = next(hdd for hdd in s['components'] if hdd['type'] == 'HardDrive')
user1.post({'type': 'ErasePhysical', 'method': 'Shred', 'device': hdd['id']}, user1.post({'type': 'ErasePhysical', 'method': 'Shred', 'device': hdd['id']},
res=m.Action) res=m.Action)
assert sample_pc assert sample_pc
print('PC sample is', sample_pc) print('PC sample is', sample_pc)
# Link tags and eTags # Link tags and eTags
@ -132,25 +132,25 @@ class Dummy:
lot_user4, _ = user4.post({'name': 'LoteJordi'}, res=Lot) lot_user4, _ = user4.post({'name': 'LoteJordi'}, res=Lot)
lot, _ = user1.post({}, lot, _ = user1.post({},
res=Lot, res=Lot,
item='{}/devices'.format(lot_user['id']), item='{}/devices'.format(lot_user['id']),
query=[('id', pc) for pc in itertools.islice(pcs, 1, 4)]) query=[('id', pc) for pc in itertools.islice(pcs, 1, 4)])
assert len(lot['devices']) assert len(lot['devices'])
lot2, _ = user2.post({}, lot2, _ = user2.post({},
res=Lot, res=Lot,
item='{}/devices'.format(lot_user2['id']), item='{}/devices'.format(lot_user2['id']),
query=[('id', pc) for pc in itertools.islice(pcs, 4, 6)]) query=[('id', pc) for pc in itertools.islice(pcs, 4, 6)])
lot3, _ = user3.post({}, lot3, _ = user3.post({},
res=Lot, res=Lot,
item='{}/devices'.format(lot_user3['id']), item='{}/devices'.format(lot_user3['id']),
query=[('id', pc) for pc in itertools.islice(pcs, 11, 14)]) query=[('id', pc) for pc in itertools.islice(pcs, 11, 14)])
lot4, _ = user4.post({}, lot4, _ = user4.post({},
res=Lot, res=Lot,
item='{}/devices'.format(lot_user4['id']), item='{}/devices'.format(lot_user4['id']),
query=[('id', pc) for pc in itertools.islice(pcs, 14, 16)]) query=[('id', pc) for pc in itertools.islice(pcs, 14, 16)])
# Keep this at the bottom # Keep this at the bottom
inventory, _ = user1.get(res=Device) inventory, _ = user1.get(res=Device)
@ -168,7 +168,7 @@ class Dummy:
user1.post({'type': m.Prepare.t, 'devices': [sample_pc]}, res=m.Action) user1.post({'type': m.Prepare.t, 'devices': [sample_pc]}, res=m.Action)
user1.post({'type': m.Ready.t, 'devices': [sample_pc]}, res=m.Action) user1.post({'type': m.Ready.t, 'devices': [sample_pc]}, res=m.Action)
user1.post({'type': m.Price.t, 'device': sample_pc, 'currency': 'EUR', 'price': 85}, user1.post({'type': m.Price.t, 'device': sample_pc, 'currency': 'EUR', 'price': 85},
res=m.Action) res=m.Action)
# todo test reserve # todo test reserve
user1.post( # Sell device user1.post( # Sell device

View File

@ -1,13 +1,9 @@
from __future__ import with_statement from __future__ import with_statement
import os
from logging.config import fileConfig from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from alembic import context from alembic import context
from sqlalchemy import create_engine
from ereuse_devicehub.config import DevicehubConfig from ereuse_devicehub.config import DevicehubConfig
@ -24,10 +20,11 @@ fileConfig(config.config_file_name)
# from myapp import mymodel # from myapp import mymodel
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
# target_metadata = None # target_metadata = None
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
target_metadata = Thing.metadata target_metadata = Thing.metadata
# other values from the config, defined by the needs of env.py, # other values from the config, defined by the needs of env.py,
# can be acquired: # can be acquired:
# my_important_option = config.get_main_option("my_important_option") # my_important_option = config.get_main_option("my_important_option")

View File

@ -5,14 +5,10 @@ Revises: 151253ac5c55
Create Date: 2020-06-30 17:41:28.611314 Create Date: 2020-06-30 17:41:28.611314
""" """
from alembic import op
from alembic import context
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy_utils from alembic import context
from alembic import op
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
import citext
import teal
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = 'b9b0ee7d9dca' revision = 'b9b0ee7d9dca'
@ -27,6 +23,7 @@ def get_inv():
raise ValueError("Inventory value is not specified") raise ValueError("Inventory value is not specified")
return INV return INV
def upgrade(): def upgrade():
op.add_column('tag', sa.Column('owner_id', postgresql.UUID(), nullable=True), schema=f'{get_inv()}') op.add_column('tag', sa.Column('owner_id', postgresql.UUID(), nullable=True), schema=f'{get_inv()}')
op.create_foreign_key("fk_tag_owner_id_user_id", op.create_foreign_key("fk_tag_owner_id_user_id",

File diff suppressed because one or more lines are too long

View File

@ -552,5 +552,6 @@ class MigrateTo(Migrate):
class MigrateFrom(Migrate): class MigrateFrom(Migrate):
pass pass
class Transferred(ActionWithMultipleDevices): class Transferred(ActionWithMultipleDevices):
pass pass

View File

@ -1,6 +1,5 @@
from typing import Iterable
import math import math
from typing import Iterable
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device

View File

@ -3,7 +3,7 @@ from itertools import groupby
from typing import Dict, Iterable, Tuple from typing import Dict, Iterable, Tuple
from ereuse_devicehub.resources.action.models import BenchmarkDataStorage, BenchmarkProcessor, \ from ereuse_devicehub.resources.action.models import BenchmarkDataStorage, BenchmarkProcessor, \
BenchmarkProcessorSysbench, RateComputer, VisualTest BenchmarkProcessorSysbench, RateComputer
from ereuse_devicehub.resources.action.rate.rate import BaseRate from ereuse_devicehub.resources.action.rate.rate import BaseRate
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Processor, \ from ereuse_devicehub.resources.device.models import Computer, DataStorage, Processor, \
RamModule RamModule

View File

@ -454,8 +454,6 @@ class MigrateTo(Migrate):
class MigrateFrom(Migrate): class MigrateFrom(Migrate):
__doc__ = m.MigrateFrom.__doc__ __doc__ = m.MigrateFrom.__doc__
class Transferred(ActionWithMultipleDevices): class Transferred(ActionWithMultipleDevices):
__doc__ = m.Transferred.__doc__ __doc__ = m.Transferred.__doc__

View File

@ -2,7 +2,7 @@ from distutils.version import StrictVersion
from typing import List from typing import List
from uuid import UUID from uuid import UUID
from flask import current_app as app, request from flask import current_app as app, request, g
from sqlalchemy.util import OrderedSet from sqlalchemy.util import OrderedSet
from teal.marshmallow import ValidationError from teal.marshmallow import ValidationError
from teal.resource import View from teal.resource import View
@ -13,6 +13,7 @@ from ereuse_devicehub.resources.action.models import Action, RateComputer, Snaps
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
from ereuse_devicehub.resources.device.models import Component, Computer from ereuse_devicehub.resources.device.models import Component, Computer
from ereuse_devicehub.resources.enums import SnapshotSoftware, Severity from ereuse_devicehub.resources.enums import SnapshotSoftware, Severity
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
SUPPORTED_WORKBENCH = StrictVersion('11.0') SUPPORTED_WORKBENCH = StrictVersion('11.0')
@ -56,6 +57,7 @@ class ActionView(View):
# Note that if we set the device / components into the snapshot # Note that if we set the device / components into the snapshot
# model object, when we flush them to the db we will flush # model object, when we flush them to the db we will flush
# snapshot, and we want to wait to flush snapshot at the end # snapshot, and we want to wait to flush snapshot at the end
device = snapshot_json.pop('device') # type: Computer device = snapshot_json.pop('device') # type: Computer
components = None components = None
if snapshot_json['software'] == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid): if snapshot_json['software'] == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid):
@ -73,6 +75,7 @@ class ActionView(View):
assert not device.actions_one assert not device.actions_one
assert all(not c.actions_one for c in components) if components else True assert all(not c.actions_one for c in components) if components else True
db_device, remove_actions = resource_def.sync.run(device, components) db_device, remove_actions = resource_def.sync.run(device, components)
del device # Do not use device anymore del device # Do not use device anymore
snapshot.device = db_device snapshot.device = db_device
snapshot.actions |= remove_actions | actions_device # Set actions to snapshot snapshot.actions |= remove_actions | actions_device # Set actions to snapshot
@ -87,8 +90,11 @@ class ActionView(View):
component.actions_one |= actions component.actions_one |= actions
snapshot.actions |= actions snapshot.actions |= actions
# Compute ratings
if snapshot.software == SnapshotSoftware.Workbench: if snapshot.software == SnapshotSoftware.Workbench:
# Check ownership of (non-component) device to from current.user
if db_device.owner_id != g.user.id:
raise InsufficientPermission()
# Compute ratings
try: try:
rate_computer, price = RateComputer.compute(db_device) rate_computer, price = RateComputer.compute(db_device)
except CannotRate: except CannotRate:

View File

@ -1,9 +1,7 @@
import pathlib
from typing import Callable, Iterable, Tuple from typing import Callable, Iterable, Tuple
from teal.resource import Converters, Resource from teal.resource import Converters, Resource
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.deliverynote import schemas from ereuse_devicehub.resources.deliverynote import schemas
from ereuse_devicehub.resources.deliverynote.views import DeliverynoteView from ereuse_devicehub.resources.deliverynote.views import DeliverynoteView

View File

@ -1,39 +1,38 @@
import uuid import uuid
from datetime import datetime from datetime import datetime
from typing import Iterable
from boltons import urlutils from boltons import urlutils
from citext import CIText from citext import CIText
from flask import g from flask import g
from typing import Iterable
from sqlalchemy.types import ARRAY
from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.dialects.postgresql import UUID, JSONB
from teal.db import CASCADE_OWN, check_range, IntEnum from teal.db import check_range, IntEnum
from teal.resource import url_for_resource from teal.resource import url_for_resource
from ereuse_devicehub.db import db, f from ereuse_devicehub.db import db
from ereuse_devicehub.resources.enums import TransferState
from ereuse_devicehub.resources.lot.models import Lot
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import User
from ereuse_devicehub.resources.lot.models import Lot
from ereuse_devicehub.resources.enums import TransferState
class Deliverynote(Thing): class Deliverynote(Thing):
id = db.Column(UUID(as_uuid=True), primary_key=True) # uuid is generated on init by default id = db.Column(UUID(as_uuid=True), primary_key=True) # uuid is generated on init by default
document_id = db.Column(CIText(), nullable=False) document_id = db.Column(CIText(), nullable=False)
creator_id = db.Column(UUID(as_uuid=True), creator_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
nullable=False, nullable=False,
default=lambda: g.user.id) default=lambda: g.user.id)
creator = db.relationship(User, primaryjoin=creator_id == User.id) creator = db.relationship(User, primaryjoin=creator_id == User.id)
supplier_email = db.Column(CIText(), supplier_email = db.Column(CIText(),
db.ForeignKey(User.email), db.ForeignKey(User.email),
nullable=False, nullable=False,
default=lambda: g.user.email) default=lambda: g.user.email)
supplier = db.relationship(User, primaryjoin=lambda: Deliverynote.supplier_email == User.email) supplier = db.relationship(User, primaryjoin=lambda: Deliverynote.supplier_email == User.email)
receiver_address = db.Column(CIText(), receiver_address = db.Column(CIText(),
db.ForeignKey(User.email), db.ForeignKey(User.email),
nullable=False, nullable=False,
default=lambda: g.user.email) default=lambda: g.user.email)
receiver = db.relationship(User, primaryjoin=lambda: Deliverynote.receiver_address == User.email) receiver = db.relationship(User, primaryjoin=lambda: Deliverynote.receiver_address == User.email)
date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
date.comment = 'The date the DeliveryNote initiated' date.comment = 'The date the DeliveryNote initiated'
@ -49,15 +48,15 @@ class Deliverynote(Thing):
transfer_state.comment = TransferState.__doc__ transfer_state.comment = TransferState.__doc__
ethereum_address = db.Column(CIText(), unique=True, default=None) ethereum_address = db.Column(CIText(), unique=True, default=None)
lot_id = db.Column(UUID(as_uuid=True), lot_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(Lot.id), db.ForeignKey(Lot.id),
nullable=False) nullable=False)
lot = db.relationship(Lot, lot = db.relationship(Lot,
backref=db.backref('deliverynote', uselist=False, lazy=True), backref=db.backref('deliverynote', uselist=False, lazy=True),
lazy=True, lazy=True,
primaryjoin=Lot.id == lot_id) primaryjoin=Lot.id == lot_id)
def __init__(self, document_id: str, deposit: str, date, def __init__(self, document_id: str, deposit: str, date,
supplier_email: str, supplier_email: str,
expected_devices: Iterable, expected_devices: Iterable,
transfer_state: TransferState) -> None: transfer_state: TransferState) -> None:
"""Initializes a delivery note """Initializes a delivery note

View File

@ -24,13 +24,13 @@ class Lot(Thing):
description = ... # type: Column description = ... # type: Column
all_devices = ... # type: relationship all_devices = ... # type: relationship
parents = ... # type: relationship parents = ... # type: relationship
deposit = ... # type: Column deposit = ... # type: Column
owner_address = ... # type: Column owner_address = ... # type: Column
owner = ... # type: relationship owner = ... # type: relationship
transfer_state = ... # type: Column transfer_state = ... # type: Column
receiver_address = ... # type: Column receiver_address = ... # type: Column
receiver = ... # type: relationship receiver = ... # type: relationship
deliverynote_address = ... # type: Column deliverynote_address = ... # type: Column
def __init__(self, name: str, closed: bool = closed.default.arg) -> None: def __init__(self, name: str, closed: bool = closed.default.arg) -> None:
super().__init__() super().__init__()
@ -43,10 +43,10 @@ class Lot(Thing):
self.all_devices = ... # type: Set[Device] self.all_devices = ... # type: Set[Device]
self.parents = ... # type: Set[Lot] self.parents = ... # type: Set[Lot]
self.children = ... # type: Set[Lot] self.children = ... # type: Set[Lot]
self.owner_address = ... # type: UUID self.owner_address = ... # type: UUID
self.transfer_state = ... self.transfer_state = ...
self.receiver_address = ... # type: str self.receiver_address = ... # type: str
self.deliverynote_address = ... # type: str self.deliverynote_address = ... # type: str
def add_children(self, *children: Union[Lot, uuid.UUID]): def add_children(self, *children: Union[Lot, uuid.UUID]):
pass pass

View File

@ -1,13 +1,12 @@
from marshmallow import fields as f from marshmallow import fields as f
from teal.marshmallow import SanitizedStr, URL, EnumField from teal.marshmallow import SanitizedStr, EnumField
from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.deliverynote import models as m from ereuse_devicehub.resources.deliverynote import models as m
from ereuse_devicehub.resources.user import schemas as s_user from ereuse_devicehub.resources.enums import TransferState
from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.models import STR_SIZE from ereuse_devicehub.resources.models import STR_SIZE
from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.enums import TransferState from ereuse_devicehub.resources.user import schemas as s_user
class Deliverynote(Thing): class Deliverynote(Thing):
@ -21,7 +20,7 @@ class Deliverynote(Thing):
receiver = NestedOn(s_user.User, dump_only=True) receiver = NestedOn(s_user.User, dump_only=True)
date = f.DateTime('iso', required=True) date = f.DateTime('iso', required=True)
deposit = f.Integer(validate=f.validate.Range(min=0, max=100), deposit = f.Integer(validate=f.validate.Range(min=0, max=100),
description=m.Deliverynote.deposit.__doc__) description=m.Deliverynote.deposit.__doc__)
ethereum_address = f.String(description='User identifier address inside the Blockchain') ethereum_address = f.String(description='User identifier address inside the Blockchain')
expected_devices = f.List(f.Dict, required=True, data_key='expectedDevices') expected_devices = f.List(f.Dict, required=True, data_key='expectedDevices')
transferred_devices = f.List(f.Integer(), required=False, data_key='transferredDevices') transferred_devices = f.List(f.Integer(), required=False, data_key='transferredDevices')

View File

@ -1,22 +1,12 @@
import datetime import datetime
import uuid import uuid
from collections import deque
from enum import Enum
from typing import Dict, List, Set, Union
import marshmallow as ma from flask import Response, request
import teal.cache
from flask import Response, jsonify, request
from marshmallow import Schema as MarshmallowSchema, fields as f
from teal.marshmallow import EnumField
from teal.resource import View from teal.resource import View
from sqlalchemy.orm import joinedload
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.deliverynote.models import Deliverynote from ereuse_devicehub.resources.deliverynote.models import Deliverynote
from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.lot.models import Lot
from ereuse_devicehub.resources.device.models import Computer
class DeliverynoteView(View): class DeliverynoteView(View):
@ -53,7 +43,7 @@ class DeliverynoteView(View):
# if devKey in device_fields: # if devKey in device_fields:
# for dev in computers: # for dev in computers:
# setattr(dev, devKey, value) # setattr(dev, devKey, value)
db.session.commit() db.session.commit()
return Response(status=204) return Response(status=204)

View File

@ -7,17 +7,17 @@ from typing import Dict, List, Set
from boltons import urlutils from boltons import urlutils
from citext import CIText from citext import CIText
from flask import g
from ereuse_utils.naming import HID_CONVERSION_DOC, Naming from ereuse_utils.naming import HID_CONVERSION_DOC, Naming
from flask import g
from more_itertools import unique_everseen from more_itertools import unique_everseen
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \ from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
Sequence, SmallInteger, Unicode, inspect, text Sequence, SmallInteger, Unicode, inspect, text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import ColumnProperty, backref, relationship, validates from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
from sqlalchemy.util import OrderedSet from sqlalchemy.util import OrderedSet
from sqlalchemy_utils import ColorType from sqlalchemy_utils import ColorType
from sqlalchemy.dialects.postgresql import UUID
from stdnum import imei, meid from stdnum import imei, meid
from teal.db import CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, URL, \ from teal.db import CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, URL, \
check_lower, check_range, IntEnum check_lower, check_range, IntEnum
@ -382,17 +382,17 @@ class Computer(Device):
It is a subset of the Linux definition of DMI / DMI decode. It is a subset of the Linux definition of DMI / DMI decode.
""" """
ethereum_address = Column(CIText(), unique=True, default=None) ethereum_address = Column(CIText(), unique=True, default=None)
deposit = Column(Integer, check_range('deposit',min=0,max=100), default=0) deposit = Column(Integer, check_range('deposit', min=0, max=100), default=0)
owner_id = db.Column(UUID(as_uuid=True), owner_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
nullable=False, nullable=False,
default=lambda: g.user.id) default=lambda: g.user.id)
author = db.relationship(User, primaryjoin=owner_id == User.id) author = db.relationship(User, primaryjoin=owner_id == User.id)
transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False) transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False)
transfer_state.comment = TransferState.__doc__ transfer_state.comment = TransferState.__doc__
receiver_id = db.Column(UUID(as_uuid=True), receiver_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
nullable=True) nullable=True)
receiver = db.relationship(User, primaryjoin=receiver_id == User.id) receiver = db.relationship(User, primaryjoin=receiver_id == User.id)
deliverynote_address = db.Column(CIText(), nullable=True) deliverynote_address = db.Column(CIText(), nullable=True)

View File

@ -141,21 +141,21 @@ class DisplayMixin:
class Computer(DisplayMixin, Device): class Computer(DisplayMixin, Device):
components = ... # type: Column components = ... # type: Column
chassis = ... # type: Column chassis = ... # type: Column
deposit = ... # type: Column deposit = ... # type: Column
owner_address = ... # type: Column owner_address = ... # type: Column
transfer_state = ... # type: Column transfer_state = ... # type: Column
receiver_address = ... # type: Column receiver_address = ... # type: Column
deliverynote_address = ... # type: Column deliverynote_address = ... # type: Column
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self.components = ... # type: Set[Component] self.components = ... # type: Set[Component]
self.actions_parent = ... # type: Set[e.Action] self.actions_parent = ... # type: Set[e.Action]
self.chassis = ... # type: ComputerChassis self.chassis = ... # type: ComputerChassis
self.owner_address = ... # type: UUID self.owner_address = ... # type: UUID
self.transfer_state = ... self.transfer_state = ...
self.receiver_address = ... # type: str self.receiver_address = ... # type: str
self.deliverynote_address = ... # type: str self.deliverynote_address = ... # type: str
@property @property
def actions(self) -> List: def actions(self) -> List:
@ -219,7 +219,7 @@ class Mobile(Device):
meid = ... # type: Column meid = ... # type: Column
ram_size = ... # type: Column ram_size = ... # type: Column
data_storage_size = ... # type: Column data_storage_size = ... # type: Column
display_size = ... # type: Column display_size = ... # type: Column
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)

View File

@ -14,7 +14,6 @@ from ereuse_devicehub.resources import enums
from ereuse_devicehub.resources.device import models as m, states from ereuse_devicehub.resources.device import models as m, states
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, UnitCodes from ereuse_devicehub.resources.schemas import Thing, UnitCodes
from ereuse_devicehub.resources.user import schemas as s_user
class Device(Thing): class Device(Thing):
@ -124,7 +123,7 @@ class Computer(Device):
description=m.Computer.privacy.__doc__) description=m.Computer.privacy.__doc__)
ethereum_address = SanitizedStr(validate=f.validate.Length(max=42)) ethereum_address = SanitizedStr(validate=f.validate.Length(max=42))
deposit = Integer(validate=f.validate.Range(min=0, max=100), deposit = Integer(validate=f.validate.Range(min=0, max=100),
description=m.Computer.deposit.__doc__) description=m.Computer.deposit.__doc__)
# author_id = NestedOn(s_user.User,only_query='author_id') # author_id = NestedOn(s_user.User,only_query='author_id')
owner_id = UUID(data_key='ownerID') owner_id = UUID(data_key='ownerID')
transfer_state = EnumField(enums.TransferState, description=m.Computer.transfer_state.comment) transfer_state = EnumField(enums.TransferState, description=m.Computer.transfer_state.comment)

View File

@ -2,14 +2,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css" <link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css"
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> <script src="https://use.fontawesome.com/7553aecc27.js"></script>
<title>Devicehub | {{ device.__format__('t') }}</title> <title>Devicehub | {{ device.__format__('t') }}</title>
<style> <style>
/*Sticky footer*/ /*Sticky footer*/
html { html {
@ -36,174 +36,174 @@
<a href="https://www.usody.com/" target="_blank"> <a href="https://www.usody.com/" target="_blank">
<h1 align="center">Usody Public Link</h1> <h1 align="center">Usody Public Link</h1>
</a> </a>
</div> </div>
</nav> </nav>
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="page-header col-md-6 col-md-offset-3"> <div class="page-header col-md-6 col-md-offset-3">
<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="row"> <div class="row">
<div class="col-md-3"> <div class="col-md-3">
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<ul> <ul>
{% for key, value in device.physical_properties.items() %} {% for key, value in device.physical_properties.items() %}
<li>{{ key }}: {{ value }} <li>{{ key }}: {{ value }}
{% endfor %} {% endfor %}
</ul> </ul>
{% if isinstance(device, d.Computer) %} {% 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>
<tr> <tr>
<th></th> <th></th>
<th>Range</th> <th>Range</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% if device.processor_model %} {% if device.processor_model %}
<tr> <tr>
<td> <td>
CPU {{ device.processor_model }} CPU {{ device.processor_model }}
</td> </td>
<td> <td>
Processor Rate = {% if device.rate %} Processor Rate = {% if device.rate %}
{{ device.rate.processor_range }} {{ device.rate.processor_range }}
({{ device.rate.processor }}) ({{ device.rate.processor }})
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.ram_size %} {% if device.ram_size %}
<tr> <tr>
<td> <td>
RAM {{ device.ram_size // 1000 }} GB RAM {{ device.ram_size // 1000 }} GB
{{ macros.component_type(device.components, 'RamModule') }} {{ macros.component_type(device.components, 'RamModule') }}
</td> </td>
<td> <td>
RAM Rate = {% if device.rate %} RAM Rate = {% if device.rate %}
{{ device.rate.ram_range }} {{ device.rate.ram_range }}
({{ device.rate.ram }}) ({{ device.rate.ram }})
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.data_storage_size %} {% if device.data_storage_size %}
<tr> <tr>
<td> <td>
Data Storage {{ device.data_storage_size // 1000 }} GB Data Storage {{ device.data_storage_size // 1000 }} GB
{{ macros.component_type(device.components, 'SolidStateDrive') }} {{ macros.component_type(device.components, 'SolidStateDrive') }}
{{ macros.component_type(device.components, 'HardDrive') }} {{ macros.component_type(device.components, 'HardDrive') }}
</td> </td>
<td> <td>
Data Storage Rate = {% if device.rate %} Data Storage Rate = {% if device.rate %}
{{ device.rate.data_storage_range }} {{ device.rate.data_storage_range }}
({{ device.rate.data_storage }}) ({{ device.rate.data_storage }})
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.graphic_card_model %} {% if device.graphic_card_model %}
<tr> <tr>
<td> <td>
Graphics {{ device.graphic_card_model }} Graphics {{ device.graphic_card_model }}
{{ macros.component_type(device.components, 'GraphicCard') }} {{ macros.component_type(device.components, 'GraphicCard') }}
</td> </td>
<td></td> <td></td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.network_speeds %} {% if device.network_speeds %}
<tr> <tr>
<td> <td>
Network Network
{% if device.network_speeds[0] %} {% if device.network_speeds[0] %}
Ethernet Ethernet
{% if device.network_speeds[0] != None %} {% if device.network_speeds[0] != None %}
max. {{ device.network_speeds[0] }} Mbps max. {{ device.network_speeds[0] }} Mbps
{% endif %} {% 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 WiFi
{% if device.network_speeds[1] != None %} {% if device.network_speeds[1] != None %}
max. {{ device.network_speeds[1] }} Mbps max. {{ device.network_speeds[1] }} Mbps
{% endif %} {% endif %}
{% endif %} {% endif %}
{{ macros.component_type(device.components, 'NetworkAdapter') }} {{ macros.component_type(device.components, 'NetworkAdapter') }}
</td> </td>
<td></td> <td></td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.rate %} {% if device.rate %}
<tr class="active"> <tr class="active">
<td class="text-right"> <td class="text-right">
Total rate Total rate
</td> </td>
<td> <td>
{{ device.rate.rating_range }} {{ device.rate.rating_range }}
({{ device.rate.rating }}) ({{ device.rate.rating }})
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.rate and device.rate.price %} {% if device.rate and device.rate.price %}
<tr class="active"> <tr class="active">
<td class="text-right"> <td class="text-right">
Algorithm price Algorithm price
</td> </td>
<td> <td>
{{ device.rate.price }} {{ device.rate.price }}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if device.price %} {% if device.price %}
<tr class="active"> <tr class="active">
<td class="text-right"> <td class="text-right">
Actual price Actual price
</td> </td>
<td> <td>
{{ device.price }} {{ device.price }}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
</div>
<h4>Public traceability log of the device</h4>
<div class="text-right">
<small>Latest one.</small>
</div>
<ol>
{% for action in device.actions|reverse %}
<li>
<strong>
{{ action.type }}
</strong>
{{ action }}
<br>
<div class="text-muted">
<small>
{{ action._date_str }}
</small>
</div>
{% if action.certificate %}
<a href="{{ action.certificate.to_text() }}">See the certificate</a>
{% endif %}
</li>
{% endfor %}
</ol>
<div class="text-right">
<small>Oldest one.</small>
</div>
{% endif %}
</div> </div>
<h4>Public traceability log of the device</h4>
<div class="text-right">
<small>Latest one.</small>
</div>
<ol>
{% for action in device.actions|reverse %}
<li>
<strong>
{{ action.type }}
</strong>
{{ action }}
<br>
<div class="text-muted">
<small>
{{ action._date_str }}
</small>
</div>
{% if action.certificate %}
<a href="{{ action.certificate.to_text() }}">See the certificate</a>
{% endif %}
</li>
{% endfor %}
</ol>
<div class="text-right">
<small>Oldest one.</small>
</div>
{% endif %}
</div> </div>
</div>
</div> </div>
<footer class="container-fluid footer"> <footer class="container-fluid footer">
<div class="row"> <div class="row">

View File

@ -6,8 +6,7 @@ import marshmallow
from flask import g, current_app as app, render_template, request, Response from flask import g, current_app as app, render_template, request, Response
from flask.json import jsonify from flask.json import jsonify
from flask_sqlalchemy import Pagination from flask_sqlalchemy import Pagination
from marshmallow import fields, fields as f, validate as v, ValidationError, \ from marshmallow import fields, fields as f, validate as v, Schema as MarshmallowSchema
Schema as MarshmallowSchema
from teal import query from teal import query
from teal.cache import cache from teal.cache import cache
from teal.resource import View from teal.resource import View
@ -20,9 +19,9 @@ from ereuse_devicehub.resources.action import models as actions
from ereuse_devicehub.resources.device import states from ereuse_devicehub.resources.device import states
from ereuse_devicehub.resources.device.models import Device, Manufacturer, Computer from ereuse_devicehub.resources.device.models import Device, Manufacturer, Computer
from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.device.search import DeviceSearch
from ereuse_devicehub.resources.enums import SnapshotSoftware
from ereuse_devicehub.resources.lot.models import LotDeviceDescendants from ereuse_devicehub.resources.lot.models import LotDeviceDescendants
from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.tag.model import Tag
from ereuse_devicehub.resources.enums import SnapshotSoftware
class OfType(f.Str): class OfType(f.Str):
@ -103,14 +102,15 @@ class DeviceView(View):
if isinstance(dev, Computer): if isinstance(dev, Computer):
resource_def = app.resources['Computer'] resource_def = app.resources['Computer']
# TODO check how to handle the 'actions_one' # TODO check how to handle the 'actions_one'
patch_schema = resource_def.SCHEMA(only=['ethereum_address', 'transfer_state', 'deliverynote_address', 'actions_one'], partial=True) patch_schema = resource_def.SCHEMA(
only=['ethereum_address', 'transfer_state', 'deliverynote_address', 'actions_one'], partial=True)
json = request.get_json(schema=patch_schema) json = request.get_json(schema=patch_schema)
# TODO check how to handle the 'actions_one' # TODO check how to handle the 'actions_one'
json.pop('actions_one') json.pop('actions_one')
if not dev: if not dev:
raise ValueError('Device non existent') raise ValueError('Device non existent')
for key, value in json.items(): for key, value in json.items():
setattr(dev,key,value) setattr(dev, key, value)
db.session.commit() db.session.commit()
return Response(status=204) return Response(status=204)
raise ValueError('Cannot patch a non computer') raise ValueError('Cannot patch a non computer')
@ -157,20 +157,20 @@ class DeviceView(View):
query = self.visibility_filter(query) query = self.visibility_filter(query)
return query.filter(*args['filter']).order_by(*args['sort']) return query.filter(*args['filter']).order_by(*args['sort'])
def visibility_filter(self, query): def visibility_filter(self, query):
filterqs = request.args.get('filter', None) filterqs = request.args.get('filter', None)
if (filterqs and if (filterqs and
'lot' not in filterqs): 'lot' not in filterqs):
query = query.filter((Computer.id == Device.id), (Computer.owner_id == g.user.id)) query = query.filter((Computer.id == Device.id), (Computer.owner_id == g.user.id))
pass pass
return query return query
class DeviceMergeView(View):
class DeviceMergeView(View):
"""View for merging two devices """View for merging two devices
Ex. ``device/<id>/merge/id=X``. Ex. ``device/<id>/merge/id=X``.
""" """
class FindArgs(MarshmallowSchema): class FindArgs(MarshmallowSchema):
id = fields.Integer() id = fields.Integer()
@ -197,10 +197,13 @@ class DeviceMergeView(View):
This operation is highly costly as it forces refreshing This operation is highly costly as it forces refreshing
many models in session. many models in session.
""" """
snapshots = sorted(filterfalse(lambda x: not isinstance(x, actions.Snapshot), (base_device.actions + with_device.actions))) snapshots = sorted(
workbench_snapshots = [ s for s in snapshots if s.software == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid)] filterfalse(lambda x: not isinstance(x, actions.Snapshot), (base_device.actions + with_device.actions)))
latest_snapshot_device = [ d for d in (base_device, with_device) if d.id == snapshots[-1].device.id][0] workbench_snapshots = [s for s in snapshots if
latest_snapshotworkbench_device = [ d for d in (base_device, with_device) if d.id == workbench_snapshots[-1].device.id][0] s.software == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid)]
latest_snapshot_device = [d for d in (base_device, with_device) if d.id == snapshots[-1].device.id][0]
latest_snapshotworkbench_device = \
[d for d in (base_device, with_device) if d.id == workbench_snapshots[-1].device.id][0]
# Adding actions of with_device # Adding actions of with_device
with_actions_one = [a for a in with_device.actions if isinstance(a, actions.ActionWithOneDevice)] with_actions_one = [a for a in with_device.actions if isinstance(a, actions.ActionWithOneDevice)]
with_actions_multiple = [a for a in with_device.actions if isinstance(a, actions.ActionWithMultipleDevices)] with_actions_multiple = [a for a in with_device.actions if isinstance(a, actions.ActionWithMultipleDevices)]

View File

@ -30,31 +30,23 @@ class DeviceRow(OrderedDict):
self['Tag 1'] = self['Tag 2'] = self['Tag 3'] = '' self['Tag 1'] = self['Tag 2'] = self['Tag 3'] = ''
for i, tag in zip(range(1, 3), device.tags): for i, tag in zip(range(1, 3), device.tags):
self['Tag {}'.format(i)] = format(tag) self['Tag {}'.format(i)] = format(tag)
self['Serial Number'] = device.serial_number self['Serial Number'] = convert_none_to_empty_str(device.serial_number)
self['Model'] = device.model self['Model'] = convert_none_to_empty_str(device.model)
self['Manufacturer'] = device.manufacturer self['Manufacturer'] = convert_none_to_empty_str(device.manufacturer)
# self['State'] = device.last_action_of()
self['Registered in'] = format(device.created, '%c') self['Registered in'] = format(device.created, '%c')
try: try:
self['Physical state'] = device.last_action_of(*states.Physical.actions()).t self['Physical state'] = device.last_action_of(*states.Physical.actions()).t
except: except LookupError:
self['Physical state'] = '' self['Physical state'] = ''
try: try:
self['Trading state'] = device.last_action_of(*states.Trading.actions()).t self['Trading state'] = device.last_action_of(*states.Trading.actions()).t
except: except LookupError:
self['Trading state'] = '' self['Trading state'] = ''
try: self['Price'] = convert_none_to_empty_str(device.price)
self['Price'] = device.price
except:
self['Price'] = ''
if isinstance(device, d.Computer): if isinstance(device, d.Computer):
self['Processor'] = device.processor_model self['Processor'] = convert_none_to_empty_str(device.processor_model)
self['RAM (MB)'] = device.ram_size self['RAM (MB)'] = convert_none_to_empty_str(device.ram_size)
self['Data Storage Size (MB)'] = device.data_storage_size self['Data Storage Size (MB)'] = convert_none_to_empty_str(device.data_storage_size)
if isinstance(device, d.Mobile):
self['Display Size'] = device.display_size
self['RAM (MB)'] = device.ram_size
self['Data Storage Size (MB)'] = device.data_storage_size
rate = device.rate rate = device.rate
if rate: if rate:
self['Rate'] = rate.rating self['Rate'] = rate.rating
@ -135,3 +127,47 @@ class DeviceRow(OrderedDict):
self['{} {} Speed (MHz)'.format(type, i)] = component.speed self['{} {} Speed (MHz)'.format(type, i)] = component.speed
# todo add Display, NetworkAdapter, etc... # todo add Display, NetworkAdapter, etc...
class StockRow(OrderedDict):
def __init__(self, device: d.Device) -> None:
super().__init__()
self.device = device
self['Type'] = convert_none_to_empty_str(device.t)
if isinstance(device, d.Computer):
self['Chassis'] = device.chassis
else:
self['Chassis'] = ''
self['Serial Number'] = convert_none_to_empty_str(device.serial_number)
self['Model'] = convert_none_to_empty_str(device.model)
self['Manufacturer'] = convert_none_to_empty_str(device.manufacturer)
self['Registered in'] = format(device.created, '%c')
try:
self['Physical state'] = device.last_action_of(*states.Physical.actions()).t
except LookupError:
self['Physical state'] = ''
try:
self['Trading state'] = device.last_action_of(*states.Trading.actions()).t
except LookupError:
self['Trading state'] = ''
self['Price'] = convert_none_to_empty_str(device.price)
self['Processor'] = convert_none_to_empty_str(device.processor_model)
self['RAM (MB)'] = convert_none_to_empty_str(device.ram_size)
self['Data Storage Size (MB)'] = convert_none_to_empty_str(device.data_storage_size)
rate = device.rate
if rate:
self['Rate'] = rate.rating
self['Range'] = rate.rating_range
assert isinstance(rate, RateComputer)
self['Processor Rate'] = rate.processor
self['Processor Range'] = rate.processor_range
self['RAM Rate'] = rate.ram
self['RAM Range'] = rate.ram_range
self['Data Storage Rate'] = rate.data_storage
self['Data Storage Range'] = rate.data_storage_range
def convert_none_to_empty_str(s):
if s is None:
return ''
return s

View File

@ -11,7 +11,7 @@ import flask
import flask_weasyprint import flask_weasyprint
import teal.marshmallow import teal.marshmallow
from boltons import urlutils from boltons import urlutils
from flask import make_response from flask import make_response, g
from teal.cache import cache from teal.cache import cache
from teal.resource import Resource from teal.resource import Resource
@ -19,11 +19,12 @@ from ereuse_devicehub.db import db
from ereuse_devicehub.resources.action import models as evs from ereuse_devicehub.resources.action import models as evs
from ereuse_devicehub.resources.device import models as devs from ereuse_devicehub.resources.device import models as devs
from ereuse_devicehub.resources.device.views import DeviceView from ereuse_devicehub.resources.device.views import DeviceView
from ereuse_devicehub.resources.documents.device_row import DeviceRow, StockRow
from ereuse_devicehub.resources.documents.device_row import DeviceRow from ereuse_devicehub.resources.documents.device_row import DeviceRow
from ereuse_devicehub.resources.lot import LotView from ereuse_devicehub.resources.lot import LotView
from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.lot.models import Lot
from flask import g, request
class Format(enum.Enum): class Format(enum.Enum):
HTML = 'HTML' HTML = 'HTML'
@ -110,7 +111,7 @@ class DocumentView(DeviceView):
class DevicesDocumentView(DeviceView): class DevicesDocumentView(DeviceView):
@cache(datetime.timedelta(minutes=1)) @cache(datetime.timedelta(minutes=1))
def find(self, args: dict): def find(self, args: dict):
query = self.query(args) query = (x for x in self.query(args) if x.owner_id == g.user.id)
return self.generate_post_csv(query) return self.generate_post_csv(query)
def generate_post_csv(self, query): def generate_post_csv(self, query):
@ -169,7 +170,7 @@ class LotRow(OrderedDict):
class StockDocumentView(DeviceView): class StockDocumentView(DeviceView):
# @cache(datetime.timedelta(minutes=1)) # @cache(datetime.timedelta(minutes=1))
def find(self, args: dict): def find(self, args: dict):
query = self.query(args) query = (x for x in self.query(args) if x.owner_id == g.user.id)
return self.generate_post_csv(query) return self.generate_post_csv(query)
def generate_post_csv(self, query): def generate_post_csv(self, query):
@ -178,13 +179,13 @@ class StockDocumentView(DeviceView):
cw = csv.writer(data) cw = csv.writer(data)
first = True first = True
for device in query: for device in query:
d = DeviceRow(device) d = StockRow(device)
if first: if first:
cw.writerow(d.keys()) cw.writerow(d.keys())
first = False first = False
cw.writerow(d.values()) cw.writerow(d.values())
output = make_response(data.getvalue()) output = make_response(data.getvalue())
output.headers['Content-Disposition'] = 'attachment; filename=export.csv' output.headers['Content-Disposition'] = 'attachment; filename=devices-stock.csv'
output.headers['Content-type'] = 'text/csv' output.headers['Content-type'] = 'text/csv'
return output return output
@ -194,6 +195,7 @@ class DocumentDef(Resource):
SCHEMA = None SCHEMA = None
VIEW = None # We do not want to create default / documents endpoint VIEW = None # We do not want to create default / documents endpoint
AUTH = False AUTH = False
def __init__(self, app, def __init__(self, app,
import_name=__name__, import_name=__name__,
static_folder='static', static_folder='static',
@ -222,8 +224,11 @@ class DocumentDef(Resource):
devices_view = DevicesDocumentView.as_view('devicesDocumentView', devices_view = DevicesDocumentView.as_view('devicesDocumentView',
definition=self, definition=self,
auth=app.auth) auth=app.auth)
devices_view = app.auth.requires_auth(devices_view) devices_view = app.auth.requires_auth(devices_view)
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self)
stock_view = app.auth.requires_auth(stock_view)
self.add_url_rule('/devices/', defaults=d, view_func=devices_view, methods=get) self.add_url_rule('/devices/', defaults=d, view_func=devices_view, methods=get)
lots_view = LotsDocumentView.as_view('lotsDocumentView', definition=self) lots_view = LotsDocumentView.as_view('lotsDocumentView', definition=self)

View File

@ -370,6 +370,7 @@ class ErasureStandards(Enum):
standards.add(cls.HMG_IS5) standards.add(cls.HMG_IS5)
return standards return standards
@unique @unique
class TransferState(IntEnum): class TransferState(IntEnum):
"""State of transfer for a given Lot of devices. """State of transfer for a given Lot of devices.

View File

@ -5,7 +5,7 @@ from typing import Union
from boltons import urlutils from boltons import urlutils
from citext import CIText from citext import CIText
from flask import g from flask import g
from sqlalchemy import TEXT, Enum as DBEnum from sqlalchemy import TEXT
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy_utils import LtreeType from sqlalchemy_utils import LtreeType
from sqlalchemy_utils.types.ltree import LQUERY from sqlalchemy_utils.types.ltree import LQUERY
@ -14,9 +14,9 @@ from teal.resource import url_for_resource
from ereuse_devicehub.db import create_view, db, exp, f from ereuse_devicehub.db import create_view, db, exp, f
from ereuse_devicehub.resources.device.models import Component, Device from ereuse_devicehub.resources.device.models import Component, Device
from ereuse_devicehub.resources.enums import TransferState
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import User
from ereuse_devicehub.resources.enums import TransferState
class Lot(Thing): class Lot(Thing):
@ -65,15 +65,15 @@ class Lot(Thing):
""" """
deposit = db.Column(db.Integer, check_range('deposit', min=0, max=100), default=0) deposit = db.Column(db.Integer, check_range('deposit', min=0, max=100), default=0)
owner_id = db.Column(UUID(as_uuid=True), owner_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
nullable=False, nullable=False,
default=lambda: g.user.id) default=lambda: g.user.id)
owner = db.relationship(User, primaryjoin=owner_id == User.id) owner = db.relationship(User, primaryjoin=owner_id == User.id)
transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False) transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False)
transfer_state.comment = TransferState.__doc__ transfer_state.comment = TransferState.__doc__
receiver_address = db.Column(CIText(), receiver_address = db.Column(CIText(),
db.ForeignKey(User.ethereum_address), db.ForeignKey(User.ethereum_address),
nullable=True) nullable=True)
receiver = db.relationship(User, primaryjoin=receiver_address == User.ethereum_address) receiver = db.relationship(User, primaryjoin=receiver_address == User.ethereum_address)
deliverynote_address = db.Column(CIText(), nullable=True) deliverynote_address = db.Column(CIText(), nullable=True)

View File

@ -24,13 +24,13 @@ class Lot(Thing):
description = ... # type: Column description = ... # type: Column
all_devices = ... # type: relationship all_devices = ... # type: relationship
parents = ... # type: relationship parents = ... # type: relationship
deposit = ... # type: Column deposit = ... # type: Column
owner_address = ... # type: Column owner_address = ... # type: Column
owner = ... # type: relationship owner = ... # type: relationship
transfer_state = ... # type: Column transfer_state = ... # type: Column
receiver_address = ... # type: Column receiver_address = ... # type: Column
receiver = ... # type: relationship receiver = ... # type: relationship
deliverynote_address = ... # type: Column deliverynote_address = ... # type: Column
def __init__(self, name: str, closed: bool = closed.default.arg) -> None: def __init__(self, name: str, closed: bool = closed.default.arg) -> None:
super().__init__() super().__init__()
@ -43,10 +43,10 @@ class Lot(Thing):
self.all_devices = ... # type: Set[Device] self.all_devices = ... # type: Set[Device]
self.parents = ... # type: Set[Lot] self.parents = ... # type: Set[Lot]
self.children = ... # type: Set[Lot] self.children = ... # type: Set[Lot]
self.owner_address = ... # type: UUID self.owner_address = ... # type: UUID
self.transfer_state = ... self.transfer_state = ...
self.receiver_address = ... # type: str self.receiver_address = ... # type: str
self.deliverynote_address = ... # type: str self.deliverynote_address = ... # type: str
def add_children(self, *children: Union[Lot, uuid.UUID]): def add_children(self, *children: Union[Lot, uuid.UUID]):
pass pass

View File

@ -2,12 +2,12 @@ from marshmallow import fields as f
from teal.marshmallow import SanitizedStr, URL, EnumField from teal.marshmallow import SanitizedStr, URL, EnumField
from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.lot import models as m
from ereuse_devicehub.resources.deliverynote import schemas as s_deliverynote from ereuse_devicehub.resources.deliverynote import schemas as s_deliverynote
from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.enums import TransferState
from ereuse_devicehub.resources.lot import models as m
from ereuse_devicehub.resources.models import STR_SIZE from ereuse_devicehub.resources.models import STR_SIZE
from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.enums import TransferState
class Lot(Thing): class Lot(Thing):
@ -20,7 +20,7 @@ class Lot(Thing):
parents = NestedOn('Lot', many=True, dump_only=True) parents = NestedOn('Lot', many=True, dump_only=True)
url = URL(dump_only=True, description=m.Lot.url.__doc__) url = URL(dump_only=True, description=m.Lot.url.__doc__)
deposit = f.Integer(validate=f.validate.Range(min=0, max=100), deposit = f.Integer(validate=f.validate.Range(min=0, max=100),
description=m.Lot.deposit.__doc__) description=m.Lot.deposit.__doc__)
# author_id = NestedOn(s_user.User,only_query='author_id') # author_id = NestedOn(s_user.User,only_query='author_id')
owner_id = f.UUID(data_key='ownerID') owner_id = f.UUID(data_key='ownerID')
transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment) transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment)

View File

@ -1,24 +1,20 @@
import datetime
import uuid import uuid
from collections import deque from collections import deque
from enum import Enum from enum import Enum
from typing import Dict, List, Set, Union from typing import Dict, List, Set, Union
import marshmallow as ma import marshmallow as ma
import teal.cache
from flask import Response, jsonify, request, g from flask import Response, jsonify, request, g
from marshmallow import Schema as MarshmallowSchema, fields as f from marshmallow import Schema as MarshmallowSchema, fields as f
from sqlalchemy import or_
from teal.marshmallow import EnumField from teal.marshmallow import EnumField
from teal.resource import View from teal.resource import View
from sqlalchemy import or_
from sqlalchemy.orm import joinedload
from ereuse_devicehub import auth
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.query import things_response from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.deliverynote.models import Deliverynote
from ereuse_devicehub.resources.device.models import Device, Computer from ereuse_devicehub.resources.device.models import Device, Computer
from ereuse_devicehub.resources.lot.models import Lot, Path from ereuse_devicehub.resources.lot.models import Lot, Path
from ereuse_devicehub.resources.deliverynote.models import Deliverynote
class LotFormat(Enum): class LotFormat(Enum):
@ -44,7 +40,9 @@ class LotView(View):
return ret return ret
def patch(self, id): def patch(self, id):
patch_schema = self.resource_def.SCHEMA(only=('name', 'description', 'transfer_state', 'receiver_address', 'deposit', 'deliverynote_address', 'devices', 'owner_address'), partial=True) patch_schema = self.resource_def.SCHEMA(only=(
'name', 'description', 'transfer_state', 'receiver_address', 'deposit', 'deliverynote_address', 'devices',
'owner_address'), partial=True)
l = request.get_json(schema=patch_schema) l = request.get_json(schema=patch_schema)
lot = Lot.query.filter_by(id=id).one() lot = Lot.query.filter_by(id=id).one()
device_fields = ['transfer_state', 'receiver_address', 'deposit', 'deliverynote_address', 'owner_address'] device_fields = ['transfer_state', 'receiver_address', 'deposit', 'deliverynote_address', 'owner_address']

View File

@ -2,30 +2,22 @@
""" """
from collections import Iterable
from datetime import datetime from datetime import datetime
from typing import Optional, Set, Union
from uuid import uuid4 from uuid import uuid4
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 g
from sortedcontainers import SortedSet from sqlalchemy import BigInteger, Column, ForeignKey, Unicode
from sqlalchemy import BigInteger, Column, Enum as DBEnum, \
ForeignKey, Integer, Unicode
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.orderinglist import ordering_list from sqlalchemy.orm import backref, relationship
from sqlalchemy.orm import backref, relationship, validates
from sqlalchemy.util import OrderedSet
from teal.db import CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, \ from teal.db import CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, \
POLYMORPHIC_ON, StrictVersionType, URL POLYMORPHIC_ON
from teal.marshmallow import ValidationError
from teal.resource import url_for_resource from teal.resource import url_for_resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.action.models import Action, DisposeProduct, \ from ereuse_devicehub.resources.action.models import EraseBasic, Rate
EraseBasic, Rate, Trade
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user import User from ereuse_devicehub.resources.user import User
@ -83,16 +75,15 @@ class Proof(Thing):
return '<{0.t} {0.id} >'.format(self) return '<{0.t} {0.id} >'.format(self)
class ProofTransfer(JoinedTableMixin, Proof): class ProofTransfer(JoinedTableMixin, Proof):
supplier_id = db.Column(UUID(as_uuid=True), supplier_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
nullable=False, nullable=False,
default=lambda: g.user.id) default=lambda: g.user.id)
supplier = db.relationship(User, primaryjoin=lambda: ProofTransfer.supplier_id == User.id) supplier = db.relationship(User, primaryjoin=lambda: ProofTransfer.supplier_id == User.id)
receiver_id = db.Column(UUID(as_uuid=True), receiver_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
nullable=False) nullable=False)
receiver = db.relationship(User, primaryjoin=lambda: ProofTransfer.receiver_id == User.id) receiver = db.relationship(User, primaryjoin=lambda: ProofTransfer.receiver_id == User.id)
deposit = Column(db.Integer, default=0) deposit = Column(db.Integer, default=0)
@ -103,9 +94,9 @@ class ProofDataWipe(JoinedTableMixin, Proof):
result = Column(db.Boolean, default=False, nullable=False) result = Column(db.Boolean, default=False, nullable=False)
result.comment = """Identifies proof datawipe as a result.""" result.comment = """Identifies proof datawipe as a result."""
proof_author_id = Column(UUID(as_uuid=True), proof_author_id = Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
nullable=False, nullable=False,
default=lambda: g.user.id) default=lambda: g.user.id)
proof_author = relationship(User, primaryjoin=lambda: ProofDataWipe.proof_author_id == User.id) proof_author = relationship(User, primaryjoin=lambda: ProofDataWipe.proof_author_id == User.id)
erasure_id = Column(UUID(as_uuid=True), ForeignKey(EraseBasic.id), nullable=False) erasure_id = Column(UUID(as_uuid=True), ForeignKey(EraseBasic.id), nullable=False)
erasure = relationship(EraseBasic, erasure = relationship(EraseBasic,
@ -119,16 +110,16 @@ class ProofDataWipe(JoinedTableMixin, Proof):
class ProofFunction(JoinedTableMixin, Proof): class ProofFunction(JoinedTableMixin, Proof):
disk_usage = Column(db.Integer, default=0) disk_usage = Column(db.Integer, default=0)
proof_author_id = Column(UUID(as_uuid=True), proof_author_id = Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
nullable=False, nullable=False,
default=lambda: g.user.id) default=lambda: g.user.id)
proof_author = db.relationship(User, primaryjoin=lambda: ProofFunction.proof_author_id == User.id) proof_author = db.relationship(User, primaryjoin=lambda: ProofFunction.proof_author_id == User.id)
rate_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), nullable=False) rate_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), nullable=False)
rate = relationship(Rate, rate = relationship(Rate,
backref=backref('proof_function', backref=backref('proof_function',
lazy=True, lazy=True,
uselist=False, uselist=False,
cascade=CASCADE_OWN), cascade=CASCADE_OWN),
primaryjoin=Rate.id == rate_id) primaryjoin=Rate.id == rate_id)
@ -136,15 +127,15 @@ class ProofReuse(JoinedTableMixin, Proof):
receiver_segment = Column(CIText(), default='', nullable=False) receiver_segment = Column(CIText(), default='', nullable=False)
id_receipt = Column(CIText(), default='', nullable=False) id_receipt = Column(CIText(), default='', nullable=False)
supplier_id = db.Column(UUID(as_uuid=True), supplier_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
# nullable=False, # nullable=False,
# default=lambda: g.user.id) # default=lambda: g.user.id)
nullable=True) nullable=True)
supplier = db.relationship(User, primaryjoin=lambda: ProofReuse.supplier_id == User.id) supplier = db.relationship(User, primaryjoin=lambda: ProofReuse.supplier_id == User.id)
receiver_id = db.Column(UUID(as_uuid=True), receiver_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id), db.ForeignKey(User.id),
# nullable=False) # nullable=False)
nullable=True) nullable=True)
receiver = db.relationship(User, primaryjoin=lambda: ProofReuse.receiver_id == User.id) receiver = db.relationship(User, primaryjoin=lambda: ProofReuse.receiver_id == User.id)
price = Column(db.Integer) price = Column(db.Integer)

View File

@ -1,17 +1,15 @@
from flask import current_app as app from marshmallow import fields as f
from marshmallow import Schema as MarshmallowSchema, ValidationError, fields as f, validates_schema from marshmallow import fields as f
from marshmallow.fields import Boolean, DateTime, Integer, Nested, String, UUID from marshmallow.fields import Boolean, DateTime, Integer, String, UUID
from marshmallow.validate import Length from marshmallow.validate import Length
from sqlalchemy.util import OrderedSet
from teal.marshmallow import SanitizedStr, URL from teal.marshmallow import SanitizedStr, URL
from teal.resource import Schema
from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.proof import models as m
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.action import schemas as s_action from ereuse_devicehub.resources.action import schemas as s_action
from ereuse_devicehub.resources.device import schemas as s_device from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.proof import models as m
from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.user import schemas as s_user from ereuse_devicehub.resources.user import schemas as s_user
@ -19,7 +17,7 @@ class Proof(Thing):
__doc__ = m.Proof.__doc__ __doc__ = m.Proof.__doc__
id = UUID(dump_only=True) id = UUID(dump_only=True)
ethereum_hash = SanitizedStr(default='', validate=Length(max=STR_BIG_SIZE), ethereum_hash = SanitizedStr(default='', validate=Length(max=STR_BIG_SIZE),
data_key="ethereumHash", required=True) data_key="ethereumHash", required=True)
url = URL(dump_only=True, description=m.Proof.url.__doc__) url = URL(dump_only=True, description=m.Proof.url.__doc__)
device_id = Integer(load_only=True, data_key='deviceID') device_id = Integer(load_only=True, data_key='deviceID')
device = NestedOn(s_device.Device, dump_only=True) device = NestedOn(s_device.Device, dump_only=True)

View File

@ -1,18 +1,10 @@
from distutils.version import StrictVersion from distutils.version import StrictVersion
from typing import List
from uuid import UUID
from flask import current_app as app, request, jsonify from flask import current_app as app, request, jsonify
from sqlalchemy.util import OrderedSet
from teal.marshmallow import ValidationError from teal.marshmallow import ValidationError
from teal.resource import View from teal.resource import View
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.action.models import Action, RateComputer, Snapshot, VisualTest
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
from ereuse_devicehub.resources.device.models import Component, Computer
from ereuse_devicehub.resources.enums import SnapshotSoftware
SUPPORTED_WORKBENCH = StrictVersion('11.0') SUPPORTED_WORKBENCH = StrictVersion('11.0')

View File

@ -1,8 +1,8 @@
from contextlib import suppress from contextlib import suppress
from typing import Set from typing import Set
from flask import g
from boltons import urlutils from boltons import urlutils
from flask import g
from sqlalchemy import BigInteger, Column, ForeignKey, UniqueConstraint from sqlalchemy import BigInteger, Column, ForeignKey, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import backref, relationship, validates from sqlalchemy.orm import backref, relationship, validates
@ -13,8 +13,8 @@ from teal.resource import url_for_resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.agent.models import Organization from ereuse_devicehub.resources.agent.models import Organization
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.user.models import User
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User
class Tags(Set['Tag']): class Tags(Set['Tag']):

View File

@ -3,11 +3,11 @@ from sqlalchemy.util import OrderedSet
from teal.marshmallow import SanitizedStr, URL from teal.marshmallow import SanitizedStr, URL
from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.user.schemas import User
from ereuse_devicehub.resources.agent.schemas import Organization from ereuse_devicehub.resources.agent.schemas import Organization
from ereuse_devicehub.resources.device.schemas import Device from ereuse_devicehub.resources.device.schemas import Device
from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.tag import model as m from ereuse_devicehub.resources.tag import model as m
from ereuse_devicehub.resources.user.schemas import User
def without_slash(x: str) -> bool: def without_slash(x: str) -> bool:

View File

@ -3,8 +3,8 @@ from flask_sqlalchemy import Pagination
from teal.marshmallow import ValidationError from teal.marshmallow import ValidationError
from teal.resource import View, url_for_resource from teal.resource import View, url_for_resource
from ereuse_devicehub.db import db
from ereuse_devicehub import auth from ereuse_devicehub import auth
from ereuse_devicehub.db import db
from ereuse_devicehub.query import things_response from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.tag import Tag from ereuse_devicehub.resources.tag import Tag

View File

@ -1,5 +1,13 @@
from werkzeug.exceptions import Unauthorized from werkzeug.exceptions import Unauthorized, Forbidden
class WrongCredentials(Unauthorized): class WrongCredentials(Unauthorized):
description = 'There is not an user with the matching username/password' description = 'There is not an user with the matching username/password'
class InsufficientPermission(Forbidden):
description = (
"You don't have the permissions to access the requested"
"resource. It is either read-protected or not readable by the"
"server."
)

View File

@ -1,10 +1,10 @@
from uuid import uuid4 from uuid import uuid4
from citext import CIText
from flask import current_app as app from flask import current_app as app
from sqlalchemy import Column from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy_utils import EmailType, PasswordType from sqlalchemy_utils import EmailType, PasswordType
from citext import CIText
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.inventory.model import Inventory from ereuse_devicehub.resources.inventory.model import Inventory

View File

@ -17,7 +17,7 @@ class User(Thing):
password = ... # type: Column password = ... # type: Column
token = ... # type: Column token = ... # type: Column
inventories = ... # type: relationship inventories = ... # type: relationship
ethereum_address = ... # type: Column ethereum_address = ... # type: Column
def __init__(self, email: str, password: str = None, def __init__(self, email: str, password: str = None,
inventories: Set[Inventory] = None) -> None: inventories: Set[Inventory] = None) -> None:
@ -28,7 +28,7 @@ class User(Thing):
self.individuals = ... # type: Set[Individual] self.individuals = ... # type: Set[Individual]
self.token = ... # type: UUID self.token = ... # type: UUID
self.inventories = ... # type: Set[Inventory] self.inventories = ... # type: Set[Inventory]
self.ethereum_address = ... # type: str self.ethereum_address = ... # type: str
@property @property
def individual(self) -> Union[Individual, None]: def individual(self) -> Union[Individual, None]:

View File

@ -93,6 +93,18 @@ def user(app: Devicehub) -> UserClient:
return client return client
@pytest.fixture()
def user2(app: Devicehub) -> UserClient:
"""Gets a client with a logged-in dummy user."""
with app.app_context():
password = 'foo'
email = 'foo2@foo.com'
user = create_user(email=email, password=password)
client = UserClient(app, user.email, password, response_wrapper=app.response_class)
client.login()
return client
def create_user(email='foo@foo.com', password='foo') -> User: def create_user(email='foo@foo.com', password='foo') -> User:
user = User(email=email, password=password) user = User(email=email, password=password)
user.individuals.add(Person(name='Timmy')) user.individuals.add(Person(name='Timmy'))

View File

@ -0,0 +1,2 @@
Type,Chassis,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price,Processor,RAM (MB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range
Desktop,Microtower,d1s,d1ml,d1mr,Tue Jul 2 10:35:10 2019,,,,p1ml,0,0,1.0,Very low,1.0,Very low,1.0,Very low,1.0,Very low
1 Type Chassis Serial Number Model Manufacturer Registered in Physical state Trading state Price Processor RAM (MB) Data Storage Size (MB) Rate Range Processor Rate Processor Range RAM Rate RAM Range Data Storage Rate Data Storage Range
2 Desktop Microtower d1s d1ml d1mr Tue Jul 2 10:35:10 2019 p1ml 0 0 1.0 Very low 1.0 Very low 1.0 Very low 1.0 Very low

View File

@ -0,0 +1,34 @@
type: Snapshot
uuid: 123e4567-e89b-12d3-a456-426655440000
version: '11.0'
software: Workbench
elapsed: 4
device:
type: Desktop
chassis: Microtower
serialNumber: d2s
model: d1ml
manufacturer: d1mr
actions:
- type: VisualTest
appearanceRange: A
functionalityRange: B
components:
- type: GraphicCard
serialNumber: gc1s
model: gc1ml
manufacturer: gc1mr
- type: RamModule
serialNumber: rm1s
model: rm1ml
manufacturer: rm1mr
speed: 1333
- type: Processor
serialNumber: p1s
model: p1mla
manufacturer: p1mr
speed: 1.6
actions:
- type: BenchmarkProcessor
rate: 2410
elapsed: 11

View File

@ -1,11 +1,12 @@
import pytest
import teal.marshmallow
from ereuse_utils.test import ANY
import csv import csv
from datetime import datetime from datetime import datetime
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
import pytest
import teal.marshmallow
from ereuse_utils.test import ANY
from ereuse_devicehub.client import Client, UserClient from ereuse_devicehub.client import Client, UserClient
from ereuse_devicehub.resources.action.models import Snapshot from ereuse_devicehub.resources.action.models import Snapshot
from ereuse_devicehub.resources.documents import documents from ereuse_devicehub.resources.documents import documents
@ -227,6 +228,40 @@ def test_export_multiple_different_devices(user: UserClient):
assert fixture_csv == export_csv assert fixture_csv == export_csv
@pytest.mark.mvp
def test_report_devices_stock_control(user: UserClient, user2: UserClient):
"""Test export device information in a csv file."""
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
snapshot2, _ = user2.post(file('basic.snapshot2'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='stock/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
# Open fixture csv and transform to list
with Path(__file__).parent.joinpath('files').joinpath('basic-stock.csv').open() as csv_file:
obj_csv = csv.reader(csv_file)
fixture_csv = list(obj_csv)
assert user.user['id'] != user2.user['id']
assert len(export_csv) == 2
assert isinstance(datetime.strptime(export_csv[1][5], '%c'), datetime), \
'Register in field is not a datetime'
# Pop dates fields from csv lists to compare them
fixture_csv[1] = fixture_csv[1][:5] + fixture_csv[1][6:]
export_csv[1] = export_csv[1][:5] + export_csv[1][6:]
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
assert fixture_csv == export_csv
@pytest.mark.mvp @pytest.mark.mvp
def test_get_document_lots(user: UserClient): def test_get_document_lots(user: UserClient):
"""Tests submitting and retreiving all lots.""" """Tests submitting and retreiving all lots."""