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}
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
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
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
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)
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))
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
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)
def is_valid_message(obj, message_id): workflow = get_workflow_by_instance(obj) return workflow.is_valid_message(message_id, obj.state)
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)]
def workflow(self): if not self._workflow: self._workflow = get_workflow_by_instance(self) return self._workflow
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
def get_resource(obj, resource_id): workflow = get_workflow_by_instance(obj) return workflow.get_resource(obj.state, resource_id)