Пример #1
0
class VersionedDocument:

    versioned_document_uuid = UUID()
    versioned_document_version = String()
    versioned_document = Function(fget='get_versioned_document',
                                  fset='set_versioned_document',
                                  fdel='del_versioned_document')

    def get_versioned_document(self):
        Document = self.registry.Attachment.Document
        query = Document.query()
        query = query.filter(
            Document.uuid == self.versioned_document_uuid,
            Document.version == self.versioned_document_version)
        return query.one_or_none()

    def set_versioned_document(self, document):
        if document.uuid is None:
            raise NoneValueException("Uuid value is None")

        self.versioned_document_uuid = document.uuid
        self.versioned_document_version = document.version

    def del_versioned_document(self):
        self.versioned_document_uuid = None
        self.versioned_document_version = None

    @hybrid_method
    def is_versioned_document(self, document):
        return (self.versioned_document_uuid == document.uuid
                and self.versioned_document_version == document.version)
Пример #2
0
class Role:
    """Role, allow to group some authorization for an user"""

    name = String(primary_key=True, nullable=False)
    label = String(nullable=False)
    children = Many2Many(model='Model.Pyramid.Role', many2many="parents")
    users = Many2Many(model=Pyramid.User, many2many="roles")
    roles_name = Function(fget="get_all_roles_name")

    def get_all_roles_name(self):
        """Return all the name of the roles self and dependencies
        """
        names = [self.name]
        for child in self.children:
            if child.name in names:
                continue  # pragma: no cover

            names.extend(child.roles_name)

        return list(set(names))

    @classmethod
    def before_update_orm_event(cls, mapper, connection, target):
        """Check if the role has not any cyclical dependencies
        """
        try:
            target.get_all_roles_name()
        except RecursionError:
            raise RecursionRoleError()
Пример #3
0
class LatestDocument:

    latest_document_uuid = UUID()
    latest_document = Function(fget='get_latest_document',
                               fset='set_latest_document',
                               fdel='del_latest_document')

    def get_latest_document(self):
        Document = self.registry.Attachment.Document.Latest
        query = Document.query()
        query = query.filter(Document.uuid == self.latest_document_uuid)
        return query.one_or_none()

    def set_latest_document(self, document):
        if document.uuid is None:
            raise NoneValueException("Uuid value is None")

        if document.type != 'latest':
            raise NotLatestException(
                "You try to set a versioned document, this action is "
                "forbidden")

        self.latest_document_uuid = document.uuid

    def del_latest_document(self):
        self.latest_document_uuid = None

    @hybrid_method
    def is_latest_document(self, document):
        if document.type != 'latest':
            raise NotLatestException(
                "You try to compare the latest document with a versioned "
                "document, this action is forbidden")

        return self.latest_document_uuid == document.uuid
Пример #4
0
class Reservation:

    physobj = Many2One(model=Wms.PhysObj, primary_key=True, index=True)
    quantity = Integer()
    """The quantity that this Reservation provides.

    If the PhysObj in the application have ``quantity`` field
    (see :ref:`improvement_no_quantities`), this is not necessarily its value
    within :attr:`goods`. Instead, it is the quantity within the
    :attr:`request_item` that the current Reservation provides.

    Use-case some PhysObj being sold either as packs of 10 or by
    the unit. If one wants to reserve 13 of them,
    it should be expressable as one pack of 10 and 3 units.
    Then maybe (depending on the needs), would it be actually
    smarter of the application to not issue an Unpack.
    """
    request_item = Many2One(model=Wms.Reservation.RequestItem, index=True)

    goods = Function(fget='_goods_get', fset='_goods_set', fexpr='_goods_expr')
    """Compatibility wrapper.

    Before the merge of Goods and Locations as PhysObj, :attr:`physobj` was
    ``goods``.

    This does not extend to compatibility of the former low level ``goods_id``
    column.
    """
    def _goods_get(self):
        deprecation_warn_goods()
        return self.physobj

    def _goods_set(self, value):
        deprecation_warn_goods()
        self.physobj = value

    @classmethod
    def _goods_expr(cls):
        deprecation_warn_goods()
        return cls.physobj

    @classmethod
    def define_table_args(cls):
        return super(Reservation, cls).define_table_args() + (CheckConstraint(
            'quantity > 0', name='positive_qty'), )

    def is_transaction_owner(self):
        """Check that the current transaction is the owner of the reservation.
        """
        return self.request_item.request.is_txn_reservations_owner()

    def is_transaction_allowed(self,
                               opcls,
                               state,
                               dt_execution,
                               inputs=None,
                               **kwargs):
        """TODO add allowances, like a Move not far."""
        return self.is_transaction_owner()
Пример #5
0
            class Test:

                id = Integer(primary_key=True)
                val1 = Integer()
                val2 = Function(fget='fget', fset='fset')

                def fget(self):
                    return 2 * self.val1
Пример #6
0
class User:
    """User declaration for identity"""

    first_name = String(nullable=False)
    last_name = String(nullable=False)
    name = Function(fget='get_name')

    def get_name(self):
        """Return the name of the user"""
        return self.first_name + ' ' + self.last_name.upper()
Пример #7
0
            class Employee:

                full_name = Function(fget='get_full_name')

                def get_full_name(self):
                    sub_name = ''
                    if self.type_entity == 'manager':
                        sub_name = self.manager_name
                    elif self.type_entity == 'engineer':
                        sub_name = self.engineer_name

                    return self.name + ' - ' + sub_name
Пример #8
0
    class Test:

        id = Integer(primary_key=True)
        first_name = String()
        last_name = String()
        name = Function(fget='fget', fset='fset', fdel='fdel', fexpr='fexpr')

        def fget(self):
            return '{0} {1}'.format(self.first_name, self.last_name)

        def fset(self, value):
            self.first_name, self.last_name = value.split(' ', 1)

        def fdel(self):
            self.first_name = self.last_name = None

        @classmethod
        def fexpr(cls):
            return func.concat(cls.first_name, ' ', cls.last_name)
