CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-fsm

Django friendly finite state machine support for models through FSMField and the @transition decorator.

Pending
Overview
Eval results
Files

visualization.mddocs/

Visualization and Management

Django management command for creating GraphViz visualizations of state machine transitions, helping developers understand and document complex state workflows.

Capabilities

graph_transitions Management Command

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 dot

Command Arguments:

  • appname[.model[.field]]: Optional specification to limit visualization scope
    • appname: Visualize all FSM fields in the app
    • appname.model: Visualize all FSM fields in specific model
    • appname.model.field: Visualize specific FSM field only

Command 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)

Basic Usage Examples

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 neato

Generate 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.png

Graph Generation Functions

The 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
    """

Visualization Features

State Representation

States are represented as nodes with different visual styles:

  • Regular states: Circular nodes
  • Final states: Double-circle nodes (states with no outgoing transitions)
  • Initial states: Connected to a small point node indicating entry

Transition Representation

Transitions are represented as directed edges between state nodes:

  • Edge labels: Show transition method names
  • Dotted edges: Represent error transitions (on_error paths)
  • Multiple sources: Transitions from multiple states are properly handled

Wildcard Transitions

Special transition types are visualized appropriately:

  • Source="*": Transitions from any state show connections from all existing states
  • Source="+": Transitions from any state except target show appropriate connections

Layout Options

Different GraphViz layout engines provide various visualization styles:

  • dot: Hierarchical layout (default) - good for workflow-like state machines
  • neato: Spring model layout - good for undirected-like graphs
  • circo: Circular layout - good for cyclic state machines
  • fdp: Force-directed layout - good for large graphs
  • sfdp: Scalable force-directed layout - for very large graphs
  • twopi: Radial layout - good for tree-like structures
  • osage: Cluster layout - good for clustered graphs
  • patchwork: Squarified treemap layout

Programmatic Usage

Generate Graphs in Code

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)

Integration with Documentation

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')

Custom Graph Styling

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')

Advanced Visualization Patterns

Multi-Field Visualization

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 visualization

State Machine Documentation

Generate 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

![State Diagram](./{model_class.__name__.lower()}_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 documentation

Interactive Visualization

Create 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)

Automated Graph Updates

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}")

Troubleshooting Visualizations

Common Issues

GraphViz not installed:

# Install GraphViz system dependency
# Ubuntu/Debian:
sudo apt-get install graphviz

# macOS:
brew install graphviz

# Windows: Download from graphviz.org

Missing Python graphviz package:

pip install graphviz

Large graphs are unreadable:

  • Use --layout sfdp for large graphs
  • Generate specific model/field visualizations instead of entire project
  • Increase DPI in programmatic usage

Complex transitions are cluttered:

  • Use hierarchical layout (--layout dot)
  • Split complex models into multiple visualizations
  • Consider simplifying transition logic if visualization is too complex

Install with Tessl CLI

npx tessl i tessl/pypi-django-fsm

docs

dynamic-states.md

exceptions.md

field-types.md

index.md

model-mixins.md

signals.md

transitions.md

visualization.md

tile.json