Django friendly finite state machine support for models through FSMField and the @transition decorator.
—
Django management command for creating GraphViz visualizations of state machine transitions, helping developers understand and document complex state workflows.
Django management command that generates GraphViz dot files visualizing FSM transitions. The command can create visual representations of state machines as directed graphs showing states as nodes and transitions as edges.
# Management command usage
python manage.py graph_transitions [appname[.model[.field]]] --output file.png --layout dotCommand Arguments:
appname[.model[.field]]: Optional specification to limit visualization scope
appname: Visualize all FSM fields in the appappname.model: Visualize all FSM fields in specific modelappname.model.field: Visualize specific FSM field onlyCommand Options:
--output, -o: Output file path. File extension determines format (png, jpg, svg, pdf, dot)--layout, -l: GraphViz layout engine (dot, neato, circo, fdp, sfdp, twopi, osage, patchwork)Generate visualization for entire project:
# Create dot file output to stdout
python manage.py graph_transitions
# Generate PNG image of all state machines
python manage.py graph_transitions --output transitions.png
# Generate SVG with different layout
python manage.py graph_transitions --output workflow.svg --layout neatoGenerate visualization for specific app:
# All FSM fields in 'orders' app
python manage.py graph_transitions orders --output orders.png
# Specific model in app
python manage.py graph_transitions orders.Order --output order_states.png
# Specific field in model
python manage.py graph_transitions orders.Order.status --output order_status.pngThe command uses several internal functions that can be imported for programmatic use:
def generate_dot(fields_data):
"""
Generate GraphViz representation of FSM transitions.
Parameters:
- fields_data: List of (field, model) tuples
Returns:
graphviz.Digraph: GraphViz graph object
"""
def all_fsm_fields_data(model):
"""
Extract FSM fields from model.
Parameters:
- model: Django model class
Returns:
List of (field, model) tuples for FSM fields
"""
def node_name(field, state):
"""
Generate unique node name for GraphViz.
Parameters:
- field: FSM field instance
- state: State value
Returns:
str: Unique node identifier
"""
def node_label(field, state):
"""
Generate human-readable node label.
Parameters:
- field: FSM field instance
- state: State value
Returns:
str: Display label for node
"""States are represented as nodes with different visual styles:
Transitions are represented as directed edges between state nodes:
Special transition types are visualized appropriately:
Different GraphViz layout engines provide various visualization styles:
Use the graph generation functions programmatically:
from django_fsm.management.commands.graph_transitions import generate_dot, all_fsm_fields_data
from myapp.models import Order, Document
# Generate graph for specific models
fields_data = []
for model in [Order, Document]:
fields_data.extend(all_fsm_fields_data(model))
graph = generate_dot(fields_data)
# Save as different formats
graph.format = 'png'
graph.render('my_state_machines')
# Or get dot source
dot_source = str(graph)
print(dot_source)Generate graphs as part of documentation builds:
# In your documentation build script
import os
from django.core.management import call_command
def generate_fsm_docs():
"""Generate FSM visualizations for documentation."""
# Generate overview of all state machines
call_command('graph_transitions',
output='docs/images/all_fsm.png',
layout='dot')
# Generate specific model visualizations
models_to_document = [
'orders.Order.status',
'documents.Document.state',
'workflows.Task.workflow_state'
]
for model_spec in models_to_document:
output_name = model_spec.replace('.', '_').lower()
call_command('graph_transitions', model_spec,
output=f'docs/images/{output_name}.png',
layout='dot')Customize graph appearance by modifying the generate_dot function:
import graphviz
from django_fsm.management.commands.graph_transitions import generate_dot as base_generate_dot
def styled_generate_dot(fields_data, style_config=None):
"""Generate styled FSM graph."""
graph = base_generate_dot(fields_data)
if style_config:
# Apply custom styling
graph.graph_attr.update(style_config.get('graph', {}))
graph.node_attr.update(style_config.get('node', {}))
graph.edge_attr.update(style_config.get('edge', {}))
return graph
# Usage with custom styling
style = {
'graph': {'bgcolor': 'white', 'dpi': '300'},
'node': {'style': 'filled', 'fillcolor': 'lightblue'},
'edge': {'color': 'darkblue', 'fontsize': '10'}
}
fields_data = all_fsm_fields_data(Order)
styled_graph = styled_generate_dot(fields_data, style)
styled_graph.render('styled_order_fsm')Visualize models with multiple FSM fields:
class Order(models.Model):
payment_status = FSMField(default='unpaid')
fulfillment_status = FSMField(default='pending')
# Payment transitions
@transition(field=payment_status, source='unpaid', target='paid')
def process_payment(self):
pass
# Fulfillment transitions
@transition(field=fulfillment_status, source='pending', target='shipped')
def ship_order(self):
pass
# The graph_transitions command will show both state machines
# as separate subgraphs within the same visualizationGenerate documentation with embedded graphs:
def generate_fsm_documentation(model_class):
"""Generate markdown documentation with embedded graphs."""
from django_fsm.management.commands.graph_transitions import all_fsm_fields_data, generate_dot
fields_data = all_fsm_fields_data(model_class)
documentation = f"""
# {model_class.__name__} State Machine
## State Diagram
}_fsm.png)
## States and Transitions
"""
# Generate the graph
graph = generate_dot(fields_data)
graph.format = 'png'
graph.render(f'{model_class.__name__.lower()}_fsm')
# Add transition documentation
for field, model in fields_data:
documentation += f"\n### {field.name.title()} Field\n\n"
transitions = field.get_all_transitions(model)
for transition in transitions:
documentation += f"- **{transition.name}**: {transition.source} → {transition.target}\n"
return documentationCreate interactive HTML visualizations:
def generate_interactive_fsm(model_class, output_file):
"""Generate interactive HTML FSM visualization."""
from django_fsm.management.commands.graph_transitions import all_fsm_fields_data, generate_dot
fields_data = all_fsm_fields_data(model_class)
graph = generate_dot(fields_data)
# Generate SVG for interactivity
graph.format = 'svg'
svg_content = graph.pipe(format='svg').decode('utf-8')
# Wrap in HTML with interactivity
html_template = f"""
<!DOCTYPE html>
<html>
<head>
<title>{model_class.__name__} State Machine</title>
<style>
.transition {{ cursor: pointer; }}
.transition:hover {{ stroke-width: 3; }}
.state {{ cursor: pointer; }}
.state:hover {{ fill: yellow; }}
</style>
</head>
<body>
<h1>{model_class.__name__} State Machine</h1>
{svg_content}
<script>
// Add click handlers for interactivity
document.querySelectorAll('.transition').forEach(edge => {{
edge.addEventListener('click', function() {{
alert('Transition: ' + this.textContent);
}});
}});
</script>
</body>
</html>
"""
with open(output_file, 'w') as f:
f.write(html_template)Set up automated graph regeneration:
# In your Django settings or management command
from django_fsm.signals import post_transition
from django.dispatch import receiver
@receiver(post_transition)
def update_fsm_documentation(sender, **kwargs):
"""Regenerate FSM graphs when models change."""
if settings.DEBUG: # Only in development
from django.core.management import call_command
try:
call_command('graph_transitions',
f'{sender._meta.app_label}.{sender.__name__}',
output=f'docs/graphs/{sender.__name__.lower()}_fsm.png')
except Exception as e:
logger.warning(f"Failed to update FSM graph for {sender}: {e}")GraphViz not installed:
# Install GraphViz system dependency
# Ubuntu/Debian:
sudo apt-get install graphviz
# macOS:
brew install graphviz
# Windows: Download from graphviz.orgMissing Python graphviz package:
pip install graphvizLarge graphs are unreadable:
--layout sfdp for large graphsComplex transitions are cluttered:
--layout dot)Install with Tessl CLI
npx tessl i tessl/pypi-django-fsm