Ejemplo n.º 1
0
    def __init__(self, app_namespace, flow_namespace, position, state,
                 state_store, url_args, url_kwargs):
        self._app_namespace = app_namespace
        self._flow_namespace = flow_namespace
        self._position = position
        self._state = state
        self._flow_components = []
        self.state_store = state_store

        self._url_args = url_args or []
        self._url_kwargs = url_kwargs or {}

        for flow_component_class in self._position.flow_component_classes:
            flow_component = flow_component_class()
            flow_component._flow_position_instance = self
            flow_component.set_url_args(*self._url_args, **self._url_kwargs)
            flow_component.state = state
            flow_component.task_id = self.task_id
            flow_component.app_namespace = self._app_namespace
            flow_component.flow_namespace = self._flow_namespace

            self._flow_components.append(flow_component)

        self._history = FlowHistory(self)

        self._validate()
Ejemplo n.º 2
0
    def __init__(self, app_namespace, flow_namespace, position, state, state_store, url_args, url_kwargs):
        self._app_namespace = app_namespace
        self._flow_namespace = flow_namespace
        self._position = position
        self._state = state
        self._flow_components = []
        self.state_store = state_store

        self._url_args = url_args or []
        self._url_kwargs = url_kwargs or {}

        for flow_component_class in self._position.flow_component_classes:
            flow_component = flow_component_class()
            flow_component._flow_position_instance = self
            flow_component.set_url_args(*self._url_args, **self._url_kwargs)
            flow_component.state = state
            flow_component.task_id = self.task_id
            flow_component.app_namespace = self._app_namespace
            flow_component.flow_namespace = self._flow_namespace

            self._flow_components.append( flow_component )

        self._history = FlowHistory(self)

        self._validate()
