summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomas Sedovic <tomas@sedovic.cz>2013-08-09 14:43:41 +0200
committerTomas Sedovic <tomas@sedovic.cz>2013-08-09 14:43:41 +0200
commit1b8705bedfd167e7636907ce2ce31d730ac9c119 (patch)
treeee67a2a1b20bc6fdbbd1b30d8ac8dcd406e594fe
parent1839264f2a0b7afa69209d1a6a4d3041fa6cde96 (diff)
downloadtuskar-ui-1b8705bedfd167e7636907ce2ce31d730ac9c119.tar.gz
Fix the Resource Class creation error
Had to clean up some of the horizon.workflows code we were carrying around. Signed-off-by: Tomas Sedovic <tomas@sedovic.cz>
-rw-r--r--tuskar_ui/workflows.py830
1 files changed, 15 insertions, 815 deletions
diff --git a/tuskar_ui/workflows.py b/tuskar_ui/workflows.py
index 60d8a1e9..45993887 100644
--- a/tuskar_ui/workflows.py
+++ b/tuskar_ui/workflows.py
@@ -36,412 +36,14 @@ from horizon import base
from horizon import exceptions
from horizon.templatetags.horizon import has_permissions
from horizon.utils import html
+import horizon.workflows
LOG = logging.getLogger(__name__)
-class WorkflowContext(dict):
- def __init__(self, workflow, *args, **kwargs):
- super(WorkflowContext, self).__init__(*args, **kwargs)
- self._workflow = workflow
-
- def __setitem__(self, key, val):
- super(WorkflowContext, self).__setitem__(key, val)
- return self._workflow._trigger_handlers(key)
-
- def __delitem__(self, key):
- return self.__setitem__(key, None)
-
- def set(self, key, val):
- return self.__setitem__(key, val)
-
- def unset(self, key):
- return self.__delitem__(key)
-
-
-class ActionMetaclass(forms.forms.DeclarativeFieldsMetaclass):
- def __new__(mcs, name, bases, attrs):
- # Pop Meta for later processing
- opts = attrs.pop("Meta", None)
- # Create our new class
- cls = super(ActionMetaclass, mcs).__new__(mcs, name, bases, attrs)
- # Process options from Meta
- cls.name = getattr(opts, "name", name)
- cls.slug = getattr(opts, "slug", slugify(name))
- cls.permissions = getattr(opts, "permissions", ())
- cls.progress_message = getattr(opts,
- "progress_message",
- _("Processing..."))
- cls.help_text = getattr(opts, "help_text", "")
- cls.help_text_template = getattr(opts, "help_text_template", None)
- return cls
-
-
-class Action(forms.Form):
- """
- An ``Action`` represents an atomic logical interaction you can have with
- the system. This is easier to understand with a conceptual example: in the
- context of a "launch instance" workflow, actions would include "naming
- the instance", "selecting an image", and ultimately "launching the
- instance".
-
- Because ``Actions`` are always interactive, they always provide form
- controls, and thus inherit from Django's ``Form`` class. However, they
- have some additional intelligence added to them:
-
- * ``Actions`` are aware of the permissions required to complete them.
-
- * ``Actions`` have a meta-level concept of "help text" which is meant to be
- displayed in such a way as to give context to the action regardless of
- where the action is presented in a site or workflow.
-
- * ``Actions`` understand how to handle their inputs and produce outputs,
- much like :class:`~horizon.forms.SelfHandlingForm` does now.
-
- ``Action`` classes may define the following attributes in a ``Meta``
- class within them:
-
- .. attribute:: name
-
- The verbose name for this action. Defaults to the name of the class.
-
- .. attribute:: slug
-
- A semi-unique slug for this action. Defaults to the "slugified" name
- of the class.
-
- .. attribute:: permissions
-
- A list of permission names which this action requires in order to be
- completed. Defaults to an empty list (``[]``).
-
- .. attribute:: help_text
-
- A string of simple help text to be displayed alongside the Action's
- fields.
-
- .. attribute:: help_text_template
-
- A path to a template which contains more complex help text to be
- displayed alongside the Action's fields. In conjunction with
- :meth:`~horizon.workflows.Action.get_help_text` method you can
- customize your help text template to display practically anything.
- """
-
- __metaclass__ = ActionMetaclass
-
- def __init__(self, request, context, *args, **kwargs):
- if request.method == "POST":
- super(Action, self).__init__(request.POST, initial=context)
- else:
- super(Action, self).__init__(initial=context)
-
- if not hasattr(self, "handle"):
- raise AttributeError("The action %s must define a handle method."
- % self.__class__.__name__)
- self.request = request
- self._populate_choices(request, context)
-
- def __unicode__(self):
- return force_unicode(self.name)
-
- def __repr__(self):
- return "<%s: %s>" % (self.__class__.__name__, self.slug)
-
- def _populate_choices(self, request, context):
- for field_name, bound_field in self.fields.items():
- meth = getattr(self, "populate_%s_choices" % field_name, None)
- if meth is not None and callable(meth):
- bound_field.choices = meth(request, context)
-
- def get_help_text(self, extra_context=None):
- """ Returns the help text for this step. """
- text = ""
- extra_context = extra_context or {}
- if self.help_text_template:
- tmpl = template.loader.get_template(self.help_text_template)
- context = template.RequestContext(self.request, extra_context)
- text += tmpl.render(context)
- else:
- text += linebreaks(force_unicode(self.help_text))
- return safe(text)
-
- def add_error(self, message):
- """
- Adds an error to the Action's Step based on API issues.
- """
- self._get_errors()[NON_FIELD_ERRORS] = self.error_class([message])
-
- def handle(self, request, context):
- """
- Handles any requisite processing for this action. The method should
- return either ``None`` or a dictionary of data to be passed to
- :meth:`~horizon.workflows.Step.contribute`.
-
- Returns ``None`` by default, effectively making it a no-op.
- """
- return None
-
-
-class Step(object):
- """
- A step is a wrapper around an action which defines it's context in a
- workflow. It knows about details such as:
-
- * The workflow's context data (data passed from step to step).
-
- * The data which must be present in the context to begin this step (the
- step's dependencies).
-
- * The keys which will be added to the context data upon completion of the
- step.
-
- * The connections between this step's fields and changes in the context
- data (e.g. if that piece of data changes, what needs to be updated in
- this step).
-
- A ``Step`` class has the following attributes:
-
- .. attribute:: action
-
- The :class:`~horizon.workflows.Action` class which this step wraps.
-
- .. attribute:: depends_on
-
- A list of context data keys which this step requires in order to
- begin interaction.
-
- .. attribute:: contributes
-
- A list of keys which this step will contribute to the workflow's
- context data. Optional keys should still be listed, even if their
- values may be set to ``None``.
-
- .. attribute:: connections
-
- A dictionary which maps context data key names to lists of callbacks.
- The callbacks may be functions, dotted python paths to functions
- which may be imported, or dotted strings beginning with ``"self"``
- to indicate methods on the current ``Step`` instance.
-
- .. attribute:: before
-
- Another ``Step`` class. This optional attribute is used to provide
- control over workflow ordering when steps are dynamically added to
- workflows. The workflow mechanism will attempt to place the current
- step before the step specified in the attribute.
-
- .. attribute:: after
-
- Another ``Step`` class. This attribute has the same purpose as
- :meth:`~horizon.workflows.Step.before` except that it will instead
- attempt to place the current step after the given step.
-
- .. attribute:: help_text
-
- A string of simple help text which will be prepended to the ``Action``
- class' help text if desired.
-
- .. attribute:: template_name
-
- A path to a template which will be used to render this step. In
- general the default common template should be used. Default:
- ``"horizon/common/_workflow_step.html"``.
-
- .. attribute:: has_errors
-
- A boolean value which indicates whether or not this step has any
- errors on the action within it or in the scope of the workflow. This
- attribute will only accurately reflect this status after validation
- has occurred.
-
- .. attribute:: slug
-
- Inherited from the ``Action`` class.
-
- .. attribute:: name
-
- Inherited from the ``Action`` class.
-
- .. attribute:: permissions
-
- Inherited from the ``Action`` class.
- """
- action_class = None
- depends_on = ()
- contributes = ()
- connections = None
- before = None
- after = None
- help_text = ""
- template_name = "horizon/common/_workflow_step.html"
-
- def __repr__(self):
- return "<%s: %s>" % (self.__class__.__name__, self.slug)
-
- def __unicode__(self):
- return force_unicode(self.name)
-
- def __init__(self, workflow):
- super(Step, self).__init__()
- self.workflow = workflow
-
- cls = self.__class__.__name__
- if not (self.action_class and issubclass(self.action_class, Action)):
- raise AttributeError("You must specify an action for %s." % cls)
-
- self.slug = self.action_class.slug
- self.name = self.action_class.name
- self.permissions = self.action_class.permissions
- self.has_errors = False
- self._handlers = {}
-
- if self.connections is None:
- # We want a dict, but don't want to declare a mutable type on the
- # class directly.
- self.connections = {}
-
- # Gather our connection handlers and make sure they exist.
- for key, handlers in self.connections.items():
- self._handlers[key] = []
- # TODO(gabriel): This is a poor substitute for broader handling
- if not isinstance(handlers, (list, tuple)):
- raise TypeError("The connection handlers for %s must be a "
- "list or tuple." % cls)
- for possible_handler in handlers:
- if callable(possible_handler):
- # If it's callable we know the function exists and is valid
- self._handlers[key].append(possible_handler)
- continue
- elif not isinstance(possible_handler, basestring):
- return TypeError("Connection handlers must be either "
- "callables or strings.")
- bits = possible_handler.split(".")
- if bits[0] == "self":
- root = self
- for bit in bits[1:]:
- try:
- root = getattr(root, bit)
- except AttributeError:
- raise AttributeError("The connection handler %s "
- "could not be found on %s."
- % (possible_handler, cls))
- handler = root
- elif len(bits) == 1:
- # Import by name from local module not supported
- raise ValueError("Importing a local function as a string "
- "is not supported for the connection "
- "handler %s on %s."
- % (possible_handler, cls))
- else:
- # Try a general import
- module_name = ".".join(bits[:-1])
- try:
- mod = import_module(module_name)
- handler = getattr(mod, bits[-1])
- except ImportError:
- raise ImportError("Could not import %s from the "
- "module %s as a connection "
- "handler on %s."
- % (bits[-1], module_name, cls))
- except AttributeError:
- raise AttributeError("Could not import %s from the "
- "module %s as a connection "
- "handler on %s."
- % (bits[-1], module_name, cls))
- self._handlers[key].append(handler)
-
- @property
- def action(self):
- if not getattr(self, "_action", None):
- try:
- # Hook in the action context customization.
- workflow_context = dict(self.workflow.context)
- context = self.prepare_action_context(self.workflow.request,
- workflow_context)
- self._action = self.action_class(self.workflow.request,
- context)
- except:
- LOG.exception("Problem instantiating action class.")
- raise
- return self._action
-
- def prepare_action_context(self, request, context):
- """
- Allows for customization of how the workflow context is passed to the
- action; this is the reverse of what "contribute" does to make the
- action outputs sane for the workflow. Changes to the context are not
- saved globally here. They are localized to the action.
-
- Simply returns the unaltered context by default.
- """
- return context
-
- def get_id(self):
- """ Returns the ID for this step. Suitable for use in HTML markup. """
- return "%s__%s" % (self.workflow.slug, self.slug)
-
- def _verify_contributions(self, context):
- for key in self.contributes:
- # Make sure we don't skip steps based on weird behavior of
- # POST query dicts.
- field = self.action.fields.get(key, None)
- if field and field.required and not context.get(key):
- context.pop(key, None)
- failed_to_contribute = set(self.contributes)
- failed_to_contribute -= set(context.keys())
- if failed_to_contribute:
- raise exceptions.WorkflowError("The following expected data was "
- "not added to the workflow context "
- "by the step %s: %s."
- % (self.__class__,
- failed_to_contribute))
- return True
-
- def contribute(self, data, context):
- """
- Adds the data listed in ``contributes`` to the workflow's shared
- context. By default, the context is simply updated with all the data
- returned by the action.
-
- Note that even if the value of one of the ``contributes`` keys is
- not present (e.g. optional) the key should still be added to the
- context with a value of ``None``.
- """
- if data:
- for key in self.contributes:
- context[key] = data.get(key, None)
- return context
-
- def render(self):
- """ Renders the step. """
- step_template = template.loader.get_template(self.template_name)
- extra_context = {"form": self.action,
- "step": self}
-
- # FIXME: TableStep:
- if issubclass(self.__class__, TableStep):
- extra_context.update(self.get_context_data(self.workflow.request))
-
- context = template.RequestContext(self.workflow.request, extra_context)
- return step_template.render(context)
-
- def get_help_text(self):
- """ Returns the help text for this step. """
- text = linebreaks(force_unicode(self.help_text))
- text += self.action.get_help_text()
- return safe(text)
-
- def add_error(self, message):
- """
- Adds an error to the Step based on API issues.
- """
- self.action.add_error(message)
-
-
# FIXME: TableStep
-class TableStep(Step):
+class TableStep(horizon.workflows.Step):
"""
A :class:`~horizon.workflows.Step` class which knows how to deal with
:class:`~horizon.tables.DataTable` classes rendered inside of it.
@@ -475,6 +77,19 @@ class TableStep(Step):
self._tables = SortedDict(table_instances)
self._table_data_loaded = False
+ def render(self):
+ """ Renders the step. """
+ step_template = template.loader.get_template(self.template_name)
+ extra_context = {"form": self.action,
+ "step": self}
+
+ # FIXME: TableStep:
+ if issubclass(self.__class__, TableStep):
+ extra_context.update(self.get_context_data(self.workflow.request))
+
+ context = template.RequestContext(self.workflow.request, extra_context)
+ return step_template.render(context)
+
def load_table_data(self):
"""
Calls the ``get_{{ table_name }}_data`` methods for each table class
@@ -517,418 +132,3 @@ class TableStep(Step):
def has_more_data(self, table):
return False
-
-
-class WorkflowMetaclass(type):
- def __new__(mcs, name, bases, attrs):
- super(WorkflowMetaclass, mcs).__new__(mcs, name, bases, attrs)
- attrs["_cls_registry"] = set([])
- return type.__new__(mcs, name, bases, attrs)
-
-
-class UpdateMembersStep(Step):
- """A step that allows a user to add/remove members from a group.
-
- .. attribute:: show_roles
-
- Set to False to disable the display of the roles dropdown.
-
- .. attribute:: available_list_title
-
- The title used for the available list column.
-
- .. attribute:: members_list_title
-
- The title used for the members list column.
-
- .. attribute:: no_available_text
-
- The placeholder text used when the available list is empty.
-
- .. attribute:: no_members_text
-
- The placeholder text used when the members list is empty.
-
- """
- template_name = "horizon/common/_workflow_step_update_members.html"
- show_roles = True
- available_list_title = _("All available")
- members_list_title = _("Members")
- no_available_text = _("None available.")
- no_members_text = _("No members.")
-
-
-class Workflow(html.HTMLElement):
- """
- A Workflow is a collection of Steps. It's interface is very
- straightforward, but it is responsible for handling some very
- important tasks such as:
-
- * Handling the injection, removal, and ordering of arbitrary steps.
-
- * Determining if the workflow can be completed by a given user at runtime
- based on all available information.
-
- * Dispatching connections between steps to ensure that when context data
- changes all the applicable callback functions are executed.
-
- * Verifying/validating the overall data integrity and subsequently
- triggering the final method to complete the workflow.
-
- The ``Workflow`` class has the following attributes:
-
- .. attribute:: name
-
- The verbose name for this workflow which will be displayed to the user.
- Defaults to the class name.
-
- .. attribute:: slug
-
- The unique slug for this workflow. Required.
-
- .. attribute:: steps
-
- Read-only access to the final ordered set of step instances for
- this workflow.
-
- .. attribute:: default_steps
-
- A list of :class:`~horizon.workflows.Step` classes which serve as the
- starting point for this workflow's ordered steps. Defaults to an empty
- list (``[]``).
-
- .. attribute:: finalize_button_name
-
- The name which will appear on the submit button for the workflow's
- form. Defaults to ``"Save"``.
-
- .. attribute:: success_message
-
- A string which will be displayed to the user upon successful completion
- of the workflow. Defaults to
- ``"{{ workflow.name }} completed successfully."``
-
- .. attribute:: failure_message
-
- A string which will be displayed to the user upon failure to complete
- the workflow. Defaults to ``"{{ workflow.name }} did not complete."``
-
- .. attribute:: depends_on
-
- A roll-up list of all the ``depends_on`` values compiled from the
- workflow's steps.
-
- .. attribute:: contributions
-
- A roll-up list of all the ``contributes`` values compiled from the
- workflow's steps.
-
- .. attribute:: template_name
-
- Path to the template which should be used to render this workflow.
- In general the default common template should be used. Default:
- ``"horizon/common/_workflow.html"``.
-
- .. attribute:: entry_point
-
- The slug of the step which should initially be active when the
- workflow is rendered. This can be passed in upon initialization of
- the workflow, or set anytime after initialization but before calling
- either ``get_entry_point`` or ``render``.
-
- .. attribute:: redirect_param_name
-
- The name of a parameter used for tracking the URL to redirect to upon
- completion of the workflow. Defaults to ``"next"``.
-
- .. attribute:: object
-
- The object (if any) which this workflow relates to. In the case of
- a workflow which creates a new resource the object would be the created
- resource after the relevant creation steps have been undertaken. In
- the case of a workflow which updates a resource it would be the
- resource being updated after it has been retrieved.
-
- """
- __metaclass__ = WorkflowMetaclass
- slug = None
- default_steps = ()
- template_name = "horizon/common/_workflow.html"
- finalize_button_name = _("Save")
- success_message = _("%s completed successfully.")
- failure_message = _("%s did not complete.")
- redirect_param_name = "next"
- multipart = False
- _registerable_class = Step
-
- def __unicode__(self):
- return self.name
-
- def __repr__(self):
- return "<%s: %s>" % (self.__class__.__name__, self.slug)
-
- def __init__(self, request=None, context_seed=None, entry_point=None,
- *args, **kwargs):
- super(Workflow, self).__init__(*args, **kwargs)
- if self.slug is None:
- raise AttributeError("The workflow %s must have a slug."
- % self.__class__.__name__)
- self.name = getattr(self, "name", self.__class__.__name__)
- self.request = request
- self.depends_on = set([])
- self.contributions = set([])
- self.entry_point = entry_point
- self.object = None
-
- # Put together our steps in order. Note that we pre-register
- # non-default steps so that we can identify them and subsequently
- # insert them in order correctly.
- self._registry = dict([(step_class, step_class(self)) for step_class
- in self.__class__._cls_registry
- if step_class not in self.default_steps])
- self._gather_steps()
-
- # Determine all the context data we need to end up with.
- for step in self.steps:
- self.depends_on = self.depends_on | set(step.depends_on)
- self.contributions = self.contributions | set(step.contributes)
-
- # Initialize our context. For ease we can preseed it with a
- # regular dictionary. This should happen after steps have been
- # registered and ordered.
- self.context = WorkflowContext(self)
- context_seed = context_seed or {}
- clean_seed = dict([(key, val)
- for key, val in context_seed.items()
- if key in self.contributions | self.depends_on])
- self.context_seed = clean_seed
- self.context.update(clean_seed)
-
- if request and request.method == "POST":
- for step in self.steps:
- valid = step.action.is_valid()
- # Be sure to use the CLEANED data if the workflow is valid.
- if valid:
- data = step.action.cleaned_data
- else:
- data = request.POST
- self.context = step.contribute(data, self.context)
-
- @property
- def steps(self):
- if getattr(self, "_ordered_steps", None) is None:
- self._gather_steps()
- return self._ordered_steps
-
- def get_step(self, slug):
- """ Returns the instantiated step matching the given slug. """
- for step in self.steps:
- if step.slug == slug:
- return step
-
- def _gather_steps(self):
- ordered_step_classes = self._order_steps()
- for default_step in self.default_steps:
- self.register(default_step)
- self._registry[default_step] = default_step(self)
- self._ordered_steps = [self._registry[step_class]
- for step_class in ordered_step_classes
- if has_permissions(self.request.user,
- self._registry[step_class])]
-
- def _order_steps(self):
- steps = list(copy.copy(self.default_steps))
- additional = self._registry.keys()
- for step in additional:
- try:
- min_pos = steps.index(step.after)
- except ValueError:
- min_pos = 0
- try:
- max_pos = steps.index(step.before)
- except ValueError:
- max_pos = len(steps)
- if min_pos > max_pos:
- raise exceptions.WorkflowError("The step %(new)s can't be "
- "placed between the steps "
- "%(after)s and %(before)s; the "
- "step %(before)s comes before "
- "%(after)s."
- % {"new": additional,
- "after": step.after,
- "before": step.before})
- steps.insert(max_pos, step)
- return steps
-
- def get_entry_point(self):
- """
- Returns the slug of the step which the workflow should begin on.
-
- This method takes into account both already-available data and errors
- within the steps.
- """
- # If we have a valid specified entry point, use it.
- if self.entry_point:
- if self.get_step(self.entry_point):
- return self.entry_point
- # Otherwise fall back to calculating the appropriate entry point.
- for step in self.steps:
- if step.has_errors:
- return step.slug
- try:
- step._verify_contributions(self.context)
- except exceptions.WorkflowError:
- return step.slug
- # If nothing else, just return the first step.
- return self.steps[0].slug
-
- def _trigger_handlers(self, key):
- responses = []
- handlers = [(step.slug, f) for step in self.steps
- for f in step._handlers.get(key, [])]
- for slug, handler in handlers:
- responses.append((slug, handler(self.request, self.context)))
- return responses
-
- @classmethod
- def register(cls, step_class):
- """ Registers a :class:`~horizon.workflows.Step` with the workflow. """
- if not inspect.isclass(step_class):
- raise ValueError('Only classes may be registered.')
- elif not issubclass(step_class, cls._registerable_class):
- raise ValueError('Only %s classes or subclasses may be registered.'
- % cls._registerable_class.__name__)
- if step_class in cls._cls_registry:
- return False
- else:
- cls._cls_registry.add(step_class)
- return True
-
- @classmethod
- def unregister(cls, step_class):
- """
- Unregisters a :class:`~horizon.workflows.Step` from the workflow.
- """
- try:
- cls._cls_registry.remove(step_class)
- except KeyError:
- raise base.NotRegistered('%s is not registered' % cls)
- return cls._unregister(step_class)
-
- def validate(self, context):
- """
- Hook for custom context data validation. Should return a boolean
- value or raise :class:`~horizon.exceptions.WorkflowValidationError`.
- """
- return True
-
- def is_valid(self):
- """
- Verified that all required data is present in the context and
- calls the ``validate`` method to allow for finer-grained checks
- on the context data.
- """
- missing = self.depends_on - set(self.context.keys())
- if missing:
- raise exceptions.WorkflowValidationError(
- "Unable to complete the workflow. The values %s are "
- "required but not present." % ", ".join(missing))
-
- # Validate each step. Cycle through all of them to catch all errors
- # in one pass before returning.
- steps_valid = True
- for step in self.steps:
- if not step.action.is_valid():
- steps_valid = False
- step.has_errors = True
- if not steps_valid:
- return steps_valid
- return self.validate(self.context)
-
- def finalize(self):
- """
- Finalizes a workflow by running through all the actions in order
- and calling their ``handle`` methods. Returns ``True`` on full success,
- or ``False`` for a partial success, e.g. there were non-critical
- errors. (If it failed completely the function wouldn't return.)
- """
- partial = False
- for step in self.steps:
- try:
- data = step.action.handle(self.request, self.context)
- if data is True or data is None:
- continue
- elif data is False:
- partial = True
- else:
- self.context = step.contribute(data or {}, self.context)
- except:
- partial = True
- exceptions.handle(self.request)
- if not self.handle(self.request, self.context):
- partial = True
- return not partial
-
- def handle(self, request, context):
- """
- Handles any final processing for this workflow. Should return a boolean
- value indicating success.
- """
- return True
-
- def get_success_url(self):
- """
- Returns a URL to redirect the user to upon completion. By default it
- will attempt to parse a ``success_url`` attribute on the workflow,
- which can take the form of a reversible URL pattern name, or a
- standard HTTP URL.
- """
- try:
- return urlresolvers.reverse(self.success_url)
- except urlresolvers.NoReverseMatch:
- return self.success_url
-
- def format_status_message(self, message):
- """
- Hook to allow customization of the message returned to the user
- upon successful or unsuccessful completion of the workflow.
-
- By default it simply inserts the workflow's name into the message
- string.
- """
- if "%s" in message:
- return message % self.name
- else:
- return message
-
- def render(self):
- """ Renders the workflow. """
- workflow_template = template.loader.get_template(self.template_name)
- extra_context = {"workflow": self}
- if self.request.is_ajax():
- extra_context['modal'] = True
- context = template.RequestContext(self.request, extra_context)
- return workflow_template.render(context)
-
- def get_absolute_url(self):
- """ Returns the canonical URL for this workflow.
-
- This is used for the POST action attribute on the form element
- wrapping the workflow.
-
- For convenience it defaults to the value of
- ``request.get_full_path()`` with any query string stripped off,
- e.g. the path at which the workflow was requested.
- """
- return self.request.get_full_path().partition('?')[0]
-
- def add_error_to_step(self, message, slug):
- """
- Adds an error to the workflow's Step with the
- specifed slug based on API issues. This is useful
- when you wish for API errors to appear as errors on
- the form rather than using the messages framework.
- """
- step = self.get_step(slug)
- if step:
- step.add_error(message)