Merge branch 'testing' into feature/endpoints

This commit is contained in:
Cayo Puigdefabregas 2020-08-18 12:15:50 +02:00
commit 3e653ee190
37 changed files with 1190 additions and 557 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

@ -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

@ -457,5 +457,3 @@ class MigrateFrom(Migrate):
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,20 +1,19 @@
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):

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):

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):

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

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):

View File

@ -3,11 +3,10 @@ import uuid
from itertools import filterfalse from itertools import filterfalse
import marshmallow import marshmallow
from flask import 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):
@ -64,7 +63,8 @@ class Filters(query.Query):
# todo This part of the query is really slow # todo This part of the query is really slow
# And forces usage of distinct, as it returns many rows # And forces usage of distinct, as it returns many rows
# due to having multiple paths to the same # due to having multiple paths to the same
lot = query.Join(Device.id == LotDeviceDescendants.device_id, LotQ) lot = query.Join((Device.id == LotDeviceDescendants.device_id),
LotQ)
class Sorting(query.Sort): class Sorting(query.Sort):
@ -102,7 +102,8 @@ 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')
@ -153,14 +154,23 @@ class DeviceView(View):
).order_by( ).order_by(
search.Search.rank(properties, search_p) + search.Search.rank(tags, search_p) search.Search.rank(properties, search_p) + search.Search.rank(tags, search_p)
) )
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):
filterqs = request.args.get('filter', None)
if (filterqs and
'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 """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()
@ -187,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)))
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_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] 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,27 +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)
rate = device.rate rate = device.rate
if rate: if rate:
self['Rate'] = rate.rating self['Rate'] = rate.rating
@ -137,35 +133,27 @@ class StockRow(OrderedDict):
def __init__(self, device: d.Device) -> None: def __init__(self, device: d.Device) -> None:
super().__init__() super().__init__()
self.device = device self.device = device
self['Type'] = device.t self['Type'] = convert_none_to_empty_str(device.t)
if isinstance(device, d.Computer): if isinstance(device, d.Computer):
self['Chassis'] = device.chassis self['Chassis'] = device.chassis
else: else:
self['Chassis'] = '' self['Chassis'] = ''
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['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 self['Processor'] = convert_none_to_empty_str(device.processor_model)
except: self['RAM (MB)'] = convert_none_to_empty_str(device.ram_size)
self['Price'] = '' self['Data Storage Size (MB)'] = convert_none_to_empty_str(device.data_storage_size)
try:
self['Processor'] = device.processor_model
self['RAM (MB)'] = device.ram_size
self['Data Storage Size (MB)'] = device.data_storage_size
except:
self['Processor'] = ''
self['RAM (MB)'] = ''
self['Data Storage Size (MB)'] = ''
rate = device.rate rate = device.rate
if rate: if rate:
self['Rate'] = rate.rating self['Rate'] = rate.rating
@ -177,3 +165,9 @@ class StockRow(OrderedDict):
self['RAM Range'] = rate.ram_range self['RAM Range'] = rate.ram_range
self['Data Storage Rate'] = rate.data_storage self['Data Storage Rate'] = rate.data_storage
self['Data Storage Range'] = rate.data_storage_range self['Data Storage Range'] = rate.data_storage_range
def convert_none_to_empty_str(s):
if s is None:
return ''
return s

View File

@ -18,10 +18,10 @@ 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, StockRow
class Format(enum.Enum): class Format(enum.Enum):
HTML = 'HTML' HTML = 'HTML'
PDF = 'PDF' PDF = 'PDF'
@ -107,7 +107,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):
@ -172,19 +172,25 @@ class DocumentDef(Resource):
get = {'GET'} get = {'GET'}
view = DocumentView.as_view('main', definition=self, auth=app.auth) view = DocumentView.as_view('main', definition=self, auth=app.auth)
# TODO @cayop This two lines never pass
if self.AUTH: if self.AUTH:
view = app.auth.requires_auth(view) view = app.auth.requires_auth(view)
self.add_url_rule('/erasures/', defaults=d, view_func=view, methods=get) self.add_url_rule('/erasures/', defaults=d, view_func=view, methods=get)
self.add_url_rule('/erasures/<{}:{}>'.format(self.ID_CONVERTER.value, self.ID_NAME), self.add_url_rule('/erasures/<{}:{}>'.format(self.ID_CONVERTER.value, self.ID_NAME),
view_func=view, methods=get) view_func=view, methods=get)
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)
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self) stock_view = StockDocumentView.as_view('stockDocumentView', definition=self)
stock_view = app.auth.requires_auth(stock_view) stock_view = app.auth.requires_auth(stock_view)
if self.AUTH:
devices_view = app.auth.requires_auth(devices_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)
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self, auth=app.auth)
stock_view = app.auth.requires_auth(stock_view)
self.add_url_rule('/stock/', defaults=d, view_func=stock_view, methods=get) self.add_url_rule('/stock/', defaults=d, view_func=stock_view, methods=get)

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):

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):