Пример #9
0
class User:
    active = Function(fget='fget_active', fexpr='fexpr_active')
    lang = Selection(selections='get_languages')

    @classmethod
    def get_languages(cls):
        return {
            'en': 'English',
            'fr': 'French',
        }

    def fget_active(self):
        credential = self.anyblok.Pyramid.CredentialStore.query().filter_by(
            login=self.login).one_or_none()

        return True if credential else False

    @classmethod
    def fexpr_active(cls):
        return cls.anyblok.Pyramid.CredentialStore.login == cls.login
Пример #10
0
            class Test:

                id = Integer(primary_key=True)
                _name = String()
                name = Function(fget='fget',
                                fset='fset',
                                fdel='fdel',
                                fexpr='fexpr')

                def fget(self):
                    return self._name

                def fset(self, value):
                    self._name = value

                def fdel(self):
                    self._name = None

                @classmethod
                def fexpr(cls):
                    return cls._name
Пример #11
0
            class Manager:

                full_name = Function(fget='get_full_name')

                def get_full_name(self):
                    return self.name + ' - ' + self.manager_name
Пример #12
0
class User:
    """User declaration need for Auth"""

    login = String(primary_key=True, nullable=False)
    first_name = String(nullable=False)
    last_name = String(nullable=False)
    name = Function(fget='get_name')

    def get_name(self):
        """Return the name of the user"""
        return self.first_name + ' ' + self.last_name.upper()

    @classmethod
    def get_roles(cls, login):
        """Return the roles of an user

        :param login: str, login attribute of the user
        :rtype: list of str (name of the roles)
        """
        # cache the method
        roles = [login]
        user = cls.query().filter(cls.login == login).one_or_none()
        if user:
            for role in user.roles:
                roles.extend(role.roles_name)

        return list(set(roles))

    @classmethod
    def get_acl(cls, login, resource, params=None):
        """Retun the ACL for a ressource and a user

        Auth, does not implement any rule to compute ACL,
        This method allow all user to use the resource ask
        by controllers.

        For other configuration, this method must be overwrite

        :param login: str, login attribute of the user
        :param resource: str, name of a resource
        :param params: all options need to compute ACL
        """
        # cache the method
        return [(Allow, login, ALL_PERMISSIONS)]

    @classmethod
    def format_login_params(cls, request):
        """Return the login and password from query

        By default the query come from json_body and are named
        **login** and **password**

        If the entries come from another place, this method must be overwrite
        :param request: the request from the controllers
        """
        return request.json_body

    @classmethod
    def check_login(cls, login=None, password=None, **kwargs):
        """Check login / password

        This method raise an exception, because any credential
        is stored in this bloks

        .. warning::

            This method must be overwriting by anycredential blok


        :param login: str, the login attribute of the user
        :param password: str
        :param kwargs: any options need to validate credential
        """
        raise HTTPUnauthorized()

    @classmethod
    def get_login_location_to(cls, login, request):
        """Return the default path after the login"""
        return '/'

    @classmethod
    def get_logout_location_to(cls, request):
        """Return the default path after the logout"""
        return '/'
Пример #13
0
class Document:
    DOCUMENT_TYPE = None

    uuid = UUID(primary_key=True, binary=False, nullable=False)
    version_number = Integer(primary_key=True, nullable=False)
    version = Function(fget="get_version")

    created_at = DateTime(default=datetime.now, nullable=False)
    historied_at = DateTime()

    data = Json(default=dict)

    file_added_at = DateTime()
    filename = String(size=256)
    contenttype = String()
    filesize = Integer()
    file = LargeBinary()
    hash = String(size=256)

    type = Selection(selections={
        'latest': 'Latest',
        'history': 'History'
    },
                     nullable=False)

    previous_doc_uuid = UUID()
    previous_doc_version_number = Integer()
    previous_version = Function(fget="get_previous_version")
    next_version = Function(fget="get_next_version")
    previous_versions = Function(fget="get_previous_versions")

    @classmethod
    def get_file_fields(cls):
        return [
            'file', 'file_added_at', 'contenttype', 'filename', 'hash',
            'filesize'
        ]

    def get_file(self):
        return self.to_dict(*self.get_file_fields())

    def set_file(self, file_):
        self.file = file_

    def get_version(self):
        return "V-%06d" % self.version_number

    def get_previous_version(self):
        Doc = self.anyblok.Attachment.Document
        query = Doc.query()
        query = query.filter(Doc.uuid == self.previous_doc_uuid)
        query = query.filter(
            Doc.version_number == self.previous_doc_version_number)
        return query.one_or_none()

    def get_next_version(self):
        Doc = self.anyblok.Attachment.Document
        query = Doc.query()
        query = query.filter(Doc.previous_doc_uuid == self.uuid)
        query = query.filter(
            Doc.previous_doc_version_number == self.version_number)
        return query.one_or_none()

    def get_previous_versions(self):
        res = []
        current = self
        while current.previous_version:
            current = current.previous_version
            res.append(current)

        return res

    @classmethod
    def define_mapper_args(cls):
        mapper_args = super(Document, cls).define_mapper_args()
        if cls.__registry_name__ == 'Model.Attachment.Document':
            mapper_args.update({'polymorphic_on': cls.type})

        mapper_args.update({'polymorphic_identity': cls.DOCUMENT_TYPE})
        return mapper_args

    @classmethod
    def insert(cls, *args, **kwargs):
        if cls.__registry_name__ == 'Model.Attachment.Document':
            return cls.anyblok.Attachment.Document.Latest.insert(
                *args, **kwargs)

        return super(Document, cls).insert(*args, **kwargs)

    @classmethod
    def query(cls, *args, **kwargs):
        query = super(Document, cls).query(*args, **kwargs)
        if cls.__registry_name__ != 'Model.Attachment.Document':
            query = query.filter(cls.type == cls.DOCUMENT_TYPE)

        return query

    def has_file(self):
        if self.file:
            return True

        return False

    @classmethod
    def filter_has_not_file(cls):
        return cls.file == None  # noqa
