Пример #1
0
    def flow_entry_link(self, request, flow_class_or_name, on_complete_url=None,
                              with_state=False, flow_namespace=None, url_args=None, url_kwargs=None,
                              url_queryargs=None):

        flow_class = get_by_class_or_name(flow_class_or_name)
        
        position = PossibleFlowPosition(self.app_namespace, flow_namespace, flow_class.get_initial_action_tree())
        if with_state:
            kwargs = {} if on_complete_url is None else {'_on_complete': on_complete_url}
            state = self._new_state(request, **kwargs)
        else:
            state = {'_id': ''} # TODO: this is a bit of a hack, but task_id is required...
        instance = position.create_instance(state, self.state_store, url_args=url_args, url_kwargs=url_kwargs)
        
        inst_url = instance.get_absolute_url(include_flow_id=False)
        
        parts = urlparse.urlparse(inst_url)
        query = urlparse.parse_qs(parts.query)
        if on_complete_url is not None:
            query['_on_complete'] = on_complete_url
        if url_queryargs is not None:
            query.update(url_queryargs)
        parts = list(parts)
        parts[4] = urllib.urlencode(query)
        
        return urlparse.urlunparse(parts)        
Пример #2
0
    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)
Пример #3
0
    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)
Пример #4
0
    def flow_entry_link(self,
                        request,
                        flow_class_or_name,
                        on_complete_url=None,
                        with_state=False,
                        flow_namespace=None,
                        url_args=None,
                        url_kwargs=None,
                        url_queryargs=None):

        flow_class = get_by_class_or_name(flow_class_or_name)

        position = PossibleFlowPosition(self.app_namespace, flow_namespace,
                                        flow_class.get_initial_action_tree())
        if with_state:
            kwargs = {} if on_complete_url is None else {
                '_on_complete': on_complete_url
            }
            state = self._new_state(request, **kwargs)
        else:
            state = {
                '_id': ''
            }  # TODO: this is a bit of a hack, but task_id is required...
        instance = position.create_instance(state,
                                            self.state_store,
                                            url_args=url_args,
                                            url_kwargs=url_kwargs)

        inst_url = instance.get_absolute_url(include_flow_id=False)

        parts = urlparse.urlparse(inst_url)
        query = urlparse.parse_qs(parts.query)
        if on_complete_url is not None:
            query['_on_complete'] = on_complete_url

        if url_queryargs is not None:
            query.update(url_queryargs)

        parts = list(parts)
        parts[4] = urllib.urlencode(query, doseq=True)

        return urlparse.urlunparse(parts)
Пример #5
0
    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
Пример #6
0
    def flow_entry_link(self,
                        request,
                        flow_class_or_name,
                        on_complete_url=None,
                        with_state=False,
                        initial_state=None,
                        flow_namespace=None,
                        url_args=None,
                        url_kwargs=None,
                        url_queryargs=None):
        """
        This method is how you create a URL which allows a user to enter a flow.

        There are two main times you will need this, both with different consequences
        for the values you should pass as arguments.

        1) If you display a page to a user including a URL which starts a flow, but which
        the user may not necessarily begin, then you should use a stateless task. That
        is to say, don't include `initial_state`. If the flow needs to behave differently
        based on some context, then include it using `url_args` and `url_kwargs`, and have
        the flow Action or Scaffold handle that in their `url` configuration. As an example,
        you may be on a product page, and want a link for the user should they choose to
        purchase it. In this case, few users may actually click the link, so creating
        task state which will never be used is inefficient. In that case, the product ID
        could be passed into the flow via URL parameters - this is more like a normal Django
        URL

        2) If a user performs an action that enters them into a flow explicitly. If a
        user clicks something and you want the user to immediately enter a flow, you
        can pass in `initial_state`.

        Parameters
        ----------
        request : django.http.HttpRequest
           A request object supplied by Django

        flow_class_or_name : str | Action | Scaffold
          The flow entry point to create a URL for. This can be a string to do a lookup
          by name, or the Action or Scaffold class itself, which is preferred. Note that
          the flow action must have been registered as an entry point with this handler
          via `register_entry_point`

        on_complete_url : str
          The URL to redirect to once the flow is complete, used if the actions do not
          redirect themselves and instead rely on the automatic transitions.

        with_state : bool
          *Deprecated* By default, state is not created until the user begins the flow,
          to avoid creating unnecessary database objects, especially when the link is
          simply created to be displayed in a page. Sometimes, however, when constructing
          a link to redirect to as the result of a user action, it's useful to create the
          state for the flow. This has been deprecated in favour of "initial_state"

        initial_state : dict
          If you want to create a flow with some initial state, you can pass it in here. Note
          this is not suitable for, eg, URLs on an HTML page, since it would create a task
          state object for every pageview regardless of the user's intention. It is much
          better if the user has performed an action, such as submitted a form, which
          implies they want to immediately enter a flow. See also `url_args` and `url_kwargs`.

        flow_namespace : str

        url_args : list | tuple
        url_kwargs : dict
          The arguments to pass in to the URL for the flow, if your actions specify any
          parameters in their URL pattern. This is typically the way to supply "initial
          state" for flow entry URLs generated before a user action has taken place. See
          also `initial_state`.

        url_queryargs : dict
          Query parameters to append to the generated URL. You should probably be using
          `url_args` and `url_kwargs` along with URL patterns, or `initial_state`, instead.
        """

        if initial_state is None:
            initial_state = {}
        else:
            # override the with_state option in the case that
            # we have some initial state to explicitly set
            with_state = True

        flow_class = get_by_class_or_name(flow_class_or_name)

        position = PossibleFlowPosition(self.app_namespace, flow_namespace,
                                        flow_class.get_initial_action_tree())

        if with_state:
            if on_complete_url is not None:
                initial_state['_on_complete'] = on_complete_url
            state = self._new_state(request, **initial_state)
        else:
            state = {
                '_id': ''
            }  # TODO: this is a bit of a hack, but task_id is required...
        instance = position.create_instance(state,
                                            self.state_store,
                                            url_args=url_args,
                                            url_kwargs=url_kwargs)

        # if we have state, then we need to include the task ID in the URL
        # returned, otherwise it'll be seen as a "new entry" and new empty
        # task state will be created
        inst_url = instance.get_absolute_url(include_flow_id=with_state)

        parts = urlparse.urlparse(inst_url)
        query = urlparse.parse_qsl(parts.query)

        if on_complete_url is not None:
            query['_on_complete'] = on_complete_url
        if url_queryargs is not None:
            query.update(url_queryargs)

        parts = list(parts)
        parts[4] = urllib.urlencode(query, doseq=True)

        return urlparse.urlunparse(parts)