View File

@ -1,19 +1,18 @@
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
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.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.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
@ -41,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']
@ -85,15 +86,23 @@ class LotView(View):
} }
else: else:
query = Lot.query query = Lot.query
query = self.visibility_filter(query)
if args['search']: if args['search']:
query = query.filter(Lot.name.ilike(args['search'] + '%')) query = query.filter(Lot.name.ilike(args['search'] + '%'))
lots = query.paginate(per_page=6 if args['search'] else 30) lots = query.paginate(per_page=6 if args['search'] else 30)
return things_response( return things_response(
self.schema.dump(lots.items, many=True, nested=0), self.schema.dump(lots.items, many=True, nested=2),
lots.page, lots.per_page, lots.total, lots.prev_num, lots.next_num lots.page, lots.per_page, lots.total, lots.prev_num, lots.next_num
) )
return jsonify(ret) return jsonify(ret)
def visibility_filter(self, query):
query = query.outerjoin(Deliverynote) \
.filter(or_(Deliverynote.receiver_address == g.user.email,
Deliverynote.supplier_email == g.user.email,
Lot.owner_id == g.user.id))
return query
def delete(self, id): def delete(self, id):
lot = Lot.query.filter_by(id=id).one() lot = Lot.query.filter_by(id=id).one()
lot.delete() lot.delete()

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,7 +75,6 @@ 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),

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

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

@ -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
@ -18,7 +19,7 @@ def test_erasure_certificate_public_one(user: UserClient, client: Client):
s = file('erase-sectors.snapshot') s = file('erase-sectors.snapshot')
snapshot, _ = user.post(s, res=Snapshot) snapshot, _ = user.post(s, res=Snapshot)
doc, response = client.get(res=documents.DocumentDef.t, doc, response = user.get(res=documents.DocumentDef.t,
item='erasures/{}'.format(snapshot['device']['id']), item='erasures/{}'.format(snapshot['device']['id']),
accept=ANY) accept=ANY)
assert 'html' in response.content_type assert 'html' in response.content_type
@ -145,7 +146,7 @@ def test_export_empty(user: UserClient):
assert len(export_csv) == 0, 'Csv is not empty' assert len(export_csv) == 0, 'Csv is not empty'
@pytest.mark.mvp @pytest.mark.xfail(reason='Feature not developed (Beta)')
def test_export_computer_monitor(user: UserClient): def test_export_computer_monitor(user: UserClient):
"""Test a export device type computer monitor.""" """Test a export device type computer monitor."""
snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot) snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
@ -170,6 +171,7 @@ def test_export_computer_monitor(user: UserClient):
assert fixture_csv[1] == export_csv[1], 'Component information are not equal' assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
@pytest.mark.xfail(reason='Feature not developed (Beta)')
def test_export_keyboard(user: UserClient): def test_export_keyboard(user: UserClient):
"""Test a export device type keyboard.""" """Test a export device type keyboard."""
snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot) snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
@ -193,7 +195,7 @@ def test_export_keyboard(user: UserClient):
assert fixture_csv[1] == export_csv[1], 'Component information are not equal' assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
@pytest.mark.mvp @pytest.mark.xfail(reason='Feature not developed (Beta)')
def test_export_multiple_different_devices(user: UserClient): def test_export_multiple_different_devices(user: UserClient):
"""Test function 'Export' of multiple different device types (like """Test function 'Export' of multiple different device types (like
computers, keyboards, monitors, etc..) computers, keyboards, monitors, etc..)