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 flask.globals import _app_ctx_stack, g
from flask_sqlalchemy import SQLAlchemy
from teal.teal import Teal
from teal.db import SchemaSQLAlchemy
from teal.teal import Teal
from ereuse_devicehub.auth import Auth
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.resources.device.search import DeviceSearch
from ereuse_devicehub.resources.inventory import Inventory, InventoryDef
from ereuse_devicehub.resources.user import User
from ereuse_devicehub.templating import Environment
@ -117,7 +116,6 @@ class Devicehub(Teal):
self.db.session.commit()
print('done.')
def _init_db(self, exclude_schema=None) -> bool:
if exclude_schema:
assert isinstance(self.db, SchemaSQLAlchemy)

View File

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

View File

@ -1,13 +1,9 @@
from __future__ import with_statement
import os
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 sqlalchemy import create_engine
from ereuse_devicehub.config import DevicehubConfig
@ -24,10 +20,11 @@ fileConfig(config.config_file_name)
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
# target_metadata = None
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.models import Thing
target_metadata = Thing.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# 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
"""
from alembic import op
from alembic import context
import sqlalchemy as sa
import sqlalchemy_utils
from alembic import context
from alembic import op
from sqlalchemy.dialects import postgresql
import citext
import teal
# revision identifiers, used by Alembic.
revision = 'b9b0ee7d9dca'
@ -27,6 +23,7 @@ def get_inv():
raise ValueError("Inventory value is not specified")
return INV
def upgrade():
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",

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,6 +1,5 @@
from typing import Iterable
import math
from typing import Iterable
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 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.device.models import Computer, DataStorage, Processor, \
RamModule

View File

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

View File

@ -2,7 +2,7 @@ from distutils.version import StrictVersion
from typing import List
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 teal.marshmallow import ValidationError
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.device.models import Component, Computer
from ereuse_devicehub.resources.enums import SnapshotSoftware, Severity
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
SUPPORTED_WORKBENCH = StrictVersion('11.0')
@ -56,6 +57,7 @@ class ActionView(View):
# Note that if we set the device / components into the snapshot
# model object, when we flush them to the db we will flush
# snapshot, and we want to wait to flush snapshot at the end
device = snapshot_json.pop('device') # type: Computer
components = None
if snapshot_json['software'] == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid):
@ -73,6 +75,7 @@ class ActionView(View):
assert not device.actions_one
assert all(not c.actions_one for c in components) if components else True
db_device, remove_actions = resource_def.sync.run(device, components)
del device # Do not use device anymore
snapshot.device = db_device
snapshot.actions |= remove_actions | actions_device # Set actions to snapshot
@ -87,8 +90,11 @@ class ActionView(View):
component.actions_one |= actions
snapshot.actions |= actions
# Compute ratings
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:
rate_computer, price = RateComputer.compute(db_device)
except CannotRate:

View File

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

View File

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

View File

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

View File

@ -1,13 +1,12 @@
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.resources.deliverynote import models as m
from ereuse_devicehub.resources.user import schemas as s_user
from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.enums import TransferState
from ereuse_devicehub.resources.models import STR_SIZE
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):
@ -21,7 +20,7 @@ class Deliverynote(Thing):
receiver = NestedOn(s_user.User, dump_only=True)
date = f.DateTime('iso', required=True)
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')
expected_devices = f.List(f.Dict, required=True, data_key='expectedDevices')
transferred_devices = f.List(f.Integer(), required=False, data_key='transferredDevices')

View File

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

View File

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

View File

@ -141,21 +141,21 @@ class DisplayMixin:
class Computer(DisplayMixin, Device):
components = ... # type: Column
chassis = ... # type: Column
deposit = ... # type: Column
owner_address = ... # type: Column
transfer_state = ... # type: Column
receiver_address = ... # type: Column
deliverynote_address = ... # type: Column
deposit = ... # type: Column
owner_address = ... # type: Column
transfer_state = ... # type: Column
receiver_address = ... # type: Column
deliverynote_address = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.components = ... # type: Set[Component]
self.actions_parent = ... # type: Set[e.Action]
self.chassis = ... # type: ComputerChassis
self.owner_address = ... # type: UUID
self.owner_address = ... # type: UUID
self.transfer_state = ...
self.receiver_address = ... # type: str
self.deliverynote_address = ... # type: str
self.receiver_address = ... # type: str
self.deliverynote_address = ... # type: str
@property
def actions(self) -> List:
@ -219,7 +219,7 @@ class Mobile(Device):
meid = ... # type: Column
ram_size = ... # type: Column
data_storage_size = ... # type: Column
display_size = ... # type: Column
display_size = ... # type: Column
def __init__(self, **kwargs) -> None:
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.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
from ereuse_devicehub.resources.user import schemas as s_user
class Device(Thing):
@ -124,7 +123,7 @@ class Computer(Device):
description=m.Computer.privacy.__doc__)
ethereum_address = SanitizedStr(validate=f.validate.Length(max=42))
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')
owner_id = UUID(data_key='ownerID')
transfer_state = EnumField(enums.TransferState, description=m.Computer.transfer_state.comment)

View File

@ -2,14 +2,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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"
rel="stylesheet"
integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT"
crossorigin="anonymous">
<script src="https://use.fontawesome.com/7553aecc27.js"></script>
<title>Devicehub | {{ device.__format__('t') }}</title>
<meta charset="UTF-8">
<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"
rel="stylesheet"
integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT"
crossorigin="anonymous">
<script src="https://use.fontawesome.com/7553aecc27.js"></script>
<title>Devicehub | {{ device.__format__('t') }}</title>
<style>
/*Sticky footer*/
html {
@ -36,174 +36,174 @@
<a href="https://www.usody.com/" target="_blank">
<h1 align="center">Usody Public Link</h1>
</a>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="page-header col-md-6 col-md-offset-3">
<h1>{{ device.__format__('t') }}<br>
<small>{{ device.__format__('s') }}</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-6">
<ul>
{% for key, value in device.physical_properties.items() %}
<li>{{ key }}: {{ value }}
{% endfor %}
</ul>
{% if isinstance(device, d.Computer) %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th>Range</th>
</tr>
</thead>
<tbody>
{% if device.processor_model %}
<tr>
<td>
CPU {{ device.processor_model }}
</td>
<td>
Processor Rate = {% if device.rate %}
{{ device.rate.processor_range }}
({{ device.rate.processor }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.ram_size %}
<tr>
<td>
RAM {{ device.ram_size // 1000 }} GB
{{ macros.component_type(device.components, 'RamModule') }}
</td>
<td>
RAM Rate = {% if device.rate %}
{{ device.rate.ram_range }}
({{ device.rate.ram }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.data_storage_size %}
<tr>
<td>
Data Storage {{ device.data_storage_size // 1000 }} GB
{{ macros.component_type(device.components, 'SolidStateDrive') }}
{{ macros.component_type(device.components, 'HardDrive') }}
</td>
<td>
Data Storage Rate = {% if device.rate %}
{{ device.rate.data_storage_range }}
({{ device.rate.data_storage }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.graphic_card_model %}
<tr>
<td>
Graphics {{ device.graphic_card_model }}
{{ macros.component_type(device.components, 'GraphicCard') }}
</td>
<td></td>
</tr>
{% endif %}
{% if device.network_speeds %}
<tr>
<td>
Network
{% if device.network_speeds[0] %}
Ethernet
{% if device.network_speeds[0] != None %}
max. {{ device.network_speeds[0] }} Mbps
{% endif %}
{% endif %}
{% if device.network_speeds[0] and device.network_speeds[1] %}
+
{% endif %}
{% if device.network_speeds[1] %}
WiFi
{% if device.network_speeds[1] != None %}
max. {{ device.network_speeds[1] }} Mbps
{% endif %}
{% endif %}
{{ macros.component_type(device.components, 'NetworkAdapter') }}
</td>
<td></td>
</tr>
{% endif %}
{% if device.rate %}
<tr class="active">
<td class="text-right">
Total rate
</td>
<td>
{{ device.rate.rating_range }}
({{ device.rate.rating }})
</td>
</tr>
{% endif %}
{% if device.rate and device.rate.price %}
<tr class="active">
<td class="text-right">
Algorithm price
</td>
<td>
{{ device.rate.price }}
</td>
</tr>
{% endif %}
{% if device.price %}
<tr class="active">
<td class="text-right">
Actual price
</td>
<td>
{{ device.price }}
</td>
</tr>
{% endif %}
</tbody>
</table>
<div class="row">
<div class="page-header col-md-6 col-md-offset-3">
<h1>{{ device.__format__('t') }}<br>
<small>{{ device.__format__('s') }}</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-6">
<ul>
{% for key, value in device.physical_properties.items() %}
<li>{{ key }}: {{ value }}
{% endfor %}
</ul>
{% if isinstance(device, d.Computer) %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th>Range</th>
</tr>
</thead>
<tbody>
{% if device.processor_model %}
<tr>
<td>
CPU {{ device.processor_model }}
</td>
<td>
Processor Rate = {% if device.rate %}
{{ device.rate.processor_range }}
({{ device.rate.processor }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.ram_size %}
<tr>
<td>
RAM {{ device.ram_size // 1000 }} GB
{{ macros.component_type(device.components, 'RamModule') }}
</td>
<td>
RAM Rate = {% if device.rate %}
{{ device.rate.ram_range }}
({{ device.rate.ram }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.data_storage_size %}
<tr>
<td>
Data Storage {{ device.data_storage_size // 1000 }} GB
{{ macros.component_type(device.components, 'SolidStateDrive') }}
{{ macros.component_type(device.components, 'HardDrive') }}
</td>
<td>
Data Storage Rate = {% if device.rate %}
{{ device.rate.data_storage_range }}
({{ device.rate.data_storage }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.graphic_card_model %}
<tr>
<td>
Graphics {{ device.graphic_card_model }}
{{ macros.component_type(device.components, 'GraphicCard') }}
</td>
<td></td>
</tr>
{% endif %}
{% if device.network_speeds %}
<tr>
<td>
Network
{% if device.network_speeds[0] %}
Ethernet
{% if device.network_speeds[0] != None %}
max. {{ device.network_speeds[0] }} Mbps
{% endif %}
{% endif %}
{% if device.network_speeds[0] and device.network_speeds[1] %}
+
{% endif %}
{% if device.network_speeds[1] %}
WiFi
{% if device.network_speeds[1] != None %}
max. {{ device.network_speeds[1] }} Mbps
{% endif %}
{% endif %}
{{ macros.component_type(device.components, 'NetworkAdapter') }}
</td>
<td></td>
</tr>
{% endif %}
{% if device.rate %}
<tr class="active">
<td class="text-right">
Total rate
</td>
<td>
{{ device.rate.rating_range }}
({{ device.rate.rating }})
</td>
</tr>
{% endif %}
{% if device.rate and device.rate.price %}
<tr class="active">
<td class="text-right">
Algorithm price
</td>
<td>
{{ device.rate.price }}
</td>
</tr>
{% endif %}
{% if device.price %}
<tr class="active">
<td class="text-right">
Actual price
</td>
<td>
{{ device.price }}
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<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>
<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>
<footer class="container-fluid footer">
<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.json import jsonify
from flask_sqlalchemy import Pagination
from marshmallow import fields, fields as f, validate as v, ValidationError, \
Schema as MarshmallowSchema
from marshmallow import fields, fields as f, validate as v, Schema as MarshmallowSchema
from teal import query
from teal.cache import cache
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.models import Device, Manufacturer, Computer
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.tag.model import Tag
from ereuse_devicehub.resources.enums import SnapshotSoftware
class OfType(f.Str):
@ -103,14 +102,15 @@ class DeviceView(View):
if isinstance(dev, Computer):
resource_def = app.resources['Computer']
# 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)
# TODO check how to handle the 'actions_one'
json.pop('actions_one')
if not dev:
raise ValueError('Device non existent')
for key, value in json.items():
setattr(dev,key,value)
setattr(dev, key, value)
db.session.commit()
return Response(status=204)
raise ValueError('Cannot patch a non computer')
@ -157,20 +157,20 @@ class DeviceView(View):
query = self.visibility_filter(query)
return query.filter(*args['filter']).order_by(*args['sort'])
def visibility_filter(self, query):
filterqs = request.args.get('filter', None)
if (filterqs and
'lot' not in filterqs):
'lot' not in filterqs):
query = query.filter((Computer.id == Device.id), (Computer.owner_id == g.user.id))
pass
return query
class DeviceMergeView(View):
class DeviceMergeView(View):
"""View for merging two devices
Ex. ``device/<id>/merge/id=X``.
"""
class FindArgs(MarshmallowSchema):
id = fields.Integer()
@ -197,10 +197,13 @@ class DeviceMergeView(View):
This operation is highly costly as it forces refreshing
many models in session.
"""
snapshots = sorted(filterfalse(lambda x: not isinstance(x, actions.Snapshot), (base_device.actions + with_device.actions)))
workbench_snapshots = [ s for s in snapshots if 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]
snapshots = sorted(
filterfalse(lambda x: not isinstance(x, actions.Snapshot), (base_device.actions + with_device.actions)))
workbench_snapshots = [s for s in snapshots if
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
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)]

