Beispiel #1
0
class Importer(Declarations.Mixin.IOMixin):

    file_to_import = LargeBinary(nullable=False)
    offset = Integer(default=0)
    nb_grouped_lines = Integer(nullable=False, default=50)
    commit_at_each_grouped = Boolean(default=True)
    check_import = Boolean(default=False)

    def run(self, blokname=None):
        return self.get_model(self.mode)(self, blokname=blokname).run()

    def get_key_mapping(self, key):
        Mapping = self.registry.IO.Mapping
        return Mapping.get(self.model, key)

    def commit(self):
        if self.check_import:
            return False
        elif not self.commit_at_each_grouped:
            return False

        self.registry.commit()
        return True

    def str2value(self, value, ctype, external_id=False, model=None):
        formater = self.get_formater(ctype)
        if external_id:
            return formater.externalIdStr2value(value, model)

        return formater.str2value(value, model)
Beispiel #2
0
class DecrementCommitment(Model.REA.Entity):
    """
    Commitment is a promise or obligation of economic agents to perform
    an economic event in the future. For example, line items on a sales order
    represent commitments to sell goods.

    # Model-Driven Design Using Business Patterns
    # Authors: Hruby, Pavel
    # ISBN-10 3-540-30154-2 Springer Berlin Heidelberg New York
    # ISBN-13 978-3-540-30154-7 Springer Berlin Heidelberg New York
    """
    id = Integer(primary_key=True, foreign_key=Model.REA.Entity.use('id'))

    recipient = Many2One(label="Agent recipient",
                         model=Model.REA.Agent,
                         nullable=False)

    resource = Many2One(label="Reservation Resource",
                        model=Model.REA.Resource,
                        nullable=False)

    value = Decimal(label="Value decrement", default=decimalType(0))

    fulfilled = Boolean(label="Is Fulfilled", default=False)

    def fulfill(self):
        """
        :return: True if commitment is fulfilled
        """
        if not self.fulfilled:
            self.registry.REA.DecrementEvent.create_event_from_commitment(self)
            self.fulfilled = True
            return True
        return False
Beispiel #3
0
            class Test(Mixin.ConditionalForbidDelete):

                id = Integer(primary_key=True)
                forbid_delete = Boolean(default=False)

                def check_if_forbid_delete_condition_is_true(self):
                    return self.forbid_delete
Beispiel #4
0
            class Test(Mixin.ConditionalForbidUpdate):

                id = Integer(primary_key=True)
                forbid_update = Boolean(default=False)
                name = String()

                def check_if_forbid_update_condition_is_true(self, **changed):
                    return self.forbid_update
Beispiel #5
0
            class Test:

                id = Integer(primary_key=True)
                val = Boolean(default=False)

                @listen('Model.Test', 'before_insert')
                def my_event(cls, mapper, connection, target):
                    target.val = True
Beispiel #6
0
class BooleanReadOnly(Mixin.ConditionalForbidUpdate,
                      Mixin.ConditionalForbidDelete):

    readonly = Boolean(default=False)

    def check_if_forbid_update_condition_is_true(self, **previous_values):
        return previous_values.get('readonly', self.readonly)

    def check_if_forbid_delete_condition_is_true(self):
        return self.readonly
Beispiel #7
0
    class Test(Mixin.ConditionalReadOnly):

        id = Integer(primary_key=True)
        readonly = Boolean(default=False)
        name = String()

        def check_if_forbid_update_condition_is_true(self, **previous_values):
            return previous_values.get('readonly', self.readonly)

        def check_if_forbid_delete_condition_is_true(self):
            return self.readonly
class Todo:

    id = Integer(label="Identifier", primary_key=True)
    task = String(label="Task description")
    done = Boolean(default=False)

    def __str__(self):
        return "%s is %s" % (
            self.task,
            "over. Good job!" if self.done else "waiting for you!"
        )
Beispiel #9
0
class Team():
    class FuretUIAdapter(Adapter):
        @Adapter.tag('active')
        def tag_is_active(self, querystring, query):
            query = query.filter(self.Model.active.is_(True))
            return query

    id = Integer(primary_key=True)
    first_name = String(nullable=False)
    last_name = String(nullable=False)
    active = Boolean(default=True)
Beispiel #10
0
class Resource(Declarations.Model.FuretUI.Menu, Declarations.Mixin.FuretUIMenu,
               Declarations.Mixin.FuretUIMenuParent):
    resource = Many2One(model=Declarations.Model.FuretUI.Resource,
                        nullable=False)
    default = Boolean(default=False)
    tags = String()
    order_by = String()
    filters = Json(default={})

    def check_acl(self):
        return self.resource.check_acl()
