Library internals

This page documents the internal mechanisms of django_xworkflows.

Binding to a workflow

The mechanism to bind a django model to a xworkflows.Workflow relies on the WorkflowEnabled and StateField classes.

class django_xworkflows.models.StateField(django.db.models.Field)

This class is a simple Django Field, specifically tuned for a Workflow.

It is internally backed by a CharField containing the name of the state.

Reading the value always returns a xworkflows.base.StateWrapper, writing checks that the value is a valid state or a valid state name.

workflow

Mandatory; holds the Workflow to which this StateField relates

choices

The workflow states, as a list of (name, title) tuples, for use in forms.

default

The name of the inital state of the workflow

max_length

The length of the longest state name in the workflow.

blank

Such a field cannot be blanked (otherwise, the workflow wouldn’t have a meaning).

null

Since the field cannot be empty, is cannot be null either.

south_field_triple(self)

Returns the south description of this field. When unfreezing, a fake Workflow will be retrieved with the same states and initial_state as present at freezing time.

This allows reading states that no longer exist in the workflow.

class django_xworkflows.models.WorkflowEnabled(models.Model)

This class inherits from Django’s Model class, performing some transformations on the subclass: each attr = StateField(SomeWorkflow, ...) attribute will enable XWorkflows’ transition detection and wrapping.

Most of this job is performed through WorkflowEnabledMeta.

_get_FIELD_display(self, field)

This method overrides the default django one to retrieve the title from a StateField field.

Transitions

Transitions mostly follow XWorkflows’ mechanism.

Implementation wrappers

django_xworkflows provides two custom implementation wrappers specially suited for Django:

class django_xworkflows.models.DjangoImplementationWrapper(xworkflows.base.ImplementationWrapper)

This wrapper simply adds two special attributes for interpretation in Django templates:

alters_data

Set to True to prevent Django templating system to call a transition, e.g in {{ foo.confirm }}

do_not_call_in_templates

This attribute signals Django templating system (starting from Django 1.4) that the transition implementation should not be called, but its attributes should be made available.

This allows such constructs:

{% if obj.confirm.is_available %}
<form method="POST" action="">
    <input type="submit" value="Confirm" />
</form>
{% endif %}
class django_xworkflows.models.TransactionalImplementationWrapper(DjangoImplementationWrapper)

This specific wrapper runs all transition-related code, including hooks, in a single database transaction.

The TransactionalImplementationWrapper can be enabled by setting it to the implementation_class attribute of a xworkflows.Workflow or of a Workflow:

class MyWorkflow(models.Workflow):
    implementation_class = models.TransactionalImplementationWrapper

Workflow and logging

class django_xworkflows.models.Workflow(xworkflows.Workflow)

This xworkflows.Workflow subclass performs a few customization:

  • Logging transition logs in database
  • Saving updated objects after the transition
log_model

This holds the name of the model to use to log to the database. If empty, no database logging is performed.

log_model_class

This holds the class of the model to use to log to the database.

Takes precedence over log_model. If this attribute is empty but log_model has been provided, it will be filled at first access.

db_log(self, transition, from_state, instance, *args, **kwargs)

Logs the transition into the database, saving the following elements:

  • Name of the transition
  • Name of the initial state
  • GenericForeignKey to the modified instance
  • ForeignKey to the user responsible for the transition
  • timestamp of the operation

The default TransitionLog model is django_xworkflows.xworkflow_log.models.TransitionLog, but an alternative one can be specified in log_model or log_model_class.

Hint

Override this method to log to a custom TransitionLog with complex fields and storage.

log_transition(self, transition, from_state, instance, save=True, log=True, *args, **kwargs)

In addition to xworkflows.Workflow.log_transition(), additional actions are performed:

  • If save is True, the instance is saved.
  • If log is True, the db_log() method is called to register the transition in the database.

Transition database logging

Transition logs can be stored in the database. This is performed by the db_log() method of the Workflow class.

The default method will save informations about the transition into an adapted model. The actual model to log will be:

Such models are expected to have a few fields, a good basis for writing your own is to inherit from either BaseTransitionLog or GenericTransitionLog (which provides a default storage through a GenericForeignKey).

The BaseTransitionLog class provides all required fields for logging a transition.