Пример #7
0
    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
Пример #8
0
    def flow_entry_link(self, request, flow_class_or_name, on_complete_url=None,
                        with_state=False, initial_state=None,
                        flow_namespace=None,
                        url_args=None, url_kwargs=None, url_queryargs=None):
        """
        This method is how you create a URL which allows a user to enter a flow.

        There are two main times you will need this, both with different consequences
        for the values you should pass as arguments.

        1) If you display a page to a user including a URL which starts a flow, but which
        the user may not necessarily begin, then you should use a stateless task. That
        is to say, don't include `initial_state`. If the flow needs to behave differently
        based on some context, then include it using `url_args` and `url_kwargs`, and have
        the flow Action or Scaffold handle that in their `url` configuration. As an example,
        you may be on a product page, and want a link for the user should they choose to
        purchase it. In this case, few users may actually click the link, so creating
        task state which will never be used is inefficient. In that case, the product ID
        could be passed into the flow via URL parameters - this is more like a normal Django
        URL

        2) If a user performs an action that enters them into a flow explicitly. If a
        user clicks something and you want the user to immediately enter a flow, you
        can pass in `initial_state`.

        Parameters
        ----------
        request : django.http.HttpRequest
           A request object supplied by Django

        flow_class_or_name : str | Action | Scaffold
          The flow entry point to create a URL for. This can be a string to do a lookup
          by name, or the Action or Scaffold class itself, which is preferred. Note that
          the flow action must have been registered as an entry point with this handler
          via `register_entry_point`

        on_complete_url : str
          The URL to redirect to once the flow is complete, used if the actions do not
          redirect themselves and instead rely on the automatic transitions.

        with_state : bool
          *Deprecated* By default, state is not created until the user begins the flow,
          to avoid creating unnecessary database objects, especially when the link is
          simply created to be displayed in a page. Sometimes, however, when constructing
          a link to redirect to as the result of a user action, it's useful to create the
          state for the flow. This has been deprecated in favour of "initial_state"

        initial_state : dict
          If you want to create a flow with some initial state, you can pass it in here. Note
          this is not suitable for, eg, URLs on an HTML page, since it would create a task
          state object for every pageview regardless of the user's intention. It is much
          better if the user has performed an action, such as submitted a form, which
          implies they want to immediately enter a flow. See also `url_args` and `url_kwargs`.

        flow_namespace : str

        url_args : list | tuple
        url_kwargs : dict
          The arguments to pass in to the URL for the flow, if your actions specify any
          parameters in their URL pattern. This is typically the way to supply "initial
          state" for flow entry URLs generated before a user action has taken place. See
          also `initial_state`.

        url_queryargs : dict
          Query parameters to append to the generated URL. You should probably be using
          `url_args` and `url_kwargs` along with URL patterns, or `initial_state`, instead.
        """

        if initial_state is None:
            initial_state = {}
        else:
            # override the with_state option in the case that
            # we have some initial state to explicitly set
            with_state = True

        flow_class = get_by_class_or_name(flow_class_or_name)

        position = PossibleFlowPosition(self.app_namespace, flow_namespace, flow_class.get_initial_action_tree())

        if with_state:
            if on_complete_url is not None:
                initial_state['_on_complete'] = on_complete_url
            state = self._new_state(request, **initial_state)
        else:
            state = {'_id': ''}  # TODO: this is a bit of a hack, but task_id is required...
        instance = position.create_instance(state, self.state_store, url_args=url_args, url_kwargs=url_kwargs)

        # if we have state, then we need to include the task ID in the URL
        # returned, otherwise it'll be seen as a "new entry" and new empty
        # task state will be created
        inst_url = instance.get_absolute_url(include_flow_id=with_state)

        parts = urlparse.urlparse(inst_url)
        query = urlparse.parse_qsl(parts.query)

        if on_complete_url is not None:
            query['_on_complete'] = on_complete_url
        if url_queryargs is not None:
            query.update(url_queryargs)

        parts = list(parts)
        parts[4] = urllib.urlencode(query, doseq=True)

        return urlparse.urlunparse(parts)