Beispiel #11
0
class Relay(Model.Iot.State):

    STATE_TYPE = "RELAY"

    uuid: uuid4 = UUID(
        primary_key=True,
        default=uuid4,
        binary=False,
        foreign_key=Model.Iot.State.use("uuid").options(ondelete="CASCADE"),
    )
    is_open: bool = Boolean(label="Is open ?", default=True)
    """Current circuit state. is_open == True means circuit open, it's turned
class PredictionModelInput(Mixin.IdColumn):
    """Sets of inputs for a model"""
    input_order = Integer(label='Number in the inputs vector', nullable=False)
    input_name = String(label='Name of the input', nullable=False)
    is_internal_feature = Boolean(label='Is internal feature',
                                  default=True,
                                  nullable=False)
    prediction_model = Many2One(label='Model to feed with',
                                model=PredictionModel,
                                nullable=False,
                                one2many='model_inputs')
    input_vector = Many2One(
        label='Input group used to predict',
        model=PredictionInputVector,
    )

    def __repr__(self):
        msg = '<PredictionModelInput: ' \
              'input_order={self.input_order}>, input_name={self.input_name}, ' \
              'prediction_model={self.prediction_model}>'
        return msg.format(self=self)
Beispiel #13
0
class WmsSplitterOperation:
    """Mixin for operations on a single input that can split.

    This is to be applied after :class:`Mixin.WmsSingleInputOperation
    <anyblok_wms_base.core.operation.single_input.WmsSingleInputOperation>`.
    Use :class:`WmsSplitterSingleInputOperation` to get both at once.

    It defines the :attr:`quantity` field to express that the Operation only
    works on some of the quantity held by the PhysObj of the single input.

    In case the Operation's :attr:`quantity` is less than in the PhysObj
    record, a :class:`Split <.split.Split>` will be inserted properly in
    history, and the Operation implementation can ignore quantities
    completely, as it will always, in truth, work on the whole of the input
    it will see.

    Subclasses can use the :attr:`partial` field if they need to know
    if that happened, but this should be useful only in special cases.
    """
    quantity = Decimal(default=1)
    """The quantity this Operation will work on.

    Can be less than the quantity of our single input.
    """

    partial = Boolean(label="Operation induced a split")
    """Record if a Split will be or has been inserted in the history.

    Such insertions should happen if the operation's original PhysObj
    have greater quantity than the operation needs.

    This is useful because once the Split is executed,
    this information can't be deduced from the quantities involved any more.
    """
    @classmethod
    def define_table_args(cls):
        return super(WmsSplitterOperation, cls).define_table_args() + (
            CheckConstraint('quantity > 0', name='positive_qty'), )

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

    @classmethod
    def check_create_conditions(cls,
                                state,
                                dt_execution,
                                inputs=None,
                                quantity=None,
                                **kwargs):

        super(WmsSplitterOperation,
              cls).check_create_conditions(state,
                                           dt_execution,
                                           inputs=inputs,
                                           quantity=quantity,
                                           **kwargs)

        phobj = inputs[0].obj
        if quantity is None:
            raise OperationMissingQuantityError(
                cls, "The 'quantity keyword argument must be passed to "
                "the create() method")
        if quantity > phobj.quantity:
            raise OperationQuantityError(
                cls,
                "Can't split a greater quantity ({op_quantity}) than held in "
                "{input} (which have quantity={input.obj.quantity})",
                op_quantity=quantity,
                input=inputs[0])

    def check_execute_conditions(self):
        """Check that the quantity (after possible Split) is as on the input.

        If a Split has been inserted, then this calls the base class
        for the input of the Split, instead of ``self``, because the input of
        ``self`` is in that case the outcome of the Split, and it's normal
        that it's in state ``future``: the Split will be executed during
        ``self.execute()``, which comes once the present method has agreed.
        """
        if self.quantity != self.input.obj.quantity:
            raise OperationQuantityError(
                self,
                "Can't execute planned for a different quantity {op_quantity} "
                "than held in its input {input} "
                "(which have quantity={input.obj.quantity}). "
                "If it's less, a Split should have occured first ",
                input=input)
        if self.partial:
            self.input.outcome_of.check_execute_conditions()
        else:
            super(WmsSplitterOperation, self).check_execute_conditions()

    @classmethod
    def before_insert(cls,
                      state='planned',
                      follows=None,
                      inputs=None,
                      quantity=None,
                      dt_execution=None,
                      dt_start=None,
                      **kwargs):
        """Override to introduce a Split if needed

        In case the value of :attr:`quantity` does not match the one from the
        ``goods`` field, a :class:`Split <.split.Split>` is inserted
        transparently in the history, as if it'd been there in the first place:
        subclasses can implement :meth:`after_insert` as if the quantities were
        matching from the beginning.
        """
        avatar = inputs[0]
        partial = quantity < avatar.obj.quantity
        if not partial:
            return inputs, None

        Split = cls.registry.Wms.Operation.Split
        split = Split.create(input=avatar,
                             quantity=quantity,
                             state=state,
                             dt_execution=dt_execution)
        return [split.wished_outcome], dict(partial=partial)

    def execute_planned(self):
        """Execute the :class:`Split <.split.Split>` if any, then self."""
        if self.partial:
            split_op = next(iter(self.follows))
            split_op.execute(dt_execution=self.dt_execution)
        super(WmsSplitterOperation, self).execute_planned()
        self.registry.flush()
Beispiel #14
0
class Sequence:
    """Database sequences.

    This Model allows applications to define and use Database sequences easily.

    It is a rewrapping of `SQLAlchemy sequences
    <http://docs.sqlalchemy.org/en/latest/core/defaults.html
    #sqlalchemy.schema.Sequence>`_, with additional formatting
    capabilities to use them, e.g, in fields of applicative Models.

    Sample usage::

        sequence = registry.System.Sequence.insert(
        code="string code",
        formater="One prefix {seq} One suffix")

    .. seealso:: The :attr:`formater` field.

    To get the next formatted value of the sequence::

        sequence.nextval()

    Full example in a Python shell::

        >>> seq = Sequence.insert(code='SO', formater="{code}-{seq:06d}")
        >>> seq.nextval()
        'SO-000001'
        >>> seq.nextval()
        'SO-000002'

    You can create a Sequence without gap warranty using `no_gap` while
    creating the sequence::


        >>> seq = Sequence.insert(
                code='SO', formater="{code}-{seq:06d}", no_gap=True)
        >>> commit()

        >>> # Transaction 1:
        >>> Sequence.nextvalBy(code='SO')
        'SO-000001'

        >>> # Concurrent transaction 2:
        >>> Sequence.nextvalBy(code='SO')
        ...
        sqlalchemy.exc.OperationalError: (psycopg2.errors.LockNotAvailable)
        ...
    """

    _cls_seq_name = 'system_sequence_seq_name'

    id = Integer(primary_key=True)
    code = String(nullable=False)
    start = Integer(default=1)
    current = Integer(default=None)
    seq_name = String(nullable=False)
    """Name of the sequence in the database.

    Most databases identify sequences by names which must be globally
    unique.

    If not passed at insertion, the value of this field is automatically
    generated.
    """
    formater = String(nullable=False, default="{seq}")
    """Python format string to render the sequence values.

    This format string is used in :meth:`nextval`. Within it, you can use the
    following variables:

       * seq: current value of the underlying database sequence
       * code: :attr:`code` field
       * id: :attr:`id` field
    """
    no_gap = Boolean(default=False, nullable=False)
    """If no_gap is False, it will use Database sequence. Otherwise, if `True`
    it will ensure there is no gap while getting next value locking the
    sequence row until transaction is released (rollback/commit). If a
    concurrent transaction try to get a lock an
    `sqlalchemy.exc.OperationalError: (psycopg2.errors.LockNotAvailable)`
    exception is raised.
    """
    @classmethod
    def initialize_model(cls):
        """ Create the sequence to determine name """
        super(Sequence, cls).initialize_model()
        seq = SQLASequence(cls._cls_seq_name)
        seq.create(cls.registry.bind)

        to_create = getattr(cls.registry,
                            '_need_sequence_to_create_if_not_exist', ())
        if to_create is None:
            return

        for vals in to_create:
            if cls.query().filter(cls.code == vals['code']).count():
                continue

            formatter = vals.get('formater')
            if formatter is None:
                del vals['formater']

            cls.insert(**vals)

    @classmethod
    def create_sequence(cls, values):
        """Create the database sequence for an instance of Sequence Model.

        :return: suitable field values for insertion of the Model instance
        :rtype: dict
        """
        seq_name = values.get('seq_name')
        start = values.setdefault('start', 1)

        if values.get("no_gap"):
            values.setdefault('seq_name', values.get("code", "no_gap"))
        else:
            if seq_name is None:
                seq_id = cls.registry.execute(SQLASequence(cls._cls_seq_name))
                seq_name = '%s_%d' % (cls.__tablename__, seq_id)
                values['seq_name'] = seq_name

            seq = SQLASequence(seq_name, start=start)
            seq.create(cls.registry.bind)
        return values

    @classmethod
    def insert(cls, **kwargs):
        """Overwrite to call :meth:`create_sequence` on the fly."""
        return super(Sequence, cls).insert(**cls.create_sequence(kwargs))

    @classmethod
    def multi_insert(cls, *args):
        """Overwrite to call :meth:`create_sequence` on the fly."""
        res = [cls.create_sequence(x) for x in args]
        return super(Sequence, cls).multi_insert(*res)

    def nextval(self):
        """Format and return the next value of the sequence.

        :rtype: str
        """
        if self.no_gap:
            self.refresh(with_for_update={"nowait": True})
            nextval = self.start if self.current is None else self.current + 1
        else:
            nextval = self.registry.execute(SQLASequence(self.seq_name))
        self.update(current=nextval)
        return self.formater.format(code=self.code, seq=nextval, id=self.id)

    @classmethod
    def nextvalBy(cls, **crit):
        """Return next value of the first Sequence matching given criteria.

        :param crit: criteria to match, e.g., ``code=SO``
        :return: :meth:`next_val` result for the first matching Sequence,
                 or ``None`` if there's no match.
        """
        filters = [getattr(cls, k) == v for k, v in crit.items()]
        seq = cls.query().filter(*filters).first()
        if seq is None:
            return None
        return seq.nextval()
Beispiel #15
0
class Column(System.Field):

    name = String(primary_key=True)
    model = String(primary_key=True)
    autoincrement = Boolean(label="Auto increment")
    foreign_key = String()
    primary_key = Boolean()
    unique = Boolean()
    nullable = Boolean()
    remote_model = String()

    def _description(self):
        res = super(Column, self)._description()
        res.update(nullable=self.nullable,
                   primary_key=self.primary_key,
                   model=self.remote_model)
        return res

    @classmethod
    def get_cname(self, field, cname):
        """ Return the real name of the column

        :param field: the instance of the column
        :param cname: Not use here
        :rtype: string of the real column name
        """
        return cname

    @classmethod
    def add_field(cls, cname, column, model, table, ftype):
        """ Insert a column definition

        :param cname: name of the column
        :param column: instance of the column
        :param model: namespace of the model
        :param table: name of the table of the model
        :param ftype: type of the AnyBlok Field
        """
        Model = cls.anyblok.get(model)
        if hasattr(Model, anyblok_column_prefix + cname):
            c = getattr(Model, anyblok_column_prefix + cname)
        else:
            c = column.property.columns[0]  # pragma: no cover

        autoincrement = c.autoincrement

        if autoincrement == 'auto':
            autoincrement = (True if c.primary_key and ftype == 'Integer' else
                             False)

        vals = dict(autoincrement=autoincrement,
                    code=table + '.' + cname,
                    model=model,
                    name=cname,
                    foreign_key=c.info.get('foreign_key'),
                    label=c.info.get('label'),
                    nullable=c.nullable,
                    primary_key=c.primary_key,
                    ftype=ftype,
                    remote_model=c.info.get('remote_model'),
                    unique=c.unique)
        cls.insert(**vals)

    @classmethod
    def alter_field(cls, column, meta_column, ftype):
        """ Update an existing column

        :param column: instance of the Column model to update
        :param meta_column: instance of the SqlAlchemy column
        :param ftype: type of the AnyBlok Field
        """
        Model = cls.anyblok.get(column.model)
        if hasattr(Model, anyblok_column_prefix + column.name):
            c = getattr(Model, anyblok_column_prefix + column.name)
        else:
            c = meta_column.property.columns[0]  # pragma: no cover

        autoincrement = c.autoincrement

        if autoincrement == 'auto':
            autoincrement = (True if c.primary_key and ftype == 'Integer' else
                             False)

        if column.autoincrement != autoincrement:
            column.autoincrement = autoincrement  # pragma: no cover

        for col in ('nullable', 'primary_key', 'unique'):
            if getattr(column, col) != getattr(c, col):
                setattr(column, col, getattr(c, col))  # pragma: no cover

        for col in ('foreign_key', 'label', 'remote_model'):
            if getattr(column, col) != c.info.get(col):
                setattr(column, col, c.info.get(col))  # pragma: no cover

        if column.ftype != ftype:
            column.ftype = ftype  # pragma: no cover
Beispiel #16
0
class Parameter:
    """System Parameter"""

    key = String(primary_key=True)
    value = Json(nullable=False)
    multi = Boolean(default=False)

    @classmethod
    def set(cls, key, value):
        """ Insert or Update parameter for the key

        :param key: key to save
        :param value: value to save
        """
        multi = False
        if not isinstance(value, dict):
            value = {'value': value}
        else:
            multi = True

        if cls.is_exist(key):
            param = cls.from_primary_keys(key=key)
            param.update(value=value, multi=multi)
        else:
            cls.insert(key=key, value=value, multi=multi)

    @classmethod
    def is_exist(cls, key):
        """ Check if one parameter exist for the key

        :param key: key to check
        :rtype: Boolean, True if exist
        """
        query = cls.query().filter(cls.key == key)
        return True if query.count() else False

    @classmethod
    def get(cls, key):
        """ Return the value of the key

        :param key: key to check
        :rtype: return value
        :exception: ExceptionParameter
        """
        if not cls.is_exist(key):
            raise ParameterException("unexisting key %r" % key)

        param = cls.from_primary_keys(key=key)
        if param.multi:
            return param.value

        return param.value['value']

    @classmethod
    def pop(cls, key):
        """Remove return the value of the key

        :param key: key to check
        :rtype: return value
        :exception: ExceptionParameter
        """
        if not cls.is_exist(key):
            raise ParameterException("unexisting key %r" % key)

        param = cls.from_primary_keys(key=key)
        if param.multi:
            res = param.value
        else:
            res = param.value['value']

        param.delete()
        return res
Beispiel #17
0
class RelationShip(System.Field):

    name = String(primary_key=True)
    model = String(primary_key=True)
    local_columns = String()
    remote_columns = String()
    remote_name = String()
    remote_model = String(nullable=False)
    remote = Boolean(default=False)
    nullable = Boolean()

    def _description(self):
        res = super(RelationShip, self)._description()
        remote_name = self.remote_name or ''
        local_columns = []
        if self.local_columns:
            local_columns = [x.strip() for x in self.local_columns.split(',')]

        remote_columns = []
        if self.remote_columns:
            remote_columns = [
                x.strip() for x in self.remote_columns.split(',')
            ]

        res.update(
            nullable=self.nullable,
            model=self.remote_model,
            remote_name=remote_name,
            local_columns=local_columns,
            remote_columns=remote_columns,
        )
        return res

    @classmethod
    def add_field(cls, rname, relation, model, table, ftype):
        """ Insert a relationship definition

        :param rname: name of the relationship
        :param relation: instance of the relationship
        :param model: namespace of the model
        :param table: name of the table of the model
        :param ftype: type of the AnyBlok Field
        """
        local_columns = relation.info.get('local_columns', "")
        if isinstance(local_columns, list):
            local_columns = ','.join(local_columns)
        remote_columns = relation.info.get('remote_columns', "")
        if isinstance(remote_columns, list):
            remote_columns = ','.join(remote_columns)

        remote_model = relation.info.get('remote_model')
        remote_name = relation.info.get('remote_name')
        label = relation.info.get('label')
        nullable = relation.info.get('nullable', True)

        vals = dict(code=table + '.' + rname,
                    model=model,
                    name=rname,
                    local_columns=local_columns,
                    remote_model=remote_model,
                    remote_name=remote_name,
                    remote_columns=remote_columns,
                    label=label,
                    nullable=nullable,
                    ftype=ftype)
        cls.insert(**vals)

        if remote_name:
            remote_type = "Many2One"
            if ftype == "Many2One":
                remote_type = "One2Many"
            elif ftype == 'Many2Many':
                remote_type = "Many2Many"
            elif ftype == "One2One":
                remote_type = "One2One"

            m = cls.registry.get(remote_model)
            vals = dict(code=m.__tablename__ + '.' + remote_name,
                        model=remote_model,
                        name=remote_name,
                        local_columns=remote_columns,
                        remote_model=model,
                        remote_name=rname,
                        remote_columns=local_columns,
                        label=remote_name.capitalize().replace('_', ' '),
                        nullable=True,
                        ftype=remote_type,
                        remote=True)
            cls.insert(**vals)

    @classmethod
    def alter_field(cls, field, field_, ftype):
        field.label = field_.info['label']
Beispiel #18
0
class View:
    mode_name = None

    id = Integer(primary_key=True)
    order = Integer(sequence='web__view_order_seq', nullable=False)
    action = Many2One(model=Model.Web.Action, one2many='views', nullable=False,
                      foreign_key_options={'ondelete': 'cascade'})
    mode = Selection(selections='get_mode_choices', nullable=False)
    template = String(nullable=False)
    add_delete = Boolean(default=True)
    add_new = Boolean(default=True)
    add_edit = Boolean(default=True)

    @classmethod
    def get_mode_choices(cls):
        """ Return the View type available

        :rtype: dict(registry name: label)
        """
        return {
            'Model.Web.View.List': 'List view',
            'Model.Web.View.Thumbnail': 'Thumbnails view',
            'Model.Web.View.Form': 'Form view',
        }

    @classmethod
    def define_mapper_args(cls):
        mapper_args = super(View, cls).define_mapper_args()
        mapper_args.update({
            'polymorphic_identity': '',
            'polymorphic_on': cls.mode,
        })
        return mapper_args

    def render(self):
        buttons = [{'label': b.label, 'buttonId': b.method}
                   for b in self.action.buttons + self.buttons
                   if b.mode == 'action']
        return {
            'type': 'UPDATE_VIEW',
            'viewId': str(self.id),
            'viewType': self.mode_name,
            'creatable': self.action.add_new and self.add_new or False,
            'deletable': self.action.add_delete and self.add_delete or False,
            'editable': self.action.add_edit and self.add_edit or False,
            'model': self.action.model,
            'buttons': buttons,
        }

    @classmethod
    def bulk_render(cls, actionId=None, viewId=None, **kwargs):
        action = cls.registry.Web.Action.query().get(int(actionId))
        buttons = [{'label': b.label, 'buttonId': b.method}
                   for b in action.buttons
                   if b.mode == 'action']
        return {
            'type': 'UPDATE_VIEW',
            'viewId': viewId,
            'viewType': cls.mode_name,
            'creatable': action.add_new or False,
            'deletable': action.add_delete or False,
            'editable': action.add_edit or False,
            'model': action.model,
            'buttons': buttons,
        }

    @classmethod
    def get_view_render(cls, viewId, params):
        try:
            view = cls.query().get(int(viewId))
        except ValueError:
            _View = getattr(cls, viewId.split('-')[0])
            if 'actionId' not in params:
                params['actionId'] = viewId.split('-')[-1]
            return _View.bulk_render(**params)
        else:
            return view.render()
Beispiel #19
0
class JourneyWish(Mixin.UuidColumn, Mixin.TrackModel, Mixin.WorkFlow):

    """This model has Model.JourneyWish as a namespace. It is intented for
       storing journey wishes that represents travels intentions.

       For instance, an intention may be caracterized by start date, with an
       a time frame delimited by earlier and latest departure times,
       departure and arrival stations, passengers, maximum price, etc...

       Implemented fields are the following :

           * Departure station : Many2One relationship with Model.Station
           * Arrival station : Many2One relationship with Model.Station
           * Start date : DateTime representing earlier time after which train
           may leave the departure station
           * End date : DateTime representing latest time after which train may
           leave departure station.
           * Passengers : Many2Many relationship to Model.Passenger
           * Transportation mean : String, containing type of transport
           required by user (train, coach, etc...)
           * Active : boolean that stores if wish has to be processed
           * Activation date : Date, represent the moment when the wish could
           start being processed"""

    user = Many2One(
        label="User", model=Model.User, nullable=False, one2many="wishes"
    )

    departure = Many2One(
        label="Departure Station",
        model=Model.Station,
        nullable=True,
        one2many="departures",
    )

    arrival = Many2One(
        label="Arrival Station",
        model=Model.Station,
        nullable=True,
        one2many="arrivals",
    )

    from_date = DateTime(label="Earlier Departure Date", nullable=True)
    end_date = DateTime(label="Latest Departure Date", nullable=True)
    activation_date = Date(label="Activation Date", nullable=True)

    passengers = Many2Many(
        model=Model.Passenger,
        join_table="join_passengers_by_wishes",
        remote_columns="uuid",
        local_columns="uuid",
        m2m_remote_columns="p_uuid",
        m2m_local_columns="w_uuid",
        many2many="wishes",
    )

    transportation_mean = String(
        label="Transportation Mean", default="any", nullable=True
    )

    active = Boolean(label="Active Wish", nullable=True)

    @classmethod
    def get_workflow_definition(cls):

        """This method is aimed at defining workflow used for model
           Model.JourneyWish"""

        return {
            "draft": {
                "default": True,
                "allowed_to": ["running", "pending"],
                "apply_change": "deactivate",
            },
            "running": {
                "allowed_to": ["cancelled", "expired"],
                "apply_change": "activate",
            },
            "pending": {
                "allowed_to": ["cancelled", "expired", "running"],
                "apply_change": "deactivate",
            },
            "expired": {"apply_change": "deactivate"},
            "cancelled": {"apply_change": "deactivate"},
        }

    def activate(self, from_state):

        if from_state == "draft" and not self.activation_date:
            self.activation_date = date.today()

        self.active = True

    def deactivate(self, from_state):
        self.active = False

    def check_state(self):

        """This method is aimed at being used in order to automatically set
           workflow state, depending on record attributes."""

        if self.state == "pending":
            # If wish is pending, check is activation_date is set
            if self.activation_date and self.activation_date <= date.today():
                self.state_to("running")

        elif self.state == "running":
            # If wish is already running, check that from_date is not in the
            # past

            try:
                now = datetime.now().astimezone()
            except ValueError:
                # Python3.5 and below do not support astimezone on 'naive'
                # dates provided by datetime.now()
                now = datetime.now(timezone.utc).astimezone()

            if self.from_date and self.from_date < now:
                self.state_to("expired")
Beispiel #20
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()
Beispiel #21
0
class Request:
    id = Integer(label="Identifier", primary_key=True)
    """Primary key.

    In this model, the ordering of ``id`` ordering is actually
    important (whereas on many others, it's a matter of habit to have
    a serial id): the smaller it is, the older the Request.

    Requests have to be reserved in order.

    Note that ``serial`` columns in PostgreSQL don't induce conflicts, as
    the sequence is evaluated out of transaction.
    """

    purpose = Jsonb()
    """Flexible field to describe what the reservations will be for.

    This is typically used by a planner, to produce an appropriate
    chain of Operations to fulfill that purpose.

    Example: in a simple sales system, we would record a sale order
    reference here, and the planner would then take the related PhysObj
    and issue (planned) Moves and Departures for their 'present' or
    'future' Avatars.
    """

    reserved = Boolean(nullable=False, default=False)
    """Indicates that all reservations are taken.

    TODO: find a way to represent if the Request is partially done ?
    Some use-cases would require planning partial deliveries and the
    like in that case.
    """

    planned = Boolean(nullable=False, default=False)
    """Indicates that the planner has finished with that Request.

    It's better than deleting, because it allows to cancel all
    Operations, set this back to ``True``, and plan again.
    """

    txn_owned_reservations = set()
    """The set of Request ids whose current transaction owns reservations."""
    @classmethod
    @contextmanager
    def claim_reservations(cls, query=None, **filter_by):
        """Context manager to claim ownership over this Request's reservations.

        This is meant for planners and works on fully reserved Requests.
        Example::

           Request = registry.Wms.Reservation.Request
           with Request.claim_reservations() as req_id:
               request = Request.query().get(req_id)
               (...) read request.purpose, plan Operations (...)

        By calling this, the current transaction becomes responsible
        for all Request's reservations, meaning that it has the
        liberty to issue any Operation affecting its PhysObj or their Avatars.

        :return: id of claimed Request
        :param dict filter_by: direct filtering criteria to add to the
                               query, e.g, a planner looking for planning to
                               be done would pass ``planned=False``.
        :param query: if specified, is used to form the final SQL query,
                      instead of creating a new one.
                      The passed query must have the present model class in
                      its ``FROM`` clause and return only the ``id`` column
                      of the present model. The criteria of
                      ``filter_by`` are still applied if also provided.

        This is safe with respect to concurrency: no other transaction
        can claim the same Request (guaranteed by a PostgreSQL lock).

        The session will forget about this Request as soon as one
        exits the ``with`` statement, and the underlying PG lock is
        released at the end of the transaction.

        TODO for now it's a context manager. I'd found it more
        elegant to tie it to the transaction, to get automatic
        release, without a ``with`` syntax, but that requires more
        digging into SQLAlchemy and Anyblok internals.

        TODO I think FOR UPDATE actually creates a new internal PG row
        (table bloat). Shall we switch to advisory locks (see PG doc) with an
        harcoded mapping to an integer ?
        If that's true, then performance-wise it's equivalent for us
        to set the txn id in some service column (but that would
        require inconditional cleanup, a complication)
        """
        if query is None:
            query = cls.query('id')
        if filter_by is not None:
            query = query.filter_by(reserved=True, **filter_by)

        # issues a SELECT FOR UPDATE SKIP LOCKED (search
        #   'with_for_update' within
        #   http://docs.sqlalchemy.org/en/latest/core/selectable.html
        # also, noteworthy, SKIP LOCKED appeared within PostgreSQL 9.5
        #   (https://www.postgresql.org/docs/current/static/release-9-5.html)
        cols = query.with_for_update(skip_locked=True,
                                     of=cls).order_by(cls.id).first()
        request_id = None if cols is None else cols[0]

        if request_id is not None:
            cls.txn_owned_reservations.add(request_id)
        yield request_id

        if request_id is not None:
            cls.txn_owned_reservations.discard(request_id)

    def is_txn_reservations_owner(self):
        """Tell if transaction is the owner of this Request's reservations.

        :return:
          ``True`` if the current transaction has claimed ownership,
          using the :meth:``claim_reservations`` method.
        """
        return self.id in self.txn_owned_reservations

    def reserve(self):
        """Try and perform reservation for all RequestItems.

        :return: ``True`` if all reservations are now taken
        :rtype: bool

        Should not fail if reservations are already done.
        """
        Item = self.registry.Wms.Reservation.RequestItem
        # could use map() and all(), but it's not recommended style
        # if there are strong side effects.
        all_reserved = True
        for item in Item.query().filter(Item.request == self).all():
            all_reserved = all_reserved and item.reserve()
        self.reserved = all_reserved
        return all_reserved

    @classmethod
    def lock_unreserved(cls, batch_size, query_filter=None, offset=0):
        """Take exclusivity over not yet reserved Requests

        This is used in :ref:`Reservers <arch_reserver>` implementations.

        :param int batch: maximum of reservations to lock at once.

        Since reservations have to be taken in order, this produces a hard
        error in case there's a conflicting database lock, instead of skipping
        them like :meth:`claim_reservations` does.

        This conflicts in particular locks taken with
        :meth`claim_reservations`, but in principle,
        only :ref:`reservers <arch_reserver>` should take locks
        over reservation Requests that are not reserved yet, and these should
        not run in concurrency (or in a very controlled way, using
        ``query_filter``).
        """
        query = cls.query().filter(cls.reserved.is_(False))
        if query_filter is not None:
            query = query_filter(query)
        query = query.with_for_update(nowait=True).order_by(cls.id)
        try:
            return query.limit(batch_size).offset(offset).all()
        except sqlalchemy.exc.OperationalError as op_err:
            cls.registry.rollback()
            raise cls.ReservationsLocked(op_err)

    class ReservationsLocked(RuntimeError):
        """Used to rewrap concurrency errors while taking locks."""
        def __init__(self, db_exc):
            self.db_exc = db_exc

    @classmethod
    def reserve_all(cls,
                    batch_size=10,
                    nb_attempts=5,
                    retry_delay=1,
                    query_filter=None):
        """Try and perform all reservations for pending Requests.

        This walks all pending (:attr:`reserved` equal to ``False``)
        Requests that haven't been reserved from the oldest and locks
        them by batches of ``batch_size``.

        Reservation is attempted for each request, in order, meaning that
        each request will grab as much PhysObj as it can before the next one
        gets processed.

        :param int batch_size:
           number of pending Requests to grab at each iteration
        :param nb_attempts:
           number of attempts (in the face of conflicts) for each batch
        :param retry_delay:
           time to wait before retrying to grab a batch (hoping other
           transactions holding locks would have released them)
        :param query_filter:
           optional function to add filtering to the query used to grab the
           reservations. The caller can use this to implement controlled
           concurrency in the reservation process: several processes can
           focus on different Requests, as long as they don't compete for
           PhysObj to reserve.

        The transaction is committed for each batch, and that's essential
        for proper operation under concurrency.
        """
        skip = 0
        while True:
            # TODO log.info
            count = 1
            while True:
                try:
                    requests = cls.lock_unreserved(batch_size,
                                                   offset=skip,
                                                   query_filter=query_filter)
                except cls.ReservationsLocked:
                    # TODO log.warning
                    if count == nb_attempts:
                        raise
                    time.sleep(retry_delay)
                    count += 1
                else:
                    break
            if not requests:
                break

            for request in requests:
                if not request.reserve():
                    skip += 1
            cls.registry.commit()
Beispiel #22
0
class BooleanForbidUpdate(Mixin.ConditionalForbidUpdate):

    forbid_update = Boolean(default=False)

    def check_if_forbid_update_condition_is_true(self, **previous_values):
        return previous_values.get('forbid_update', self.forbid_update)
Beispiel #23
0
class DesiredRelay(Model.Iot.State):

    STATE_TYPE = "DESIRED_RELAY"

    uuid: uuid4 = UUID(
        primary_key=True,
        default=uuid4,
        binary=False,
        foreign_key=Model.Iot.State.use("uuid").options(ondelete="CASCADE"),
    )
    is_open: bool = Boolean(label="Is open ?", default=True)
    """Current circuit state. is_open == True means circuit open, it's turned
    off"""
    @classmethod
    def get_device_state(cls,
                         code: str,
                         date: datetime = None) -> "DesiredRelay":
        """return desired state for given device code"""
        mode = ThermostatMode(
            cls.registry.System.Parameter.get("mode", default="manual"))
        if mode is ThermostatMode.manual:
            return super().get_device_state(code)
        else:
            if not date:
                date = datetime.now()
            return {
                "BURNER": cls.get_burner_thermostat_desired_state,
                "ENGINE": cls.get_engine_thermostat_desired_state,
            }[code](date)

    @classmethod
    def get_burner_thermostat_desired_state(cls,
                                            date: datetime) -> "DesiredRelay":
        Thermometer = cls.registry.Iot.State.Thermometer
        Range = cls.registry.Iot.Thermostat.Range
        celsius_avg = Thermometer.get_living_room_avg(date=date, minutes=5)
        if not celsius_avg:
            # there are no thermometer working
            # user should move to manual mode
            return cls(is_open=True)
        if Thermometer.wait_min_return_desired_temperature(
                DEVICE.DEPARTURE_SENSOR,
                DEVICE.MIN_RET_DESIRED,
                DEVICE.MAX_DEP_DESIRED,
        ):
            # departure is to hot or waiting under ret min desired
            return cls(is_open=True)
        celsius_desired = Range.get_desired_living_room_temperature(date)
        if celsius_avg >= celsius_desired:
            # living room is already warm
            return cls(is_open=True)
        if Thermometer.get_last_value(
                DEVICE.RETURN_SENOR.value) >= Thermometer.get_last_value(
                    DEVICE.MAX_RET_DESIRED.value):
            # return temperature is to hot
            return cls(is_open=True)
        if Thermometer.wait_min_return_desired_temperature(
                DEVICE.RETURN_SENOR,
                DEVICE.MIN_RET_DESIRED,
                DEVICE.MAX_RET_DESIRED,
        ):
            # wait return temperature to get the DEVICE.MIN_RET_DESIRED.value
            # temperature before start burner again
            return cls(is_open=True)
        return cls(is_open=False)

    @classmethod
    def get_engine_thermostat_desired_state(cls,
                                            date: datetime,
                                            delta_minutes=120
                                            ) -> "DesiredRelay":
        """
        If 
          burner relay was on in last delta_minutes,
          or if return water - living room is more than diff config value
        then engine must be turned on
        """
        Thermometer = cls.registry.Iot.State.Thermometer
        Relay = cls.registry.Iot.State.Relay
        Device = cls.registry.Iot.Device
        count_states = (Relay.query().join(Device).filter(
            Relay.create_date > date - timedelta(minutes=delta_minutes),
            Relay.create_date <= date,
            Device.code == "BURNER",
            Relay.is_open == False,
        ))
        living_room_avg_temp = Thermometer.get_living_room_avg(date=date,
                                                               minutes=5)
        return_temp = Thermometer.get_last_value(DEVICE.RETURN_SENOR.value)
        min_diff = Thermometer.get_last_value(DEVICE.MIN_DIFF_DESIRED.value)
        return cls(
            is_open=not ((count_states.count() > 0) or
                         (return_temp - living_room_avg_temp > min_diff)))
Beispiel #24
0
class BooleanForbidDelete(Mixin.ConditionalForbidDelete):

    forbid_delete = Boolean(default=False)

    def check_if_forbid_delete_condition_is_true(self):
        return self.forbid_delete
Beispiel #25
0
class List(Model.Web.View, Mixin.Multi):
    "List View"

    mode_name = 'List'
    unclickable = False

    id = Integer(
        primary_key=True,
        foreign_key=Model.Web.View.use('id').options(ondelete="CASCADE")
    )
    selectable = Boolean(default=False)
    default_sort = String()

    @classmethod
    def define_mapper_args(cls):
        mapper_args = super(List, cls).define_mapper_args()
        mapper_args.update({
            'polymorphic_identity': 'Model.Web.View.List',
        })
        return mapper_args

    @classmethod
    def field_for_(cls, field, fields2read, **kwargs):
        res = {
            'name': field['id'],
            'label': field['label'],
            'component': 'furet-ui-list-field-' + field['type'].lower(),
        }
        if 'sortable' in kwargs:
            field['sortable'] = bool(eval(kwargs['sortable'], {}, {}))
        if 'help' in kwargs:
            field['tooltip'] = kwargs['help']

        fields2read.append(field['id'])
        for k in field:
            if k in ('id', 'label', 'nullable', 'primary_key'):
                continue
            elif k == 'type':
                res['numeric'] = (
                    True if field['type'] in ('Integer', 'Float', 'Decimal')
                    else False
                )

            elif k == 'model':
                if field[k]:
                    res[k] = field[k]
            else:
                res[k] = field[k]

        return res

    @classmethod
    def field_for_relationship(cls, field, fields2read, **kwargs):
        f = field.copy()
        Model = cls.registry.get(f['model'])
        Mapping = cls.registry.IO.Mapping
        if 'display' in kwargs:
            display = kwargs['display']
            for op in ('!=', '==', '<', '<=', '>', '>='):
                display = display.replace(op, ' ')

            display = display.replace('!', '')
            fields = []
            for d in display:
                if 'fields.' in d:
                    fields.append(d.split('.')[1])

            f['display'] = kwargs['display']
            del kwargs['display']
        else:
            fields = Model.get_display_fields(mode=cls.__registry_name__)
            f['display'] = " + ', ' + ".join(['fields.' + x for x in fields])

        if 'action' in kwargs:
            action = Mapping.get('Model.Web.Action', kwargs['action'])
            del kwargs['action']
        else:
            action = Model.get_default_action(mode=cls.__registry_name__)

        if 'menu' in kwargs:
            menu = Mapping.get('Model.Web.Menu', kwargs['menu'])
            del kwargs['menu']
        else:
            menu = Model.get_default_menu_linked_with_action(
                action=action, mode=cls.__registry_name__)

        f['actionId'] = str(action.id) if action else None
        f['menuId'] = str(menu.id) if menu else None
        fields2read.append([field['id'], fields])
        return cls.field_for_(f, [], **kwargs)

    @classmethod
    def field_for_Many2One(cls, field, fields2read, **kwargs):
        return cls.field_for_relationship(field, fields2read, **kwargs)

    @classmethod
    def field_for_One2One(cls, field, fields2read, **kwargs):
        f = field.copy()
        f['type'] = 'Many2One'
        return cls.field_for_relationship(field, fields2read, **kwargs)

    @classmethod
    def field_for_One2Many(cls, field, fields2read, **kwargs):
        return cls.field_for_relationship(field, fields2read, **kwargs)

    @classmethod
    def field_for_Many2Many(cls, field, fields2read, **kwargs):
        return cls.field_for_relationship(field, fields2read, **kwargs)

    @classmethod
    def field_for_BigInteger(cls, field, fields2read, **kwargs):
        f = field.copy()
        f['type'] = 'Integer'
        return cls.field_for_(f, fields2read, **kwargs)

    @classmethod
    def field_for_SmallInteger(cls, field, fields2read, **kwargs):
        f = field.copy()
        f['type'] = 'Integer'
        return cls.field_for_(f, fields2read, **kwargs)

    @classmethod
    def field_for_LargeBinary(cls, field, fields2read, **kwargs):
        f = field.copy()
        f['type'] = 'file'
        return cls.field_for_(f, fields2read, **kwargs)

    @classmethod
    def field_for_Sequence(cls, field, fields2read, **kwargs):
        f = field.copy()
        f['type'] = 'string'
        res = cls.field_for_(f, fields2read, **kwargs)
        res['readonly'] = True
        return res

    @classmethod
    def field_for_Selection(cls, field, fields2read, **kwargs):
        f = field.copy()
        if 'selections' in kwargs:
            f['selections'] = loads(kwargs['selections'])
            del kwargs['selections']

        if 'selections' not in f:
            f['selections'] = {}

        if isinstance(f['selections'], list):
            f['selections'] = dict(f['selections'])

        return cls.field_for_(f, fields2read, **kwargs)

    @classmethod
    def field_for_UUID(cls, field, fields2read, **kwargs):
        f = field.copy()
        f['type'] = 'string'
        res = cls.field_for_(f, fields2read, **kwargs)
        res['readonly'] = True
        return res

    @classmethod
    def bulk_render(cls, actionId=None, viewId=None, **kwargs):
        action = cls.registry.Web.Action.query().get(int(actionId))
        res = super(List, cls).bulk_render(
            actionId=actionId, viewId=viewId, **kwargs)
        Model = cls.registry.get(action.model)
        headers = []
        search = []
        fd = Model.fields_description()
        fields = list(fd.keys())
        fields.sort()
        fields2read = []
        for field_name in fields:
            field = fd[field_name]
            if field['type'] in ('FakeColumn', 'Many2Many', 'One2Many',
                                 'Function'):
                continue

            meth = 'field_for_' + field['type']
            if hasattr(cls, meth):
                headers.append(getattr(cls, meth)(field, fields2read))
            else:
                headers.append(cls.field_for_(field, fields2read))

            search.append({
                'key': field_name,
                'label': field['label'],
                'type': 'search',
            })

        buttons2 = [{'label': b.label, 'buttonId': b.method}
                    for b in action.buttons
                    if b.mode == 'more']
        res.update({
            'selectable': False,
            'onSelect': 'Form-%d' % action.id,
            'headers': headers,
            'search': search,
            'buttons': [],
            'onSelect_buttons': buttons2,
            'fields': fields2read,
        })
        return res

    def render(self):
        res = super(List, self).render()
        Model = self.registry.get(self.action.model)
        fd = Model.fields_description()
        template = self.registry.furetui_views.get_template(
            self.template, tostring=False)
        fields2read = []
        headers = []
        for field in template.findall('.//field'):
            attributes = deepcopy(field.attrib)
            field = fd[attributes.pop('name')]
            _type = attributes.pop('type', field['type'])
            meth = 'field_for_' + _type
            if hasattr(self.__class__, meth):
                headers.append(getattr(self.__class__, meth)(
                    field, fields2read, **attributes))
            else:
                headers.append(self.__class__.field_for_(
                    field, fields2read, **attributes))

        buttons = [{'label': b.label, 'buttonId': b.method}
                   for b in self.action.buttons
                   if b.mode == 'action']
        buttons2 = [{'label': b.label, 'buttonId': b.method}
                    for b in self.action.buttons
                    if b.mode == 'more']
        colors = {c.name: c.condition for c in self.colors}
        res.update({
            'onSelect': self.get_form_view(),
            'selectable': self.selectable,
            'default_sort': self.default_sort.split(' '),
            'empty': '',  # TODO
            'headers': headers,
            'search': self.registry.Web.Action.Search.get_from_view(self),
            'buttons': buttons,
            'onSelect_buttons': buttons2,
            'colors': colors,
            'fields': fields2read,
        })
        return res
Beispiel #26
0
class Regular:
    """A regular worker, processing time slices.

    A time slice is the batch operation equivalent of a day's work,
    or if one prefers, a team's shift.
    """
    id = Integer(label="Identifier", primary_key=True)
    pid = Integer()
    done_timeslice = Integer(label="Latest done timeslice",
                             nullable=False,
                             default=0)
    max_timeslice = Integer(label="Greatest timeslice to run")
    active = Boolean()
    sales_per_timeslice = Integer(default=10)

    other = set()

    simulate_sleep = 10

    conflicts = 0
    """Used to report number of database conflicts."""
    def process_one(self):
        """To be implemented by concrete subclasses.

        The default implementation is a stub that simply sleeps for a while.
        """
        time.sleep(random.randrange(self.simulate_sleep) / 100.0)
        return False

    def begin_timeslice(self):
        """Do all business logic that has to be done at the timeslice start.
        """

    @property
    def current_timeslice(self):
        done = self.done_timeslice
        if done is None:
            return 1
        return done + 1

    def __str__(self):
        # in tests, id and pid can be None, hence let's avoid %d
        return "Regular Worker (id=%s, pid=%s)" % (self.id, self.pid)

    def stop(self):
        self.registry.rollback()
        self.active = False
        self.registry.commit()
        return

    def run_timeslice(self):
        tsl = self.current_timeslice
        self_str = str(self)
        logger.info("%s, starting timeslice %d", self_str, tsl)
        self.begin_timeslice()
        logger.info(
            "%s, begin sequence for timeslice %d finished, now "
            "proceeding to normal work", self, tsl)
        # it's important to start with a fresh MVCC snapshot
        # no matter what (especially requests due to logging)
        self.registry.commit()
        proceed = True
        while proceed:
            try:
                op = self.process_one()
                self.registry.commit()
                if op is None:
                    proceed = False
                elif op is not True:
                    logger.info("%s, %s(id=%d) done and committed", self_str,
                                op[0], op[1])
            except KeyboardInterrupt:
                raise
            except OperationalError as exc:
                if isinstance(exc.orig, TransactionRollbackError):
                    self.conflicts += 1
                    logger.warning("%s, got conflict: %s", self_str, exc)
                else:
                    logger.exception("%s, catched exception in main loop",
                                     self_str)
                self.registry.rollback()
            except:
                self.registry.rollback()
                logger.exception("%s, exception in process_one()", self_str)

        self.done_timeslice = tsl
        if tsl == self.max_timeslice:
            self.active = False
        self.registry.commit()
        logger.info(
            "%s, finished timeslice %d. "
            "Cumulated number of conflicts: %d", self_str, tsl, self.conflicts)
        sys.stderr.flush()
        self.registry.session.execute("NOTIFY timeslice_finished, '%d'" % tsl)
        self.registry.commit()

    def wait_others(self, timeslice):
        self.registry.session.execute("LISTEN timeline_finished")
        while 1:
            self.registry.commit()
            conn = self.registry.session.connection().connection
            if select.select([conn], [], [],
                             self.simulate_sleep) == ([], [], []):
                logger.warning("Timeout in LISTEN")
                if self.all_finished(timeslice):
                    return
            else:
                conn.poll()
                while conn.notifies:
                    notify = conn.notifies.pop(0)
                    logger.debug(
                        "Process %d got end signal for process_id %d, "
                        "timeslice %s", os.getpid(), notify.pid,
                        notify.payload)
                    if self.all_finished(timeslice):
                        return

    def all_finished(self, timeslice):
        cls = self.__class__
        query = cls.query().filter(cls.done_timeslice < timeslice,
                                   cls.active.is_(True))
        if query.count():
            logger.info("%s: timeslice %d not done yet for %s", self,
                        timeslice, [p.pid for p in query.all()])
            return False
        return True
Beispiel #27
0
class Parameter:
    """Applications parameters.

    This Model is provided by ``anyblok-core`` to give applications a uniform
    way of specifying in-database configuration.

    It is a simple key/value representation, where values can be of any type
    that can be encoded as JSON.

    A simple access API is provided with the :meth:`get`, :meth:`set`,
    :meth:`is_exist` and further methods.
    """

    key = String(primary_key=True)
    value = Json(nullable=False)
    multi = Boolean(default=False)

    @classmethod
    def set(cls, key, value):
        """ Insert or update parameter value for a key.

        .. note:: if the key already exists, the value will be updated

        :param str key: key to save
        :param value: value to save
        """
        multi = False
        if not isinstance(value, dict):
            value = {'value': value}
        else:
            multi = True

        if cls.is_exist(key):
            param = cls.from_primary_keys(key=key)
            param.update(value=value, multi=multi)
        else:
            cls.insert(key=key, value=value, multi=multi)

    @classmethod
    def is_exist(cls, key):
        """ Check if one parameter exist for the key

        :param key: key to check
        :rtype: bool
        """
        query = cls.query().filter(cls.key == key)
        return True if query.count() else False

    @classmethod
    def get(cls, key, default=NOT_PROVIDED):
        """ Return the value of the key

        :param key: key whose value to retrieve
        :param default: default value if key does not exists
        :return: associated value
        :rtype: anything JSON encodable
        :raises ParameterException: if the key doesn't exist and default is not
                                    set.
        """
        if not cls.is_exist(key):
            if default is NOT_PROVIDED:
                raise ParameterException(
                    "unexisting key %r" % key)
            return default

        param = cls.from_primary_keys(key=key)
        if param.multi:
            return param.value

        return param.value['value']

    @classmethod
    def pop(cls, key):
        """Remove the given key and return the associated value.

        :param str key: the key to remove
        :return: the value before removal
        :rtype: any JSON encodable type
        :raises ParameterException: if the key wasn't present
        """
        if not cls.is_exist(key):
            raise ParameterException(
                "unexisting key %r" % key)

        param = cls.from_primary_keys(key=key)
        if param.multi:
            res = param.value
        else:
            res = param.value['value']

        param.delete()
        return res
Beispiel #28
0
class WkHtml2Pdf:

    id = Integer(primary_key=True, nullable=False)
    label = String(nullable=False)
    created_at = DateTime(nullable=False, default=datetime.now)
    updated_at = DateTime(nullable=False,
                          default=datetime.now,
                          auto_update=True)

    copies = Integer(nullable=False, default=1)
    grayscale = Boolean(default=False)
    lowquality = Boolean(default=False)
    dpi = Integer()
    page_offset = Integer(nullable=False, default=0)
    minimum_font_size = Integer()
    margin_bottom = Integer(nullable=False, default=10)
    margin_left = Integer(nullable=False, default=10)
    margin_right = Integer(nullable=False, default=10)
    margin_top = Integer(nullable=False, default=10)
    orientation = Selection(selections={
        'Landscape': 'Landscape',
        'Portrait': 'Portrait'
    },
                            nullable=False,
                            default='Portrait')
    page = Many2One(model='Model.Attachment.WkHtml2Pdf.Page', nullable=False)
    background = Boolean(default=True)
    collate = Boolean(default=True)
    encoding = String(default='utf-8', nullable=False)
    images = Boolean(default=True)
    javascript = Boolean(default=True)
    local_file_access = Boolean(default=True)
    javascript_delay = Integer(nullable=False, default=200)
    load_error_handling = Selection(selections='get_error_handling',
                                    default='abort',
                                    nullable=False)
    load_media_error_handling = Selection(selections='get_error_handling',
                                          default='abort',
                                          nullable=False)

    @classmethod
    def get_error_handling(cls):
        return {x: x.capitalize() for x in ('abort', 'ignore', 'skip')}

    @classmethod
    def define_table_args(cls):
        table_args = super(WkHtml2Pdf, cls).define_table_args()
        return table_args + (
            CheckConstraint('copies > 0', name="copies_upper_than_0"),
            CheckConstraint('page_offset >= 0', name="offset_upper_than_0"),
            CheckConstraint('margin_bottom >= 0',
                            name="marge_bottom_upper_than_0"),
            CheckConstraint('margin_left >= 0',
                            name="marge_left_upper_than_0"),
            CheckConstraint('margin_right >= 0',
                            name="marge_right_upper_than_0"),
            CheckConstraint('margin_top >= 0', name="marge_top_upper_than_0"),
            CheckConstraint('javascript_delay >= 0',
                            name="js_delay_upper_than_0"),
        )

    def options_from_self(self):
        options = []
        for option in ('margin_bottom', 'margin_right', 'margin_left',
                       'margin_top', 'orientation', 'encoding',
                       'javascript_delay', 'load_error_handling',
                       'load_media_error_handling', 'copies', 'dpi',
                       'minimum_font_size'):
            val = getattr(self, option)
            if val is not None:
                options.append('--' + option.replace('_', '-'))
                options.append(str(val))

        for option in ('grayscale', 'lowquality'):
            val = getattr(self, option)
            if val is not None:
                options.append('--' + option.replace('_', '-'))

        options.extend(self.page.get_options())

        for option in ('background', 'images', 'collate'):
            val = getattr(self, option)
            options.append(('--' if val else '--no-') + option)

        for option in ('javascript', 'local_file_access'):
            val = getattr(self, option)
            options.append(('--enable-' if val else '--disable-') +
                           option.replace('_', '-'))

        return options

    def options_from_configuration(self):
        options = []
        if not Configuration.get('wkhtmltopdf_unquiet'):
            options.append('--quiet')
        if Configuration.get('wkhtmltopdf_debug_javascript'):
            options.append('--debug-javascript')
        else:
            options.append('--no-debug-javascript')

        return options

    def cast_html2pdf(self, prefix, html_content):
        """Cast html document to a pdf document

        :param prefix: prefix use for the tempory document
        :param html_content: html file (bytes)
        :rtype: bytes
        :exception: WkHtml2PdfException
        """
        tmp_dir = tempfile.mkdtemp(prefix + '-html2pdf')
        html_path = os.path.join(tmp_dir, 'in.html')
        pdf_path = os.path.join(tmp_dir, 'out.pdf')

        with open(html_path, 'wb') as fd:
            fd.write(html_content)

        cmd = ['wkhtmltopdf']
        cmd.extend(self.options_from_self())
        cmd.extend(self.options_from_configuration())
        cmd.extend([html_path, pdf_path])

        logger.debug('Rendering PDF, cmd=%r', cmd)
        wkhtmltopdf = subprocess.Popen(cmd,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
        out, err = wkhtmltopdf.communicate()

        if wkhtmltopdf.returncode != 0:
            logger.error("wkhtmltopdf failure with stdout=%r, stderr=%r", out,
                         err)
            raise WkHtml2PdfException(
                ("wkhtmltopdf {cmd} in dir {tmp_dir} failed with code "
                 "{code}, check error log for details").format(
                     cmd=' '.join(cmd),
                     tmp_dir=tmp_dir,
                     code=wkhtmltopdf.returncode))

        logger.debug("wkhtmltopdf finished stdout=%r, stderr=%r", out, err)

        with open(pdf_path, 'rb') as fd:
            file_ = fd.read()

        try:
            shutil.rmtree(tmp_dir)
        except Exception:
            logger.warning("Could not clean up temporary directory %r",
                           tmp_dir,
                           exc_info=True)
        return file_