0
# Visualization and Management
1
2
Django management command for creating GraphViz visualizations of state machine transitions, helping developers understand and document complex state workflows.
3
4
## Capabilities
5
6
### graph_transitions Management Command
7
8
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.
9
10
```python { .api }
11
# Management command usage
12
python manage.py graph_transitions [appname[.model[.field]]] --output file.png --layout dot
13
```
14
15
**Command Arguments:**
16
- `appname[.model[.field]]`: Optional specification to limit visualization scope
17
- `appname`: Visualize all FSM fields in the app
18
- `appname.model`: Visualize all FSM fields in specific model
19
- `appname.model.field`: Visualize specific FSM field only
20
21
**Command Options:**
22
- `--output, -o`: Output file path. File extension determines format (png, jpg, svg, pdf, dot)
23
- `--layout, -l`: GraphViz layout engine (dot, neato, circo, fdp, sfdp, twopi, osage, patchwork)
24
25
### Basic Usage Examples
26
27
Generate visualization for entire project:
28
29
```bash
30
# Create dot file output to stdout
31
python manage.py graph_transitions
32
33
# Generate PNG image of all state machines
34
python manage.py graph_transitions --output transitions.png
35
36
# Generate SVG with different layout
37
python manage.py graph_transitions --output workflow.svg --layout neato
38
```
39
40
Generate visualization for specific app:
41
42
```bash
43
# All FSM fields in 'orders' app
44
python manage.py graph_transitions orders --output orders.png
45
46
# Specific model in app
47
python manage.py graph_transitions orders.Order --output order_states.png
48
49
# Specific field in model
50
python manage.py graph_transitions orders.Order.status --output order_status.png
51
```
52
53
### Graph Generation Functions
54
55
The command uses several internal functions that can be imported for programmatic use:
56
57
```python { .api }
58
def generate_dot(fields_data):
59
"""
60
Generate GraphViz representation of FSM transitions.
61
62
Parameters:
63
- fields_data: List of (field, model) tuples
64
65
Returns:
66
graphviz.Digraph: GraphViz graph object
67
"""
68
69
def all_fsm_fields_data(model):
70
"""
71
Extract FSM fields from model.
72
73
Parameters:
74
- model: Django model class
75
76
Returns:
77
List of (field, model) tuples for FSM fields
78
"""
79
80
def node_name(field, state):
81
"""
82
Generate unique node name for GraphViz.
83
84
Parameters:
85
- field: FSM field instance
86
- state: State value
87
88
Returns:
89
str: Unique node identifier
90
"""
91
92
def node_label(field, state):
93
"""
94
Generate human-readable node label.
95
96
Parameters:
97
- field: FSM field instance
98
- state: State value
99
100
Returns:
101
str: Display label for node
102
"""
103
```
104
105
## Visualization Features
106
107
### State Representation
108
109
States are represented as nodes with different visual styles:
110
111
- **Regular states**: Circular nodes
112
- **Final states**: Double-circle nodes (states with no outgoing transitions)
113
- **Initial states**: Connected to a small point node indicating entry
114
115
### Transition Representation
116
117
Transitions are represented as directed edges between state nodes:
118
119
- **Edge labels**: Show transition method names
120
- **Dotted edges**: Represent error transitions (on_error paths)
121
- **Multiple sources**: Transitions from multiple states are properly handled
122
123
### Wildcard Transitions
124
125
Special transition types are visualized appropriately:
126
127
- **Source="*"**: Transitions from any state show connections from all existing states
128
- **Source="+"**: Transitions from any state except target show appropriate connections
129
130
### Layout Options
131
132
Different GraphViz layout engines provide various visualization styles:
133
134
- **dot**: Hierarchical layout (default) - good for workflow-like state machines
135
- **neato**: Spring model layout - good for undirected-like graphs
136
- **circo**: Circular layout - good for cyclic state machines
137
- **fdp**: Force-directed layout - good for large graphs
138
- **sfdp**: Scalable force-directed layout - for very large graphs
139
- **twopi**: Radial layout - good for tree-like structures
140
- **osage**: Cluster layout - good for clustered graphs
141
- **patchwork**: Squarified treemap layout
142
143
## Programmatic Usage
144
145
### Generate Graphs in Code
146
147
Use the graph generation functions programmatically:
148
149
```python
150
from django_fsm.management.commands.graph_transitions import generate_dot, all_fsm_fields_data
151
from myapp.models import Order, Document
152
153
# Generate graph for specific models
154
fields_data = []
155
for model in [Order, Document]:
156
fields_data.extend(all_fsm_fields_data(model))
157
158
graph = generate_dot(fields_data)
159
160
# Save as different formats
161
graph.format = 'png'
162
graph.render('my_state_machines')
163
164
# Or get dot source
165
dot_source = str(graph)
166
print(dot_source)
167
```
168
169
### Integration with Documentation
170
171
Generate graphs as part of documentation builds:
172
173
```python
174
# In your documentation build script
175
import os
176
from django.core.management import call_command
177
178
def generate_fsm_docs():
179
"""Generate FSM visualizations for documentation."""
180
181
# Generate overview of all state machines
182
call_command('graph_transitions',
183
output='docs/images/all_fsm.png',
184
layout='dot')
185
186
# Generate specific model visualizations
187
models_to_document = [
188
'orders.Order.status',
189
'documents.Document.state',
190
'workflows.Task.workflow_state'
191
]
192
193
for model_spec in models_to_document:
194
output_name = model_spec.replace('.', '_').lower()
195
call_command('graph_transitions', model_spec,
196
output=f'docs/images/{output_name}.png',
197
layout='dot')
198
```
199
200
### Custom Graph Styling
201
202
Customize graph appearance by modifying the generate_dot function:
203
204
```python
205
import graphviz
206
from django_fsm.management.commands.graph_transitions import generate_dot as base_generate_dot
207
208
def styled_generate_dot(fields_data, style_config=None):
209
"""Generate styled FSM graph."""
210
graph = base_generate_dot(fields_data)
211
212
if style_config:
213
# Apply custom styling
214
graph.graph_attr.update(style_config.get('graph', {}))
215
graph.node_attr.update(style_config.get('node', {}))
216
graph.edge_attr.update(style_config.get('edge', {}))
217
218
return graph
219
220
# Usage with custom styling
221
style = {
222
'graph': {'bgcolor': 'white', 'dpi': '300'},
223
'node': {'style': 'filled', 'fillcolor': 'lightblue'},
224
'edge': {'color': 'darkblue', 'fontsize': '10'}
225
}
226
227
fields_data = all_fsm_fields_data(Order)
228
styled_graph = styled_generate_dot(fields_data, style)
229
styled_graph.render('styled_order_fsm')
230
```
231
232
## Advanced Visualization Patterns
233
234
### Multi-Field Visualization
235
236
Visualize models with multiple FSM fields:
237
238
```python
239
class Order(models.Model):
240
payment_status = FSMField(default='unpaid')
241
fulfillment_status = FSMField(default='pending')
242
243
# Payment transitions
244
@transition(field=payment_status, source='unpaid', target='paid')
245
def process_payment(self):
246
pass
247
248
# Fulfillment transitions
249
@transition(field=fulfillment_status, source='pending', target='shipped')
250
def ship_order(self):
251
pass
252
253
# The graph_transitions command will show both state machines
254
# as separate subgraphs within the same visualization
255
```
256
257
### State Machine Documentation
258
259
Generate documentation with embedded graphs:
260
261
```python
262
def generate_fsm_documentation(model_class):
263
"""Generate markdown documentation with embedded graphs."""
264
from django_fsm.management.commands.graph_transitions import all_fsm_fields_data, generate_dot
265
266
fields_data = all_fsm_fields_data(model_class)
267
268
documentation = f"""
269
# {model_class.__name__} State Machine
270
271
## State Diagram
272
273
}_fsm.png)
274
275
## States and Transitions
276
277
"""
278
279
# Generate the graph
280
graph = generate_dot(fields_data)
281
graph.format = 'png'
282
graph.render(f'{model_class.__name__.lower()}_fsm')
283
284
# Add transition documentation
285
for field, model in fields_data:
286
documentation += f"\n### {field.name.title()} Field\n\n"
287
288
transitions = field.get_all_transitions(model)
289
for transition in transitions:
290
documentation += f"- **{transition.name}**: {transition.source} → {transition.target}\n"
291
292
return documentation
293
```
294
295
### Interactive Visualization
296
297
Create interactive HTML visualizations:
298
299
```python
300
def generate_interactive_fsm(model_class, output_file):
301
"""Generate interactive HTML FSM visualization."""
302
from django_fsm.management.commands.graph_transitions import all_fsm_fields_data, generate_dot
303
304
fields_data = all_fsm_fields_data(model_class)
305
graph = generate_dot(fields_data)
306
307
# Generate SVG for interactivity
308
graph.format = 'svg'
309
svg_content = graph.pipe(format='svg').decode('utf-8')
310
311
# Wrap in HTML with interactivity
312
html_template = f"""
313
<!DOCTYPE html>
314
<html>
315
<head>
316
<title>{model_class.__name__} State Machine</title>
317
<style>
318
.transition {{ cursor: pointer; }}
319
.transition:hover {{ stroke-width: 3; }}
320
.state {{ cursor: pointer; }}
321
.state:hover {{ fill: yellow; }}
322
</style>
323
</head>
324
<body>
325
<h1>{model_class.__name__} State Machine</h1>
326
{svg_content}
327
<script>
328
// Add click handlers for interactivity
329
document.querySelectorAll('.transition').forEach(edge => {{
330
edge.addEventListener('click', function() {{
331
alert('Transition: ' + this.textContent);
332
}});
333
}});
334
</script>
335
</body>
336
</html>
337
"""
338
339
with open(output_file, 'w') as f:
340
f.write(html_template)
341
```
342
343
### Automated Graph Updates
344
345
Set up automated graph regeneration:
346
347
```python
348
# In your Django settings or management command
349
from django_fsm.signals import post_transition
350
from django.dispatch import receiver
351
352
@receiver(post_transition)
353
def update_fsm_documentation(sender, **kwargs):
354
"""Regenerate FSM graphs when models change."""
355
if settings.DEBUG: # Only in development
356
from django.core.management import call_command
357
358
try:
359
call_command('graph_transitions',
360
f'{sender._meta.app_label}.{sender.__name__}',
361
output=f'docs/graphs/{sender.__name__.lower()}_fsm.png')
362
except Exception as e:
363
logger.warning(f"Failed to update FSM graph for {sender}: {e}")
364
```
365
366
## Troubleshooting Visualizations
367
368
### Common Issues
369
370
**GraphViz not installed:**
371
```bash
372
# Install GraphViz system dependency
373
# Ubuntu/Debian:
374
sudo apt-get install graphviz
375
376
# macOS:
377
brew install graphviz
378
379
# Windows: Download from graphviz.org
380
```
381
382
**Missing Python graphviz package:**
383
```bash
384
pip install graphviz
385
```
386
387
**Large graphs are unreadable:**
388
- Use `--layout sfdp` for large graphs
389
- Generate specific model/field visualizations instead of entire project
390
- Increase DPI in programmatic usage
391
392
**Complex transitions are cluttered:**
393
- Use hierarchical layout (`--layout dot`)
394
- Split complex models into multiple visualizations
395
- Consider simplifying transition logic if visualization is too complex