import datetime from marshmallow import fields as f from marshmallow import post_load, pre_load from marshmallow.fields import ( UUID, Boolean, Date, DateTime, Dict, Float, Integer, List, Str, String, ) from marshmallow.validate import Length, OneOf, Range from sqlalchemy.util import OrderedSet from stdnum import imei, meid from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.resources import enums from ereuse_devicehub.resources.device import models as m from ereuse_devicehub.resources.device import states from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE from ereuse_devicehub.resources.schemas import Thing, UnitCodes from ereuse_devicehub.teal.enums import Layouts from ereuse_devicehub.teal.marshmallow import ( URL, EnumField, SanitizedStr, ValidationError, ) from ereuse_devicehub.teal.resource import Schema class Device(Thing): __doc__ = m.Device.__doc__ id = Integer(description=m.Device.id.comment, dump_only=True) hid = SanitizedStr(lower=True, description=m.Device.hid.comment) tags = NestedOn( 'Tag', many=True, collection_class=OrderedSet, description='A set of tags that identify the device.', ) model = SanitizedStr( lower=True, validate=Length(max=STR_BIG_SIZE), description=m.Device.model.comment, ) manufacturer = SanitizedStr( lower=True, validate=Length(max=STR_SIZE), description=m.Device.manufacturer.comment, ) serial_number = SanitizedStr( lower=True, validate=Length(max=STR_BIG_SIZE), data_key='serialNumber' ) part_number = SanitizedStr( lower=True, validate=Length(max=STR_BIG_SIZE), data_key='partNumber' ) brand = SanitizedStr( validate=Length(max=STR_BIG_SIZE), description=m.Device.brand.comment ) generation = Integer( validate=Range(1, 100), description=m.Device.generation.comment ) version = SanitizedStr(description=m.Device.version) weight = Float( validate=Range(0.1, 5), unit=UnitCodes.kgm, description=m.Device.weight.comment ) width = Float( validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.width.comment ) height = Float( validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.height.comment ) depth = Float( validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.depth.comment ) # TODO TimeOut 2. Comment actions and lots if there are time out. actions = NestedOn( 'Action', many=True, dump_only=True, description=m.Device.actions.__doc__ ) # TODO TimeOut 2. Comment actions_one and lots if there are time out. actions_one = NestedOn( 'Action', many=True, load_only=True, collection_class=OrderedSet ) problems = NestedOn( 'Action', many=True, dump_only=True, description=m.Device.problems.__doc__ ) url = URL(dump_only=True, description=m.Device.url.__doc__) # TODO TimeOut 2. Comment actions and lots if there are time out. lots = NestedOn( 'Lot', many=True, dump_only=True, description='The lots where this device is directly under.', ) rate = NestedOn('Rate', dump_only=True, description=m.Device.rate.__doc__) price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__) tradings = Dict(dump_only=True, description='') physical = EnumField( states.Physical, dump_only=True, description=m.Device.physical.__doc__ ) traking = EnumField( states.Traking, dump_only=True, description=m.Device.physical.__doc__ ) usage = EnumField( states.Usage, dump_only=True, description=m.Device.physical.__doc__ ) revoke = UUID(dump_only=True) physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor') production_date = DateTime( 'iso', description=m.Device.updated.comment, data_key='productionDate' ) working = NestedOn( 'Action', many=True, dump_only=True, description=m.Device.working.__doc__ ) variant = SanitizedStr(description=m.Device.variant.comment) sku = SanitizedStr(description=m.Device.sku.comment) image = URL(description=m.Device.image.comment) allocated = Boolean(description=m.Device.allocated.comment) dhid = SanitizedStr( data_key='devicehubID', description=m.Device.devicehub_id.comment ) family = SanitizedStr(validate=Length(max=STR_BIG_SIZE)) @pre_load def from_actions_to_actions_one(self, data: dict): """ Not an elegant way of allowing submitting actions to a device (in the context of Snapshots) without creating an ``actions`` field at the model (which is not possible). :param data: :return: """ # Note that it is secure to allow uploading actions_one # as the only time an user can send a device object is # in snapshots. data['actions_one'] = data.pop('actions', []) return data @post_load def validate_snapshot_actions(self, data): """Validates that only snapshot-related actions can be uploaded.""" from ereuse_devicehub.resources.action.models import ( Benchmark, EraseBasic, Install, Rate, Test, ) for action in data['actions_one']: if not isinstance(action, (Install, EraseBasic, Rate, Test, Benchmark)): raise ValidationError( 'You cannot upload {}'.format(action), field_names=['actions'] ) class Computer(Device): __doc__ = m.Computer.__doc__ # TODO TimeOut 1. Comment components if there are time out. components = NestedOn( 'Component', many=True, dump_only=True, collection_class=OrderedSet, description='The components that are inside this computer.', ) chassis = EnumField(enums.ComputerChassis, description=m.Computer.chassis.comment) ram_size = Integer( dump_only=True, data_key='ramSize', description=m.Computer.ram_size.__doc__ ) data_storage_size = Integer( dump_only=True, data_key='dataStorageSize', description=m.Computer.data_storage_size.__doc__, ) processor_model = Str( dump_only=True, data_key='processorModel', description=m.Computer.processor_model.__doc__, ) graphic_card_model = Str( dump_only=True, data_key='graphicCardModel', description=m.Computer.graphic_card_model.__doc__, ) network_speeds = List( Integer(dump_only=True), dump_only=True, data_key='networkSpeeds', description=m.Computer.network_speeds.__doc__, ) privacy = NestedOn( 'Action', many=True, dump_only=True, collection_class=set, description=m.Computer.privacy.__doc__, ) amount = Integer( validate=f.validate.Range(min=0, max=100), description=m.Computer.amount.__doc__ ) # author_id = NestedOn(s_user.User, only_query='author_id') owner_id = UUID(data_key='ownerID') transfer_state = EnumField( enums.TransferState, description=m.Computer.transfer_state.comment ) receiver_id = UUID(data_key='receiverID') system_uuid = UUID(required=False) class Desktop(Computer): __doc__ = m.Desktop.__doc__ class Laptop(Computer): __doc__ = m.Laptop.__doc__ layout = EnumField(Layouts, description=m.Laptop.layout.comment) class Server(Computer): __doc__ = m.Server.__doc__ class DisplayMixin: __doc__ = m.DisplayMixin.__doc__ size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150)) technology = EnumField( enums.DisplayTech, description=m.DisplayMixin.technology.comment ) resolution_width = Integer( data_key='resolutionWidth', validate=Range(10, 20000), description=m.DisplayMixin.resolution_width.comment, ) resolution_height = Integer( data_key='resolutionHeight', validate=Range(10, 20000), description=m.DisplayMixin.resolution_height.comment, ) refresh_rate = Integer(data_key='refreshRate', validate=Range(10, 1000)) contrast_ratio = Integer(data_key='contrastRatio', validate=Range(100, 100000)) touchable = Boolean(description=m.DisplayMixin.touchable.comment) aspect_ratio = String( dump_only=True, description=m.DisplayMixin.aspect_ratio.__doc__ ) widescreen = Boolean(dump_only=True, description=m.DisplayMixin.widescreen.__doc__) class NetworkMixin: __doc__ = m.NetworkMixin.__doc__ speed = Integer( validate=Range(min=10, max=10000), unit=UnitCodes.mbps, description=m.NetworkAdapter.speed.comment, ) wireless = Boolean(required=True) class Monitor(DisplayMixin, Device): __doc__ = m.Monitor.__doc__ class ComputerMonitor(Monitor): __doc__ = m.ComputerMonitor.__doc__ class TelevisionSet(Monitor): __doc__ = m.TelevisionSet.__doc__ class Projector(Monitor): __doc__ = m.Projector.__doc__ class Mobile(Device): __doc__ = m.Mobile.__doc__ imei = Integer(description=m.Mobile.imei.comment) meid = Str(description=m.Mobile.meid.comment) ram_size = Integer( validate=Range(min=128, max=36000), data_key='ramSize', unit=UnitCodes.mbyte, description=m.Mobile.ram_size.comment, ) data_storage_size = Integer( validate=Range(0, 10**8), data_key='dataStorageSize', description=m.Mobile.data_storage_size, ) display_size = Float( validate=Range(min=0.1, max=30.0), data_key='displaySize', description=m.Mobile.display_size.comment, ) @pre_load def convert_check_imei(self, data): if data.get('imei', None): data['imei'] = int(imei.validate(data['imei'])) return data @pre_load def convert_check_meid(self, data: dict): if data.get('meid', None): data['meid'] = meid.compact(data['meid']) return data class Smartphone(Mobile): __doc__ = m.Smartphone.__doc__ class Tablet(Mobile): __doc__ = m.Tablet.__doc__ class Cellphone(Mobile): __doc__ = m.Cellphone.__doc__ class Component(Device): __doc__ = m.Component.__doc__ parent = NestedOn(Device, dump_only=True) class GraphicCard(Component): __doc__ = m.GraphicCard.__doc__ memory = Integer( validate=Range(0, 10000), unit=UnitCodes.mbyte, description=m.GraphicCard.memory.comment, ) class DataStorage(Component): __doc__ = m.DataStorage.__doc__ size = Integer( validate=Range(0, 10**8), unit=UnitCodes.mbyte, description=m.DataStorage.size.comment, ) interface = EnumField(enums.DataStorageInterface) privacy = NestedOn('Action', dump_only=True) class HardDrive(DataStorage): __doc__ = m.HardDrive.__doc__ class SolidStateDrive(DataStorage): __doc__ = m.SolidStateDrive.__doc__ class Motherboard(Component): __doc__ = m.Motherboard.__doc__ slots = Integer(validate=Range(0, 20), description=m.Motherboard.slots.comment) usb = Integer(validate=Range(0, 20), description=m.Motherboard.usb.comment) firewire = Integer( validate=Range(0, 20), description=m.Motherboard.firewire.comment ) serial = Integer(validate=Range(0, 20), description=m.Motherboard.serial.comment) pcmcia = Integer(validate=Range(0, 20), description=m.Motherboard.pcmcia.comment) bios_date = Date( validate=Range( datetime.date(year=1980, month=1, day=1), datetime.date(year=2030, month=1, day=1), ), data_key='biosDate', description=m.Motherboard.bios_date, ) ram_slots = Integer(validate=Range(1), data_key='ramSlots') ram_max_size = Integer(validate=Range(1), data_key='ramMaxSize') class NetworkAdapter(NetworkMixin, Component): __doc__ = m.NetworkAdapter.__doc__ class Processor(Component): __doc__ = m.Processor.__doc__ speed = Float( validate=Range(min=0.1, max=15), unit=UnitCodes.ghz, description=m.Processor.speed.comment, ) cores = Integer( validate=Range(min=1, max=10), description=m.Processor.cores.comment ) threads = Integer( validate=Range(min=1, max=20), description=m.Processor.threads.comment ) address = Integer( validate=OneOf({8, 16, 32, 64, 128, 256}), description=m.Processor.address.comment, ) abi = SanitizedStr(lower=True, description=m.Processor.abi.comment) class RamModule(Component): __doc__ = m.RamModule.__doc__ size = Integer( validate=Range(min=128, max=17000), unit=UnitCodes.mbyte, description=m.RamModule.size.comment, ) speed = Integer(validate=Range(min=100, max=10000), unit=UnitCodes.mhz) interface = EnumField(enums.RamInterface) format = EnumField(enums.RamFormat) class SoundCard(Component): __doc__ = m.SoundCard.__doc__ class Display(DisplayMixin, Component): __doc__ = m.Display.__doc__ class Battery(Component): __doc__ = m.Battery.__doc__ wireless = Boolean(description=m.Battery.wireless.comment) technology = EnumField( enums.BatteryTechnology, description=m.Battery.technology.comment ) size = Integer(required=True, description=m.Battery.size.comment) class Camera(Component): __doc__ = m.Camera.__doc__ focal_length = Integer(data_key='focalLength') video_height = Integer(data_key='videoHeight') video_width = Integer(data_key='videoWidth') horizontal_view_angle = Integer(data_key='horizontalViewAngle') facing = Integer() vertical_view_angle = Integer(data_key='verticalViewAngle') video_stabilization = Integer(data_key='videoStabilization') flash = Integer() class Manufacturer(Schema): __doc__ = m.Manufacturer.__doc__ name = String(dump_only=True) url = URL(dump_only=True) logo = URL(dump_only=True) class ComputerAccessory(Device): __doc__ = m.ComputerAccessory.__doc__ class Mouse(ComputerAccessory): __doc__ = m.Mouse.__doc__ class MemoryCardReader(ComputerAccessory): __doc__ = m.MemoryCardReader.__doc__ class SAI(ComputerAccessory): __doc__ = m.SAI.__doc__ class Keyboard(ComputerAccessory): __doc__ = m.Keyboard.__doc__ layout = EnumField(Layouts) class Networking(NetworkMixin, Device): __doc__ = m.Networking.__doc__ class Router(Networking): __doc__ = m.Router.__doc__ class Switch(Networking): __doc__ = m.Switch.__doc__ class Hub(Networking): __doc__ = m.Hub.__doc__ class WirelessAccessPoint(Networking): __doc__ = m.WirelessAccessPoint.__doc__ class Printer(Device): __doc__ = m.Printer.__doc__ wireless = Boolean( required=True, missing=False, description=m.Printer.wireless.comment ) scanning = Boolean( required=True, missing=False, description=m.Printer.scanning.comment ) technology = EnumField( enums.PrinterTechnology, required=True, description=m.Printer.technology.comment ) monochrome = Boolean( required=True, missing=True, description=m.Printer.monochrome.comment ) class LabelPrinter(Printer): __doc__ = m.LabelPrinter.__doc__ class Sound(Device): __doc__ = m.Sound.__doc__ class Microphone(Sound): __doc__ = m.Microphone.__doc__ class Video(Device): __doc__ = m.Video.__doc__ class VideoScaler(Video): __doc__ = m.VideoScaler.__doc__ class Videoconference(Video): __doc__ = m.Videoconference.__doc__ class Cooking(Device): __doc__ = m.Cooking.__doc__ class Mixer(Cooking): __doc__ = m.Mixer.__doc__ class DIYAndGardening(Device): pass class Drill(DIYAndGardening): max_drill_bit_size = Integer(data_key='maxDrillBitSize') class PackOfScrewdrivers(DIYAndGardening): size = Integer() class Home(Device): pass class Dehumidifier(Home): size = Integer() class Stairs(Home): max_allowed_weight = Integer(data_key='maxAllowedWeight') class Recreation(Device): pass class Bike(Recreation): wheel_size = Integer(data_key='wheelSize') gears = Integer() class Racket(Recreation): pass class Other(Device): pass