Last active
May 20, 2025 07:33
-
-
Save cb109/6ee383606577fca1772cdbb98e374ce3 to your computer and use it in GitHub Desktop.
Visualize Django Template Include Hierarchy with Graphviz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Point script at Django template name and output DOT text describing the includes. | |
| Use like: | |
| cat visualize_django_template_include_hierarchy_with_graphviz.py | python manage.py shell | |
| Starting template name must be something relative like | |
| 'myapp/mysubfolder/template_1.html'. The script will follow the inputs | |
| and output a text like: | |
| digraph G { | |
| node [shape="rectangle"]; | |
| "myapp/mysubfolder/template_1.html" -> "myapp/mysubfolder/template_2.html"; | |
| "myapp/mysubfolder/template_2.html" -> "myapp/mysubfolder/template_3.html"; | |
| ... | |
| } | |
| This output can then be visualized with graphviz e.g. via | |
| https://dreampuf.github.io/GraphvizOnline. It's supposed to quickly | |
| visualize template hierarchies so we don't forget to update them as | |
| needed. | |
| Caveat: Template {% extends ... %} statements are not handled here, as | |
| are other things like CBV based template detection, dynamic template name | |
| in the include tag, custom template loaders etc. | |
| """ | |
| import os | |
| import re | |
| from django.template.utils import get_app_template_dirs | |
| TEMPLATES_FOLDER_BASE_NAME = "templates" | |
| template_dirs = list(get_app_template_dirs(TEMPLATES_FOLDER_BASE_NAME)) | |
| RE_PATTERN_INCLUDE = ( | |
| r"\{\%\s*include\s*(?:\"|\')(?P<template>[\w\/]+\.html)(?:\"|\')(?:.*)\%\}" | |
| ) | |
| # ---------------------------------------------------------------------- | |
| # Change this as needed! | |
| # ---------------------------------------------------------------------- | |
| STARTING_TEMPLATE_NAME = "myapp/mysubfolder/template_1.html" | |
| def main(): | |
| template_name_to_abs_filepath = {} | |
| for template_dir in template_dirs: | |
| for root, _, filenames in os.walk(template_dir): | |
| for filename in filenames: | |
| if not filename.endswith(".html"): | |
| continue | |
| abs_filepath = os.path.join(root, filename) | |
| _, template_name = abs_filepath.split( | |
| f"{TEMPLATES_FOLDER_BASE_NAME}/", 1 | |
| ) | |
| template_name_to_abs_filepath[template_name] = abs_filepath | |
| def filepath_for_template_name(template_name): | |
| return template_name_to_abs_filepath[template_name] | |
| class TemplateNode: | |
| def __init__(self, template_name, template_filepath, parent=None, level=0): | |
| self.template_name = template_name | |
| self.template_filepath = template_filepath | |
| self.parent = parent | |
| if self.parent: | |
| self.parent.children.append(self) | |
| self.children = [] | |
| self.level = level | |
| def build_tree(self): | |
| with open(self.template_filepath) as f: | |
| content = f.read() | |
| matches = re.findall(RE_PATTERN_INCLUDE, content) | |
| for template_name in matches: | |
| node = TemplateNode( | |
| template_name=template_name, | |
| template_filepath=filepath_for_template_name(template_name), | |
| parent=self, | |
| level=self.level + 1, | |
| ) | |
| node.build_tree() | |
| def collect_graphviz_lines(self, lines=None): | |
| basename = os.path.basename(self.template_name) | |
| dirname = os.path.dirname(self.template_name) | |
| node_line = ( | |
| f' "{self.template_name}"' | |
| f"[color=gray]" | |
| f'[label=<{basename}<BR/><FONT COLOR="gray47" ' | |
| f'POINT-SIZE="11">{dirname}</FONT>>];' | |
| ) | |
| lines.append(node_line) | |
| if self.parent: | |
| lines.append( | |
| f' "{self.parent.template_name}" -> "{self.template_name}";' | |
| ) | |
| for child in self.children: | |
| child.collect_graphviz_lines(lines=lines) | |
| def tree_to_graphviz(node): | |
| lines = [ | |
| "digraph G {", | |
| ' node [shape="rectangle"][fontname="Arial"];', | |
| ] | |
| node.collect_graphviz_lines(lines=lines) | |
| lines.append("}") | |
| text = "\n".join(lines) | |
| return text | |
| node = TemplateNode( | |
| template_name=STARTING_TEMPLATE_NAME, | |
| template_filepath=filepath_for_template_name(STARTING_TEMPLATE_NAME), | |
| ) | |
| node.build_tree() | |
| graphviz_text = tree_to_graphviz(node) | |
| print(graphviz_text) | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
