From 2810990fda894f7a5eae0bb1a2bb056c5b9e465f Mon Sep 17 00:00:00 2001 From: emmdim Date: Wed, 11 Mar 2020 18:39:58 +0100 Subject: [PATCH 1/6] Convertes expected_devices into a JSON Array, closes #10 --- ereuse_devicehub/resources/deliverynote/models.py | 10 ++++++---- ereuse_devicehub/resources/deliverynote/schemas.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ereuse_devicehub/resources/deliverynote/models.py b/ereuse_devicehub/resources/deliverynote/models.py index 7396977e..9e2bb7d3 100644 --- a/ereuse_devicehub/resources/deliverynote/models.py +++ b/ereuse_devicehub/resources/deliverynote/models.py @@ -4,8 +4,9 @@ from datetime import datetime from boltons import urlutils from citext import CIText from flask import g +from typing import Iterable from sqlalchemy.types import ARRAY -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.dialects.postgresql import UUID, JSONB from teal.db import CASCADE_OWN, check_range, IntEnum from teal.resource import url_for_resource @@ -41,7 +42,8 @@ class Deliverynote(Thing): # to SnapshotDelivery entity. # At this stage of implementation they will treated as a # comma-separated string of the devices expexted/transfered - expected_devices = db.Column(db.ARRAY(db.Integer, dimensions=1), nullable=False) + expected_devices = db.Column(JSONB, nullable=False) + # expected_devices = db.Column(db.ARRAY(JSONB, dimensions=1), nullable=False) transferred_devices = db.Column(db.ARRAY(db.Integer, dimensions=1), nullable=True) transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False) transfer_state.comment = TransferState.__doc__ @@ -55,8 +57,8 @@ class Deliverynote(Thing): primaryjoin=Lot.id == lot_id) def __init__(self, document_id: str, deposit: str, date, - supplier_email: str, - expected_devices: str, + supplier_email: str, + expected_devices: Iterable, transfer_state: TransferState) -> None: """Initializes a delivery note """ diff --git a/ereuse_devicehub/resources/deliverynote/schemas.py b/ereuse_devicehub/resources/deliverynote/schemas.py index 3e649b4e..0a6cf4ca 100644 --- a/ereuse_devicehub/resources/deliverynote/schemas.py +++ b/ereuse_devicehub/resources/deliverynote/schemas.py @@ -24,6 +24,6 @@ class Deliverynote(Thing): description=m.Deliverynote.deposit.__doc__) ethereum_address = f.String(description='User identifier address inside the Blockchain', data_key='ethereumAddress') - expected_devices = f.List(f.Integer(), required=True, data_key='expectedDevices') + expected_devices = f.List(f.Dict, required=True, data_key='expectedDevices') transferred_devices = f.List(f.Integer(), required=False, data_key='transferredDevices') transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment) From 2aa527c0c30d1b49066027f29fac527da50b5825 Mon Sep 17 00:00:00 2001 From: emmdim Date: Wed, 11 Mar 2020 19:28:15 +0100 Subject: [PATCH 2/6] Resolves minor naming issue --- ereuse_devicehub/resources/deliverynote/schemas.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ereuse_devicehub/resources/deliverynote/schemas.py b/ereuse_devicehub/resources/deliverynote/schemas.py index 0a6cf4ca..3028c30f 100644 --- a/ereuse_devicehub/resources/deliverynote/schemas.py +++ b/ereuse_devicehub/resources/deliverynote/schemas.py @@ -22,8 +22,7 @@ class Deliverynote(Thing): date = f.DateTime('iso', required=True) deposit = f.Integer(validate=f.validate.Range(min=0, max=100), description=m.Deliverynote.deposit.__doc__) - ethereum_address = f.String(description='User identifier address inside the Blockchain', - data_key='ethereumAddress') + ethereum_address = f.String(description='User identifier address inside the Blockchain') expected_devices = f.List(f.Dict, required=True, data_key='expectedDevices') transferred_devices = f.List(f.Integer(), required=False, data_key='transferredDevices') transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment) From 994dce4349bce92424f0229d158bab19935ff923 Mon Sep 17 00:00:00 2001 From: Big Lebowski Date: Thu, 12 Mar 2020 00:40:41 +0100 Subject: [PATCH 3/6] 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 4/6] 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 5/6] 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 6/6] 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