Ejemplo n.º 3
0
class FlowPositionInstance(object):
    """
    A FlowPositionInstance represents a concrete instance of a PossibleFlowPosition -
    that is, a user is currently performing an action as part of a flow
    """
    def __init__(self, app_namespace, flow_namespace, position, state,
                 state_store, url_args, url_kwargs):
        self._app_namespace = app_namespace
        self._flow_namespace = flow_namespace
        self._position = position
        self._state = state
        self._flow_components = []
        self.state_store = state_store

        self._url_args = url_args or []
        self._url_kwargs = url_kwargs or {}

        for flow_component_class in self._position.flow_component_classes:
            flow_component = flow_component_class()
            flow_component._flow_position_instance = self
            flow_component.set_url_args(*self._url_args, **self._url_kwargs)
            flow_component.state = state
            flow_component.task_id = self.task_id
            flow_component.app_namespace = self._app_namespace
            flow_component.flow_namespace = self._flow_namespace

            self._flow_components.append(flow_component)

        self._history = FlowHistory(self)

        self._validate()

    def _validate(self):
        pass
        # TODO: assert that only the last element is an Action and that the
        # rest are Scaffolds

    @property
    def task_id(self):
        return self._state['_id']

    def get_root_component(self):
        return self._flow_components[0]

    def get_action(self):
        return self._flow_components[-1]

    def get_back_url(self):
        return self._history.get_back_url()

    def get_absolute_url(self, include_flow_id=True):
        args = []
        kwargs = {}
        for flow_component in self._flow_components:
            flow_args, flow_kwargs = flow_component.get_url_args()
            args += flow_args
            kwargs.update(flow_kwargs)

        url_name = self._position.url_name
        url = reverse(url_name, args=args, kwargs=kwargs)

        if include_flow_id:

            separator = '&' if '?' in url else '?'

            return '%(root)s%(url)s%(separator)s%(task_id_param_name)s=%(task_id)s' % {
                'root': config.FLOWS_SITE_ROOT,
                'url': url,
                'separator': separator,
                'task_id_param_name': config.FLOWS_TASK_ID_PARAM,
                'task_id': self.task_id
            }
        else:
            return '%(root)s%(url)s' % {
                'root': config.FLOWS_SITE_ROOT,
                'url': url
            }

    def position_instance_for(self, component_class_or_name):
        # figure out where we're being sent to
        FC = get_by_class_or_name(component_class_or_name)

        # it should be a sibling of one of the current items
        # for example, if we are in position [A,B,E]:
        #
        #         A
        #      /  |  \
        #    B    C   D
        #   /  \      |  \
        #  E   F      G   H
        #
        #  E can send to F (its own sibling) or C (sibling of its parent)

        for fci in self._flow_components[
                -2::-1]:  # go backwards but skip the last element (the action)
            if FC in fci.action_set:
                # we found the relevant action set, which means we know the root
                # part of the tree, and now we can construct the rest
                idx = self._flow_components.index(fci)
                break
        else:
            raise ValueError('Could not figure out how to redirect to %s' % FC)

        # so the new tree is from the root to the parent of the one we just found,
        # coupled with the initial subtree from the component we're tring to redirect
        # to
        tree_root = self._position.flow_component_classes[:idx + 1]

        # figure out the action tree for the new first component - either
        # we have been given an action, in which case it's just one single
        # item, or we have been given a scaffold, in which case there could
        # be a list of [scaffold, scaffold..., action]
        new_subtree = FC.get_initial_action_tree()

        # we use our current tree and replace the current leaf with this new
        # subtree to get the new position
        new_position = PossibleFlowPosition(self._app_namespace,
                                            self._flow_namespace,
                                            tree_root + new_subtree)

        # now create an instance of the position with the current state
        return new_position.create_instance(self._state, self.state_store,
                                            self._url_args, self._url_kwargs)

    def handle(self, request, *args, **kwargs):
        # first validate that we can actually run by checking for
        # required state, for example
        response = None
        for flow_component in self._flow_components:
            response = flow_component.check_preconditions(request)
            if response is not None:
                break

        if response is None:
            # now call each of the prepare methods for the components
            for flow_component in self._flow_components:
                # TODO: passing in *args and **kwargs to prepare is deprecated
                response = flow_component.prepare(request, *args, **kwargs)
                if response is not None:
                    # we allow prepare methods to give out responses if they
                    # want to, eg, redirect
                    break

        if response is None:

            # FIXME: mjtamlyn promises to fix this in Django 1.7, but right now we need
            # to set up the magic attributes usually set up by a closure in View.as_view
            # so we can call dispatch on Django>1.5
            action = self.get_action()
            if hasattr(action, 'request'):
                raise Exception('Action re-use?')
            action.request = request
            action.args = args
            action.kwargs = kwargs

            # now that everything is set up, we can handle the request
            response = self.get_action().dispatch(request, *args, **kwargs)

            # if this is a GET request, then we displayed something to the user, so
            # we should record this in the history, unless the request returned a
            # redirect, in which case we haven't displayed anything
            if request.method == 'GET' and not isinstance(
                    response, HttpResponseRedirect):
                self._history.add_to_history(self)

        # now we have a response, we need to decide what to do with it
        for flow_component in self._flow_components[::
                                                    -1]:  # go from leaf to root, ie, backwards
            response = flow_component.handle_response(response)

        # now we have some kind of response, figure out what it is exactly
        if response == COMPLETE:
            # this means that the entire flow finished - we should redirect
            # to the on_complete url if we have one, or get upset if we don't
            next_url = self._state.get('_on_complete', None)
            if next_url is None:
                # oh, we don't know where to go...
                raise ImproperlyConfigured(
                    'Flow completed without an _on_complete URL or an explicit redirect - %s'
                    % self.__repr__())
            else:
                response = redirect(next_url)

            # if we are done, then we should remove the task state
            self.state_store.delete_state(self.task_id)

        else:
            # update the state if necessary
            self.state_store.put_state(self.task_id, self._state)

            if inspect.isclass(response):
                # we got given a class, which implies the code should redirect
                # to this new (presumably Action) class
                response = redirect(
                    self.position_instance_for(response).get_absolute_url())

            elif isinstance(response, Action):
                # this is a new action for the user, so redirect to it
                absurl = response.get_absolute_url()
                response = redirect(absurl)

            elif isinstance(response, basestring):
                # this is a string which should be the name of an action
                # which couldn't be referenced as a class for some reason
                flow_component = get_by_class_or_name(response)
                response = redirect(flow_component.get_absolute_url())

        return response

    def __repr__(self):
        return 'Instance of %s' % self._position.__repr__()
