Source code for anyblok_wms_base.core.goods.type

# -*- coding: utf-8 -*-
# This file is a part of the AnyBlok / WMS Base project
#
#    Copyright (C) 2018 Georges Racinet <gracinet@anybox.fr>
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file,You can
# obtain one at http://mozilla.org/MPL/2.0/.

from anyblok import Declarations
from anyblok.column import Text
from anyblok.column import Integer
from anyblok.relationship import Many2One
from anyblok_postgres.column import Jsonb
from anyblok_wms_base.utils import dict_merge

_missing = object()
"""A marker to use as default value in get-like functions/methods."""


register = Declarations.register
Model = Declarations.Model


[docs]@register(Model.Wms.Goods) class Type: """Types of Goods. For a full functional discussion, see :ref:`goods_type`. """ id = Integer(label="Identifier", primary_key=True) """Primary key""" code = Text(label=u"Identifying code", index=True, unique=True, nullable=False) """Uniquely identifying code. As a convenience, and for sharing with other applications. """ label = Text(label=u"Label") behaviours = Jsonb(label="Behaviours in operations") """ Goods Types specify with this flexible field how various :class:`Operations <anyblok_wms_base.core.operation.base.Operation>` will treat the represented Goods. .. seealso:: :class:`Unpack <anyblok_wms_base.core.operation.unpack.Unpack>` for a complex example. The value is a key/value mapping (behaviour name/value). .. warning:: direct read access to a behaviour is to be avoided in favour of :meth:`get_behaviour` (see :ref:`improvement_goods_type_hierarchy`). This field is also open for downstream libraries and applications to make use of it to define some of their specific logic, but care must be taken not to conflict with the keys used by ``wms-core`` and other bloks (TODO introduce namespacing, then ? at least make a list available by using constants from an autodocumented module) """ properties = Jsonb(label="Properties") """Goods Types also have flexible properties. These are usually read from the Goods themselves (where they act as default values if not defined on the Goods), and are useful with generic Types, i.e., those that have children. Operations that handle Properties can do interesting things by using properties that actually come from Type information. """ parent = Many2One(model='Model.Wms.Goods.Type') """This field expresses the hierarchy of Goods Types.""" def __str__(self): return "(id={self.id}, code={self.code!r})".format(self=self) def __repr__(self): return "Wms.Goods.Type" + str(self) # TODO PERF cache ?
[docs] def get_behaviour(self, name, default=None): """Get the value of the behaviour with given name. This method is the preferred way to access a given behaviour. It resolves the wished behaviour by looking it up within the :attr:`behaviours` :class:`dict`, and recursively on its parent. It also takes care of corner cases, such as when :attr:`behaviours` is ``None`` as a whole. """ behaviours = self.behaviours parent = self.parent if parent is None: parent_beh = _missing else: parent_beh = self.parent.get_behaviour(name, default=_missing) if behaviours is None: beh = _missing else: beh = behaviours.get(name, _missing) if beh is _missing: if parent_beh is _missing: return default return parent_beh if parent_beh is _missing: return beh return dict_merge(beh, parent_beh)
def is_sub_type(self, gt): """True if ``self`` is a sub type of ``gt``, inclusively. TODO PERF the current implementation recurses over ancestors. A subsequent implementation could add caching and/or recursive SQL queries. """ if self == gt: return True parent = self.parent if parent is None: return False return parent.is_sub_type(gt) def get_property(self, k, default=None): """Read a property value recursively. If the current Type does not have the wished property key, but has a parent, then the lookup continues on the parent. """ props = self.properties val = _missing if props is None else props.get(k, _missing) if val is _missing: parent = self.parent if parent is None: return default return parent.get_property(k, default=default) return val def has_property_values(self, mapping): return all(self.get_property(k, default=_missing) == v for k, v in mapping.items()) def has_property(self, name): if self.properties is not None and name in self.properties: return True parent = self.parent if parent is not None: return parent.has_property(name) return False def has_properties(self, wanted_props): if not wanted_props: return True properties = self.properties if properties is None: missing = wanted_props else: missing = (p for p in wanted_props if p not in properties) parent = self.parent if parent is None: for x in missing: # could be a generator, a list etc. return False return True return parent.has_properties(missing)