Last active
December 21, 2021 17:06
-
-
Save cb109/6ee383606577fca1772cdbb98e374ce3 to your computer and use it in GitHub Desktop.
Visualize Django Template Include Hierarchy with Graphviz
This file contains 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. | |
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. | |
Template {% extends ... %} statements are not handled here, as are other | |
things like CBV based template detection, custom template loaders etc. | |
""" | |
import os | |
import re | |
# from django.conf import settings | |
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)) | |
# for template_config in settings.TEMPLATES: | |
# dirs = template_config.get("DIRS") | |
# if not dirs: | |
# continue | |
# template_dirs.extend(dirs) | |
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