Ejemplo n.º 4
0
class FlowPositionInstance(object):
    """
    A FlowPositionInstance represents a concrete instance of a PossibleFlowPosition -
    that is, a user is currently performing an action as part of a flow
    """

    def __init__(self, app_namespace, flow_namespace, position, state, state_store, url_args, url_kwargs):
        self._app_namespace = app_namespace
        self._flow_namespace = flow_namespace
        self._position = position
        self._state = state
        self._flow_components = []
        self.state_store = state_store

        self._url_args = url_args or []
        self._url_kwargs = url_kwargs or {}

        for flow_component_class in self._position.flow_component_classes:
            flow_component = flow_component_class()
            flow_component._flow_position_instance = self
            flow_component.set_url_args(*self._url_args, **self._url_kwargs)
            flow_component.state = state
            flow_component.task_id = self.task_id
            flow_component.app_namespace = self._app_namespace
            flow_component.flow_namespace = self._flow_namespace

            self._flow_components.append( flow_component )

        self._history = FlowHistory(self)

        self._validate()

    def _validate(self):
        pass
        # TODO: assert that only the last element is an Action and that the
        # rest are Scaffolds

    @property
    def task_id(self):
        return self._state['_id']

    def get_root_component(self):
        return self._flow_components[0]

    def get_action(self):
        return self._flow_components[-1]

    def get_back_url(self):
        return self._history.get_back_url()

    def get_absolute_url(self, include_flow_id=True):
        args = []
        kwargs = {}
        for flow_component in self._flow_components:
            flow_args, flow_kwargs = flow_component.get_url_args()
            args += flow_args
            kwargs.update(flow_kwargs)

        url_name = self._position.url_name
        url = reverse(url_name, args=args, kwargs=kwargs)

        if include_flow_id:

            separator = '&' if '?' in url else '?'

            return '%(root)s%(url)s%(separator)s%(task_id_param_name)s=%(task_id)s' % {
                                     'root': config.FLOWS_SITE_ROOT,
                                     'url': url, 'separator': separator,
                                     'task_id_param_name': config.FLOWS_TASK_ID_PARAM,
                                     'task_id': self.task_id  }
        else:
            return '%(root)s%(url)s' % { 'root': config.FLOWS_SITE_ROOT, 'url': url }

    def position_instance_for(self, component_class_or_name):
        # figure out where we're being sent to
        FC = get_by_class_or_name(component_class_or_name)

        # it should be a sibling of one of the current items
        # for example, if we are in position [A,B,E]:
        #
        #         A
        #      /  |  \
        #    B    C   D
        #   /  \      |  \
        #  E   F      G   H
        #
        #  E can send to F (its own sibling) or C (sibling of its parent)

        for fci in self._flow_components[-2::-1]: # go backwards but skip the last element (the action)
            if FC in fci.action_set:
                # we found the relevant action set, which means we know the root
                # part of the tree, and now we can construct the rest
                idx = self._flow_components.index(fci)
                break
        else:
            raise ValueError('Could not figure out how to redirect to %s' % FC)

        # so the new tree is from the root to the parent of the one we just found,
        # coupled with the initial subtree from the component we're tring to redirect
        # to
        tree_root = self._position.flow_component_classes[:idx+1]

        # figure out the action tree for the new first component - either
        # we have been given an action, in which case it's just one single
        # item, or we have been given a scaffold, in which case there could
        # be a list of [scaffold, scaffold..., action]
        new_subtree = FC.get_initial_action_tree()

        # we use our current tree and replace the current leaf with this new
        # subtree to get the new position
        new_position = PossibleFlowPosition(self._app_namespace, self._flow_namespace, tree_root + new_subtree)

        # now create an instance of the position with the current state
        return new_position.create_instance(self._state, self.state_store, self._url_args, self._url_kwargs)

    def handle(self, request, *args, **kwargs):
        # first validate that we can actually run by checking for
        # required state, for example
        response = None
        for flow_component in self._flow_components:
            response = flow_component.check_preconditions(request)
            if response is not None:
                break

        if response is None:
            # now call each of the prepare methods for the components
            for flow_component in self._flow_components:
                # TODO: passing in *args and **kwargs to prepare is deprecated
                response = flow_component.prepare(request, *args, **kwargs)
                if response is not None:
                    # we allow prepare methods to give out responses if they
                    # want to, eg, redirect
                    break

        if response is None:

            # FIXME: mjtamlyn promises to fix this in Django 1.7, but right now we need
            # to set up the magic attributes usually set up by a closure in View.as_view
            # so we can call dispatch on Django>1.5
            action = self.get_action()
            if hasattr(action, 'request'):
                raise Exception('Action re-use?')
            action.request = request
            action.args = args
            action.kwargs = kwargs

            # now that everything is set up, we can handle the request
            response = self.get_action().dispatch(request, *args, **kwargs)

            # if this is a GET request, then we displayed something to the user, so
            # we should record this in the history, unless the request returned a
            # redirect, in which case we haven't displayed anything
            if request.method == 'GET' and not isinstance(response, HttpResponseRedirect):
                self._history.add_to_history(self)

        # now we have a response, we need to decide what to do with it
        for flow_component in self._flow_components[::-1]: # go from leaf to root, ie, backwards
            response = flow_component.handle_response(response)

        # now we have some kind of response, figure out what it is exactly
        if response == COMPLETE:
            # this means that the entire flow finished - we should redirect
            # to the on_complete url if we have one, or get upset if we don't
            next_url = self._state.get('_on_complete', None)
            if next_url is None:
                # oh, we don't know where to go...
                raise ImproperlyConfigured('Flow completed without an _on_complete URL or an explicit redirect - %s' % self.__repr__())
            else:
                response = redirect(next_url)

            # if we are done, then we should remove the task state
            self.state_store.delete_state(self.task_id)

        else:
            # update the state if necessary
            self.state_store.put_state(self.task_id, self._state)

            if inspect.isclass(response):
                # we got given a class, which implies the code should redirect
                # to this new (presumably Action) class
                response = redirect(self.position_instance_for(response).get_absolute_url())

            elif isinstance(response, Action):
                # this is a new action for the user, so redirect to it
                absurl = response.get_absolute_url()
                response = redirect(absurl)

            elif isinstance(response, basestring):
                # this is a string which should be the name of an action
                # which couldn't be referenced as a class for some reason
                flow_component = get_by_class_or_name(response)
                response = redirect(flow_component.get_absolute_url())

        return response

    def __repr__(self):
        return 'Instance of %s' % self._position.__repr__()