From 994dce4349bce92424f0229d158bab19935ff923 Mon Sep 17 00:00:00 2001 From: Big Lebowski Date: Thu, 12 Mar 2020 00:40:41 +0100 Subject: [PATCH 01/18] Add base proof models --- ereuse_devicehub/config.py | 1 + ereuse_devicehub/resources/proof/__init__.py | 279 +++++++++++ ereuse_devicehub/resources/proof/models.py | 127 +++++ ereuse_devicehub/resources/proof/schemas.py | 461 +++++++++++++++++++ ereuse_devicehub/resources/proof/views.py | 43 ++ 5 files changed, 911 insertions(+) create mode 100644 ereuse_devicehub/resources/proof/__init__.py create mode 100644 ereuse_devicehub/resources/proof/models.py create mode 100644 ereuse_devicehub/resources/proof/schemas.py create mode 100644 ereuse_devicehub/resources/proof/views.py diff --git a/ereuse_devicehub/config.py b/ereuse_devicehub/config.py index be15650d..36c2dbd7 100644 --- a/ereuse_devicehub/config.py +++ b/ereuse_devicehub/config.py @@ -21,6 +21,7 @@ class DevicehubConfig(Config): import_resource(agent), import_resource(lot), import_resource(deliverynote), + import_resource(proof), import_resource(documents), import_resource(inventory)), ) diff --git a/ereuse_devicehub/resources/proof/__init__.py b/ereuse_devicehub/resources/proof/__init__.py new file mode 100644 index 00000000..7f6e6232 --- /dev/null +++ b/ereuse_devicehub/resources/proof/__init__.py @@ -0,0 +1,279 @@ +from typing import Callable, Iterable, Tuple + +from teal.resource import Converters, Resource + +from ereuse_devicehub.resources.action import schemas +from ereuse_devicehub.resources.action.views import ActionView +from ereuse_devicehub.resources.device.sync import Sync + + +class ProofDef(Resource): + SCHEMA = schemas.Proof + VIEW = ProofView + AUTH = True + ID_CONVERTER = Converters.uuid + + +class ActionDef(Resource): + SCHEMA = schemas.Action + VIEW = ActionView + AUTH = True + ID_CONVERTER = Converters.uuid + + +class AddDef(ActionDef): + VIEW = None + SCHEMA = schemas.Add + + +class RemoveDef(ActionDef): + VIEW = None + SCHEMA = schemas.Remove + + +class EraseBasicDef(ActionDef): + VIEW = None + SCHEMA = schemas.EraseBasic + + +class EraseSectorsDef(EraseBasicDef): + VIEW = None + SCHEMA = schemas.EraseSectors + + +class ErasePhysicalDef(EraseBasicDef): + VIEW = None + SCHEMA = schemas.ErasePhysical + + +class StepDef(Resource): + VIEW = None + SCHEMA = schemas.Step + + +class StepZeroDef(StepDef): + VIEW = None + SCHEMA = schemas.StepZero + + +class StepRandomDef(StepDef): + VIEW = None + SCHEMA = schemas.StepRandom + + +class BenchmarkDef(ActionDef): + VIEW = None + SCHEMA = schemas.Benchmark + + +class BenchmarkDataStorageDef(BenchmarkDef): + VIEW = None + SCHEMA = schemas.BenchmarkDataStorage + + +class BenchmarkWithRateDef(BenchmarkDef): + VIEW = None + SCHEMA = schemas.BenchmarkWithRate + + +class BenchmarkProcessorDef(BenchmarkWithRateDef): + VIEW = None + SCHEMA = schemas.BenchmarkProcessor + + +class BenchmarkProcessorSysbenchDef(BenchmarkProcessorDef): + VIEW = None + SCHEMA = schemas.BenchmarkProcessorSysbench + + +class BenchmarkRamSysbenchDef(BenchmarkWithRateDef): + VIEW = None + SCHEMA = schemas.BenchmarkRamSysbench + + +class TestDef(ActionDef): + VIEW = None + SCHEMA = schemas.Test + + +class MeasureBattery(TestDef): + VIEW = None + SCHEMA = schemas.MeasureBattery + + +class TestDataStorageDef(TestDef): + VIEW = None + SCHEMA = schemas.TestDataStorage + + +class StressTestDef(TestDef): + VIEW = None + SCHEMA = schemas.StressTest + + +class TestAudioDef(TestDef): + VIEW = None + SCHEMA = schemas.TestAudio + + +class TestConnectivityDef(TestDef): + VIEW = None + SCHEMA = schemas.TestConnectivity + + +class TestCameraDef(TestDef): + VIEW = None + SCHEMA = schemas.TestCamera + + +class TestKeyboardDef(TestDef): + VIEW = None + SCHEMA = schemas.TestKeyboard + + +class TestTrackpadDef(TestDef): + VIEW = None + SCHEMA = schemas.TestTrackpad + + +class TestBiosDef(TestDef): + VIEW = None + SCHEMA = schemas.TestBios + + +class VisualTestDef(TestDef): + VIEW = None + SCHEMA = schemas.VisualTest + + +class RateDef(ActionDef): + VIEW = None + SCHEMA = schemas.Rate + + +class RateComputerDef(RateDef): + VIEW = None + SCHEMA = schemas.RateComputer + + +class PriceDef(ActionDef): + VIEW = None + SCHEMA = schemas.Price + + +class EreusePriceDef(ActionDef): + VIEW = None + SCHEMA = schemas.EreusePrice + + +class InstallDef(ActionDef): + VIEW = None + SCHEMA = schemas.Install + + +class SnapshotDef(ActionDef): + VIEW = None + SCHEMA = schemas.Snapshot + + def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None, + static_url_path=None, + template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, + root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): + url_prefix = '/{}'.format(ActionDef.resource) + super().__init__(app, import_name, static_folder, static_url_path, template_folder, + url_prefix, subdomain, url_defaults, root_path, cli_commands) + self.sync = Sync() + + +class ToRepairDef(ActionDef): + VIEW = None + SCHEMA = schemas.ToRepair + + +class RepairDef(ActionDef): + VIEW = None + SCHEMA = schemas.Repair + + +class ReadyDef(ActionDef): + VIEW = None + SCHEMA = schemas.Ready + + +class ToPrepareDef(ActionDef): + VIEW = None + SCHEMA = schemas.ToPrepare + + +class PrepareDef(ActionDef): + VIEW = None + SCHEMA = schemas.Prepare + + +class LiveDef(ActionDef): + VIEW = None + SCHEMA = schemas.Live + + +class ReserveDef(ActionDef): + VIEW = None + SCHEMA = schemas.Reserve + + +class CancelReservationDef(ActionDef): + VIEW = None + SCHEMA = schemas.CancelReservation + + +class SellDef(ActionDef): + VIEW = None + SCHEMA = schemas.Sell + + +class DonateDef(ActionDef): + VIEW = None + SCHEMA = schemas.Donate + + +class RentDef(ActionDef): + VIEW = None + SCHEMA = schemas.Rent + + +class MakeAvailable(ActionDef): + VIEW = None + SCHEMA = schemas.MakeAvailable + + +class CancelTradeDef(ActionDef): + VIEW = None + SCHEMA = schemas.CancelTrade + + +class ToDisposeProductDef(ActionDef): + VIEW = None + SCHEMA = schemas.ToDisposeProduct + + +class DisposeProductDef(ActionDef): + VIEW = None + SCHEMA = schemas.DisposeProduct + + +class ReceiveDef(ActionDef): + VIEW = None + SCHEMA = schemas.Receive + + +class MigrateToDef(ActionDef): + VIEW = None + SCHEMA = schemas.MigrateTo + + +class MigrateFromDef(ActionDef): + VIEW = None + SCHEMA = schemas.MigrateFrom + +class TransferredDef(ActionDef): + VIEW = None + SCHEMA = schemas.Transferred diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py new file mode 100644 index 00000000..ec6ed171 --- /dev/null +++ b/ereuse_devicehub/resources/proof/models.py @@ -0,0 +1,127 @@ +"""This file contains all proofs related to actions + +""" + +from collections import Iterable +from contextlib import suppress +from datetime import datetime, timedelta, timezone +from decimal import Decimal, ROUND_HALF_EVEN, ROUND_UP +from typing import Optional, Set, Union +from uuid import uuid4 + +import inflection +import teal.db +from boltons import urlutils +from citext import CIText +from flask import current_app as app, g +from sortedcontainers import SortedSet +from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, Enum as DBEnum, \ + Float, ForeignKey, Integer, Interval, JSON, Numeric, SmallInteger, Unicode, event, orm +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.orm.events import AttributeEvents as Events +from sqlalchemy.util import OrderedSet +from teal.db import CASCADE_OWN, INHERIT_COND, IP, POLYMORPHIC_ID, \ + POLYMORPHIC_ON, StrictVersionType, URL, check_lower, check_range +from teal.enums import Country, Currency, Subdivision +from teal.marshmallow import ValidationError +from teal.resource import url_for_resource + +from ereuse_devicehub.db import db +from ereuse_devicehub.resources.agent.models import Agent +from ereuse_devicehub.resources.device.models import Component, Computer, DataStorage, Desktop, \ + Device, Laptop, Server +from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, BiosAccessRange, \ + ErasureStandards, FunctionalityRange, PhysicalErasureMethod, PriceSoftware, \ + R_NEGATIVE, R_POSITIVE, RatingRange, ReceiverRole, Severity, SnapshotSoftware, \ + TestDataStorageLength +from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing +from ereuse_devicehub.resources.user.models import User + + +class JoinedTableMixin: + # noinspection PyMethodParameters + @declared_attr + def id(cls): + return Column(UUID(as_uuid=True), ForeignKey(Proof.id), primary_key=True) + + +class Proof(Thing): + """Proof over an action. + + """ + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + type = Column(Unicode, nullable=False) + ethereum_hashes = Column(CIText(), default='', nullable=False) + + @property + def url(self) -> urlutils.URL: + """The URL where to GET this proof.""" + return urlutils.URL(url_for_resource(Proof, item_id=self.id)) + + @property + def certificate(self) -> Optional[urlutils.URL]: + return None + + # noinspection PyMethodParameters + @declared_attr + def __mapper_args__(cls): + """Defines inheritance. + + From `the guide `_ + """ + args = {POLYMORPHIC_ID: cls.t} + if cls.t == 'Proof': + args[POLYMORPHIC_ON] = cls.type + # noinspection PyUnresolvedReferences + if JoinedTableMixin in cls.mro(): + args[INHERIT_COND] = cls.id == Proof.id + return args + + def __init__(self, **kwargs) -> None: + # sortedset forces us to do this before calling our parent init + super().__init__(**kwargs) + + def __repr__(self): + return '<{0.t} {0.id} >'.format(self) + + +class ProofTransfer(JoinedTableMixin, Proof): + transfer_id = Column(BigInteger, ForeignKey(Action.id), nullable=False) + transfer = relationship(DisposeProduct, + primaryjoin=DisposeProduct.id == transfer_id) + + +class ProofDataWipe(JoinedTableMixin, Proof): + erasure_type = Column(CIText()) + date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + result = db.Column(db.Boolean, default=False, nullable=False) + erasure_id = Column(BigInteger, ForeignKey(Device.id), nullable=False) + erasure = relationship(EraseBasic, + backref=backref('proofs_datawipe', + lazy=True, + cascade=CASCADE_OWN), + primaryjoin=EraseBasic.id == erasure_id) + + +class ProofFunction(JoinedTableMixin, Proof): + disk_usage = db.Column(db.Integer, default=0) + rate_id = Column(BigInteger, ForeignKey(Rate.id), nullable=False) + rate = relationship(Rate, + primaryjoin=Rate.id == rate_id) + + +class ProofReuse(JoinedTableMixin, Proof): + price = db.Column(db.Integer, required=True) + + +class ProofRecycling(JoinedTableMixin, Proof): + collection_point = Column(CIText()) + date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + contact = Column(CIText()) + ticket = Column(CIText()) + gps_location = Column(CIText()) diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py new file mode 100644 index 00000000..73791cc1 --- /dev/null +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -0,0 +1,461 @@ +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, Decimal, Float, Integer, Nested, String, \ + TimeDelta, UUID +from marshmallow.validate import Length, OneOf, Range +from sqlalchemy.util import OrderedSet +from teal.enums import Country, Currency, Subdivision +from teal.marshmallow import EnumField, IP, SanitizedStr, URL, Version +from teal.resource import Schema + +from ereuse_devicehub.marshmallow import NestedOn +from ereuse_devicehub.resources import enums +from ereuse_devicehub.resources.action import models as m +from ereuse_devicehub.resources.agent import schemas as s_agent +from ereuse_devicehub.resources.device import schemas as s_device +from ereuse_devicehub.resources.enums import AppearanceRange, BiosAccessRange, FunctionalityRange, \ + PhysicalErasureMethod, R_POSITIVE, RatingRange, ReceiverRole, \ + Severity, SnapshotSoftware, TestDataStorageLength +from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE +from ereuse_devicehub.resources.schemas import Thing +from ereuse_devicehub.resources.user import schemas as s_user + + +class Proof(Thing): + __doc__ = m.Action.__doc__ + id = UUID(dump_only=True) + ethereumHashes = SanitizedStr(default='', + validate=Length(max=STR_BIG_SIZE)) + url = URL(dump_only=True, description=m.Action.url.__doc__) + + +class Action(Thing): + __doc__ = m.Action.__doc__ + id = UUID(dump_only=True) + name = SanitizedStr(default='', + validate=Length(max=STR_BIG_SIZE), + description=m.Action.name.comment) + closed = Boolean(missing=True, description=m.Action.closed.comment) + severity = EnumField(Severity, description=m.Action.severity.comment) + description = SanitizedStr(default='', description=m.Action.description.comment) + start_time = DateTime(data_key='startTime', description=m.Action.start_time.comment) + end_time = DateTime(data_key='endTime', description=m.Action.end_time.comment) + snapshot = NestedOn('Snapshot', dump_only=True) + agent = NestedOn(s_agent.Agent, description=m.Action.agent_id.comment) + author = NestedOn(s_user.User, dump_only=True, exclude=('token',)) + components = NestedOn(s_device.Component, dump_only=True, many=True) + parent = NestedOn(s_device.Computer, dump_only=True, description=m.Action.parent_id.comment) + url = URL(dump_only=True, description=m.Action.url.__doc__) + + +class ActionWithOneDevice(Action): + __doc__ = m.ActionWithOneDevice.__doc__ + device = NestedOn(s_device.Device, only_query='id') + + +class ActionWithMultipleDevices(Action): + __doc__ = m.ActionWithMultipleDevices.__doc__ + devices = NestedOn(s_device.Device, + many=True, + required=True, # todo test ensuring len(devices) >= 1 + only_query='id', + collection_class=OrderedSet) + + +class Add(ActionWithOneDevice): + __doc__ = m.Add.__doc__ + + +class Remove(ActionWithOneDevice): + __doc__ = m.Remove.__doc__ + + +class Allocate(ActionWithMultipleDevices): + __doc__ = m.Allocate.__doc__ + to = NestedOn(s_user.User, + description='The user the devices are allocated to.') + organization = SanitizedStr(validate=Length(max=STR_SIZE), + description='The organization where the ' + 'user was when this happened.') + + +class Deallocate(ActionWithMultipleDevices): + __doc__ = m.Deallocate.__doc__ + from_rel = Nested(s_user.User, + data_key='from', + description='The user where the devices are not allocated to anymore.') + organization = SanitizedStr(validate=Length(max=STR_SIZE), + description='The organization where the ' + 'user was when this happened.') + + +class EraseBasic(ActionWithOneDevice): + __doc__ = m.EraseBasic.__doc__ + steps = NestedOn('Step', many=True) + standards = f.List(EnumField(enums.ErasureStandards), dump_only=True) + certificate = URL(dump_only=True) + + +class EraseSectors(EraseBasic): + __doc__ = m.EraseSectors.__doc__ + + +class ErasePhysical(EraseBasic): + __doc__ = m.ErasePhysical.__doc__ + method = EnumField(PhysicalErasureMethod, description=PhysicalErasureMethod.__doc__) + + +class Step(Schema): + __doc__ = m.Step.__doc__ + type = String(description='Only required when it is nested.') + start_time = DateTime(required=True, data_key='startTime') + end_time = DateTime(required=True, data_key='endTime') + severity = EnumField(Severity, description=m.Action.severity.comment) + + +class StepZero(Step): + __doc__ = m.StepZero.__doc__ + + +class StepRandom(Step): + __doc__ = m.StepRandom.__doc__ + + +class Benchmark(ActionWithOneDevice): + __doc__ = m.Benchmark.__doc__ + elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) + + +class BenchmarkDataStorage(Benchmark): + __doc__ = m.BenchmarkDataStorage.__doc__ + read_speed = Float(required=True, data_key='readSpeed') + write_speed = Float(required=True, data_key='writeSpeed') + + +class BenchmarkWithRate(Benchmark): + __doc__ = m.BenchmarkWithRate.__doc__ + rate = Float(required=True) + + +class BenchmarkProcessor(BenchmarkWithRate): + __doc__ = m.BenchmarkProcessor.__doc__ + + +class BenchmarkProcessorSysbench(BenchmarkProcessor): + __doc__ = m.BenchmarkProcessorSysbench.__doc__ + + +class BenchmarkRamSysbench(BenchmarkWithRate): + __doc__ = m.BenchmarkRamSysbench.__doc__ + + +class BenchmarkGraphicCard(BenchmarkWithRate): + __doc__ = m.BenchmarkGraphicCard.__doc__ + + +class Test(ActionWithOneDevice): + __doc__ = m.Test.__doc__ + + +class MeasureBattery(Test): + __doc__ = m.MeasureBattery.__doc__ + size = Integer(required=True, description=m.MeasureBattery.size.comment) + voltage = Integer(required=True, description=m.MeasureBattery.voltage.comment) + cycle_count = Integer(data_key='cycleCount', description=m.MeasureBattery.cycle_count.comment) + health = EnumField(enums.BatteryHealth, description=m.MeasureBattery.health.comment) + + +class TestDataStorage(Test): + __doc__ = m.TestDataStorage.__doc__ + elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) + length = EnumField(TestDataStorageLength, required=True) + status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True) + lifetime = TimeDelta(precision=TimeDelta.HOURS) + assessment = Boolean() + reallocated_sector_count = Integer(data_key='reallocatedSectorCount') + power_cycle_count = Integer(data_key='powerCycleCount') + reported_uncorrectable_errors = Integer(data_key='reportedUncorrectableErrors') + command_timeout = Integer(data_key='commandTimeout') + current_pending_sector_count = Integer(data_key='currentPendingSectorCount') + offline_uncorrectable = Integer(data_key='offlineUncorrectable') + remaining_lifetime_percentage = Integer(data_key='remainingLifetimePercentage') + + +class StressTest(Test): + __doc__ = m.StressTest.__doc__ + elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) + + +class TestAudio(Test): + __doc__ = m.TestAudio.__doc__ + speaker = Boolean(description=m.TestAudio._speaker.comment) + microphone = Boolean(description=m.TestAudio._microphone.comment) + + +class TestConnectivity(Test): + __doc__ = m.TestConnectivity.__doc__ + + +class TestCamera(Test): + __doc__ = m.TestCamera.__doc__ + + +class TestKeyboard(Test): + __doc__ = m.TestKeyboard.__doc__ + + +class TestTrackpad(Test): + __doc__ = m.TestTrackpad.__doc__ + + +class TestBios(Test): + __doc__ = m.TestBios.__doc__ + bios_power_on = Boolean() + access_range = EnumField(BiosAccessRange, data_key='accessRange') + + +class VisualTest(Test): + __doc__ = m.VisualTest.__doc__ + appearance_range = EnumField(AppearanceRange, required=True, data_key='appearanceRange') + functionality_range = EnumField(FunctionalityRange, + required=True, + data_key='functionalityRange') + labelling = Boolean() + + +class Rate(ActionWithOneDevice): + __doc__ = m.Rate.__doc__ + rating = Integer(validate=Range(*R_POSITIVE), + dump_only=True, + description=m.Rate._rating.comment) + version = Version(dump_only=True, + description=m.Rate.version.comment) + appearance = Integer(validate=Range(enums.R_NEGATIVE), + dump_only=True, + description=m.Rate._appearance.comment) + functionality = Integer(validate=Range(enums.R_NEGATIVE), + dump_only=True, + description=m.Rate._functionality.comment) + rating_range = EnumField(RatingRange, + dump_only=True, + data_key='ratingRange', + description=m.Rate.rating_range.__doc__) + + +class RateComputer(Rate): + __doc__ = m.RateComputer.__doc__ + processor = Float(dump_only=True) + ram = Float(dump_only=True) + data_storage = Float(dump_only=True, data_key='dataStorage') + graphic_card = Float(dump_only=True, data_key='graphicCard') + + data_storage_range = EnumField(RatingRange, dump_only=True, data_key='dataStorageRange') + ram_range = EnumField(RatingRange, dump_only=True, data_key='ramRange') + processor_range = EnumField(RatingRange, dump_only=True, data_key='processorRange') + graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange') + + +class Price(ActionWithOneDevice): + __doc__ = m.Price.__doc__ + currency = EnumField(Currency, required=True, description=m.Price.currency.comment) + price = Decimal(places=m.Price.SCALE, + rounding=m.Price.ROUND, + required=True, + description=m.Price.price.comment) + version = Version(dump_only=True, description=m.Price.version.comment) + rating = NestedOn(Rate, dump_only=True, description=m.Price.rating_id.comment) + + +class EreusePrice(Price): + __doc__ = m.EreusePrice.__doc__ + + class Service(MarshmallowSchema): + class Type(MarshmallowSchema): + amount = Float() + percentage = Float() + + standard = Nested(Type) + warranty2 = Nested(Type) + + warranty2 = Float() + refurbisher = Nested(Service) + retailer = Nested(Service) + platform = Nested(Service) + + +class Install(ActionWithOneDevice): + __doc__ = m.Install.__doc__ + name = SanitizedStr(validate=Length(min=4, max=STR_BIG_SIZE), + required=True, + description='The name of the OS installed.') + elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) + address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256})) + + +class Snapshot(ActionWithOneDevice): + __doc__ = m.Snapshot.__doc__ + """ + The Snapshot updates the state of the device with information about + its components and actions performed at them. + + See docs for more info. + """ + uuid = UUID() + software = EnumField(SnapshotSoftware, + required=True, + description='The software that generated this Snapshot.') + version = Version(required=True, description='The version of the software.') + actions = NestedOn(Action, many=True, dump_only=True) + elapsed = TimeDelta(precision=TimeDelta.SECONDS) + components = NestedOn(s_device.Component, + many=True, + description='A list of components that are inside of the device' + 'at the moment of this Snapshot.' + 'Order is preserved, so the component num 0 when' + 'submitting is the component num 0 when returning it back.') + + @validates_schema + def validate_workbench_version(self, data: dict): + if data['software'] == SnapshotSoftware.Workbench: + if data['version'] < app.config['MIN_WORKBENCH']: + raise ValidationError( + 'Min. supported Workbench version is ' + '{} but yours is {}.'.format(app.config['MIN_WORKBENCH'], data['version']), + field_names=['version'] + ) + + @validates_schema + def validate_components_only_workbench(self, data: dict): + if data['software'] != SnapshotSoftware.Workbench: + if data.get('components', None) is not None: + raise ValidationError('Only Workbench can add component info', + field_names=['components']) + + @validates_schema + def validate_only_workbench_fields(self, data: dict): + """Ensures workbench has ``elapsed`` and ``uuid`` and no others.""" + # todo test + if data['software'] == SnapshotSoftware.Workbench: + if not data.get('uuid', None): + raise ValidationError('Snapshots from Workbench must have uuid', + field_names=['uuid']) + if data.get('elapsed', None) is None: + raise ValidationError('Snapshots from Workbench must have elapsed', + field_names=['elapsed']) + else: + if data.get('uuid', None): + raise ValidationError('Only Snapshots from Workbench can have uuid', + field_names=['uuid']) + if data.get('elapsed', None): + raise ValidationError('Only Snapshots from Workbench can have elapsed', + field_names=['elapsed']) + + +class ToRepair(ActionWithMultipleDevices): + __doc__ = m.ToRepair.__doc__ + + +class Repair(ActionWithMultipleDevices): + __doc__ = m.Repair.__doc__ + + +class Ready(ActionWithMultipleDevices): + __doc__ = m.Ready.__doc__ + + +class ToPrepare(ActionWithMultipleDevices): + __doc__ = m.ToPrepare.__doc__ + + +class Prepare(ActionWithMultipleDevices): + __doc__ = m.Prepare.__doc__ + + +class Live(ActionWithOneDevice): + __doc__ = m.Live.__doc__ + ip = IP(dump_only=True) + subdivision_confidence = Integer(dump_only=True, data_key='subdivisionConfidence') + subdivision = EnumField(Subdivision, dump_only=True) + country = EnumField(Country, dump_only=True) + city = SanitizedStr(lower=True, dump_only=True) + city_confidence = Integer(dump_only=True, data_key='cityConfidence') + isp = SanitizedStr(lower=True, dump_only=True) + organization = SanitizedStr(lower=True, dump_only=True) + organization_type = SanitizedStr(lower=True, dump_only=True, data_key='organizationType') + + +class Organize(ActionWithMultipleDevices): + __doc__ = m.Organize.__doc__ + + +class Reserve(Organize): + __doc__ = m.Reserve.__doc__ + + +class CancelReservation(Organize): + __doc__ = m.CancelReservation.__doc__ + + +class Trade(ActionWithMultipleDevices): + __doc__ = m.Trade.__doc__ + shipping_date = DateTime(data_key='shippingDate') + invoice_number = SanitizedStr(validate=Length(max=STR_SIZE), data_key='invoiceNumber') + price = NestedOn(Price) + to = NestedOn(s_agent.Agent, only_query='id', required=True, comment=m.Trade.to_comment) + confirms = NestedOn(Organize) + + +class Sell(Trade): + __doc__ = m.Sell.__doc__ + + +class Donate(Trade): + __doc__ = m.Donate.__doc__ + + +class Rent(Trade): + __doc__ = m.Rent.__doc__ + + +class MakeAvailable(ActionWithMultipleDevices): + __doc__ = m.MakeAvailable.__doc__ + + +class CancelTrade(Trade): + __doc__ = m.CancelTrade.__doc__ + + +class ToDisposeProduct(Trade): + __doc__ = m.ToDisposeProduct.__doc__ + + +class DisposeProduct(Trade): + __doc__ = m.DisposeProduct.__doc__ + + +class TransferOwnershipBlockchain(Trade): + __doc__ = m.TransferOwnershipBlockchain.__doc__ + + +class Receive(ActionWithMultipleDevices): + __doc__ = m.Receive.__doc__ + role = EnumField(ReceiverRole) + + +class Migrate(ActionWithMultipleDevices): + __doc__ = m.Migrate.__doc__ + other = URL() + + +class MigrateTo(Migrate): + __doc__ = m.MigrateTo.__doc__ + + +class MigrateFrom(Migrate): + __doc__ = m.MigrateFrom.__doc__ + + +class Transferred(ActionWithMultipleDevices): + __doc__ = m.Transferred.__doc__ + + diff --git a/ereuse_devicehub/resources/proof/views.py b/ereuse_devicehub/resources/proof/views.py new file mode 100644 index 00000000..2d127666 --- /dev/null +++ b/ereuse_devicehub/resources/proof/views.py @@ -0,0 +1,43 @@ +from distutils.version import StrictVersion +from typing import List +from uuid import UUID + +from flask import current_app as app, request +from sqlalchemy.util import OrderedSet +from teal.marshmallow import ValidationError +from teal.resource import View + +from ereuse_devicehub.db import db +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') + + +class ProofView(View): + def post(self): + """Posts batches of proofs.""" + json = request.get_json(validate=False) + if not json: + raise ValidationError('JSON is not correct.') + # todo there should be a way to better get subclassess resource + # defs + if json['batch']: + for proof in json['proofs']: + resource_def = app.resources[proof['type']] + a = resource_def.schema.load(json) + if json['type'] == Snapshot.t: + return self.snapshot(a, resource_def) + if json['type'] == VisualTest.t: + pass + # TODO JN add compute rate with new visual test and old components device + Model = db.Model._decl_class_registry.data[json['type']]() + action = Model(**a) + db.session.add(action) + db.session().final_flush() + ret = self.schema.jsonify(action) + ret.status_code = 201 + db.session.commit() + return ret From ac8375c98f1c4803cd66fe17a3b756a4bda95125 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Thu, 12 Mar 2020 15:11:38 +0100 Subject: [PATCH 02/18] Add view and schemas --- ereuse_devicehub/resources/proof/__init__.py | 265 +---------- ereuse_devicehub/resources/proof/models.py | 30 +- ereuse_devicehub/resources/proof/schemas.py | 472 ++----------------- ereuse_devicehub/resources/proof/views.py | 11 +- 4 files changed, 56 insertions(+), 722 deletions(-) diff --git a/ereuse_devicehub/resources/proof/__init__.py b/ereuse_devicehub/resources/proof/__init__.py index 7f6e6232..93b8e9f3 100644 --- a/ereuse_devicehub/resources/proof/__init__.py +++ b/ereuse_devicehub/resources/proof/__init__.py @@ -1,10 +1,7 @@ -from typing import Callable, Iterable, Tuple - from teal.resource import Converters, Resource from ereuse_devicehub.resources.action import schemas -from ereuse_devicehub.resources.action.views import ActionView -from ereuse_devicehub.resources.device.sync import Sync +from ereuse_devicehub.resources.proof.views import ProofView class ProofDef(Resource): @@ -14,266 +11,26 @@ class ProofDef(Resource): ID_CONVERTER = Converters.uuid -class ActionDef(Resource): - SCHEMA = schemas.Action - VIEW = ActionView - AUTH = True - ID_CONVERTER = Converters.uuid - - -class AddDef(ActionDef): +class ProofTransferDef(ProofDef): VIEW = None - SCHEMA = schemas.Add + SCHEMA = schemas.ProofTransfer -class RemoveDef(ActionDef): +class ProofDataWipeDef(ProofDef): VIEW = None - SCHEMA = schemas.Remove + SCHEMA = schemas.ProofDataWipe -class EraseBasicDef(ActionDef): +class ProofFunction(ProofDef): VIEW = None - SCHEMA = schemas.EraseBasic + SCHEMA = schemas.ProofFunction -class EraseSectorsDef(EraseBasicDef): +class ProofReuse(ProofDef): VIEW = None - SCHEMA = schemas.EraseSectors + SCHEMA = schemas.ProofReuse -class ErasePhysicalDef(EraseBasicDef): +class ProofRecycling(ProofDef): VIEW = None - SCHEMA = schemas.ErasePhysical - - -class StepDef(Resource): - VIEW = None - SCHEMA = schemas.Step - - -class StepZeroDef(StepDef): - VIEW = None - SCHEMA = schemas.StepZero - - -class StepRandomDef(StepDef): - VIEW = None - SCHEMA = schemas.StepRandom - - -class BenchmarkDef(ActionDef): - VIEW = None - SCHEMA = schemas.Benchmark - - -class BenchmarkDataStorageDef(BenchmarkDef): - VIEW = None - SCHEMA = schemas.BenchmarkDataStorage - - -class BenchmarkWithRateDef(BenchmarkDef): - VIEW = None - SCHEMA = schemas.BenchmarkWithRate - - -class BenchmarkProcessorDef(BenchmarkWithRateDef): - VIEW = None - SCHEMA = schemas.BenchmarkProcessor - - -class BenchmarkProcessorSysbenchDef(BenchmarkProcessorDef): - VIEW = None - SCHEMA = schemas.BenchmarkProcessorSysbench - - -class BenchmarkRamSysbenchDef(BenchmarkWithRateDef): - VIEW = None - SCHEMA = schemas.BenchmarkRamSysbench - - -class TestDef(ActionDef): - VIEW = None - SCHEMA = schemas.Test - - -class MeasureBattery(TestDef): - VIEW = None - SCHEMA = schemas.MeasureBattery - - -class TestDataStorageDef(TestDef): - VIEW = None - SCHEMA = schemas.TestDataStorage - - -class StressTestDef(TestDef): - VIEW = None - SCHEMA = schemas.StressTest - - -class TestAudioDef(TestDef): - VIEW = None - SCHEMA = schemas.TestAudio - - -class TestConnectivityDef(TestDef): - VIEW = None - SCHEMA = schemas.TestConnectivity - - -class TestCameraDef(TestDef): - VIEW = None - SCHEMA = schemas.TestCamera - - -class TestKeyboardDef(TestDef): - VIEW = None - SCHEMA = schemas.TestKeyboard - - -class TestTrackpadDef(TestDef): - VIEW = None - SCHEMA = schemas.TestTrackpad - - -class TestBiosDef(TestDef): - VIEW = None - SCHEMA = schemas.TestBios - - -class VisualTestDef(TestDef): - VIEW = None - SCHEMA = schemas.VisualTest - - -class RateDef(ActionDef): - VIEW = None - SCHEMA = schemas.Rate - - -class RateComputerDef(RateDef): - VIEW = None - SCHEMA = schemas.RateComputer - - -class PriceDef(ActionDef): - VIEW = None - SCHEMA = schemas.Price - - -class EreusePriceDef(ActionDef): - VIEW = None - SCHEMA = schemas.EreusePrice - - -class InstallDef(ActionDef): - VIEW = None - SCHEMA = schemas.Install - - -class SnapshotDef(ActionDef): - VIEW = None - SCHEMA = schemas.Snapshot - - def __init__(self, app, import_name=__name__.split('.')[0], static_folder=None, - static_url_path=None, - template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, - root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): - url_prefix = '/{}'.format(ActionDef.resource) - super().__init__(app, import_name, static_folder, static_url_path, template_folder, - url_prefix, subdomain, url_defaults, root_path, cli_commands) - self.sync = Sync() - - -class ToRepairDef(ActionDef): - VIEW = None - SCHEMA = schemas.ToRepair - - -class RepairDef(ActionDef): - VIEW = None - SCHEMA = schemas.Repair - - -class ReadyDef(ActionDef): - VIEW = None - SCHEMA = schemas.Ready - - -class ToPrepareDef(ActionDef): - VIEW = None - SCHEMA = schemas.ToPrepare - - -class PrepareDef(ActionDef): - VIEW = None - SCHEMA = schemas.Prepare - - -class LiveDef(ActionDef): - VIEW = None - SCHEMA = schemas.Live - - -class ReserveDef(ActionDef): - VIEW = None - SCHEMA = schemas.Reserve - - -class CancelReservationDef(ActionDef): - VIEW = None - SCHEMA = schemas.CancelReservation - - -class SellDef(ActionDef): - VIEW = None - SCHEMA = schemas.Sell - - -class DonateDef(ActionDef): - VIEW = None - SCHEMA = schemas.Donate - - -class RentDef(ActionDef): - VIEW = None - SCHEMA = schemas.Rent - - -class MakeAvailable(ActionDef): - VIEW = None - SCHEMA = schemas.MakeAvailable - - -class CancelTradeDef(ActionDef): - VIEW = None - SCHEMA = schemas.CancelTrade - - -class ToDisposeProductDef(ActionDef): - VIEW = None - SCHEMA = schemas.ToDisposeProduct - - -class DisposeProductDef(ActionDef): - VIEW = None - SCHEMA = schemas.DisposeProduct - - -class ReceiveDef(ActionDef): - VIEW = None - SCHEMA = schemas.Receive - - -class MigrateToDef(ActionDef): - VIEW = None - SCHEMA = schemas.MigrateTo - - -class MigrateFromDef(ActionDef): - VIEW = None - SCHEMA = schemas.MigrateFrom - -class TransferredDef(ActionDef): - VIEW = None - SCHEMA = schemas.Transferred + SCHEMA = schemas.ProofRecycling diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index ec6ed171..47888943 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -3,42 +3,30 @@ """ from collections import Iterable -from contextlib import suppress -from datetime import datetime, timedelta, timezone -from decimal import Decimal, ROUND_HALF_EVEN, ROUND_UP +from datetime import datetime from typing import Optional, Set, Union from uuid import uuid4 -import inflection -import teal.db from boltons import urlutils from citext import CIText from flask import current_app as app, g from sortedcontainers import SortedSet -from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, Enum as DBEnum, \ - Float, ForeignKey, Integer, Interval, JSON, Numeric, SmallInteger, Unicode, event, orm +from sqlalchemy import BigInteger, Column, Enum as DBEnum, \ + ForeignKey, Integer, 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.orm.events import AttributeEvents as Events from sqlalchemy.util import OrderedSet -from teal.db import CASCADE_OWN, INHERIT_COND, IP, POLYMORPHIC_ID, \ - POLYMORPHIC_ON, StrictVersionType, URL, check_lower, check_range -from teal.enums import Country, Currency, Subdivision +from teal.db import CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, \ + POLYMORPHIC_ON, StrictVersionType, URL from teal.marshmallow import ValidationError from teal.resource import url_for_resource from ereuse_devicehub.db import db -from ereuse_devicehub.resources.agent.models import Agent -from ereuse_devicehub.resources.device.models import Component, Computer, DataStorage, Desktop, \ - Device, Laptop, Server -from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, BiosAccessRange, \ - ErasureStandards, FunctionalityRange, PhysicalErasureMethod, PriceSoftware, \ - R_NEGATIVE, R_POSITIVE, RatingRange, ReceiverRole, Severity, SnapshotSoftware, \ - TestDataStorageLength -from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing -from ereuse_devicehub.resources.user.models import User +from ereuse_devicehub.resources.action.models import Action, DisposeProduct, \ + EraseBasic, Rate +from ereuse_devicehub.resources.models import Thing class JoinedTableMixin: @@ -100,7 +88,7 @@ class ProofDataWipe(JoinedTableMixin, Proof): erasure_type = Column(CIText()) date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) result = db.Column(db.Boolean, default=False, nullable=False) - erasure_id = Column(BigInteger, ForeignKey(Device.id), nullable=False) + erasure_id = Column(BigInteger, ForeignKey(EraseBasic.id), nullable=False) erasure = relationship(EraseBasic, backref=backref('proofs_datawipe', lazy=True, diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 73791cc1..650a2090 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -1,461 +1,55 @@ 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, Decimal, Float, Integer, Nested, String, \ - TimeDelta, UUID -from marshmallow.validate import Length, OneOf, Range +from marshmallow import Schema as MarshmallowSchema, ValidationError, validates_schema +from marshmallow.fields import Boolean, DateTime, Integer, Nested, String, UUID +from marshmallow.validate import Length from sqlalchemy.util import OrderedSet -from teal.enums import Country, Currency, Subdivision -from teal.marshmallow import EnumField, IP, SanitizedStr, URL, Version +from teal.marshmallow import SanitizedStr, URL from teal.resource import Schema from ereuse_devicehub.marshmallow import NestedOn -from ereuse_devicehub.resources import enums -from ereuse_devicehub.resources.action import models as m -from ereuse_devicehub.resources.agent import schemas as s_agent -from ereuse_devicehub.resources.device import schemas as s_device -from ereuse_devicehub.resources.enums import AppearanceRange, BiosAccessRange, FunctionalityRange, \ - PhysicalErasureMethod, R_POSITIVE, RatingRange, ReceiverRole, \ - Severity, SnapshotSoftware, TestDataStorageLength +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.user import schemas as s_user +from ereuse_devicehub.resources.action import schemas as s_action class Proof(Thing): - __doc__ = m.Action.__doc__ + __doc__ = m.Proof.__doc__ id = UUID(dump_only=True) - ethereumHashes = SanitizedStr(default='', - validate=Length(max=STR_BIG_SIZE)) - url = URL(dump_only=True, description=m.Action.url.__doc__) + ethereumHashes = SanitizedStr(default='', validate=Length(max=STR_BIG_SIZE)) + url = URL(dump_only=True, description=m.Proof.url.__doc__) -class Action(Thing): - __doc__ = m.Action.__doc__ - id = UUID(dump_only=True) - name = SanitizedStr(default='', - validate=Length(max=STR_BIG_SIZE), - description=m.Action.name.comment) - closed = Boolean(missing=True, description=m.Action.closed.comment) - severity = EnumField(Severity, description=m.Action.severity.comment) - description = SanitizedStr(default='', description=m.Action.description.comment) - start_time = DateTime(data_key='startTime', description=m.Action.start_time.comment) - end_time = DateTime(data_key='endTime', description=m.Action.end_time.comment) - snapshot = NestedOn('Snapshot', dump_only=True) - agent = NestedOn(s_agent.Agent, description=m.Action.agent_id.comment) - author = NestedOn(s_user.User, dump_only=True, exclude=('token',)) - components = NestedOn(s_device.Component, dump_only=True, many=True) - parent = NestedOn(s_device.Computer, dump_only=True, description=m.Action.parent_id.comment) - url = URL(dump_only=True, description=m.Action.url.__doc__) - - -class ActionWithOneDevice(Action): - __doc__ = m.ActionWithOneDevice.__doc__ - device = NestedOn(s_device.Device, only_query='id') - - -class ActionWithMultipleDevices(Action): - __doc__ = m.ActionWithMultipleDevices.__doc__ - devices = NestedOn(s_device.Device, - many=True, - required=True, # todo test ensuring len(devices) >= 1 - only_query='id', - collection_class=OrderedSet) - - -class Add(ActionWithOneDevice): - __doc__ = m.Add.__doc__ - - -class Remove(ActionWithOneDevice): - __doc__ = m.Remove.__doc__ - - -class Allocate(ActionWithMultipleDevices): - __doc__ = m.Allocate.__doc__ - to = NestedOn(s_user.User, - description='The user the devices are allocated to.') - organization = SanitizedStr(validate=Length(max=STR_SIZE), - description='The organization where the ' - 'user was when this happened.') - - -class Deallocate(ActionWithMultipleDevices): - __doc__ = m.Deallocate.__doc__ - from_rel = Nested(s_user.User, - data_key='from', - description='The user where the devices are not allocated to anymore.') - organization = SanitizedStr(validate=Length(max=STR_SIZE), - description='The organization where the ' - 'user was when this happened.') - - -class EraseBasic(ActionWithOneDevice): - __doc__ = m.EraseBasic.__doc__ - steps = NestedOn('Step', many=True) - standards = f.List(EnumField(enums.ErasureStandards), dump_only=True) - certificate = URL(dump_only=True) - - -class EraseSectors(EraseBasic): - __doc__ = m.EraseSectors.__doc__ - - -class ErasePhysical(EraseBasic): - __doc__ = m.ErasePhysical.__doc__ - method = EnumField(PhysicalErasureMethod, description=PhysicalErasureMethod.__doc__) - - -class Step(Schema): - __doc__ = m.Step.__doc__ - type = String(description='Only required when it is nested.') - start_time = DateTime(required=True, data_key='startTime') - end_time = DateTime(required=True, data_key='endTime') - severity = EnumField(Severity, description=m.Action.severity.comment) - - -class StepZero(Step): - __doc__ = m.StepZero.__doc__ - - -class StepRandom(Step): - __doc__ = m.StepRandom.__doc__ - - -class Benchmark(ActionWithOneDevice): - __doc__ = m.Benchmark.__doc__ - elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) - - -class BenchmarkDataStorage(Benchmark): - __doc__ = m.BenchmarkDataStorage.__doc__ - read_speed = Float(required=True, data_key='readSpeed') - write_speed = Float(required=True, data_key='writeSpeed') - - -class BenchmarkWithRate(Benchmark): - __doc__ = m.BenchmarkWithRate.__doc__ - rate = Float(required=True) - - -class BenchmarkProcessor(BenchmarkWithRate): - __doc__ = m.BenchmarkProcessor.__doc__ - - -class BenchmarkProcessorSysbench(BenchmarkProcessor): - __doc__ = m.BenchmarkProcessorSysbench.__doc__ - - -class BenchmarkRamSysbench(BenchmarkWithRate): - __doc__ = m.BenchmarkRamSysbench.__doc__ - - -class BenchmarkGraphicCard(BenchmarkWithRate): - __doc__ = m.BenchmarkGraphicCard.__doc__ - - -class Test(ActionWithOneDevice): - __doc__ = m.Test.__doc__ - - -class MeasureBattery(Test): - __doc__ = m.MeasureBattery.__doc__ - size = Integer(required=True, description=m.MeasureBattery.size.comment) - voltage = Integer(required=True, description=m.MeasureBattery.voltage.comment) - cycle_count = Integer(data_key='cycleCount', description=m.MeasureBattery.cycle_count.comment) - health = EnumField(enums.BatteryHealth, description=m.MeasureBattery.health.comment) - - -class TestDataStorage(Test): - __doc__ = m.TestDataStorage.__doc__ - elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) - length = EnumField(TestDataStorageLength, required=True) - status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True) - lifetime = TimeDelta(precision=TimeDelta.HOURS) - assessment = Boolean() - reallocated_sector_count = Integer(data_key='reallocatedSectorCount') - power_cycle_count = Integer(data_key='powerCycleCount') - reported_uncorrectable_errors = Integer(data_key='reportedUncorrectableErrors') - command_timeout = Integer(data_key='commandTimeout') - current_pending_sector_count = Integer(data_key='currentPendingSectorCount') - offline_uncorrectable = Integer(data_key='offlineUncorrectable') - remaining_lifetime_percentage = Integer(data_key='remainingLifetimePercentage') - - -class StressTest(Test): - __doc__ = m.StressTest.__doc__ - elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) - - -class TestAudio(Test): - __doc__ = m.TestAudio.__doc__ - speaker = Boolean(description=m.TestAudio._speaker.comment) - microphone = Boolean(description=m.TestAudio._microphone.comment) - - -class TestConnectivity(Test): - __doc__ = m.TestConnectivity.__doc__ - - -class TestCamera(Test): - __doc__ = m.TestCamera.__doc__ - - -class TestKeyboard(Test): - __doc__ = m.TestKeyboard.__doc__ - - -class TestTrackpad(Test): - __doc__ = m.TestTrackpad.__doc__ - - -class TestBios(Test): - __doc__ = m.TestBios.__doc__ - bios_power_on = Boolean() - access_range = EnumField(BiosAccessRange, data_key='accessRange') - - -class VisualTest(Test): - __doc__ = m.VisualTest.__doc__ - appearance_range = EnumField(AppearanceRange, required=True, data_key='appearanceRange') - functionality_range = EnumField(FunctionalityRange, - required=True, - data_key='functionalityRange') - labelling = Boolean() - - -class Rate(ActionWithOneDevice): - __doc__ = m.Rate.__doc__ - rating = Integer(validate=Range(*R_POSITIVE), - dump_only=True, - description=m.Rate._rating.comment) - version = Version(dump_only=True, - description=m.Rate.version.comment) - appearance = Integer(validate=Range(enums.R_NEGATIVE), - dump_only=True, - description=m.Rate._appearance.comment) - functionality = Integer(validate=Range(enums.R_NEGATIVE), - dump_only=True, - description=m.Rate._functionality.comment) - rating_range = EnumField(RatingRange, - dump_only=True, - data_key='ratingRange', - description=m.Rate.rating_range.__doc__) - - -class RateComputer(Rate): - __doc__ = m.RateComputer.__doc__ - processor = Float(dump_only=True) - ram = Float(dump_only=True) - data_storage = Float(dump_only=True, data_key='dataStorage') - graphic_card = Float(dump_only=True, data_key='graphicCard') - - data_storage_range = EnumField(RatingRange, dump_only=True, data_key='dataStorageRange') - ram_range = EnumField(RatingRange, dump_only=True, data_key='ramRange') - processor_range = EnumField(RatingRange, dump_only=True, data_key='processorRange') - graphic_card_range = EnumField(RatingRange, dump_only=True, data_key='graphicCardRange') - - -class Price(ActionWithOneDevice): - __doc__ = m.Price.__doc__ - currency = EnumField(Currency, required=True, description=m.Price.currency.comment) - price = Decimal(places=m.Price.SCALE, - rounding=m.Price.ROUND, - required=True, - description=m.Price.price.comment) - version = Version(dump_only=True, description=m.Price.version.comment) - rating = NestedOn(Rate, dump_only=True, description=m.Price.rating_id.comment) - - -class EreusePrice(Price): - __doc__ = m.EreusePrice.__doc__ - - class Service(MarshmallowSchema): - class Type(MarshmallowSchema): - amount = Float() - percentage = Float() - - standard = Nested(Type) - warranty2 = Nested(Type) - - warranty2 = Float() - refurbisher = Nested(Service) - retailer = Nested(Service) - platform = Nested(Service) - - -class Install(ActionWithOneDevice): - __doc__ = m.Install.__doc__ - name = SanitizedStr(validate=Length(min=4, max=STR_BIG_SIZE), +class ProofTransfer(Proof): + __doc__ = m.ProofTransfer.__doc__ + transfer = NestedOn(s_action.DisposeProduct, required=True, - description='The name of the OS installed.') - elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) - address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256})) + only_query='id') -class Snapshot(ActionWithOneDevice): - __doc__ = m.Snapshot.__doc__ - """ - The Snapshot updates the state of the device with information about - its components and actions performed at them. - - See docs for more info. - """ - uuid = UUID() - software = EnumField(SnapshotSoftware, - required=True, - description='The software that generated this Snapshot.') - version = Version(required=True, description='The version of the software.') - actions = NestedOn(Action, many=True, dump_only=True) - elapsed = TimeDelta(precision=TimeDelta.SECONDS) - components = NestedOn(s_device.Component, - many=True, - description='A list of components that are inside of the device' - 'at the moment of this Snapshot.' - 'Order is preserved, so the component num 0 when' - 'submitting is the component num 0 when returning it back.') - - @validates_schema - def validate_workbench_version(self, data: dict): - if data['software'] == SnapshotSoftware.Workbench: - if data['version'] < app.config['MIN_WORKBENCH']: - raise ValidationError( - 'Min. supported Workbench version is ' - '{} but yours is {}.'.format(app.config['MIN_WORKBENCH'], data['version']), - field_names=['version'] - ) - - @validates_schema - def validate_components_only_workbench(self, data: dict): - if data['software'] != SnapshotSoftware.Workbench: - if data.get('components', None) is not None: - raise ValidationError('Only Workbench can add component info', - field_names=['components']) - - @validates_schema - def validate_only_workbench_fields(self, data: dict): - """Ensures workbench has ``elapsed`` and ``uuid`` and no others.""" - # todo test - if data['software'] == SnapshotSoftware.Workbench: - if not data.get('uuid', None): - raise ValidationError('Snapshots from Workbench must have uuid', - field_names=['uuid']) - if data.get('elapsed', None) is None: - raise ValidationError('Snapshots from Workbench must have elapsed', - field_names=['elapsed']) - else: - if data.get('uuid', None): - raise ValidationError('Only Snapshots from Workbench can have uuid', - field_names=['uuid']) - if data.get('elapsed', None): - raise ValidationError('Only Snapshots from Workbench can have elapsed', - field_names=['elapsed']) +class ProofDataWipe(Proof): + __doc__ = m.ProofDataWipe.__doc__ + erasure_type = SanitizedStr(default='') + date = DateTime() + result = Boolean(missing=False) + erasure = NestedOn(s_action.EraseBasic, required=True, only_query='id') -class ToRepair(ActionWithMultipleDevices): - __doc__ = m.ToRepair.__doc__ +class ProofFunction(Proof): + __doc__ = m.ProofFunction.__doc__ + disk_usage = Integer() + rate = NestedOn(s_action.Rate, required=True, only_query='id') -class Repair(ActionWithMultipleDevices): - __doc__ = m.Repair.__doc__ - - -class Ready(ActionWithMultipleDevices): - __doc__ = m.Ready.__doc__ - - -class ToPrepare(ActionWithMultipleDevices): - __doc__ = m.ToPrepare.__doc__ - - -class Prepare(ActionWithMultipleDevices): - __doc__ = m.Prepare.__doc__ - - -class Live(ActionWithOneDevice): - __doc__ = m.Live.__doc__ - ip = IP(dump_only=True) - subdivision_confidence = Integer(dump_only=True, data_key='subdivisionConfidence') - subdivision = EnumField(Subdivision, dump_only=True) - country = EnumField(Country, dump_only=True) - city = SanitizedStr(lower=True, dump_only=True) - city_confidence = Integer(dump_only=True, data_key='cityConfidence') - isp = SanitizedStr(lower=True, dump_only=True) - organization = SanitizedStr(lower=True, dump_only=True) - organization_type = SanitizedStr(lower=True, dump_only=True, data_key='organizationType') - - -class Organize(ActionWithMultipleDevices): - __doc__ = m.Organize.__doc__ - - -class Reserve(Organize): - __doc__ = m.Reserve.__doc__ - - -class CancelReservation(Organize): - __doc__ = m.CancelReservation.__doc__ - - -class Trade(ActionWithMultipleDevices): - __doc__ = m.Trade.__doc__ - shipping_date = DateTime(data_key='shippingDate') - invoice_number = SanitizedStr(validate=Length(max=STR_SIZE), data_key='invoiceNumber') - price = NestedOn(Price) - to = NestedOn(s_agent.Agent, only_query='id', required=True, comment=m.Trade.to_comment) - confirms = NestedOn(Organize) - - -class Sell(Trade): - __doc__ = m.Sell.__doc__ - - -class Donate(Trade): - __doc__ = m.Donate.__doc__ - - -class Rent(Trade): - __doc__ = m.Rent.__doc__ - - -class MakeAvailable(ActionWithMultipleDevices): - __doc__ = m.MakeAvailable.__doc__ - - -class CancelTrade(Trade): - __doc__ = m.CancelTrade.__doc__ - - -class ToDisposeProduct(Trade): - __doc__ = m.ToDisposeProduct.__doc__ - - -class DisposeProduct(Trade): - __doc__ = m.DisposeProduct.__doc__ - - -class TransferOwnershipBlockchain(Trade): - __doc__ = m.TransferOwnershipBlockchain.__doc__ - - -class Receive(ActionWithMultipleDevices): - __doc__ = m.Receive.__doc__ - role = EnumField(ReceiverRole) - - -class Migrate(ActionWithMultipleDevices): - __doc__ = m.Migrate.__doc__ - other = URL() - - -class MigrateTo(Migrate): - __doc__ = m.MigrateTo.__doc__ - - -class MigrateFrom(Migrate): - __doc__ = m.MigrateFrom.__doc__ - - -class Transferred(ActionWithMultipleDevices): - __doc__ = m.Transferred.__doc__ +class ProofReuse(Proof): + __doc__ = m.ProofReuse.__doc__ + price = Integer() +class ProofRecycling(Proof): + __doc__ = m.ProofRecycling.__doc__ + collection_point = SanitizedStr(default='') + date = DateTime() + contact = SanitizedStr(default='') + ticket = SanitizedStr(default='') + gps_location = SanitizedStr(default='') diff --git a/ereuse_devicehub/resources/proof/views.py b/ereuse_devicehub/resources/proof/views.py index 2d127666..61886ae8 100644 --- a/ereuse_devicehub/resources/proof/views.py +++ b/ereuse_devicehub/resources/proof/views.py @@ -28,16 +28,11 @@ class ProofView(View): for proof in json['proofs']: resource_def = app.resources[proof['type']] a = resource_def.schema.load(json) - if json['type'] == Snapshot.t: - return self.snapshot(a, resource_def) - if json['type'] == VisualTest.t: - pass - # TODO JN add compute rate with new visual test and old components device Model = db.Model._decl_class_registry.data[json['type']]() action = Model(**a) db.session.add(action) db.session().final_flush() ret = self.schema.jsonify(action) - ret.status_code = 201 - db.session.commit() - return ret + db.session.commit() + ret.status_code = 201 + return ret From c9f6217e4264cb98cb6559c1d81018dd4af092b4 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Thu, 12 Mar 2020 17:07:35 +0100 Subject: [PATCH 03/18] Implement batch view --- ereuse_devicehub/config.py | 3 ++- ereuse_devicehub/resources/proof/__init__.py | 5 +++-- ereuse_devicehub/resources/proof/models.py | 10 +++++----- ereuse_devicehub/resources/proof/schemas.py | 5 +++-- ereuse_devicehub/resources/proof/views.py | 17 +++++++++-------- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/ereuse_devicehub/config.py b/ereuse_devicehub/config.py index 36c2dbd7..b1db6e9e 100644 --- a/ereuse_devicehub/config.py +++ b/ereuse_devicehub/config.py @@ -7,7 +7,8 @@ from teal.config import Config from teal.enums import Currency from teal.utils import import_resource -from ereuse_devicehub.resources import action, agent, deliverynote, inventory, lot, tag, user +from ereuse_devicehub.resources import action, agent, deliverynote, inventory, \ + lot, proof, tag, user from ereuse_devicehub.resources.device import definitions from ereuse_devicehub.resources.documents import documents from ereuse_devicehub.resources.enums import PriceSoftware diff --git a/ereuse_devicehub/resources/proof/__init__.py b/ereuse_devicehub/resources/proof/__init__.py index 93b8e9f3..ef3a68a6 100644 --- a/ereuse_devicehub/resources/proof/__init__.py +++ b/ereuse_devicehub/resources/proof/__init__.py @@ -1,13 +1,14 @@ from teal.resource import Converters, Resource -from ereuse_devicehub.resources.action import schemas +from ereuse_devicehub.resources.proof import schemas from ereuse_devicehub.resources.proof.views import ProofView class ProofDef(Resource): SCHEMA = schemas.Proof VIEW = ProofView - AUTH = True + # AUTH = True + AUTH = False ID_CONVERTER = Converters.uuid diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 47888943..48f016f0 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -25,7 +25,7 @@ from teal.resource import url_for_resource from ereuse_devicehub.db import db from ereuse_devicehub.resources.action.models import Action, DisposeProduct, \ - EraseBasic, Rate + EraseBasic, Rate, Trade from ereuse_devicehub.resources.models import Thing @@ -79,7 +79,7 @@ class Proof(Thing): class ProofTransfer(JoinedTableMixin, Proof): - transfer_id = Column(BigInteger, ForeignKey(Action.id), nullable=False) + transfer_id = Column(UUID, ForeignKey(Trade.id), nullable=False) transfer = relationship(DisposeProduct, primaryjoin=DisposeProduct.id == transfer_id) @@ -88,7 +88,7 @@ class ProofDataWipe(JoinedTableMixin, Proof): erasure_type = Column(CIText()) date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) result = db.Column(db.Boolean, default=False, nullable=False) - erasure_id = Column(BigInteger, ForeignKey(EraseBasic.id), nullable=False) + erasure_id = Column(UUID, ForeignKey(EraseBasic.id), nullable=False) erasure = relationship(EraseBasic, backref=backref('proofs_datawipe', lazy=True, @@ -98,13 +98,13 @@ class ProofDataWipe(JoinedTableMixin, Proof): class ProofFunction(JoinedTableMixin, Proof): disk_usage = db.Column(db.Integer, default=0) - rate_id = Column(BigInteger, ForeignKey(Rate.id), nullable=False) + rate_id = Column(UUID, ForeignKey(Rate.id), nullable=False) rate = relationship(Rate, primaryjoin=Rate.id == rate_id) class ProofReuse(JoinedTableMixin, Proof): - price = db.Column(db.Integer, required=True) + price = db.Column(db.Integer) class ProofRecycling(JoinedTableMixin, Proof): diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 650a2090..1aba739e 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -16,7 +16,8 @@ from ereuse_devicehub.resources.action import schemas as s_action class Proof(Thing): __doc__ = m.Proof.__doc__ id = UUID(dump_only=True) - ethereumHashes = SanitizedStr(default='', validate=Length(max=STR_BIG_SIZE)) + ethereum_hashes = SanitizedStr(default='', validate=Length(max=STR_BIG_SIZE), + data_key="ethereumHashes") url = URL(dump_only=True, description=m.Proof.url.__doc__) @@ -32,7 +33,7 @@ class ProofDataWipe(Proof): erasure_type = SanitizedStr(default='') date = DateTime() result = Boolean(missing=False) - erasure = NestedOn(s_action.EraseBasic, required=True, only_query='id') + erasure = NestedOn(s_action.EraseBasic, dump_only=True, only_query='id') class ProofFunction(Proof): diff --git a/ereuse_devicehub/resources/proof/views.py b/ereuse_devicehub/resources/proof/views.py index 61886ae8..2485a6a2 100644 --- a/ereuse_devicehub/resources/proof/views.py +++ b/ereuse_devicehub/resources/proof/views.py @@ -24,15 +24,16 @@ class ProofView(View): raise ValidationError('JSON is not correct.') # todo there should be a way to better get subclassess resource # defs + proofs = list() if json['batch']: - for proof in json['proofs']: - resource_def = app.resources[proof['type']] - a = resource_def.schema.load(json) - Model = db.Model._decl_class_registry.data[json['type']]() - action = Model(**a) - db.session.add(action) - db.session().final_flush() - ret = self.schema.jsonify(action) + for prf in json['proofs']: + resource_def = app.resources[prf['type']] + p = resource_def.schema.load(prf) + Model = db.Model._decl_class_registry.data[prf['type']]() + proof = Model(**p) + db.session.add(proof) + proofs.append(self.schema.dump(proof)) db.session.commit() + ret = self.schema.jsonify(proofs) ret.status_code = 201 return ret From 0d9dccda47c97ab9186472d3066a1dad3f822fc6 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Mon, 16 Mar 2020 00:26:20 +0100 Subject: [PATCH 04/18] First implementation of batch POST of ProofDataWipe --- ereuse_devicehub/resources/proof/models.py | 34 ++++++++++++--------- ereuse_devicehub/resources/proof/schemas.py | 4 +-- ereuse_devicehub/resources/proof/views.py | 12 +++++--- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 48f016f0..60e18ae3 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -49,10 +49,6 @@ class Proof(Thing): """The URL where to GET this proof.""" return urlutils.URL(url_for_resource(Proof, item_id=self.id)) - @property - def certificate(self) -> Optional[urlutils.URL]: - return None - # noinspection PyMethodParameters @declared_attr def __mapper_args__(cls): @@ -81,35 +77,43 @@ class Proof(Thing): 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) class ProofDataWipe(JoinedTableMixin, Proof): - erasure_type = Column(CIText()) - date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) - result = db.Column(db.Boolean, default=False, nullable=False) - erasure_id = Column(UUID, ForeignKey(EraseBasic.id), 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.""" + erasure_id = Column(UUID(as_uuid=True), ForeignKey(EraseBasic.id), nullable=False) erasure = relationship(EraseBasic, - backref=backref('proofs_datawipe', + backref=backref('proof_datawipe', lazy=True, cascade=CASCADE_OWN), primaryjoin=EraseBasic.id == erasure_id) class ProofFunction(JoinedTableMixin, Proof): - disk_usage = db.Column(db.Integer, default=0) + disk_usage = Column(db.Integer, default=0) rate_id = Column(UUID, ForeignKey(Rate.id), nullable=False) rate = relationship(Rate, + backref=backref('proof_function', + lazy=True, + cascade=CASCADE_OWN), primaryjoin=Rate.id == rate_id) class ProofReuse(JoinedTableMixin, Proof): - price = db.Column(db.Integer) + price = Column(db.Integer) class ProofRecycling(JoinedTableMixin, Proof): - collection_point = Column(CIText()) + collection_point = Column(CIText(), default='', nullable=False) date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) - contact = Column(CIText()) - ticket = Column(CIText()) - gps_location = Column(CIText()) + contact = Column(CIText(), default='', nullable=False) + ticket = Column(CIText(), default='', nullable=False) + gps_location = Column(CIText(), default='', nullable=False) diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 1aba739e..4859150a 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -31,9 +31,9 @@ class ProofTransfer(Proof): class ProofDataWipe(Proof): __doc__ = m.ProofDataWipe.__doc__ erasure_type = SanitizedStr(default='') - date = DateTime() + date = DateTime('iso', required=True) result = Boolean(missing=False) - erasure = NestedOn(s_action.EraseBasic, dump_only=True, only_query='id') + erasure = NestedOn(s_action.EraseBasic, only_query='id') class ProofFunction(Proof): diff --git a/ereuse_devicehub/resources/proof/views.py b/ereuse_devicehub/resources/proof/views.py index 2485a6a2..c645479d 100644 --- a/ereuse_devicehub/resources/proof/views.py +++ b/ereuse_devicehub/resources/proof/views.py @@ -2,12 +2,13 @@ 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, 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 @@ -34,6 +35,9 @@ class ProofView(View): db.session.add(proof) proofs.append(self.schema.dump(proof)) db.session.commit() - ret = self.schema.jsonify(proofs) - ret.status_code = 201 - return ret + response = jsonify({ + 'items': proofs, + 'url': request.path + }) + response.status_code = 201 + return response From 621e172e5581b4964c2712213b4fed7195de90ab Mon Sep 17 00:00:00 2001 From: Big Lebowski Date: Tue, 17 Mar 2020 00:57:14 +0100 Subject: [PATCH 05/18] Add required field result and ethereumHash on POST --- ereuse_devicehub/resources/proof/models.py | 2 +- ereuse_devicehub/resources/proof/schemas.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 60e18ae3..3fe4ab7b 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -42,7 +42,7 @@ 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) @property def url(self) -> urlutils.URL: diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 4859150a..aa7cba1c 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -16,8 +16,8 @@ from ereuse_devicehub.resources.action import schemas as s_action 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__) @@ -30,10 +30,10 @@ class ProofTransfer(Proof): class ProofDataWipe(Proof): __doc__ = m.ProofDataWipe.__doc__ - erasure_type = SanitizedStr(default='') + erasure_type = SanitizedStr(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) + erasure = NestedOn(s_action.EraseBasic, only_query='id', data_key='erasureID') class ProofFunction(Proof): From 3ce32f2dd43b94a93f1555aa6bb23ccab318980f Mon Sep 17 00:00:00 2001 From: Big Lebowski Date: Tue, 17 Mar 2020 02:03:48 +0100 Subject: [PATCH 06/18] Add Proof N:M Device relation and support deviceIDs on POST --- ereuse_devicehub/resources/proof/models.py | 13 +++++++++++++ ereuse_devicehub/resources/proof/schemas.py | 7 +++++++ ereuse_devicehub/resources/proof/views.py | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 3fe4ab7b..13b81cc9 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -26,6 +26,7 @@ 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 @@ -43,6 +44,11 @@ class Proof(Thing): id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) type = Column(Unicode, nullable=False) ethereum_hash = Column(CIText(), default='', nullable=False) + devices = relationship(Device, + backref=backref('proofs_multiple', lazy=True), + secondary=lambda: ProofDevice.__table__, + order_by=lambda: Device.id, + collection_class=OrderedSet) @property def url(self) -> urlutils.URL: @@ -74,6 +80,13 @@ class Proof(Thing): return '<{0.t} {0.id} >'.format(self) + +class ProofDevice(db.Model): + device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) + proof_id = Column(UUID(as_uuid=True), ForeignKey(Proof.id), + primary_key=True) + + class ProofTransfer(JoinedTableMixin, Proof): transfer_id = Column(UUID, ForeignKey(Trade.id), nullable=False) transfer = relationship(DisposeProduct, diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index aa7cba1c..16a833b7 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -11,6 +11,7 @@ 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 class Proof(Thing): @@ -19,6 +20,12 @@ class Proof(Thing): 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__) + devices = NestedOn(s_device.Device, + many=True, + required=True, # todo test ensuring len(devices) >= 1 + only_query='id', + data_key='deviceIDs', + collection_class=OrderedSet) class ProofTransfer(Proof): diff --git a/ereuse_devicehub/resources/proof/views.py b/ereuse_devicehub/resources/proof/views.py index c645479d..cf63bdfe 100644 --- a/ereuse_devicehub/resources/proof/views.py +++ b/ereuse_devicehub/resources/proof/views.py @@ -33,8 +33,8 @@ class ProofView(View): Model = db.Model._decl_class_registry.data[prf['type']]() proof = Model(**p) db.session.add(proof) + db.session.commit() proofs.append(self.schema.dump(proof)) - db.session.commit() response = jsonify({ 'items': proofs, 'url': request.path From e3f960aa8b4436a857dea12d194caacb3750adb1 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Tue, 17 Mar 2020 23:36:27 +0100 Subject: [PATCH 07/18] Add ProofFunction view except author field --- ereuse_devicehub/resources/proof/models.py | 2 +- ereuse_devicehub/resources/proof/schemas.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 13b81cc9..4aa4f2fa 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -112,7 +112,7 @@ class ProofDataWipe(JoinedTableMixin, Proof): class ProofFunction(JoinedTableMixin, Proof): disk_usage = Column(db.Integer, default=0) - rate_id = Column(UUID, ForeignKey(Rate.id), nullable=False) + rate_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), nullable=False) rate = relationship(Rate, backref=backref('proof_function', lazy=True, diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 16a833b7..26479e50 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -45,8 +45,9 @@ class ProofDataWipe(Proof): class ProofFunction(Proof): __doc__ = m.ProofFunction.__doc__ - disk_usage = Integer() - rate = NestedOn(s_action.Rate, required=True, only_query='id') + disk_usage = Integer(data_key='diskUsage') + rate = NestedOn(s_action.Rate, required=True, + only_query='id', data_key='rateID') class ProofReuse(Proof): From 390fb30d4f8bfb10fa1b3195565c2822edd8ca64 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Tue, 17 Mar 2020 23:45:23 +0100 Subject: [PATCH 08/18] Update ProofRecycle --- ereuse_devicehub/resources/proof/models.py | 2 ++ ereuse_devicehub/resources/proof/schemas.py | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 4aa4f2fa..fb1f676c 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -130,3 +130,5 @@ class ProofRecycling(JoinedTableMixin, Proof): 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 26479e50..bc9143bc 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -57,8 +57,9 @@ class ProofReuse(Proof): 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) From f6af58ef51e81b39db03a1d7f57a17745b32574b Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Tue, 17 Mar 2020 23:47:02 +0100 Subject: [PATCH 09/18] Update ProofTransfer --- ereuse_devicehub/resources/proof/models.py | 2 +- ereuse_devicehub/resources/proof/schemas.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index fb1f676c..07739ecb 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -88,7 +88,7 @@ class ProofDevice(db.Model): class ProofTransfer(JoinedTableMixin, Proof): - transfer_id = Column(UUID, ForeignKey(Trade.id), nullable=False) + transfer_id = Column(UUID(as_uuid=True), ForeignKey(Trade.id), nullable=False) transfer = relationship(DisposeProduct, backref=backref("proof_transfer", lazy=True, diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index bc9143bc..1751dc20 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -32,6 +32,7 @@ class ProofTransfer(Proof): __doc__ = m.ProofTransfer.__doc__ transfer = NestedOn(s_action.DisposeProduct, required=True, + data_key='transferID', only_query='id') From dfb518068ddf6223c5d3245925fdeaa81ad49045 Mon Sep 17 00:00:00 2001 From: emmdim Date: Thu, 19 Mar 2020 01:55:26 +0100 Subject: [PATCH 10/18] Adds ethereum address to device, and adds PATCH --- ereuse_devicehub/resources/device/models.py | 1 + ereuse_devicehub/resources/device/schemas.py | 1 + ereuse_devicehub/resources/device/views.py | 25 ++++++++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 1ecbe8f3..68134b8e 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -382,6 +382,7 @@ 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) owner_address = db.Column(CIText(), db.ForeignKey(User.ethereum_address), diff --git a/ereuse_devicehub/resources/device/schemas.py b/ereuse_devicehub/resources/device/schemas.py index 093cec75..61c399ad 100644 --- a/ereuse_devicehub/resources/device/schemas.py +++ b/ereuse_devicehub/resources/device/schemas.py @@ -122,6 +122,7 @@ 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') diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 665acd68..71e77cfc 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 @@ -92,7 +92,24 @@ class DeviceView(View): description: The device or devices. """ 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', '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: From b70c2294854a24c4c0811bdd42193b1efc95ea63 Mon Sep 17 00:00:00 2001 From: emmdim Date: Fri, 20 Mar 2020 15:20:22 +0100 Subject: [PATCH 11/18] Implements #13, but currently can be applied only to transferred devices --- ereuse_devicehub/resources/deliverynote/views.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ereuse_devicehub/resources/deliverynote/views.py b/ereuse_devicehub/resources/deliverynote/views.py index 0b0cb35a..75834c09 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(): + # Transalate ethereum_address attribute + devKey = key + if key == 'ethereum_address': + devKey = 'deliverynote_address' setattr(dlvnote, key, value) + if devKey in device_fields: + for dev in computers: + setattr(dev, devKey, value) + db.session.commit() return Response(status=204) From bd396f0642e607593102e92c3bf095f9ed388cf2 Mon Sep 17 00:00:00 2001 From: emmdim Date: Fri, 20 Mar 2020 15:47:42 +0100 Subject: [PATCH 12/18] Adds new PATCH properties for device --- ereuse_devicehub/resources/device/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 71e77cfc..1aaa1f93 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -98,7 +98,7 @@ 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', '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') From 4f6eb00d3a5ded52a2319e20cf5805eac84b95ae Mon Sep 17 00:00:00 2001 From: emmdim Date: Fri, 20 Mar 2020 15:48:40 +0100 Subject: [PATCH 13/18] Reverts temporarilty the propagation of values for devices until a clear Device registration strategy takes place --- .../resources/deliverynote/views.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ereuse_devicehub/resources/deliverynote/views.py b/ereuse_devicehub/resources/deliverynote/views.py index 75834c09..02a1ff8b 100644 --- a/ereuse_devicehub/resources/deliverynote/views.py +++ b/ereuse_devicehub/resources/deliverynote/views.py @@ -42,17 +42,17 @@ 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)] + # device_fields = ['transfer_state', 'deliverynote_address'] + # computers = [x for x in dlvnote.transferred_devices if isinstance(x, Computer)] for key, value in d.items(): - # Transalate ethereum_address attribute - devKey = key - if key == 'ethereum_address': - devKey = 'deliverynote_address' setattr(dlvnote, key, value) - if devKey in device_fields: - for dev in computers: - setattr(dev, devKey, 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) From 8a818bdc007fc3c4a8cedbb211ca4f87a48b78f0 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Fri, 20 Mar 2020 23:57:30 +0100 Subject: [PATCH 14/18] Add schemas for all proofs, redefince and 1-1 relation to device --- ereuse_devicehub/resources/proof/models.py | 47 +++++++++++++++------ ereuse_devicehub/resources/proof/schemas.py | 19 +++++---- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 07739ecb..ff7a252e 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -28,6 +28,7 @@ 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: @@ -44,11 +45,13 @@ class Proof(Thing): id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) type = Column(Unicode, nullable=False) ethereum_hash = Column(CIText(), default='', nullable=False) - devices = relationship(Device, - backref=backref('proofs_multiple', lazy=True), - secondary=lambda: ProofDevice.__table__, - order_by=lambda: Device.id, - collection_class=OrderedSet) + device_id = db.Column(BigInteger, + db.ForeignKey(Device.id), + nullable=False) + device = db.relationship(Device, + backref=db.backref('devices', uselist=True, lazy=True), + lazy=True, + primaryjoin=Device.id == device_id) @property def url(self) -> urlutils.URL: @@ -81,19 +84,13 @@ class Proof(Thing): -class ProofDevice(db.Model): - device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) - proof_id = Column(UUID(as_uuid=True), ForeignKey(Proof.id), - primary_key=True) - - class ProofTransfer(JoinedTableMixin, Proof): transfer_id = Column(UUID(as_uuid=True), ForeignKey(Trade.id), nullable=False) transfer = relationship(DisposeProduct, backref=backref("proof_transfer", lazy=True, + uselist=False, cascade=CASCADE_OWN), - uselist=False, primaryjoin=DisposeProduct.id == transfer_id) @@ -102,33 +99,55 @@ class ProofDataWipe(JoinedTableMixin, Proof): 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(CIText(), + db.ForeignKey(User.ethereum_address), + nullable=False, + default=lambda: g.user.ethereum_address) + proof_author = relationship(User, primaryjoin=lambda: ProofDataWipe.proof_author_id == User.ethereum_address) 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) + proof_author_id = Column(CIText(), + db.ForeignKey(User.ethereum_address), + nullable=False, + default=lambda: g.user.ethereum_address) + proof_author = db.relationship(User, primaryjoin=lambda: ProofFunction.proof_author_id == User.ethereum_address) 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(CIText(), + db.ForeignKey(User.ethereum_address), + nullable=False, + default=lambda: g.user.ethereum_address) + supplier = db.relationship(User, primaryjoin=lambda: ProofReuse.supplier_id == User.ethereum_address) + receiver_id = db.Column(CIText(), + db.ForeignKey(User.ethereum_address), + nullable=False) + receiver = db.relationship(User, primaryjoin=lambda: ProofReuse.receiver_id == User.ethereum_address) 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 1751dc20..3070448e 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -12,6 +12,7 @@ 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): @@ -20,40 +21,40 @@ class Proof(Thing): 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__) - devices = NestedOn(s_device.Device, - many=True, - required=True, # todo test ensuring len(devices) >= 1 - only_query='id', - data_key='deviceIDs', - collection_class=OrderedSet) + device = NestedOn(s_device.Device, only_query='id', required=True, data_key='deviceID') class ProofTransfer(Proof): __doc__ = m.ProofTransfer.__doc__ transfer = NestedOn(s_action.DisposeProduct, required=True, - data_key='transferID', only_query='id') class ProofDataWipe(Proof): __doc__ = m.ProofDataWipe.__doc__ - erasure_type = SanitizedStr(default='', data_key='erasureType') + erasure_type = String(default='', data_key='erasureType') date = DateTime('iso', required=True) result = Boolean(required=True) + proof_author = NestedOn(s_user.User, only_query='id', data_key='proofAuthor') erasure = NestedOn(s_action.EraseBasic, only_query='id', data_key='erasureID') class ProofFunction(Proof): __doc__ = m.ProofFunction.__doc__ disk_usage = Integer(data_key='diskUsage') + proof_author = NestedOn(s_user.User, only_query='id', data_key='proofAuthor') 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 = NestedOn(s_user.User, only_query='ethereum_address', required=True, data_key='supplierAddress') + receiver = NestedOn(s_user.User, only_query='ethereum_address', required=True, data_key='receiverAddress') + price = Integer(required=True) class ProofRecycling(Proof): From e78d62602b104729757e1d37ccedba2f0767ce2f Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Mon, 23 Mar 2020 18:49:22 +0100 Subject: [PATCH 15/18] Align ProofTransfer to Chapter 6 on specs, fixes #12 --- ereuse_devicehub/resources/proof/models.py | 17 ++++++++++------- ereuse_devicehub/resources/proof/schemas.py | 10 ++++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index ff7a252e..6f63404b 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -85,13 +85,16 @@ class Proof(Thing): class ProofTransfer(JoinedTableMixin, Proof): - transfer_id = Column(UUID(as_uuid=True), ForeignKey(Trade.id), nullable=False) - transfer = relationship(DisposeProduct, - backref=backref("proof_transfer", - lazy=True, - uselist=False, - cascade=CASCADE_OWN), - primaryjoin=DisposeProduct.id == transfer_id) + supplier_id = db.Column(CIText(), + db.ForeignKey(User.ethereum_address), + nullable=False, + default=lambda: g.user.ethereum_address) + supplier = db.relationship(User, primaryjoin=lambda: ProofTransfer.supplier_id == User.ethereum_address) + receiver_id = db.Column(CIText(), + db.ForeignKey(User.ethereum_address), + nullable=False) + receiver = db.relationship(User, primaryjoin=lambda: ProofTransfer.receiver_id == User.ethereum_address) + deposit = Column(db.Integer, default=0) class ProofDataWipe(JoinedTableMixin, Proof): diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 3070448e..9726381a 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 @@ -26,9 +26,11 @@ class Proof(Thing): class ProofTransfer(Proof): __doc__ = m.ProofTransfer.__doc__ - transfer = NestedOn(s_action.DisposeProduct, - required=True, - only_query='id') + deposit = Integer() + supplier_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), + load_only=True, required=True, data_key='supplierID') + receiver_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), + load_only=True, required=True, data_key='receiverID') class ProofDataWipe(Proof): From bd2d72ad69956b7c96595402ba40520d22971b97 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Tue, 24 Mar 2020 22:34:53 +0100 Subject: [PATCH 16/18] Replace ethereum_address for User with id --- ereuse_devicehub/resources/proof/models.py | 48 ++++++++++----------- ereuse_devicehub/resources/proof/schemas.py | 27 ++++++------ ereuse_devicehub/resources/proof/views.py | 5 ++- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index 6f63404b..ed8bd798 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -49,7 +49,7 @@ class Proof(Thing): db.ForeignKey(Device.id), nullable=False) device = db.relationship(Device, - backref=db.backref('devices', uselist=True, lazy=True), + backref=db.backref('proofs_device', uselist=True, lazy=True), lazy=True, primaryjoin=Device.id == device_id) @@ -85,28 +85,28 @@ class Proof(Thing): class ProofTransfer(JoinedTableMixin, Proof): - supplier_id = db.Column(CIText(), - db.ForeignKey(User.ethereum_address), + supplier_id = db.Column(UUID(as_uuid=True), + db.ForeignKey(User.id), nullable=False, - default=lambda: g.user.ethereum_address) - supplier = db.relationship(User, primaryjoin=lambda: ProofTransfer.supplier_id == User.ethereum_address) - receiver_id = db.Column(CIText(), - db.ForeignKey(User.ethereum_address), + 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.ethereum_address) + 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(CIText(), - db.ForeignKey(User.ethereum_address), + proof_author_id = Column(UUID(as_uuid=True), + db.ForeignKey(User.id), nullable=False, - default=lambda: g.user.ethereum_address) - proof_author = relationship(User, primaryjoin=lambda: ProofDataWipe.proof_author_id == User.ethereum_address) + 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', @@ -118,11 +118,11 @@ class ProofDataWipe(JoinedTableMixin, Proof): class ProofFunction(JoinedTableMixin, Proof): disk_usage = Column(db.Integer, default=0) - proof_author_id = Column(CIText(), - db.ForeignKey(User.ethereum_address), + proof_author_id = Column(UUID(as_uuid=True), + db.ForeignKey(User.id), nullable=False, - default=lambda: g.user.ethereum_address) - proof_author = db.relationship(User, primaryjoin=lambda: ProofFunction.proof_author_id == User.ethereum_address) + 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', @@ -135,15 +135,15 @@ class ProofFunction(JoinedTableMixin, Proof): class ProofReuse(JoinedTableMixin, Proof): receiver_segment = Column(CIText(), default='', nullable=False) id_receipt = Column(CIText(), default='', nullable=False) - supplier_id = db.Column(CIText(), - db.ForeignKey(User.ethereum_address), + supplier_id = db.Column(UUID(as_uuid=True), + db.ForeignKey(User.id), nullable=False, - default=lambda: g.user.ethereum_address) - supplier = db.relationship(User, primaryjoin=lambda: ProofReuse.supplier_id == User.ethereum_address) - receiver_id = db.Column(CIText(), - db.ForeignKey(User.ethereum_address), + default=lambda: g.user.id) + 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) - receiver = db.relationship(User, primaryjoin=lambda: ProofReuse.receiver_id == User.ethereum_address) + receiver = db.relationship(User, primaryjoin=lambda: ProofReuse.receiver_id == User.id) price = Column(db.Integer) diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 9726381a..581ad681 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -21,31 +21,34 @@ class Proof(Thing): 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 = NestedOn(s_device.Device, only_query='id', required=True, data_key='deviceID') + device_id = Integer(load_only=True, data_key='deviceID') + device = NestedOn(s_device.Device, dump_only=True) class ProofTransfer(Proof): __doc__ = m.ProofTransfer.__doc__ - deposit = Integer() - supplier_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), - load_only=True, required=True, data_key='supplierID') - receiver_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), - load_only=True, required=True, data_key='receiverID') + 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 = String(default='', data_key='erasureType') + # erasure_type = String(default='', data_key='erasureType') date = DateTime('iso', required=True) result = Boolean(required=True) - proof_author = NestedOn(s_user.User, only_query='id', data_key='proofAuthor') + proof_author_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), + load_only=True, required=True, data_key='proofAuthor') + 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(data_key='diskUsage') - proof_author = NestedOn(s_user.User, only_query='id', data_key='proofAuthor') + 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='proofAuthor') + proof_author = NestedOn(s_user.User, dump_only=True) rate = NestedOn(s_action.Rate, required=True, only_query='id', data_key='rateID') @@ -54,8 +57,8 @@ class ProofReuse(Proof): __doc__ = m.ProofReuse.__doc__ receiver_segment = String(default='', data_key='receiverSegment', required=True) id_receipt = String(default='', data_key='idReceipt', required=True) - supplier = NestedOn(s_user.User, only_query='ethereum_address', required=True, data_key='supplierAddress') - receiver = NestedOn(s_user.User, only_query='ethereum_address', required=True, data_key='receiverAddress') + supplier_id = UUID(load_only=True, required=True, data_key='supplierID') + receiver_id = UUID(load_only=True, required=True, data_key='receiverID') price = Integer(required=True) diff --git a/ereuse_devicehub/resources/proof/views.py b/ereuse_devicehub/resources/proof/views.py index cf63bdfe..9b016df9 100644 --- a/ereuse_devicehub/resources/proof/views.py +++ b/ereuse_devicehub/resources/proof/views.py @@ -33,8 +33,9 @@ class ProofView(View): Model = db.Model._decl_class_registry.data[prf['type']]() proof = Model(**p) db.session.add(proof) - db.session.commit() - proofs.append(self.schema.dump(proof)) + proofs.append(resource_def.schema.dump(proof)) + db.session().final_flush() + db.session.commit() response = jsonify({ 'items': proofs, 'url': request.path From edf2e39fc7c7ba1b05f60bd094955ea8bca7e38a Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Wed, 25 Mar 2020 15:38:29 +0100 Subject: [PATCH 17/18] Change proofAuthor to proofAuthorID and resolves #18 --- ereuse_devicehub/resources/proof/schemas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index 581ad681..a07fc524 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -38,7 +38,7 @@ class ProofDataWipe(Proof): date = DateTime('iso', required=True) result = Boolean(required=True) proof_author_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), - load_only=True, required=True, data_key='proofAuthor') + 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') @@ -47,7 +47,7 @@ class ProofFunction(Proof): __doc__ = m.ProofFunction.__doc__ 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='proofAuthor') + 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') From bc69d194f4040e0630095de88f7ca5d8b6b8ed48 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Fri, 27 Mar 2020 11:48:16 +0100 Subject: [PATCH 18/18] Make supplier_id, receiver_id optional for ProofReuse, fixes #21 --- ereuse_devicehub/resources/proof/models.py | 8 +++++--- ereuse_devicehub/resources/proof/schemas.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ereuse_devicehub/resources/proof/models.py b/ereuse_devicehub/resources/proof/models.py index ed8bd798..2562b446 100644 --- a/ereuse_devicehub/resources/proof/models.py +++ b/ereuse_devicehub/resources/proof/models.py @@ -137,12 +137,14 @@ class ProofReuse(JoinedTableMixin, Proof): 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=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=False) + nullable=True) receiver = db.relationship(User, primaryjoin=lambda: ProofReuse.receiver_id == User.id) price = Column(db.Integer) diff --git a/ereuse_devicehub/resources/proof/schemas.py b/ereuse_devicehub/resources/proof/schemas.py index a07fc524..de18a41f 100644 --- a/ereuse_devicehub/resources/proof/schemas.py +++ b/ereuse_devicehub/resources/proof/schemas.py @@ -57,8 +57,8 @@ class ProofReuse(Proof): __doc__ = m.ProofReuse.__doc__ 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=True, data_key='supplierID') - receiver_id = UUID(load_only=True, required=True, data_key='receiverID') + 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)