View File

@ -30,31 +30,23 @@ class DeviceRow(OrderedDict):
self['Tag 1'] = self['Tag 2'] = self['Tag 3'] = ''
for i, tag in zip(range(1, 3), device.tags):
self['Tag {}'.format(i)] = format(tag)
self['Serial Number'] = device.serial_number
self['Model'] = device.model
self['Manufacturer'] = device.manufacturer
# self['State'] = device.last_action_of()
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:
except LookupError:
self['Physical state'] = ''
try:
self['Trading state'] = device.last_action_of(*states.Trading.actions()).t
except:
except LookupError:
self['Trading state'] = ''
try:
self['Price'] = device.price
except:
self['Price'] = ''
self['Price'] = convert_none_to_empty_str(device.price)
if isinstance(device, d.Computer):
self['Processor'] = device.processor_model
self['RAM (MB)'] = device.ram_size
self['Data Storage Size (MB)'] = 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
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
@ -135,3 +127,47 @@ class DeviceRow(OrderedDict):
self['{} {} Speed (MHz)'.format(type, i)] = component.speed
# 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 teal.marshmallow
from boltons import urlutils
from flask import make_response
from flask import make_response, g
from teal.cache import cache
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.device import models as devs
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.lot import LotView
from ereuse_devicehub.resources.lot.models import Lot
from flask import g, request
class Format(enum.Enum):
HTML = 'HTML'
@ -110,7 +111,7 @@ class DocumentView(DeviceView):
class DevicesDocumentView(DeviceView):
@cache(datetime.timedelta(minutes=1))
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)
def generate_post_csv(self, query):
@ -169,7 +170,7 @@ class LotRow(OrderedDict):
class StockDocumentView(DeviceView):
# @cache(datetime.timedelta(minutes=1))
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)
def generate_post_csv(self, query):
@ -178,13 +179,13 @@ class StockDocumentView(DeviceView):
cw = csv.writer(data)
first = True
for device in query:
d = DeviceRow(device)
d = StockRow(device)
if first:
cw.writerow(d.keys())
first = False
cw.writerow(d.values())
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'
return output
@ -194,6 +195,7 @@ class DocumentDef(Resource):
SCHEMA = None
VIEW = None # We do not want to create default / documents endpoint
AUTH = False
def __init__(self, app,
import_name=__name__,
static_folder='static',
@ -222,8 +224,11 @@ class DocumentDef(Resource):
devices_view = DevicesDocumentView.as_view('devicesDocumentView',
definition=self,
auth=app.auth)
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)
lots_view = LotsDocumentView.as_view('lotsDocumentView', definition=self)