Пример #14
0
class Avatar:
    """PhysObj Avatar.

    See in :ref:`Core Concepts <physobj_avatar>` for a functional description.
    """

    id = Integer(label="Identifier", primary_key=True)
    """Primary key."""

    obj = Many2One(model=Model.Wms.PhysObj,
                   index=True,
                   nullable=False)
    """The PhysObj of which this is an Avatar."""

    state = Selection(label="State of existence",
                      selections=AVATAR_STATES,
                      nullable=False,
                      index=True)
    """State of existence in the premises.

    see :mod:`anyblok_wms_base.constants`.

    This may become an ENUM once Anyblok supports them.
    """

    location = Many2One(model=Model.Wms.PhysObj,
                        nullable=False,
                        index=True)
    """Where the PhysObj are/will be/were.

    See :class:`Location <anyblok_wms_base.core.location.Location>`
    for a discussion of what this should actually mean.
    """

    dt_from = DateTime(label="Exist (or will) from this date & time",
                       nullable=False)
    """Date and time from which the Avatar is meaningful, inclusively.

    Functionally, even though the default in creating Operations will be
    to use the current date and time, this is not to be confused with the
    time of creation in the database, which we don't care much about.

    The actual meaning really depends on the value of the :attr:`state`
    field:

    + In the ``past`` and ``present`` states, this is supposed to be
      a faithful representation of reality.

    + In the ``future`` state, this is completely theoretical, and
      ``wms-core`` doesn't do much about it, besides using it to avoid
      counting several :ref:`physobj_avatar` of the same physical goods
      while :meth:`peeking at quantities in the future
      <anyblok_wms_base.core.location.Location.quantity>`.
      If the end application does serious time prediction, it can use it
      freely.

    In all cases, this doesn't mean that the very same PhysObj aren't present
    at an earlier time with the same state, location, etc. That earlier time
    range would simply be another Avatar (use case: moving back and forth).
    """

    dt_until = DateTime(label="Exist (or will) until this date & time")
    """Date and time until which the Avatar record is meaningful, exclusively.

    Like :attr:`dt_from`, the meaning varies according to the value of
    :attr:`state`:

    + In the ``past`` state, this is supposed to be a faithful
      representation of reality: apart from the special case of formal
      :ref:`Splits and Aggregates <op_split_aggregate>`, the goods
      really left this location at these date and time.

    + In the ``present`` and ``future`` states, this is purely
      theoretical, and the same remarks as for the :attr:`dt_from` field
      apply readily.

    In all cases, this doesn't mean that the very same goods aren't present
    at an later time with the same state, location, etc. That later time
    range would simply be another Avatar (use case: moving back and forth).
    """

    outcome_of = Many2One(index=True,
                          model=Model.Wms.Operation, nullable=False)
    """The Operation that created this Avatar.
    """

    goods = Function(fget='_goods_get',
                     fset='_goods_set',
                     fexpr='_goods_expr')
    """Compatibility wrapper.

    Before the merge of Goods and Locations as PhysObj, :attr:`obj` was
    ``goods``.

    This does not extend to compatibility of the former low level ``goods_id``
    column.
    """

    @classmethod
    def define_table_args(cls):
        return super().define_table_args() + (
                CheckConstraint('dt_until IS NULL OR dt_until >= dt_from',
                                name='dt_range_valid'),
                Index("idx_avatar_present_unique",
                      cls.obj_id, unique=True,
                      postgresql_where=(cls.state == 'present'))
            )

    def _goods_get(self):
        deprecation_warn_goods()
        return self.obj

    def _goods_set(self, value):
        deprecation_warn_goods()
        self.obj = value

    @classmethod
    def _goods_expr(cls):
        deprecation_warn_goods()
        return cls.obj

    def __str__(self):
        return ("(id={self.id}, obj={self.obj}, state={self.state!r}, "
                "location={self.location}, "
                "dt_range=[{self.dt_from}, "
                "{self.dt_until}])".format(self=self))

    def __repr__(self):
        return ("Wms.PhysObj.Avatar(id={self.id}, "
                "obj={self.obj!r}, state={self.state!r}, "
                "location={self.location!r}, "
                "dt_range=[{self.dt_from!r}, {self.dt_until!r}])").format(
                    self=self)

    def get_property(self, k, default=None):
        return self.obj.get_property(k, default=default)
