-
-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Django nodes cache #40
base: main
Are you sure you want to change the base?
Changes from all commits
9df5730
3d149f9
390dc34
6593041
0a116ea
def43e8
0aa0d05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from copy import copy | ||
import hashlib | ||
|
||
from django.template import Context, RequestContext | ||
from django.template.base import TextNode | ||
|
@@ -12,6 +13,8 @@ | |
|
||
from render_block.exceptions import BlockNotFound | ||
|
||
_NODES_CACHE = {} | ||
|
||
|
||
def django_render_block(template, block_name, context, request=None): | ||
# Create a Django Context if needed | ||
|
@@ -30,24 +33,47 @@ def django_render_block(template, block_name, context, request=None): | |
|
||
# Get the underlying django.template.base.Template object. | ||
template = template.template | ||
cache_key = _make_node_cache_key(template, block_name) | ||
|
||
# Bind the template to the context. | ||
with context_instance.bind_template(template): | ||
# Before trying to render the template, we need to traverse the tree of | ||
# parent templates and find all blocks in them. | ||
parent_template = _build_block_context(template, context_instance) | ||
|
||
try: | ||
return _render_template_block(template, block_name, context_instance) | ||
except BlockNotFound: | ||
# The block wasn't found in the current template. | ||
node, render_context = _NODES_CACHE[cache_key] | ||
except KeyError: | ||
# Before trying to render the template, we need to traverse the tree of | ||
# parent templates and find all blocks in them. | ||
parent_template = _build_block_context(template, context_instance) | ||
|
||
# If there's no parent template (i.e. no ExtendsNode), re-raise. | ||
if not parent_template: | ||
raise | ||
try: | ||
node, render_context = _find_template_block( | ||
template, block_name, context_instance | ||
) | ||
except BlockNotFound: | ||
# The block wasn't found in the current template. | ||
|
||
# If there's no parent template (i.e. no ExtendsNode), re-raise. | ||
if not parent_template: | ||
raise | ||
|
||
# Check the parent template for this block. | ||
return _render_template_block(parent_template, block_name, context_instance) | ||
# Check the parent template for this block. | ||
node, render_context = _find_template_block( | ||
parent_template, block_name, context_instance | ||
) | ||
|
||
if cache_key and not template.engine.debug: | ||
_NODES_CACHE[cache_key] = node, render_context | ||
|
||
context_instance.render_context = render_context | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a comment added in #25 that implies reusing render contexts might be unsafe. That does have a test though so hopefully it is OK. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is ok. The issue #25 fixed |
||
return node.render(context_instance) | ||
|
||
|
||
def _make_node_cache_key(template, block_name): | ||
if template.name: | ||
key = template.name | ||
else: | ||
source = template.source.encode() | ||
key = hashlib.md5(source).hexdigest() | ||
return f"{key}@{block_name}" | ||
|
||
|
||
def _build_block_context(template, context): | ||
|
@@ -82,12 +108,12 @@ def _build_block_context(template, context): | |
break | ||
|
||
|
||
def _render_template_block(template, block_name, context): | ||
"""Renders a single block from a template.""" | ||
return _render_template_block_nodelist(template.nodelist, block_name, context) | ||
def _find_template_block(template, block_name, context): | ||
"""Finds a single block from a template.""" | ||
return _find_template_block_nodelist(template.nodelist, block_name, context) | ||
|
||
|
||
def _render_template_block_nodelist(nodelist, block_name, context): | ||
def _find_template_block_nodelist(nodelist, block_name, context): | ||
"""Recursively iterate over a node to find the wanted block.""" | ||
|
||
# Attempt to find the wanted block in the current template. | ||
|
@@ -99,7 +125,7 @@ def _render_template_block_nodelist(nodelist, block_name, context): | |
|
||
# If the name matches, you're all set and we found the block! | ||
if node.name == block_name: | ||
return node.render(context) | ||
return node, context.render_context | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm having some trouble wrapping my brain around some of this change, it seems like (This is also true of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
# If a node has children, recurse into them. Based on | ||
# django.template.base.Node.get_nodes_by_type. | ||
|
@@ -111,9 +137,7 @@ def _render_template_block_nodelist(nodelist, block_name, context): | |
|
||
# Try to find the block recursively. | ||
try: | ||
return _render_template_block_nodelist( | ||
new_nodelist, block_name, context | ||
) | ||
return _find_template_block_nodelist(new_nodelist, block_name, context) | ||
except BlockNotFound: | ||
continue | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm slightly worried that a global cache might not be safe to use here, but it likely is OK in almost every case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Django has a cache per instance of template cached loader. https://github.com/django/django/blob/21d8ea4eb3e22d458515b5278c8cd3a15b069799/django/template/loaders/cached.py#L16
Django template loaders have to handle multiple loader instances and do things like template overriding, that are impossible with a global cache.
This lib is a lot simpler than Django template loaders, so the global cache would work fine.