Esempio n. 1
0
def get_allowed(sender, obj):
    workflow = get_workflow_by_instance(obj)

    obj_state = getattr(obj, workflow.state_attr_name)

    check_result = dict(
        (c, c(obj, sender))
        for c in workflow.get_checkers_by_state(obj_state))

    messages = []
    for checker, message in workflow.get_available_messages(obj_state):
        if checker(obj, sender, cache=check_result):
            spec = workflow.get_message_spec(message)
            messages.append({'id': spec.id, 'title': unicode(spec), 'rank': spec.rank})

    resources = []
    for resource in workflow.get_available_resources(obj_state):
        if resource.permission_checker(obj, sender, cache=check_result):
            resources.append(
                {
                    'id': resource.id,
                    'description': resource.description,
                    'slug': resource.slug,
                })

    return {'allowed_messages': messages,
            'allowed_resources': resources}
Esempio n. 2
0
def get_message_specs_for_many(sender, objects):

    allowed_map = get_allowed_messages_for_many(sender, objects)

    for obj in objects:
        workflow = get_workflow_by_instance(obj)
        allowed_map[obj] = map(workflow.get_message_spec, allowed_map[obj])

    return allowed_map
Esempio n. 3
0
def get_allowed_messages(sender, obj, cache=None):
    workflow = get_workflow_by_instance(obj)

    if cache is None:
        cache = dict((c, c(obj, sender)) for c in
                workflow.get_message_checkers_by_state(obj.state))

    for checker, message in workflow.get_available_messages(obj.state):
        if checker(obj, sender, cache=cache):
            yield message
Esempio n. 4
0
def get_allowed_resources(sender, obj):

    workflow = get_workflow_by_instance(obj)

    check_result = dict((c, c(obj, sender)) for c in
            workflow.get_checkers_by_state(obj.state))

    for resource in workflow.get_available_resources(obj.state):
        if resource.permission_checker(obj, sender, cache=check_result):
            yield resource
Esempio n. 5
0
def start_workflow(obj, sender, start_message_params=None):
    if start_message_params is None:
        start_message_params = {}
    workflow = get_workflow_by_instance(obj)

    if isinstance(workflow.start_workflow, basestring):
        start_message_id = workflow.start_workflow
    elif callable(workflow.start_workflow):
        start_message_id  = workflow.start_workflow(obj, sender)
    else:
        start_message_id  = DEFAULT_START_MESSAGE

    return dispatch.dispatch(obj, sender, start_message_id,
                                                start_message_params)
Esempio n. 6
0
    def wrapper(obj, sender, edit_fields=None, **kwargs):

        handler_result = handler(obj, sender, **kwargs)
        workflow = get_workflow_by_instance(obj)

        if not edit_fields:
            return handler_result
        else:
            if isinstance(handler_result, STATE_TYPE_CONSTRAINT):
                edit_fields['state'] = handler_result
                return workflow.make_updater(edit_fields)
            else:
                assert callable(handler_result)
                updater = workflow.make_updater(edit_fields)
                return chained_apply((updater, handler_result))
Esempio n. 7
0
def perform_side_effect(old_obj, new_obj, message, workflow=None, extra_context=None, handler_result=None):

    if workflow is None:
        workflow = get_workflow_by_instance(new_obj)

    old_state = getattr(old_obj, workflow.state_attr_name)
    new_state = getattr(new_obj, workflow.state_attr_name)

    (transactional_effects, deferrable_effects) = workflow.library.get_effects_for_transition(
        old_state, new_state, message.id
    )

    if not transactional_effects and not deferrable_effects:
        logger.info(u"Effect undefined: object id %s, state %s -> %s", new_obj.id, old_state, new_state)
        return [], []

    if extra_context is None:
        extra_context = {}

    effect_kwargs = dict(
        old_obj=old_obj,
        obj=new_obj,
        sender=message.sender,
        params=message.params,
        message_spec=message.spec,
        extra_context=extra_context,
        handler_result=handler_result,
    )

    if transactional_effects:
        performed = [_perform_side_effect(effect, effect_kwargs) for effect in deferrable_effects]
    else:
        performed = []

    if deferrable_effects:
        deferred = (_perform_side_effect(effect, effect_kwargs) for effect in deferrable_effects)
    else:
        deferred = []
    return performed, deferred