Пример #15
0
class Shipment(Mixin.UuidColumn, Mixin.TrackModel):
    """ Shipment
    """
    statuses = dict(new="New",
                    label="Label",
                    transit="Transit",
                    delivered="Delivered",
                    exception="Exception",
                    error="Error")
    service = Many2One(label="Shipping service",
                       model=Declarations.Model.Delivery.Carrier.Service,
                       one2many='shipments',
                       nullable=False)
    sender_address = Many2One(label="Sender address",
                              model=Declarations.Model.Address,
                              column_names=["sender_address_uuid"],
                              nullable=False)
    recipient_address = Many2One(label="Recipient address",
                                 model=Declarations.Model.Address,
                                 column_names=["recipient_address_uuid"],
                                 nullable=False)
    reason = String(label="Reason reference")
    pack = String(label="Pack reference")
    status = Selection(label="Shipping status",
                       selections=statuses,
                       default='new',
                       nullable=False)
    properties = Jsonb(label="Properties")
    document_uuid = UUID(label="Carrier slip document reference")
    document = Function(fget='get_latest_document')
    cn23_document_uuid = UUID(label="Carrier slip document reference")
    cn23_document = Function(fget='get_latest_cn23_document')
    tracking_number = String(label="Carrier tracking number")

    def _get_latest_cocument(self, document_uuid):
        Document = self.registry.Attachment.Document.Latest
        query = Document.query().filter_by(uuid=document_uuid)
        return query.one_or_none()

    def get_latest_document(self):
        return self._get_latest_cocument(self.document_uuid)

    def get_latest_cn23_document(self):
        return self._get_latest_cocument(self.cn23_document_uuid)

    def create_label(self):
        """Retrieve a shipping label from shipping service
        """
        if not self.status == 'new':
            return

        return self.service.create_label(shipment=self)

    def get_label_status(self):
        """Retrieve a shipping label from shipping service
        """
        if self.status in ('new', 'delivered', 'error'):
            return

        return self.service.get_label_status(shipment=self)

    @classmethod
    def get_labels_status(cls):
        status = ['label', 'transit', 'exception']
        shipments = cls.query().filter(cls.status.in_(status)).all()
        for shipment in shipments:
            shipment.get_label_status()

    def _save_document(self, document, binary_file, content_type):
        document.set_file(binary_file)
        document.filesize = len(binary_file)
        document.contenttype = content_type
        hash = hashlib.sha256()
        hash.update(binary_file)
        document.hash = hash.digest()

        self.registry.flush()  # flush to update version in document

    def save_document(self, binary_file, content_type):
        document = self.document
        if document is None:
            document = self.registry.Attachment.Document.insert(
                data={'shipment': str(self.uuid)})
            self.document_uuid = document.uuid

        self._save_document(document, binary_file, content_type)

    def save_cn23_document(self, binary_file, content_type):
        document = self.cn23_document
        if document is None:
            document = self.registry.Attachment.Document.insert(
                data={'shipment': str(self.uuid)})
            self.cn23_document_uuid = document.uuid

        self._save_document(document, binary_file, content_type)
Пример #16
0
class Arrival(Mixin.WmsSingleOutcomeOperation, Operation):
    """Operation to describe physical arrival of goods in some location.

    Arrivals store data about the expected or arrived physical objects:
    properties, code…
    These are copied over to the corresponding PhysObj records in all
    cases and stay inert after the fact.

    In case the Arrival state is ``planned``,
    these are obviously only unchecked values,
    but in case it is ``done``, the actual meaning can depend
    on the application:

    - maybe the application won't use the ``planned`` state at all, and
      will only create Arrival after checking them,
    - maybe the application will inspect the Arrival properties, compare them
      to reality, update them on the created PhysObj and cancel downstream
      operations if needed, before calling :meth:`execute`.

    TODO maybe provide higher level facilities for validation scenarios.
    """
    TYPE = 'wms_arrival'

    id = Integer(label="Identifier",
                 primary_key=True,
                 autoincrement=False,
                 foreign_key=Operation.use('id').options(ondelete='cascade'))
    """Primary key."""
    physobj_type = Many2One(model='Model.Wms.PhysObj.Type')
    """Expected :class:`PhysObj Type
    <anyblok_wms_base.core.physobj.Type>`.
    """
    physobj_properties = Jsonb(label="Properties of arrived PhysObj")
    """Expected :class:`Properties
    <anyblok_wms_base.core.physobj.Properties>`.

    They are copied over to the newly created :class:`PhysObj
    <anyblok_wms_base.core.physobj.PhysObj>` as soon as the Arrival
    is planned, and aren't updated by :meth:`execute`. Matching them with
    reality is the concern of separate validation processes, and this
    field can serve for later assessments after the fact.
    """
    physobj_code = Text(label="Code to set on arrived PhysObj")
    """Expected :attr:`PhysObj code
    <anyblok_wms_base.core.physobj.PhysObj.code>`.

    Can be ``None`` in case the arrival process issues the code only
    at the time of actual arrival.
    """
    location = Many2One(model='Model.Wms.PhysObj')
    """Will be the location of the initial Avatar."""

    goods_type = Function(fget='_goods_type_get',
                          fset='_goods_type_set',
                          fexpr='_goods_type_expr')
    """Compatibility wrapper.

    Before version 0.9.0, :attr:`physobj_type` was ``goods_type``.

    This does not extend to compatibility of the former low level
    ``goods_type_id`` column.
    """

    goods_properties = Function(fget='_goods_properties_get',
                                fset='_goods_properties_set',
                                fexpr='_goods_properties_expr')
    """Compatibility wrapper.

    Before version 0.9.0, :attr:`physobj_properties` was ``goods_properties``.
    """

    goods_code = Function(fget='_goods_code_get',
                          fset='_goods_code_set',
                          fexpr='_goods_code_expr')
    """Compatibility wrapper.

    Before version 0.9.0, :attr:`physobj_code` was ``goods_code``.
    """

    inputs_number = 0
    """This Operation is a purely creative one."""

    destination_field = 'location'

    def specific_repr(self):
        return ("physobj_type={self.physobj_type!r}, "
                "location={self.location!r}").format(self=self)

    def _goods_col_get(self, suffix):
        deprecation_warn_goods_col(self, suffix)
        return getattr(self, 'physobj_' + suffix)

    def _goods_col_set(self, suffix, value):
        deprecation_warn_goods_col(self, suffix)
        setattr(self, 'physobj_' + suffix, value)

    @classmethod
    def _goods_col_expr(cls, suffix):
        deprecation_warn_goods_col(cls, suffix)
        return getattr(cls, 'physobj_' + suffix)

    def _goods_type_get(self):
        return self._goods_col_get('type')

    def _goods_type_set(self, value):
        self._goods_col_set('type', value)

    @classmethod
    def _goods_type_expr(cls):
        return cls._goods_col_expr('type')

    def _goods_properties_get(self):
        return self._goods_col_get('properties')

    def _goods_properties_set(self, value):
        self._goods_col_set('properties', value)

    @classmethod
    def _goods_properties_expr(cls):
        return cls._goods_col_expr('properties')

    def _goods_code_get(self):
        return self._goods_col_get('code')

    def _goods_code_set(self, value):
        self._goods_col_set('code', value)

    @classmethod
    def _goods_code_expr(cls):
        return cls._goods_col_expr('code')

    @classmethod
    def check_create_conditions(cls, state, dt_execution, location=None,
                                **kwargs):
        """Ensure that ``location`` is indeed a container."""
        super(Arrival, cls).check_create_conditions(state, dt_execution,
                                                    **kwargs)
        if location is None or not location.is_container():
            raise OperationContainerExpected(
                cls, "location field value {offender}",
                offender=location)

    def after_insert(self):
        PhysObj = self.registry.Wms.PhysObj
        self_props = self.physobj_properties
        if self_props is None:
            props = None
        else:
            props = PhysObj.Properties.create(**self_props)

        goods = PhysObj.insert(type=self.physobj_type,
                               properties=props,
                               code=self.physobj_code)
        PhysObj.Avatar.insert(
            obj=goods,
            location=self.location,
            outcome_of=self,
            state='present' if self.state == 'done' else 'future',
            dt_from=self.dt_execution,
        )

    def execute_planned(self):
        self.outcome.update(state='present', dt_from=self.dt_execution)

    @classmethod
    def refine_with_trailing_unpack(cls, arrivals, pack_type,
                                    dt_pack_arrival=None,
                                    dt_unpack=None,
                                    pack_properties=None,
                                    pack_code=None):
        """Replace some Arrivals by the Arrival of a pack followed by an Unpack.

        This is useful in cases where it is impossible to predict ahead how
        incoming goods will actually be packed: the arrivals of individual
        items can first be planned, and once more is known about the form
        of delivery, this classmethod can replace some of them with the
        Arrival of a parcel and the subsequent Unpack.

        Together with :meth:`refine_with_trailing_move
        <anyblok_wms_base.core.operation.base.Operation.refine_with_trailing_move>`,
        this can handle the use case detailed in
        :ref:`improvement_operation_superseding`.

        :param arrivals:
            the Arrivals considered to be superseded by the Unpack.
            It is possible that only a subset of them are superseded, and
            conversely that the Unpack has more outcomes than the superseded
            Arrivals. For more details about the matching, see
            :meth:`Unpack.plan_for_outcomes
            <anyblok_wms_base.core.operation.unpack.Unpack.plan_for_outcomes>`
        :param pack_type:
            :attr:`anyblok_wms_base.core.PhysObj.main.PhysObj.type` of the
            expected pack.
        :param pack_properties:
            optional properties of the expected Pack. This optional parameter
            is of great importance in the case of parcels with variable
            contents, since it allows to set the ``contents`` Property.
        :param str pack_code:
            Optional code of the expected Pack.
        :param datetime dt_pack_arrival:
            expected date/time for the Arrival of the pack. If not specified,
            a default one will be computed.
        :param datetime dt_unpack:
            expected date/time for the Unpack Operation. If not specified,
            a default one will be computed.
        """  # noqa (unbreakable meth crosslink)
        for arr in arrivals:
            arr.check_alterable()
        if not arrivals:
            raise OperationError(cls,
                                 "got empty collection of arrivals "
                                 "to refine: {arrivals!r}",
                                 arrivals=arrivals)
        arr_iter = iter(arrivals)
        location = next(arr_iter).location
        if not all(arr.location == location for arr in arr_iter):
            raise OperationError(cls,
                                 "can't rewrite arrivals to different "
                                 "locations, got {nb_locs} different ones in "
                                 "{arrivals}",
                                 nb_locs=len(set(arr.location
                                                 for arr in arrivals)),
                                 arrivals=arrivals)

        Wms = cls.registry.Wms
        Unpack = Wms.Operation.Unpack
        # check that the arrivals happen in the same locations
        if dt_pack_arrival is None:
            # max minimizes the number of date/time shifts to perform
            # upon later execution, min is more optimistic
            dt_pack_arrival = min(arr.dt_execution for arr in arrivals)
        pack_arr = cls.create(location=location,
                              dt_execution=dt_pack_arrival,
                              physobj_type=pack_type,
                              physobj_properties=pack_properties,
                              physobj_code=pack_code,
                              state='planned')

        arrivals_outcomes = {arr.outcome: arr for arr in arrivals}
        unpack, attached_avatars = Unpack.plan_for_outcomes(
            pack_arr.outcomes,
            arrivals_outcomes.keys(),
            dt_execution=dt_unpack)
        for att in attached_avatars:
            arrivals_outcomes[att].delete()
        return unpack
