diff --git a/ereuse_devicehub/resources/deliverynote/views.py b/ereuse_devicehub/resources/deliverynote/views.py index 0b0cb35a..02a1ff8b 100644 --- a/ereuse_devicehub/resources/deliverynote/views.py +++ b/ereuse_devicehub/resources/deliverynote/views.py @@ -16,6 +16,7 @@ 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): @@ -41,8 +42,18 @@ class DeliverynoteView(View): 'ethereum_address'), partial=True) d = request.get_json(schema=patch_schema) dlvnote = Deliverynote.query.filter_by(id=id).one() + # device_fields = ['transfer_state', 'deliverynote_address'] + # computers = [x for x in dlvnote.transferred_devices if isinstance(x, Computer)] for key, value in d.items(): setattr(dlvnote, key, value) + # Transalate ethereum_address attribute + # devKey = key + # if key == 'ethereum_address': + # devKey = 'deliverynote_address' + # if devKey in device_fields: + # for dev in computers: + # setattr(dev, devKey, value) + db.session.commit() return Response(status=204) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 5b9ba304..0d5f11e9 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -52,22 +52,22 @@ class Device(Thing): """ type = Column(Unicode(STR_SM_SIZE), nullable=False) hid = Column(Unicode(), check_lower('hid'), unique=False) - hid.comment = """The Hardware ID (HID) is the unique ID traceability + hid.comment = """The Hardware ID (HID) is the unique ID traceability systems use to ID a device globally. This field is auto-generated from Devicehub using literal identifiers from the device, - so it can re-generated *offline*. + so it can re-generated *offline*. """ + HID_CONVERSION_DOC model = Column(Unicode(), check_lower('model')) model.comment = """The model of the device in lower case. - + The model is the unambiguous, as technical as possible, denomination - for the product. This field, among others, is used to identify + for the product. This field, among others, is used to identify the product. """ manufacturer = Column(Unicode(), check_lower('manufacturer')) manufacturer.comment = """The normalized name of the manufacturer, in lower case. - + Although as of now Devicehub does not enforce normalization, users can choose a list of normalized manufacturer names from the own ``/manufacturers`` REST endpoint. @@ -76,7 +76,7 @@ class Device(Thing): serial_number.comment = """The serial number of the device in lower case.""" brand = db.Column(CIText()) brand.comment = """A naming for consumers. This field can represent - several models, so it can be ambiguous, and it is not used to + several models, so it can be ambiguous, and it is not used to identify the product. """ generation = db.Column(db.SmallInteger, check_range('generation', 0)) @@ -94,13 +94,13 @@ class Device(Thing): color = Column(ColorType) color.comment = """The predominant color of the device.""" production_date = Column(db.DateTime) - production_date.comment = """The date of production of the device. + production_date.comment = """The date of production of the device. This is timezone naive, as Workbench cannot report this data with timezone information. """ variant = Column(db.CIText()) variant.comment = """A variant or sub-model of the device.""" sku = db.Column(db.CIText()) - sku.comment = """The Stock Keeping Unit (SKU), i.e. a + sku.comment = """The Stock Keeping Unit (SKU), i.e. a merchant-specific identifier for a product or service. """ image = db.Column(db.URL) @@ -311,17 +311,17 @@ class DisplayMixin: size = Column(Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=False) size.comment = """The size of the monitor in inches.""" technology = Column(DBEnum(DisplayTech)) - technology.comment = """The technology the monitor uses to display + technology.comment = """The technology the monitor uses to display the image. """ resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000), nullable=False) - resolution_width.comment = """The maximum horizontal resolution the + resolution_width.comment = """The maximum horizontal resolution the monitor can natively support in pixels. """ resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000), nullable=False) - resolution_height.comment = """The maximum vertical resolution the + resolution_height.comment = """The maximum vertical resolution the monitor can natively support in pixels. """ refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000)) @@ -381,18 +381,19 @@ class Computer(Device): It is a subset of the Linux definition of DMI / DMI decode. """ - deposit = Column(Integer, check_range('deposit', min=0, max=100), default=0) - owner_address = db.Column(CIText(), - db.ForeignKey(User.ethereum_address), + ethereum_address = Column(CIText(), unique=True, default=None) + 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.ethereum_address) - author = db.relationship(User, primaryjoin=owner_address == User.ethereum_address) + 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_address = db.Column(CIText(), - db.ForeignKey(User.ethereum_address), + receiver_id = db.Column(UUID(as_uuid=True), + db.ForeignKey(User.id), nullable=True) - receiver = db.relationship(User, primaryjoin=receiver_address == User.ethereum_address) + receiver = db.relationship(User, primaryjoin=receiver_id == User.id) deliverynote_address = db.Column(CIText(), nullable=True) def __init__(self, chassis, **kwargs) -> None: @@ -500,11 +501,11 @@ class Mobile(Device): id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) imei = Column(BigInteger) - imei.comment = """The International Mobile Equipment Identity of + imei.comment = """The International Mobile Equipment Identity of the smartphone as an integer. """ meid = Column(Unicode) - meid.comment = """The Mobile Equipment Identifier as a hexadecimal + meid.comment = """The Mobile Equipment Identifier as a hexadecimal string. """ ram_size = db.Column(db.Integer, check_range('ram_size', min=128, max=36000)) @@ -640,7 +641,7 @@ class Motherboard(JoinedComponentTableMixin, Component): class NetworkMixin: speed = Column(SmallInteger, check_range('speed', min=10, max=10000)) - speed.comment = """The maximum speed this network adapter can handle, + speed.comment = """The maximum speed this network adapter can handle, in mbps. """ wireless = Column(Boolean, nullable=False, default=False) @@ -699,7 +700,7 @@ class Battery(JoinedComponentTableMixin, Component): technology = db.Column(db.Enum(BatteryTechnology)) size = db.Column(db.Integer, nullable=False) size.comment = """Maximum battery capacity by design, in mAh. - + Use BatteryTest's "size" to get the actual size of the battery. """ diff --git a/ereuse_devicehub/resources/device/schemas.py b/ereuse_devicehub/resources/device/schemas.py index 2eddfdc1..70eeba91 100644 --- a/ereuse_devicehub/resources/device/schemas.py +++ b/ereuse_devicehub/resources/device/schemas.py @@ -122,12 +122,13 @@ class Computer(Device): dump_only=True, collection_class=set, 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__) # author_id = NestedOn(s_user.User,only_query='author_id') - owner_address = SanitizedStr(validate=f.validate.Length(max=42)) + owner_id = UUID(data_key='ownerID') transfer_state = EnumField(enums.TransferState, description=m.Computer.transfer_state.comment) - receiver_address = SanitizedStr(validate=f.validate.Length(max=42)) + receiver_id = UUID(data_key='receiverID') deliverynote_address = SanitizedStr(validate=f.validate.Length(max=42)) diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 665acd68..2a7a5efd 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -1,10 +1,10 @@ import datetime import marshmallow -from flask import current_app as app, render_template, request +from flask import 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 +from marshmallow import fields, fields as f, validate as v, ValidationError from teal import query from teal.cache import cache from teal.resource import View @@ -15,7 +15,7 @@ from ereuse_devicehub.query import SearchQueryParser, things_response from ereuse_devicehub.resources import search from ereuse_devicehub.resources.action import models as actions from ereuse_devicehub.resources.device import states -from ereuse_devicehub.resources.device.models import Device, Manufacturer +from ereuse_devicehub.resources.device.models import Device, Manufacturer, Computer from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.lot.models import LotDeviceDescendants from ereuse_devicehub.resources.tag.model import Tag @@ -93,6 +93,23 @@ class DeviceView(View): """ return super().get(id) + def patch(self, id): + dev = Device.query.filter_by(id=id).one() + 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) + 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) + db.session.commit() + return Response(status=204) + raise ValueError('Cannot patch a non computer') + def one(self, id: int): """Gets one device.""" if not request.authorization: @@ -110,7 +127,7 @@ class DeviceView(View): return self.schema.jsonify(device) @auth.Auth.requires_auth - @cache(datetime.timedelta(minutes=1)) + # @cache(datetime.timedelta(minutes=1)) def find(self, args: dict): """Gets many devices.""" # Compute query diff --git a/ereuse_devicehub/resources/lot/models.py b/ereuse_devicehub/resources/lot/models.py index bc1a4aa9..cfb02f3c 100644 --- a/ereuse_devicehub/resources/lot/models.py +++ b/ereuse_devicehub/resources/lot/models.py @@ -33,8 +33,8 @@ class Lot(Thing): lazy=True, collection_class=set) """The **children** devices that the lot has. - - Note that the lot can have more devices, if they are inside + + Note that the lot can have more devices, if they are inside descendant lots. """ parents = db.relationship(lambda: Lot, @@ -64,11 +64,11 @@ class Lot(Thing): descendants. """ deposit = db.Column(db.Integer, check_range('deposit', min=0, max=100), default=0) - owner_address = db.Column(CIText(), - db.ForeignKey(User.ethereum_address), + owner_id = db.Column(UUID(as_uuid=True), + db.ForeignKey(User.id), nullable=False, - default=lambda: g.user.ethereum_address) - owner = db.relationship(User, primaryjoin=owner_address == User.ethereum_address) + 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(), diff --git a/ereuse_devicehub/resources/lot/schemas.py b/ereuse_devicehub/resources/lot/schemas.py index f027622b..d62eb6c6 100644 --- a/ereuse_devicehub/resources/lot/schemas.py +++ b/ereuse_devicehub/resources/lot/schemas.py @@ -22,7 +22,7 @@ class Lot(Thing): deposit = f.Integer(validate=f.validate.Range(min=0, max=100), description=m.Lot.deposit.__doc__) # author_id = NestedOn(s_user.User,only_query='author_id') - owner_address = SanitizedStr(validate=f.validate.Length(max=42)) + owner_id = f.UUID(data_key='ownerID') transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment) receiver_address = SanitizedStr(validate=f.validate.Length(max=42)) deliverynote = NestedOn(s_deliverynote.Deliverynote, dump_only=True) diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index c6a6015f..3590ba49 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -59,7 +59,7 @@ class LotView(View): lot = Lot.query.filter_by(id=id).one() # type: Lot return self.schema.jsonify(lot, nested=2) - @teal.cache.cache(datetime.timedelta(minutes=5)) + # @teal.cache.cache(datetime.timedelta(minutes=5)) def find(self, args: dict): """Gets lots. diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 60e18ae3..2562b446 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -26,7 +26,9 @@ 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.device.models import Device from ereuse_devicehub.resources.models import Thing +from ereuse_devicehub.resources.user import User class JoinedTableMixin: @@ -42,7 +44,14 @@ class Proof(Thing): """ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) type = Column(Unicode, nullable=False) - ethereum_hashes = Column(CIText(), default='', nullable=False) + ethereum_hash = Column(CIText(), default='', nullable=False) + device_id = db.Column(BigInteger, + db.ForeignKey(Device.id), + nullable=False) + device = db.relationship(Device, + backref=db.backref('proofs_device', uselist=True, lazy=True), + lazy=True, + primaryjoin=Device.id == device_id) @property def url(self) -> urlutils.URL: @@ -74,46 +83,76 @@ class Proof(Thing): return '<{0.t} {0.id} >'.format(self) + class ProofTransfer(JoinedTableMixin, Proof): - transfer_id = Column(UUID, ForeignKey(Trade.id), nullable=False) - transfer = relationship(DisposeProduct, - backref=backref("proof_transfer", - lazy=True, - cascade=CASCADE_OWN), - uselist=False, - primaryjoin=DisposeProduct.id == transfer_id) + supplier_id = db.Column(UUID(as_uuid=True), + 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) + receiver = db.relationship(User, primaryjoin=lambda: ProofTransfer.receiver_id == User.id) + deposit = Column(db.Integer, default=0) class ProofDataWipe(JoinedTableMixin, Proof): - erasure_type = Column(CIText(), default='', nullable=False) + # erasure_type = Column(CIText(), default='', nullable=False) date = Column(db.DateTime, nullable=False, default=datetime.utcnow) 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) + 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, backref=backref('proof_datawipe', lazy=True, + uselist=False, cascade=CASCADE_OWN), primaryjoin=EraseBasic.id == erasure_id) class ProofFunction(JoinedTableMixin, Proof): disk_usage = Column(db.Integer, default=0) - rate_id = Column(UUID, ForeignKey(Rate.id), nullable=False) + proof_author_id = Column(UUID(as_uuid=True), + 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), primaryjoin=Rate.id == rate_id) 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) + 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) + receiver = db.relationship(User, primaryjoin=lambda: ProofReuse.receiver_id == User.id) price = Column(db.Integer) class ProofRecycling(JoinedTableMixin, Proof): collection_point = Column(CIText(), default='', nullable=False) - date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + date = Column(db.DateTime, nullable=False, default=datetime.utcnow) contact = Column(CIText(), default='', nullable=False) ticket = Column(CIText(), default='', nullable=False) gps_location = Column(CIText(), default='', nullable=False) + recycler_code = Column(CIText(), default='', nullable=False) diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 4859150a..de18a41f 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -1,5 +1,5 @@ from flask import current_app as app -from marshmallow import Schema as MarshmallowSchema, ValidationError, validates_schema +from marshmallow import Schema as MarshmallowSchema, ValidationError, fields as f, validates_schema from marshmallow.fields import Boolean, DateTime, Integer, Nested, String, UUID from marshmallow.validate import Length from sqlalchemy.util import OrderedSet @@ -11,46 +11,62 @@ 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.user import schemas as s_user class Proof(Thing): __doc__ = m.Proof.__doc__ id = UUID(dump_only=True) - ethereum_hashes = SanitizedStr(default='', validate=Length(max=STR_BIG_SIZE), - data_key="ethereumHashes") + ethereum_hash = SanitizedStr(default='', validate=Length(max=STR_BIG_SIZE), + 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) class ProofTransfer(Proof): __doc__ = m.ProofTransfer.__doc__ - transfer = NestedOn(s_action.DisposeProduct, - required=True, - only_query='id') + deposit = Integer(validate=f.validate.Range(min=0, max=100)) + supplier_id = UUID(load_only=True, required=True, data_key='supplierID') + receiver_id = UUID(load_only=True, required=True, data_key='receiverID') class ProofDataWipe(Proof): __doc__ = m.ProofDataWipe.__doc__ - erasure_type = SanitizedStr(default='') + # erasure_type = String(default='', data_key='erasureType') date = DateTime('iso', required=True) - result = Boolean(missing=False) - erasure = NestedOn(s_action.EraseBasic, only_query='id') + result = Boolean(required=True) + proof_author_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), + load_only=True, required=True, data_key='proofAuthorID') + proof_author = NestedOn(s_user.User, dump_only=True) + erasure = NestedOn(s_action.EraseBasic, only_query='id', data_key='erasureID') class ProofFunction(Proof): __doc__ = m.ProofFunction.__doc__ - disk_usage = Integer() - rate = NestedOn(s_action.Rate, required=True, only_query='id') + disk_usage = Integer(validate=f.validate.Range(min=0, max=100), data_key='diskUsage') + proof_author_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), + load_only=True, required=True, data_key='proofAuthorID') + proof_author = NestedOn(s_user.User, dump_only=True) + rate = NestedOn(s_action.Rate, required=True, + only_query='id', data_key='rateID') class ProofReuse(Proof): __doc__ = m.ProofReuse.__doc__ - price = Integer() + receiver_segment = String(default='', data_key='receiverSegment', required=True) + id_receipt = String(default='', data_key='idReceipt', required=True) + supplier_id = UUID(load_only=True, required=False, data_key='supplierID') + receiver_id = UUID(load_only=True, required=False, data_key='receiverID') + price = Integer(required=True) class ProofRecycling(Proof): __doc__ = m.ProofRecycling.__doc__ - collection_point = SanitizedStr(default='') - date = DateTime() - contact = SanitizedStr(default='') - ticket = SanitizedStr(default='') - gps_location = SanitizedStr(default='') + collection_point = SanitizedStr(default='', data_key='collectionPoint', required=True) + date = DateTime('iso', required=True) + contact = SanitizedStr(default='', required=True) + ticket = SanitizedStr(default='', required=True) + gps_location = SanitizedStr(default='', data_key='gpsLocation', required=True) + recycler_code = SanitizedStr(default='', data_key='recyclerCode', required=True) diff --git a/ereuse_devicehub/resources/proof/views.py b/ereuse_devicehub/resources/proof/views.py index c645479d..9b016df9 100644 --- a/ereuse_devicehub/resources/proof/views.py +++ b/ereuse_devicehub/resources/proof/views.py @@ -33,7 +33,8 @@ class ProofView(View): Model = db.Model._decl_class_registry.data[prf['type']]() proof = Model(**p) db.session.add(proof) - proofs.append(self.schema.dump(proof)) + proofs.append(resource_def.schema.dump(proof)) + db.session().final_flush() db.session.commit() response = jsonify({ 'items': proofs,