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, List, 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.agent.schemas import Agent from ereuse_devicehub.resources.device.schemas import Component, Computer, Device from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \ PhysicalErasureMethod, PriceSoftware, RATE_POSITIVE, RatingRange, RatingSoftware, ReceiverRole, \ Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength from ereuse_devicehub.resources.event 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.schemas import User class Event(Thing): id = UUID(dump_only=True) name = SanitizedStr(default='', validate=Length(max=STR_BIG_SIZE), description=m.Event.name.comment) closed = Boolean(missing=True, description=m.Event.closed.comment) severity = EnumField(Severity, description=m.Event.severity.comment) description = SanitizedStr(default='', description=m.Event.description.comment) start_time = DateTime(data_key='startTime', description=m.Event.start_time.comment) end_time = DateTime(data_key='endTime', description=m.Event.end_time.comment) snapshot = NestedOn('Snapshot', dump_only=True) agent = NestedOn(Agent, description=m.Event.agent_id.comment) author = NestedOn(User, dump_only=True, exclude=('token',)) components = NestedOn(Component, dump_only=True, many=True) parent = NestedOn(Computer, dump_only=True, description=m.Event.parent_id.comment) url = URL(dump_only=True, description=m.Event.url.__doc__) class EventWithOneDevice(Event): device = NestedOn(Device, only_query='id') class EventWithMultipleDevices(Event): devices = NestedOn(Device, many=True, only_query='id', collection_class=OrderedSet) class Add(EventWithOneDevice): pass class Remove(EventWithOneDevice): pass class Allocate(EventWithMultipleDevices): to = NestedOn(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(EventWithMultipleDevices): from_rel = Nested(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(EventWithOneDevice): steps = NestedOn('Step', many=True) standards = f.List(EnumField(enums.ErasureStandards), dump_only=True) certificate = URL(dump_only=True) class EraseSectors(EraseBasic): pass class ErasePhysical(EraseBasic): method = EnumField(PhysicalErasureMethod, description=PhysicalErasureMethod.__doc__) class Step(Schema): 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.Event.severity.comment) class StepZero(Step): pass class StepRandom(Step): pass class Rate(EventWithOneDevice): rating = Integer(validate=Range(*RATE_POSITIVE), dump_only=True, description=m.Rate.rating.comment) software = EnumField(RatingSoftware, dump_only=True, description=m.Rate.software.comment) version = Version(dump_only=True, description=m.Rate.version.comment) appearance = Integer(validate=Range(-3, 5), dump_only=True) functionality = Integer(validate=Range(-3, 5), dump_only=True) rating_range = EnumField(RatingRange, dump_only=True, data_key='ratingRange') class IndividualRate(Rate): pass class ManualRate(IndividualRate): appearance_range = EnumField(AppearanceRange, required=True, data_key='appearanceRange', description=m.ManualRate.appearance_range.comment) functionality_range = EnumField(FunctionalityRange, required=True, data_key='functionalityRange', description=m.ManualRate.functionality_range.comment) labelling = Boolean(description=m.ManualRate.labelling.comment) class WorkbenchRate(ManualRate): processor = Float() ram = Float() data_storage = Float() graphic_card = Float() bios = Float() bios_range = EnumField(Bios, description=m.WorkbenchRate.bios_range.comment, data_key='biosRange') 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 AggregateRate(Rate): workbench = NestedOn(WorkbenchRate, dump_only=True, description=m.AggregateRate.workbench_id.comment) manual = NestedOn(ManualRate, dump_only=True, description=m.AggregateRate.manual_id.comment) processor = Float(dump_only=True) ram = Float(dump_only=True) data_storage = Float(dump_only=True) graphic_card = Float(dump_only=True) bios = EnumField(Bios, dump_only=True) bios_range = EnumField(Bios, description=m.WorkbenchRate.bios_range.comment, data_key='biosRange') appearance_range = EnumField(AppearanceRange, required=True, data_key='appearanceRange', description=m.ManualRate.appearance_range.comment) functionality_range = EnumField(FunctionalityRange, required=True, data_key='functionalityRange', description=m.ManualRate.functionality_range.comment) labelling = Boolean(description=m.ManualRate.labelling.comment) 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(EventWithOneDevice): 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) software = EnumField(PriceSoftware, dump_only=True, description=m.Price.software.comment) version = Version(dump_only=True, description=m.Price.version.comment) rating = NestedOn(AggregateRate, dump_only=True, description=m.Price.rating_id.comment) class EreusePrice(Price): 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(EventWithOneDevice): 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(EventWithOneDevice): """ The Snapshot updates the state of the device with information about its components and events 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.') events = NestedOn(Event, many=True, dump_only=True) expected_events = List(EnumField(SnapshotExpectedEvents), data_key='expectedEvents', description='Keep open this Snapshot until the following events' 'are performed. Setting this value will activate' 'the async Snapshot.') elapsed = TimeDelta(precision=TimeDelta.SECONDS) components = NestedOn(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 Test(EventWithOneDevice): elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) class TestDataStorage(Test): 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): pass class Benchmark(EventWithOneDevice): elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True) class BenchmarkDataStorage(Benchmark): read_speed = Float(required=True, data_key='readSpeed') write_speed = Float(required=True, data_key='writeSpeed') class BenchmarkWithRate(Benchmark): rate = Float(required=True) class BenchmarkProcessor(BenchmarkWithRate): pass class BenchmarkProcessorSysbench(BenchmarkProcessor): pass class BenchmarkRamSysbench(BenchmarkWithRate): pass class ToRepair(EventWithMultipleDevices): pass class Repair(EventWithMultipleDevices): pass class ReadyToUse(EventWithMultipleDevices): pass class ToPrepare(EventWithMultipleDevices): pass class Prepare(EventWithMultipleDevices): pass class Live(EventWithOneDevice): 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(EventWithMultipleDevices): pass class Reserve(Organize): pass class CancelReservation(Organize): pass class Trade(EventWithMultipleDevices): shipping_date = DateTime(data_key='shippingDate') invoice_number = SanitizedStr(validate=Length(max=STR_SIZE), data_key='invoiceNumber') price = NestedOn(Price) to = NestedOn(Agent, only_query='id', required=True, comment=m.Trade.to_comment) confirms = NestedOn(Organize) class Sell(Trade): pass class Donate(Trade): pass class Rent(Trade): pass class CancelTrade(Trade): pass class ToDisposeProduct(Trade): pass class DisposeProduct(Trade): pass class Receive(EventWithMultipleDevices): role = EnumField(ReceiverRole) class Migrate(EventWithMultipleDevices): other = URL() class MigrateTo(Migrate): pass class MigrateFrom(Migrate): pass