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 __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()
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__()
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__()