Пример #17
0
class Family:
    """Product.Family class
    """
    FAMILY_CODE = None
    family_schema = None
    template_schema = None
    item_schema = None

    id = Integer(label="Identifier", primary_key=True)
    create_date = DateTime(default=datetime.now, nullable=False)
    edit_date = DateTime(default=datetime.now,
                         nullable=False,
                         auto_update=True)
    code = String(label="Family code", unique=True, nullable=False)
    name = String(label="Family name", size=256)
    description = Text(label="Family description")
    properties = Jsonb(label="Family properties")

    family_code = Selection(selections='get_family_codes')
    items = Function(fget="fget_items")

    @classmethod
    def get_family_codes(cls):
        return dict()

    def fget_items(self):
        """Returns a list of products instance from this family
        """
        return self.registry.InstrumentedList(
            set([i for t in self.templates for i in t.items]))

    @classmethod
    def create(cls, **kwargs):
        data = kwargs.copy()
        if cls.family_schema:
            sch = cls.family_schema(registry=cls.registry)
            data = sch.load(kwargs)
        return cls.insert(**data)

    def amend(self, **kwargs):
        data = kwargs.copy()
        properties = data.pop('properties', dict())
        if properties:
            for k, v in properties.items():
                self.properties[k] = v
        if self.family_schema:
            sch = self.family_schema(registry=self.registry)
            data.update(dict(properties=self.properties))
            data = sch.load(data)
        self.update(**data)
        return self

    @classmethod
    def query(cls, *args, **kwargs):
        query = super(Family, cls).query(*args, **kwargs)
        if cls.__registry_name__ != 'Model.Product.Family':
            query = query.filter(cls.family_code == cls.FAMILY_CODE)

        return query

    @classmethod
    def define_mapper_args(cls):
        mapper_args = super(Family, cls).define_mapper_args()
        if cls.__registry_name__ == 'Model.Product.Family':
            mapper_args.update({'polymorphic_on': cls.family_code})

        mapper_args.update({'polymorphic_identity': cls.FAMILY_CODE})
        return mapper_args

    def __str__(self):
        return "%s : %s" % (self.code, self.name)

    def __repr__(self):
        return "<Product.Family(code=%s, name=%s)>" % (self.code, self.name)