View File

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

View File

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

View File

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

View File

@ -2,12 +2,12 @@ from marshmallow import fields as f
from teal.marshmallow import SanitizedStr, URL, EnumField
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.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.schemas import Thing
from ereuse_devicehub.resources.enums import TransferState
class Lot(Thing):
@ -20,7 +20,7 @@ class Lot(Thing):
parents = NestedOn('Lot', many=True, dump_only=True)
url = URL(dump_only=True, description=m.Lot.url.__doc__)
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')
owner_id = f.UUID(data_key='ownerID')
transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment)

View File

@ -1,24 +1,20 @@
import datetime
import uuid
from collections import deque
from enum import Enum
from typing import Dict, List, Set, Union
import marshmallow as ma
import teal.cache
from flask import Response, jsonify, request, g
from marshmallow import Schema as MarshmallowSchema, fields as f
from sqlalchemy import or_
from teal.marshmallow import EnumField
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.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.lot.models import Lot, Path
from ereuse_devicehub.resources.deliverynote.models import Deliverynote
class LotFormat(Enum):
@ -44,7 +40,9 @@ class LotView(View):
return ret
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)
lot = Lot.query.filter_by(id=id).one()
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 typing import Optional, Set, Union
from uuid import uuid4
from boltons import urlutils
from citext import CIText
from flask import current_app as app, g
from sortedcontainers import SortedSet
from sqlalchemy import BigInteger, Column, Enum as DBEnum, \
ForeignKey, Integer, Unicode
from flask import g
from sqlalchemy import BigInteger, Column, ForeignKey, Unicode
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy.orm import backref, relationship, validates
from sqlalchemy.util import OrderedSet
from sqlalchemy.orm import backref, relationship
from teal.db import CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, \
POLYMORPHIC_ON, StrictVersionType, URL
from teal.marshmallow import ValidationError
POLYMORPHIC_ON
from teal.resource import url_for_resource
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.action.models import Action, DisposeProduct, \
EraseBasic, Rate, Trade
from ereuse_devicehub.resources.action.models import EraseBasic, Rate
from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user import User
@ -83,16 +75,15 @@ class Proof(Thing):
return '<{0.t} {0.id} >'.format(self)
class ProofTransfer(JoinedTableMixin, Proof):
supplier_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id)
db.ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id)
supplier = db.relationship(User, primaryjoin=lambda: ProofTransfer.supplier_id == User.id)
receiver_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id),
nullable=False)
db.ForeignKey(User.id),
nullable=False)
receiver = db.relationship(User, primaryjoin=lambda: ProofTransfer.receiver_id == User.id)
deposit = Column(db.Integer, default=0)
@ -103,9 +94,9 @@ class ProofDataWipe(JoinedTableMixin, Proof):
result = Column(db.Boolean, default=False, nullable=False)
result.comment = """Identifies proof datawipe as a result."""
proof_author_id = Column(UUID(as_uuid=True),
db.ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id)
db.ForeignKey(User.id),
nullable=False,
default=lambda: g.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 = relationship(EraseBasic,
@ -119,16 +110,16 @@ class ProofDataWipe(JoinedTableMixin, Proof):
class ProofFunction(JoinedTableMixin, Proof):
disk_usage = Column(db.Integer, default=0)
proof_author_id = Column(UUID(as_uuid=True),
db.ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id)
db.ForeignKey(User.id),
nullable=False,
default=lambda: g.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 = relationship(Rate,
backref=backref('proof_function',
lazy=True,
uselist=False,
cascade=CASCADE_OWN),
backref=backref('proof_function',
lazy=True,
uselist=False,
cascade=CASCADE_OWN),
primaryjoin=Rate.id == rate_id)
@ -136,15 +127,15 @@ class ProofReuse(JoinedTableMixin, Proof):
receiver_segment = Column(CIText(), default='', nullable=False)
id_receipt = Column(CIText(), default='', nullable=False)
supplier_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id),
# nullable=False,
# default=lambda: g.user.id)
nullable=True)
db.ForeignKey(User.id),
# nullable=False,
# default=lambda: g.user.id)
nullable=True)
supplier = db.relationship(User, primaryjoin=lambda: ProofReuse.supplier_id == User.id)
receiver_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id),
# nullable=False)
nullable=True)
db.ForeignKey(User.id),
# nullable=False)
nullable=True)
receiver = db.relationship(User, primaryjoin=lambda: ProofReuse.receiver_id == User.id)
price = Column(db.Integer)

