diff --git a/joeflow/admin.py b/joeflow/admin.py
index 07f430d..7f7dbc5 100644
--- a/joeflow/admin.py
+++ b/joeflow/admin.py
@@ -147,12 +147,25 @@ def get_inlines(self, *args, **kwargs):
def get_readonly_fields(self, *args, **kwargs):
return [
- "get_instance_graph_svg",
+ "display_workflow_diagram",
*super().get_readonly_fields(*args, **kwargs),
"modified",
"created",
]
+ @admin.display(description="Workflow Diagram")
+ def display_workflow_diagram(self, obj):
+ """Display workflow diagram using MermaidJS for client-side rendering."""
+ if obj.pk:
+ # Get Mermaid diagram syntax
+ mermaid_syntax = obj.get_instance_graph_mermaid()
+ # Wrap in div with mermaid class for client-side rendering
+ return format_html(
+ '
',
+ mermaid_syntax
+ )
+ return ""
+
@transaction.atomic()
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
diff --git a/joeflow/models.py b/joeflow/models.py
index cfe51e1..f332f19 100644
--- a/joeflow/models.py
+++ b/joeflow/models.py
@@ -242,6 +242,39 @@ def get_graph_svg(cls):
get_graph_svg.short_description = t("graph")
+ @classmethod
+ def get_graph_mermaid(cls, color="black"):
+ """
+ Return workflow graph as Mermaid diagram syntax.
+
+ This can be used with MermaidJS for client-side rendering in browsers.
+
+ Returns:
+ (str): Mermaid diagram syntax.
+ """
+ lines = [f"graph {cls.rankdir}"]
+
+ # Add nodes
+ for name, node in cls.get_nodes():
+ node_id = name.replace(" ", "_")
+ label = name
+
+ # Determine shape based on node type
+ if node.type == HUMAN:
+ # Rounded rectangle for human tasks
+ lines.append(f" {node_id}({label})")
+ else:
+ # Rectangle for machine tasks
+ lines.append(f" {node_id}[{label}]")
+
+ # Add edges
+ for start, end in cls.edges:
+ start_id = start.name.replace(" ", "_")
+ end_id = end.name.replace(" ", "_")
+ lines.append(f" {start_id} --> {end_id}")
+
+ return "\n".join(lines)
+
def get_instance_graph(self):
"""Return workflow instance graph."""
graph = self.get_graph(color="#888888")
@@ -332,6 +365,41 @@ def get_instance_graph_svg(self, output_format="svg"):
get_instance_graph_svg.short_description = t("instance graph")
+ def get_instance_graph_mermaid(self):
+ """
+ Return instance graph as Mermaid diagram syntax.
+
+ This can be used with MermaidJS for client-side rendering in admin.
+
+ Returns:
+ (str): Mermaid diagram syntax for the instance graph.
+ """
+ lines = [f"graph {self.rankdir}"]
+
+ names = dict(self.get_nodes()).keys()
+
+ # Add all nodes from workflow definition
+ for name, node in self.get_nodes():
+ node_id = name.replace(" ", "_")
+ label = name
+
+ # Determine shape based on node type
+ if node.type == HUMAN:
+ lines.append(f" {node_id}({label})")
+ else:
+ lines.append(f" {node_id}[{label}]")
+
+ # Add edges from workflow definition
+ for start, end in self.edges:
+ start_id = start.name.replace(" ", "_")
+ end_id = end.name.replace(" ", "_")
+ lines.append(f" {start_id} --> {end_id}")
+
+ # TODO: Add styling for completed/active tasks
+ # This would require additional Mermaid syntax for node styling
+
+ return "\n".join(lines)
+
def cancel(self, user=None):
self.task_set.cancel(user)
diff --git a/joeflow/templates/admin/change_form.html b/joeflow/templates/admin/change_form.html
new file mode 100644
index 0000000..ba3a4b9
--- /dev/null
+++ b/joeflow/templates/admin/change_form.html
@@ -0,0 +1,10 @@
+{% extends "admin/change_form.html" %}
+
+{% block extrahead %}
+{{ block.super }}
+
+
+{% endblock %}
diff --git a/pyproject.toml b/pyproject.toml
index be18ada..a5fbe9e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,7 +50,6 @@ requires-python = ">=3.9"
dependencies = [
"django>=2.2",
"django-appconf",
- "graphviz>=0.18",
]
[project.optional-dependencies]
@@ -74,6 +73,10 @@ docs = [
"dramatiq",
"django_dramatiq",
"redis",
+ "graphviz>=0.18",
+]
+graphviz = [
+ "graphviz>=0.18",
]
reversion = [
"django-reversion",