Пример #18
0
class Model:
    """Models assembled"""
    def __str__(self):
        if self.description:
            return self.description

        return self.name

    name = String(size=256, primary_key=True)
    table = String(size=256)
    schema = String()
    is_sql_model = Boolean(label="Is a SQL model")
    description = Function(fget='get_model_doc_string')

    def get_model_doc_string(self):
        """ Return the docstring of the model
        """
        m = self.registry.get(self.name)
        if hasattr(m, '__doc__'):
            return m.__doc__ or ''

        return ''

    @listen('Model.System.Model', 'Update Model')
    def listener_update_model(cls, model):
        cls.registry.System.Cache.invalidate(model, '_fields_description')
        cls.registry.System.Cache.invalidate(model, 'getFieldType')
        cls.registry.System.Cache.invalidate(model, 'get_primary_keys')
        cls.registry.System.Cache.invalidate(
            model, 'find_remote_attribute_to_expire')
        cls.registry.System.Cache.invalidate(model, 'find_relationship')
        cls.registry.System.Cache.invalidate(model,
                                             'get_hybrid_property_columns')

    @classmethod
    def get_field_model(cls, field):
        ftype = field.property.__class__.__name__
        if ftype == 'ColumnProperty':
            return cls.registry.System.Column
        elif ftype == 'RelationshipProperty':
            return cls.registry.System.RelationShip
        else:
            raise Exception('Not implemented yet')

    @classmethod
    def get_field(cls, model, cname):
        if cname in model.loaded_fields.keys():
            field = model.loaded_fields[cname]
            Field = cls.registry.System.Field
        else:
            field = getattr(model, cname)
            Field = cls.get_field_model(field)

        return field, Field

    @classmethod
    def update_fields(cls, model, table):
        fsp = cls.registry.loaded_namespaces_first_step
        m = cls.registry.get(model)
        # remove useless column
        Field = cls.registry.System.Field
        query = Field.query()
        query = query.filter(Field.model == model)
        query = query.filter(Field.name.notin_(m.loaded_columns))
        for model_ in query.all():
            if model_.entity_type == 'Model.System.RelationShip':
                if model_.remote:
                    continue

                RelationShip = cls.registry.System.RelationShip
                Q = RelationShip.query()
                Q = Q.filter(RelationShip.name == model_.remote_name)
                Q = Q.filter(RelationShip.model == model_.remote_model)
                Q.delete()

            model_.delete()

        # add or update new column
        for cname in m.loaded_columns:
            ftype = fsp[model][cname].__class__.__name__
            field, Field = cls.get_field(m, cname)
            cname = Field.get_cname(field, cname)
            query = Field.query()
            query = query.filter(Field.model == model)
            query = query.filter(Field.name == cname)
            if query.count():
                Field.alter_field(query.first(), field, ftype)
            else:
                Field.add_field(cname, field, model, table, ftype)

    @classmethod
    def add_fields(cls, model, table):
        fsp = cls.registry.loaded_namespaces_first_step
        m = cls.registry.get(model)
        is_sql_model = len(m.loaded_columns) > 0
        cls.insert(name=model,
                   table=table,
                   schema=m.__db_schema__,
                   is_sql_model=is_sql_model)
        for cname in m.loaded_columns:
            field, Field = cls.get_field(m, cname)
            cname = Field.get_cname(field, cname)
            ftype = fsp[model][cname].__class__.__name__
            Field.add_field(cname, field, model, table, ftype)

    @classmethod
    def update_list(cls):
        """ Insert and update the table of models

        :exception: Exception
        """
        for model in cls.registry.loaded_namespaces.keys():
            try:
                # TODO need refactor, then try except pass whenever refactor
                # not apply
                m = cls.registry.get(model)
                table = ''
                if hasattr(m, '__tablename__'):
                    table = m.__tablename__

                _m = cls.query('name').filter(cls.name == model)
                if _m.count():
                    cls.update_fields(model, table)
                else:
                    cls.add_fields(model, table)

                if m.loaded_columns:
                    cls.fire('Update Model', model)

            except Exception as e:
                logger.exception(str(e))

        # remove model and field which are not in loaded_namespaces
        query = cls.query()
        query = query.filter(
            cls.name.notin_(cls.registry.loaded_namespaces.keys()))
        Field = cls.registry.System.Field
        for model_ in query.all():
            Q = Field.query().filter(Field.model == model_.name)
            for field in Q.all():
                field.delete()

            model_.delete()
 def test_forbid_function_field(self):
     with pytest.raises(FieldException):
         Contextual(field=Function(fget='foo'), identity='foo')