View File

@ -1,17 +1,15 @@
from flask import current_app as app
from marshmallow import Schema as MarshmallowSchema, ValidationError, fields as f, validates_schema
from marshmallow.fields import Boolean, DateTime, Integer, Nested, String, UUID
from marshmallow import fields as f
from marshmallow import fields as f
from marshmallow.fields import Boolean, DateTime, Integer, String, UUID
from marshmallow.validate import Length
from sqlalchemy.util import OrderedSet
from teal.marshmallow import SanitizedStr, URL
from teal.resource import Schema
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.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
@ -19,7 +17,7 @@ class Proof(Thing):
__doc__ = m.Proof.__doc__
id = UUID(dump_only=True)
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__)
device_id = Integer(load_only=True, data_key='deviceID')
device = NestedOn(s_device.Device, dump_only=True)

View File

@ -1,18 +1,10 @@
from distutils.version import StrictVersion
from typing import List
from uuid import UUID
from flask import current_app as app, request, jsonify
from sqlalchemy.util import OrderedSet
from teal.marshmallow import ValidationError
from teal.resource import View
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')

View File

@ -1,8 +1,8 @@
from contextlib import suppress
from typing import Set
from flask import g
from boltons import urlutils
from flask import g
from sqlalchemy import BigInteger, Column, ForeignKey, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
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.resources.agent.models import Organization
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.user.models import User
class Tags(Set['Tag']):

View File

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

View File

@ -3,8 +3,8 @@ from flask_sqlalchemy import Pagination
from teal.marshmallow import ValidationError
from teal.resource import View, url_for_resource
from ereuse_devicehub.db import db
from ereuse_devicehub import auth
from ereuse_devicehub.db import db
from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.device.models import Device
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):
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 citext import CIText
from flask import current_app as app
from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy_utils import EmailType, PasswordType
from citext import CIText
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.inventory.model import Inventory

View File

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

View File

@ -93,6 +93,18 @@ def user(app: Devicehub) -> UserClient:
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:
user = User(email=email, password=password)
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
from datetime import datetime
from io import StringIO
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.resources.action.models import Snapshot
from ereuse_devicehub.resources.documents import documents
@ -227,6 +228,40 @@ def test_export_multiple_different_devices(user: UserClient):
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
def test_get_document_lots(user: UserClient):
"""Tests submitting and retreiving all lots."""