tap-as-a-service/taas_dashboard/doc/source/topics/workflows.rst

5.5 KiB

Workflows Topic Guide

One of the most challenging aspects of building a compelling user experience is crafting complex multi-part workflows. Horizon's workflows module aims to bring that capability within everyday reach.

For a detailed API information check out the Workflows Reference Guide </ref/workflows>.

Workflows

Workflows are complex forms with tabs, each workflow must consist of classes extending the ~horizon.workflows.Workflow, ~horizon.workflows.Step and ~horizon.workflows.Action

Complex example of workflow

The following is a complex example of how data are exchanged between urls, views, workflows and templates:

  1. In urls.py, we have the named parameter. E.g. resource_class_id. :

    RESOURCE_CLASS = r'^(?P<resource_class_id>[^/]+)/%s$'
    
    urlpatterns = patterns(
    '',
    url(RESOURCE_CLASS % 'update', UpdateView.as_view(), name='update'))
  2. In views.py, we pass data to the template and to the action(form) (action can also pass data to the get_context_data method and to the template). :

    class UpdateView(workflows.WorkflowView):
        workflow_class = UpdateResourceClass
    
        def get_context_data(self, **kwargs):
            context = super(UpdateView, self).get_context_data(**kwargs)
            # Data from URL are always in self.kwargs, here we pass the data
            # to the template.
            context["resource_class_id"] = self.kwargs['resource_class_id']
            # Data contributed by Workflow's Steps are in the
            # context['workflow'].context list. We can use that in the
            # template too.
            return context
    
        def _get_object(self, *args, **kwargs):
            # Data from URL are always in self.kwargs, we can use them here
            # to load our object of interest.
            resource_class_id = self.kwargs['resource_class_id']
            # Code omitted, this method should return some object obtained
            # from API.
    
        def get_initial(self):
            resource_class = self._get_object()
            # This data will be available in the Action's methods and
            # Workflow's handle method.
            # But only if the steps will depend on them.
            return {'resource_class_id': resource_class.id,
                    'name': resource_class.name,
                    'service_type': resource_class.service_type}
  3. In workflows.py we process the data, it is just more complex django form. :

    class ResourcesAction(workflows.Action):
        # The name field will be automatically available in all action's
        # methods.
        # If we want this field to be used in the another Step or Workflow,
        # it has to be contributed by this step, then depend on in another
        # step.
        name = forms.CharField(max_length=255,
                               label=_("Testing Name"),
                               help_text="",
                               required=True)
    
        def handle(self, request, data):
            pass
            # If we want to use some data from the URL, the Action's step
            # has to depend on them. It's then available in
            # self.initial['resource_class_id'] or data['resource_class_id'].
            # In other words, resource_class_id has to be passed by view's
            # get_initial and listed in step's depends_on list.
    
            # We can also use here the data from the other steps. If we want
            # the data from the other step, the step needs to contribute the
            # data and the steps needs to be ordered properly.
    
    class UpdateResources(workflows.Step):
        # This passes data from Workflow context to action methods
        # (handle, clean). Workflow context consists of URL data and data
        # contributed by other steps.
        depends_on = ("resource_class_id",)
    
        # By contributing, the data on these indexes will become available to
        # Workflow and to other Steps (if they will depend on them). Notice,
        # that the resources_object_ids key has to be manually added in
        # contribute method first.
        contributes = ("resources_object_ids", "name")
    
        def contribute(self, data, context):
            # We can obtain the http request from workflow.
            request = self.workflow.request
            if data:
                # Only fields defined in Action are automatically
                # available for contribution. If we want to contribute
                # something else, We need to override the contribute method
                # and manually add it to the dictionary.
                context["resources_object_ids"] =\
                    request.POST.getlist("resources_object_ids")
    
            # We have to merge new context with the passed data or let
            # the superclass do this.
            context.update(data)
            return context
    
    class UpdateResourceClass(workflows.Workflow):
        default_steps = (UpdateResources,)
    
        def handle(self, request, data):
            pass
            # This method is called as last (after all Action's handle
            # methods). All data that are listed in step's 'contributes='
            # and 'depends_on=' are available here.
            # It can be easier to have the saving logic only here if steps
            # are heavily connected or complex.
    
            # data["resources_object_ids"], data["name"] and
            # data["resources_class_id"] are available here.