Пример #20
0
class Blok:

    STATES = {
        'uninstalled': 'Uninstalled',
        'installed': 'Installed',
        'toinstall': 'To install',
        'toupdate': 'To update',
        'touninstall': 'To uninstall',
        'undefined': 'Undefined',
    }

    name = String(primary_key=True, nullable=False)
    state = Selection(selections=STATES, default='uninstalled', nullable=False)
    author = String()
    order = Integer(default=-1, nullable=False)
    short_description = Function(fget='get_short_description')
    long_description = Function(fget='get_long_description')
    logo = Function(fget='get_logo')
    version = String(nullable=False)
    installed_version = String()

    def get_short_description(self):
        """ fget of the ``short_description`` Column.Selection

        :rtype: the docstring of the blok
        """
        blok = BlokManager.get(self.name)
        if hasattr(blok, '__doc__'):
            return blok.__doc__ or ''

        return ''

    def get_long_description(self):
        """ fget of the ``long_description`` Column.Selection

        :rtype: the readme file of the blok
        """
        blok = BlokManager.get(self.name)
        path = BlokManager.getPath(self.name)
        readme = getattr(blok, 'readme', 'README.rst')
        if readme == '__doc__':
            return blok.__doc__

        file_path = join(path, readme)
        description = ''
        if isfile(file_path):
            with open(file_path, 'r') as fp:
                description = fp.read()

        return description

    def get_logo(self):
        """fget of ``logo`` return the path in the blok of the logo

        :rtype: absolute path or None if unexiste logo
        """
        blok = BlokManager.get(self.name)
        blok_path = BlokManager.getPath(blok.name)
        file_path = join(blok_path, blok.logo)
        if isfile(file_path):
            return file_path

        return None

    def __repr__(self):
        return "%s (%s)" % (self.name, self.state)

    @classmethod
    def list_by_state(cls, *states):
        """ Return the blok name in function of the wanted states

        :param states: list of the state
        :rtype: list if state is a state, dict if the states is a list
        """
        if not states:
            return None

        res = {state: [] for state in states}
        bloks = cls.query().filter(cls.state.in_(states)).order_by(cls.order)
        for blok in bloks.all():
            res[blok.state].append(blok.name)

        if len(states) == 1:
            return res[states[0]]
        return res

    @classmethod
    def update_list(cls):
        """ Populate the bloks list and update the state of existing bloks
        """
        # Do not remove blok because 2 or More AnyBlok api may use the same
        # Database
        for order, blok in enumerate(BlokManager.ordered_bloks):
            b = cls.query().filter(cls.name == blok).one_or_none()
            Blok = BlokManager.bloks[blok]

            version = Blok.version
            author = Blok.author
            is_undefined = issubclass(Blok, UndefinedBlok)

            if b is None:
                cls.insert(
                    name=blok,
                    order=order,
                    version=version,
                    author=author,
                    state='undefined' if is_undefined else 'uninstalled')
            else:
                values = dict(order=order, version=version, author=author)
                if b.state == 'undefined' and not is_undefined:
                    values['state'] = 'uninstalled'
                elif is_undefined:
                    if b.state not in ('uninstalled', 'undefined'):
                        raise BlokManagerException(
                            ("Change state %r => 'undefined' for %s is "
                             "forbidden") % (b.state, blok))

                    values['state'] = 'undefined'

                b.update(**values)

    @classmethod
    def apply_state(cls, *bloks):
        """ Call the rigth method is the blok state change

        .. warning::

            for the uninstallation the method called is ``uninstall_all``

        :param bloks: list of the blok name load by the registry
        """
        for blok in bloks:
            # Make the query in the loop to be sure to keep order
            b = cls.query().filter(cls.name == blok).first()
            if b.state in ('uninstalled', 'toinstall'):
                b.install()
            elif b.state == 'toupdate':
                b.upgrade()

        uninstalled_bloks = cls.query().filter(
            cls.state == 'uninstalled').all().name

        conditional_bloks_to_install = []
        for blok in uninstalled_bloks:
            if cls.check_if_the_conditional_are_installed(blok):
                conditional_bloks_to_install.append(blok)

        if conditional_bloks_to_install:
            for b in conditional_bloks_to_install:
                cls.query().filter(cls.name == b).update(
                    {cls.state: 'toinstall'})

            return True

        return False

    @classmethod
    def uninstall_all(cls, *bloksname):
        """ Search and call the uninstall method for all the uninstalled bloks

        .. warning::

            Use the ``desc order`` to uninstall because we can't uninstall
            a dependancies before

        :param bloksname: list of the blok name to uninstall
        """
        if not bloksname:
            return

        query = cls.query().filter(cls.name.in_(bloksname))
        query = query.order_by(cls.order.desc())
        bloks = query.all()
        if bloks:
            bloks.uninstall()

    @classmethod
    def check_if_the_conditional_are_installed(cls, blok):
        """ Return True if all the conditions to install the blok are satisfied

        :param blok: blok name
        :rtype: boolean
        """
        if blok in BlokManager.bloks:
            conditional = BlokManager.bloks[blok].conditional
            if conditional:
                query = cls.query().filter(cls.name.in_(conditional))
                query = query.filter(
                    cls.state.in_(['installed', 'toinstall', 'toupdate']))
                if len(conditional) == query.count():
                    return True

        return False

    def install(self):
        """ Method to install the blok
        """
        logger.info("Install the blok %r" % self.name)
        self.fire('Update installed blok')
        entry = self.registry.loaded_bloks[self.name]
        entry.update(None)
        if self.registry.System.Parameter.get("with-demo", False):
            entry.update_demo(None)
        self.state = 'installed'
        self.installed_version = self.version

    def upgrade(self):
        """ Method to update the blok
        """
        logger.info("Update the blok %r" % self.name)
        self.fire('Update installed blok')
        entry = self.registry.loaded_bloks[self.name]
        parsed_version = (parse_version(self.installed_version)
                          if self.installed_version is not None else None)
        entry.update(parsed_version)
        if self.registry.System.Parameter.get("with-demo", False):
            entry.update_demo(parsed_version)
        self.state = 'installed'
        self.installed_version = self.version

    def uninstall(self):
        """ Method to uninstall the blok
        """
        logger.info("Uninstall the blok %r" % self.name)
        self.fire('Update installed blok')
        entry = BlokManager.bloks[self.name](self.registry)
        if self.registry.System.Parameter.get("with-demo", False):
            entry.uninstall_demo()
        entry.uninstall()
        self.state = 'uninstalled'
        self.installed_version = None

    def load(self):
        """ Method to load the blok when the registry is completly loaded
        """
        name = self.name
        blok_cls = BlokManager.get(name)
        if blok_cls is None:
            logger.warning(
                "load(): class of Blok %r not found, "
                "Blok can't be loaded", name)
            return

        logger.info("Loading Blok %r", name)
        blok_cls(self.registry).load()
        logger.debug("Succesfully loaded Blok %r", name)

    @classmethod
    def load_all(cls):
        """ Load all the installed bloks
        """
        query = cls.query().filter(cls.state == 'installed')
        bloks = query.order_by(cls.order).all()
        if bloks:
            bloks.load()

    @classmethod_cache()
    def is_installed(cls, blok_name):
        return cls.query().filter_by(name=blok_name,
                                     state='installed').count() != 0

    @listen('Model.System.Blok', 'Update installed blok')
    def listen_update_installed_blok(cls):
        cls.registry.System.Cache.invalidate(cls.__registry_name__,
                                             'is_installed')