Esempio n. 8
0
def get_action_form_html(obj, sender):
    workflow_type = obj.workflow_type
    workflow = get_workflow_by_instance(obj)
    allowed_context = get_allowed_messages(sender, obj)

    t = template_loader.select_template(
            ('workflows/%s/form_%s.html' % (workflow_type, obj.state),
                'workflows/%s/form.html' % workflow_type,
                'workflows/form.html'))

    allowed_messages = allowed_context['allowed_messages']
    if not allowed_messages:
        raise NoAvailableMessagesError(obj.id, sender)

    if callable(workflow.formcls_factory):
        form_cls = workflow.formcls_factory(allowed_messages)
        form = form_cls(instance=obj)
    else:
        # get all form subclasses from message validators, throw away duplicates
        bases = tuple(set(ms.validator_cls for ms in allowed_messages
                            if issubclass(ms.validator_cls, forms.BaseForm)))
        if bases:
            # join them all in one mixin class
            mixin_form_cls = type('WorkflowObjectMixinForm', bases, {})
            # instantiate it and put instance as argument (requires ModelForm subclass to be in bases)
            if issubclass(mixin_form_cls, forms.ModelForm):
                form = mixin_form_cls(instance=obj)
            else:
                form = mixin_form_cls(initial=obj.__dict__)
        else:
            form = forms.Form()

    dict_context = {'form': form, 'instance': obj}
    dict_context.update(allowed_context)
    context = Context(dict_context)
    return t.render(context)
Esempio n. 9
0
def is_valid_message(obj, message_id):
    workflow = get_workflow_by_instance(obj)

    return workflow.is_valid_message(message_id, obj.state)
Esempio n. 10
0
def get_message_specs(sender, obj):

    workflow = get_workflow_by_instance(obj)
    return [workflow.get_message_spec(mid)
                for mid in get_allowed_messages(sender, obj)]
Esempio n. 11
0
 def workflow(self):
     if not self._workflow:
         self._workflow = get_workflow_by_instance(self)
     return self._workflow
Esempio n. 12
0
def dispatch_message(obj, message,
                     extra_context=None,
                     transactional_side_effect=TRANSACTIONAL_SIDE_EFFECT,
                     need_lock_object=USE_SELECT_FOR_UPDATE,
                     defer_side_effect=False,
                     revision_manager=None):
    '''
    Gets an object and message and performs all actions specified by
    object's workflow.

    :param obj:
        Object that receives message. Must be an instance of
        :py:class:`yawf.base_model.WorkflowAwareModelBase`
    :param message:
        :py:class:`yawf.messages.Message` instance that incapsulates message sender and parameters

    :return:
        Tuple of three values:
         * new object instance (after state transition)
         * transition result (returned by handler object)
         * side effects results
    '''
    logger.debug(u"Backend got message from %s to %s: %s %s",
            smart_unicode(message.sender), smart_unicode(obj),
            message.id, smart_unicode(message.raw_params))

    # can raise WorkflowNotLoadedError
    workflow = get_workflow_by_instance(obj)

    # validate data and filter out trash
    message.clean(workflow, obj)

    # dehydrate message params for serializing
    message.dehydrate_params(workflow, obj)

    # find a transition handler, can raise handler-related errors
    handler = get_handler(workflow, message, obj)

    # fetch a transition, can raise app-specific handler errors
    handler_result = apply(handler, (obj, message.sender), message.params)

    # if handler returns None - do nothing
    if handler_result is None:
        raise MessageIgnored(message)

    # if handler returns type appropriate for state (string) - change state
    if isinstance(handler_result, STATE_TYPE_CONSTRAINT):
        if workflow.is_valid_state(handler_result):
            def state_transition(obj):
                setattr(obj, workflow.state_attr_name, handler_result)
                obj.save()
                return obj
        else:
            raise IllegalStateError(handler_result)

    # if handler returns callable, perform it as single transaction
    elif callable(handler_result):
        state_transition = handler_result
    else:
        raise WrongHandlerResultError(handler_result)

    if defer_side_effect:
        transition_ = transactional_transition
        transactional_side_effect = False
    else:
        transition_ = transition

    if revision_manager is None:
        revision_manager = default_revision_manager

    with revision_manager() as m:
        new_obj, transition_result, side_effect_result =\
            transition_(
                workflow, obj, message, state_transition,
                extra_context=extra_context,
                transactional_side_effect=transactional_side_effect,
                need_lock_object=need_lock_object)

        if MESSAGE_LOG_ENABLED:
            log_record = log_message(
                sender=workflow.id,
                workflow=workflow,
                message=message,
                instance=obj,
                new_instance=new_obj,
                transition_result=transition_result)

            m.bind_revision(log_record)
        else:
            log_record = None

    # TODO: send_robust + logging?
    message_handled.send(
            sender=workflow.id,
            workflow=workflow,
            message=message,
            instance=obj,
            new_instance=new_obj,
            transition_result=transition_result,
            side_effect_result=side_effect_result,
            log_record=log_record)

    return new_obj, transition_result, side_effect_result
Esempio n. 13
0
def get_resource(obj, resource_id):
    workflow = get_workflow_by_instance(obj)

    return workflow.get_resource(obj.state, resource_id)