class django_xworkflows.models.BaseTransitionLog(models.Model)

This class provides minimal functions for logging a transition to the database.

transition

This attribute holds the name of the performed transition, as a string.

from_state

Name of the source state, as a string.

to_state

Name of the target state, as a string.

timestamp

Timestamp of the operation, as a DateTimeField.

MODIFIED_OBJECT_FIELD

Name of the field where the modified instance should be passed. Logging the transition will likely fail if this is not provided.

EXTRA_LOG_ATTRIBUTES

It may be useful to log extra transition kwarg (user, ...) to the database. This attribute describes how to log those extra keyword arguments.

It takes the form of a list of 3-tuples (db_field, kwarg, default). When logging to the database, the db_field attribute of the BaseTransitionLog instance will be filled with the keyword argument passed to the transition at kwarg, if any. Otherwise, default will be used.

get_modified_object(self)

Abstract the lookup of the modified object through MODIFIED_OBJECT_FIELD.

log_transition(cls, transition, from_state, to_state, modified_object, **kwargs)

Save a new transition log from the given transition name, origin state name, target state name, modified object and extra fields.

class django_xworkflows.models.GenericTransitionLog(BaseTransitionLog)

An extended version of BaseTransitionLog uses a GenericForeignKey to store the modified object.

content_type

A foreign key to the ContentType of the modified object

content_id

The primary key of the modified object

modified_object

The GenericForeignKey pointing to the modified object.

class django_xworkflows.models.BaseLastTransitionLog(BaseTransitionLog)

This alternate BaseTransitionLog has been tuned to store only the last transition log for an object, typically with a OneToOneField.

It handles update or creation on its own.

class django_xworkflows.models.GenericLastTransitionLog(BaseLastTransitionLog)

This class is to BaseLastTransitionLog what GenericTransitionLog is to BaseTransitionLog. It holds the modified object through a GenericForeignKey, with the adequate unique_together setting.

Here is an example of a custom TransitionLog model:

# Note that we inherit from BaseTransitionLog, not GenericTransitionLog.
class MyDocumentTransitionLog(django_xworkflows.models.BaseTransitionLog):

    # This is where we'll store the modified object
    document = models.ForeignKey(Document)

    # Extra data to keep about transitions
    user = models.ForeignKey(auth_models.User, blank=True, null=True)
    client = models.ForeignKey(api_models.Client, blank=True, null=True)
    source_ip = models.CharField(max_length=24, blank=True)

    # Set the name of the field where the modified object goes
    MODIFIED_OBJECT_FIELD = 'document'

    # Define extra logging attributes
    EXTRA_LOG_ATTRIBUTES = (
        ('user', 'user', None),
        ('client', 'api_client', None),  # Transitions are called with 'api_client' kwarg
        ('source_ip', 'ip', ''),  # Transitions are called with 'ip' kwarg
    )

An example TransitionLog model is available in the django_xworkflows.xworkflow_log application. Including it to settings.INSTALLED_APPS will enable database logging of transitions for all WorkflowEnabled subclasses.

class django_xworkflows.xworkflow_log.models.TransitionLog(GenericTransitionLog)

This specific GenericTransitionLog also stores the user responsible for the transition, if provided.

The exact Model to use for that foreign key can be set in the XWORKFLOWS_USER_MODEL django setting (defaults to 'auth.User', which uses django.contrib.auth.models.User).

Internals

Note

These classes are private API.

class django_xworkflows.models.WorkflowEnabledMeta(xworkflows.base.WorkflowEnabledMeta)

This metaclass is responsible for parsing a class definition, detecting all StateField and collecting/defining the associated TransactionalImplementationWrapper.

_find_workflows(mcs, attrs)

Collect all StateField from the given attrs (the default version collects Workflow subclasses instead)

_add_workflow(mcs, field_name, state_field, attrs)

Perform necessay actions to register the Workflow stored in a StateField defined at field_name into the given attributes dict.

It differs from the base implementation which adds a StateProperty instead of keeping the StateField.

Parameters:
  • field_name (str) – The name of the attribute at which the StateField was defined
  • state_field (StateField) – The StateField wrapping the Workflow
  • attrs (dict) – The attributes dictionary to update.