2018-08-03 16:15:08 +00:00
|
|
|
from marshmallow import post_load, pre_load
|
2018-10-23 13:37:37 +00:00
|
|
|
from marshmallow.fields import Boolean, DateTime, Float, Integer, List, Str, String
|
2018-08-03 16:15:08 +00:00
|
|
|
from marshmallow.validate import Length, OneOf, Range
|
|
|
|
from sqlalchemy.util import OrderedSet
|
|
|
|
from stdnum import imei, meid
|
2018-10-23 13:37:37 +00:00
|
|
|
from teal.enums import Layouts
|
2018-09-30 17:40:28 +00:00
|
|
|
from teal.marshmallow import EnumField, SanitizedStr, URL, ValidationError
|
|
|
|
from teal.resource import Schema
|
2018-08-03 16:15:08 +00:00
|
|
|
|
2018-06-20 21:18:15 +00:00
|
|
|
from ereuse_devicehub.marshmallow import NestedOn
|
2018-11-09 10:22:13 +00:00
|
|
|
from ereuse_devicehub.resources import enums
|
2018-10-13 12:53:46 +00:00
|
|
|
from ereuse_devicehub.resources.device import models as m, states
|
2018-06-20 21:18:15 +00:00
|
|
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
|
|
|
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Device(Thing):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Device.__doc__
|
2018-06-26 13:35:13 +00:00
|
|
|
id = Integer(description=m.Device.id.comment, dump_only=True)
|
2018-09-30 10:29:33 +00:00
|
|
|
hid = SanitizedStr(lower=True, dump_only=True, description=m.Device.hid.comment)
|
2018-06-24 14:57:49 +00:00
|
|
|
tags = NestedOn('Tag',
|
|
|
|
many=True,
|
|
|
|
collection_class=OrderedSet,
|
2019-02-03 16:12:53 +00:00
|
|
|
description='A set of tags that identify the device.')
|
2018-09-30 10:29:33 +00:00
|
|
|
model = SanitizedStr(lower=True, validate=Length(max=STR_BIG_SIZE))
|
|
|
|
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
|
|
|
|
serial_number = SanitizedStr(lower=True, data_key='serialNumber')
|
2018-10-13 12:53:46 +00:00
|
|
|
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)
|
2018-06-24 14:57:49 +00:00
|
|
|
events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__)
|
2018-06-16 10:41:12 +00:00
|
|
|
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
|
2018-11-09 10:22:13 +00:00
|
|
|
problems = NestedOn('Event', many=True, dump_only=True, description=m.Device.problems.__doc__)
|
2018-10-05 15:13:23 +00:00
|
|
|
url = URL(dump_only=True, description=m.Device.url.__doc__)
|
2018-10-14 18:10:52 +00:00
|
|
|
lots = NestedOn('Lot',
|
|
|
|
many=True,
|
|
|
|
dump_only=True,
|
|
|
|
description='The lots where this device is directly under.')
|
2019-04-23 19:27:31 +00:00
|
|
|
rate = NestedOn('Rate', dump_only=True, description=m.Device.rate.__doc__)
|
2018-10-14 18:10:52 +00:00
|
|
|
price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__)
|
|
|
|
trading = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__)
|
|
|
|
physical = EnumField(states.Physical, dump_only=True, description=m.Device.physical.__doc__)
|
2018-10-13 12:53:46 +00:00
|
|
|
physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor')
|
2018-10-23 13:37:37 +00:00
|
|
|
production_date = DateTime('iso',
|
|
|
|
description=m.Device.updated.comment,
|
|
|
|
data_key='productionDate')
|
2018-11-09 10:22:13 +00:00
|
|
|
working = NestedOn('Event',
|
|
|
|
many=True,
|
|
|
|
dump_only=True,
|
|
|
|
description=m.Device.working.__doc__)
|
2018-06-16 10:41:12 +00:00
|
|
|
|
|
|
|
@pre_load
|
|
|
|
def from_events_to_events_one(self, data: dict):
|
|
|
|
"""
|
|
|
|
Not an elegant way of allowing submitting events to a device
|
|
|
|
(in the context of Snapshots) without creating an ``events``
|
|
|
|
field at the model (which is not possible).
|
|
|
|
:param data:
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
# Note that it is secure to allow uploading events_one
|
|
|
|
# as the only time an user can send a device object is
|
|
|
|
# in snapshots.
|
|
|
|
data['events_one'] = data.pop('events', [])
|
|
|
|
return data
|
|
|
|
|
|
|
|
@post_load
|
|
|
|
def validate_snapshot_events(self, data):
|
|
|
|
"""Validates that only snapshot-related events can be uploaded."""
|
2018-06-19 16:38:42 +00:00
|
|
|
from ereuse_devicehub.resources.event.models import EraseBasic, Test, Rate, Install, \
|
|
|
|
Benchmark
|
2018-06-16 10:41:12 +00:00
|
|
|
for event in data['events_one']:
|
2018-06-19 16:38:42 +00:00
|
|
|
if not isinstance(event, (Install, EraseBasic, Rate, Test, Benchmark)):
|
|
|
|
raise ValidationError('You cannot upload {}'.format(event), field_names=['events'])
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Computer(Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Computer.__doc__
|
|
|
|
components = NestedOn('Component',
|
|
|
|
many=True,
|
|
|
|
dump_only=True,
|
|
|
|
collection_class=OrderedSet,
|
|
|
|
description='The components that are inside this computer.')
|
|
|
|
chassis = EnumField(enums.ComputerChassis,
|
|
|
|
required=True,
|
|
|
|
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('Event',
|
|
|
|
many=True,
|
|
|
|
dump_only=True,
|
|
|
|
collection_class=set,
|
|
|
|
description=m.Computer.privacy.__doc__)
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Desktop(Computer):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Desktop.__doc__
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Laptop(Computer):
|
2019-02-03 16:12:53 +00:00
|
|
|
layout = EnumField(Layouts, description=m.Laptop.layout.comment)
|
|
|
|
__doc__ = m.Laptop.__doc__
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
2018-06-26 13:35:13 +00:00
|
|
|
class Server(Computer):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Server.__doc__
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
2018-06-26 13:35:13 +00:00
|
|
|
class DisplayMixin:
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.DisplayMixin.__doc__
|
|
|
|
|
2018-06-26 13:35:13 +00:00
|
|
|
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150))
|
2018-11-09 10:22:13 +00:00
|
|
|
technology = EnumField(enums.DisplayTech,
|
2018-06-26 13:35:13 +00:00
|
|
|
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)
|
2018-10-23 13:37:37 +00:00
|
|
|
refresh_rate = Integer(data_key='refreshRate', validate=Range(10, 1000))
|
|
|
|
contrast_ratio = Integer(data_key='contrastRatio', validate=Range(100, 100000))
|
|
|
|
touchable = Boolean(missing=False, description=m.DisplayMixin.touchable.comment)
|
2018-06-26 13:35:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class NetworkMixin:
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.NetworkMixin.__doc__
|
|
|
|
|
2018-06-26 13:35:13 +00:00
|
|
|
speed = Integer(validate=Range(min=10, max=10000),
|
|
|
|
unit=UnitCodes.mbps,
|
|
|
|
description=m.NetworkAdapter.speed.comment)
|
2018-07-19 19:25:06 +00:00
|
|
|
wireless = Boolean(required=True)
|
2018-06-26 13:35:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Monitor(DisplayMixin, Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Monitor.__doc__
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
2018-06-26 13:35:13 +00:00
|
|
|
class ComputerMonitor(Monitor):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.ComputerMonitor.__doc__
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
2018-06-26 13:35:13 +00:00
|
|
|
class TelevisionSet(Monitor):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.TelevisionSet.__doc__
|
2018-06-26 13:35:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Mobile(Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Mobile.__doc__
|
|
|
|
|
2018-10-04 08:59:31 +00:00
|
|
|
imei = Integer(description=m.Mobile.imei.comment)
|
|
|
|
meid = Str(description=m.Mobile.meid.comment)
|
2018-06-26 13:35:13 +00:00
|
|
|
|
2018-10-04 08:59:31 +00:00
|
|
|
@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):
|
2018-06-26 13:35:13 +00:00
|
|
|
data['meid'] = meid.compact(data['meid'])
|
2018-11-17 18:22:41 +00:00
|
|
|
return data
|
2018-06-26 13:35:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Smartphone(Mobile):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Smartphone.__doc__
|
2018-06-26 13:35:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Tablet(Mobile):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Tablet.__doc__
|
2018-06-26 13:35:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Cellphone(Mobile):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Cellphone.__doc__
|
2018-06-20 21:18:15 +00:00
|
|
|
|
|
|
|
|
2018-04-10 15:06:39 +00:00
|
|
|
class Component(Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Component.__doc__
|
|
|
|
|
2018-05-11 16:58:48 +00:00
|
|
|
parent = NestedOn(Device, dump_only=True)
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
class GraphicCard(Component):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.GraphicCard.__doc__
|
|
|
|
|
2018-04-27 17:16:43 +00:00
|
|
|
memory = Integer(validate=Range(0, 10000),
|
|
|
|
unit=UnitCodes.mbyte,
|
2018-06-26 13:35:13 +00:00
|
|
|
description=m.GraphicCard.memory.comment)
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
2018-06-10 16:47:49 +00:00
|
|
|
class DataStorage(Component):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.DataStorage.__doc__
|
|
|
|
|
2018-04-27 17:16:43 +00:00
|
|
|
size = Integer(validate=Range(0, 10 ** 8),
|
|
|
|
unit=UnitCodes.mbyte,
|
2018-06-26 13:35:13 +00:00
|
|
|
description=m.DataStorage.size.comment)
|
2018-11-09 10:22:13 +00:00
|
|
|
interface = EnumField(enums.DataStorageInterface)
|
|
|
|
privacy = NestedOn('Event', dump_only=True)
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
2018-06-10 16:47:49 +00:00
|
|
|
class HardDrive(DataStorage):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.HardDrive.__doc__
|
2018-06-10 16:47:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SolidStateDrive(DataStorage):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.SolidStateDrive.__doc__
|
2018-06-10 16:47:49 +00:00
|
|
|
|
|
|
|
|
2018-04-10 15:06:39 +00:00
|
|
|
class Motherboard(Component):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Motherboard.__doc__
|
|
|
|
|
2018-07-02 10:52:54 +00:00
|
|
|
slots = Integer(validate=Range(0, 20),
|
2018-06-26 13:35:13 +00:00
|
|
|
description=m.Motherboard.slots.comment)
|
2019-02-03 16:12:53 +00:00
|
|
|
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)
|
2018-04-10 15:06:39 +00:00
|
|
|
|
|
|
|
|
2018-06-26 13:36:21 +00:00
|
|
|
class NetworkAdapter(NetworkMixin, Component):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.NetworkAdapter.__doc__
|
2018-04-27 17:16:43 +00:00
|
|
|
|
|
|
|
|
2018-05-11 16:58:48 +00:00
|
|
|
class Processor(Component):
|
2019-02-03 16:12:53 +00:00
|
|
|
__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)
|
2018-05-11 16:58:48 +00:00
|
|
|
|
|
|
|
|
2018-04-27 17:16:43 +00:00
|
|
|
class RamModule(Component):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.RamModule.__doc__
|
|
|
|
|
|
|
|
size = Integer(validate=Range(min=128, max=17000),
|
|
|
|
unit=UnitCodes.mbyte,
|
|
|
|
description=m.RamModule.size.comment)
|
2018-07-19 19:25:06 +00:00
|
|
|
speed = Integer(validate=Range(min=100, max=10000), unit=UnitCodes.mhz)
|
2018-11-09 10:22:13 +00:00
|
|
|
interface = EnumField(enums.RamInterface)
|
|
|
|
format = EnumField(enums.RamFormat)
|
2018-06-26 13:36:21 +00:00
|
|
|
|
|
|
|
|
2018-07-02 10:52:54 +00:00
|
|
|
class SoundCard(Component):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.SoundCard.__doc__
|
2018-07-02 10:52:54 +00:00
|
|
|
|
|
|
|
|
2018-06-26 13:36:21 +00:00
|
|
|
class Display(DisplayMixin, Component):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Display.__doc__
|
2018-09-30 17:40:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Manufacturer(Schema):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Manufacturer.__doc__
|
|
|
|
|
2018-09-30 17:40:28 +00:00
|
|
|
name = String(dump_only=True)
|
|
|
|
url = URL(dump_only=True)
|
|
|
|
logo = URL(dump_only=True)
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ComputerAccessory(Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.ComputerAccessory.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Mouse(ComputerAccessory):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Mouse.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class MemoryCardReader(ComputerAccessory):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.MemoryCardReader.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SAI(ComputerAccessory):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.SAI.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Keyboard(ComputerAccessory):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Keyboard.__doc__
|
|
|
|
|
2018-10-23 13:37:37 +00:00
|
|
|
layout = EnumField(Layouts)
|
|
|
|
|
|
|
|
|
|
|
|
class Networking(NetworkMixin, Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Networking.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Router(Networking):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Router.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Switch(Networking):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Switch.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Hub(Networking):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Hub.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class WirelessAccessPoint(Networking):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.WirelessAccessPoint.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Printer(Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__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)
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class LabelPrinter(Printer):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.LabelPrinter.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Sound(Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Sound.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Microphone(Sound):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Microphone.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Video(Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Video.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class VideoScaler(Video):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.VideoScaler.__doc__
|
2018-10-23 13:37:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Videoconference(Video):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Videoconference.__doc__
|
2018-11-12 10:59:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Cooking(Device):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Cooking.__doc__
|
2018-11-12 10:59:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Mixer(Cooking):
|
2019-02-03 16:12:53 +00:00
|
|
|
__doc__ = m.Mixer.__doc__
|