Пример #21
0
class Apparition(Mixin.WmsInventoryOperation, Operation):
    """Inventory Operation to record unexpected physical objects.

    This is similar to Arrival, but has a distinct functional meaning.
    Apparitions can exist only in the ``done`` :ref:`state <op_states>`.

    Another difference with Arrivals is that Apparitions have a
    :attr:`quantity` field.
    """
    TYPE = 'wms_apparition'

    id = Integer(label="Identifier",
                 primary_key=True,
                 autoincrement=False,
                 foreign_key=Operation.use('id').options(ondelete='cascade'))
    """Primary key."""
    physobj_type = Many2One(model='Model.Wms.PhysObj.Type')
    """Observed :class:`PhysObj Type
    <anyblok_wms_base.core.physobj.Type>`.
    """
    quantity = Integer()
    """The number of identical PhysObj that have appeared.

    Here, identical means "same type, code and properties"
    """
    physobj_properties = Jsonb()
    """Observed :class:`Properties
    <anyblok_wms_base.core.physobj.Properties>`.

    They are copied over to the newly created :class:`PhysObj
    <anyblok_wms_base.core.physobj.PhysObj>`. Then the Properties can evolve on
    the PhysObj, while this Apparition field will keep the exact values
    that were observed during inventory.
    """
    physobj_code = Text()
    """Observed :attr:`PhysObj code
    <anyblok_wms_base.core.physobj.PhysObj.code>`.
    """
    location = Many2One(model='Model.Wms.PhysObj')
    """Location of appeared PhysObj.

    This will be the location of the initial Avatars.
    """

    goods_type = Function(fget='_goods_type_get',
                          fset='_goods_type_set',
                          fexpr='_goods_type_expr')
    """Compatibility wrapper.

    Before version 0.9.0, :attr:`physobj_type` was ``goods_type``.

    This does not extend to compatibility of the former low level
    ``goods_type_id`` column.
    """

    goods_properties = Function(fget='_goods_properties_get',
                                fset='_goods_properties_set',
                                fexpr='_goods_properties_expr')
    """Compatibility wrapper.

    Before version 0.9.0, :attr:`physobj_properties` was ``goods_properties``.
    """

    goods_code = Function(fget='_goods_code_get',
                          fset='_goods_code_set',
                          fexpr='_goods_code_expr')
    """Compatibility wrapper.

    Before version 0.9.0, :attr:`physobj_code` was ``goods_code``.
    """

    inputs_number = 0
    """This Operation is a purely creative one."""
    def specific_repr(self):
        return ("physobj_type={self.physobj_type!r}, "
                "location={self.location!r}").format(self=self)

    def _goods_col_get(self, suffix):
        deprecation_warn_goods_col(self, suffix)
        return getattr(self, 'physobj_' + suffix)

    def _goods_col_set(self, suffix, value):
        deprecation_warn_goods_col(self, suffix)
        setattr(self, 'physobj_' + suffix, value)

    @classmethod
    def _goods_col_expr(cls, suffix):
        deprecation_warn_goods_col(cls, suffix)
        return getattr(cls, 'physobj_' + suffix)

    def _goods_type_get(self):
        return self._goods_col_get('type')

    def _goods_type_set(self, value):
        self._goods_col_set('type', value)

    @classmethod
    def _goods_type_expr(cls):
        return cls._goods_col_expr('type')

    def _goods_properties_get(self):
        return self._goods_col_get('properties')

    def _goods_properties_set(self, value):
        self._goods_col_set('properties', value)

    @classmethod
    def _goods_properties_expr(cls):
        return cls._goods_col_expr('properties')

    def _goods_code_get(self):
        return self._goods_col_get('code')

    def _goods_code_set(self, value):
        self._goods_col_set('code', value)

    @classmethod
    def _goods_code_expr(cls):
        return cls._goods_col_expr('code')

    @classmethod
    def check_create_conditions(cls,
                                state,
                                dt_execution,
                                location=None,
                                **kwargs):
        """Forbid creation with wrong states, check location is a container.

        :raises: :class:`OperationForbiddenState
                 <anyblok_wms_base.exceptions.OperationForbiddenState>`
                 if state is not ``'done'``

                 :class:`OperationContainerExpected
                 <anyblok_wms_base.exceptions.OperationContainerExpected>`
                 if location is not a container.
        """
        if location is None or not location.is_container():
            raise OperationContainerExpected(cls,
                                             "location field value {offender}",
                                             offender=location)

        super().check_create_conditions(state, dt_execution, **kwargs)

    def after_insert(self):
        """Create the PhysObj and their Avatars.

        In the ``wms-core`` implementation, the :attr:`quantity` field
        gives rise to as many PhysObj records.
        """
        PhysObj = self.registry.Wms.PhysObj
        self_props = self.physobj_properties
        if self_props is None:
            props = None
        else:
            props = PhysObj.Properties.create(**self_props)

        for _ in range(self.quantity):
            PhysObj.Avatar.insert(obj=PhysObj.insert(type=self.physobj_type,
                                                     properties=props,
                                                     code=self.physobj_code),
                                  location=self.location,
                                  outcome_of=self,
                                  state='present',
                                  dt_from=self.dt_execution)