From 9df5730db1e7c9303f62b80860d7a148dbd5ef4c Mon Sep 17 00:00:00 2001 From: Walison Filipe Date: Fri, 22 Jan 2021 15:12:58 -0300 Subject: [PATCH 1/7] Add local nodes cache --- render_block/django.py | 52 ++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/render_block/django.py b/render_block/django.py index 977ac7d..2c9f5bb 100644 --- a/render_block/django.py +++ b/render_block/django.py @@ -12,6 +12,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 +32,36 @@ 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) + + 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. + node, render_context = _find_template_block(parent_template, block_name, context_instance) + _NODES_CACHE[cache_key] = node, render_context + + context_instance.render_context = render_context + return node.render(context_instance) - # 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) +def _make_node_cache_key(template, block_name): + return f'{template.name}@{block_name}' def _build_block_context(template, context): @@ -82,12 +96,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 +113,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 # If a node has children, recurse into them. Based on # django.template.base.Node.get_nodes_by_type. @@ -111,9 +125,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 From 3d149f92dd9d356922d0878ab72afdd009e3a696 Mon Sep 17 00:00:00 2001 From: Walison Filipe Date: Fri, 26 Feb 2021 19:50:04 -0300 Subject: [PATCH 2/7] Cache nodes only in production --- render_block/django.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/render_block/django.py b/render_block/django.py index 2c9f5bb..04097f4 100644 --- a/render_block/django.py +++ b/render_block/django.py @@ -1,5 +1,6 @@ from copy import copy +from django.conf import settings from django.template import Context, RequestContext from django.template.base import TextNode from django.template.context import RenderContext @@ -54,7 +55,9 @@ def django_render_block(template, block_name, context, request=None): # Check the parent template for this block. node, render_context = _find_template_block(parent_template, block_name, context_instance) - _NODES_CACHE[cache_key] = node, render_context + + if not settings.DEBUG: + _NODES_CACHE[cache_key] = node, render_context context_instance.render_context = render_context return node.render(context_instance) From 390dc340a4e7fe16b922350304444fcb7a2e2ed2 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sat, 9 Apr 2022 07:10:28 -0300 Subject: [PATCH 3/7] Test node cache. --- tests/tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/tests.py b/tests/tests.py index fc0c2c5..2045d83 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -4,6 +4,7 @@ from django.test import RequestFactory, TestCase, modify_settings, override_settings from render_block import BlockNotFound, UnsupportedEngine, render_block_to_string +from render_block.django import _NODES_CACHE class TestDjango(TestCase): @@ -142,6 +143,14 @@ def test_request_context(self): self.assertEqual(result, "/dummy-url") + def test_node_cache(self): + """Test rendering from cache.""" + render_block_to_string("test1.html", "block1") + _NODES_CACHE["test1.html@fakeblock"] = _NODES_CACHE["test1.html@block1"] + result = render_block_to_string("test1.html", "fakeblock") + self.assertEqual(result, "block1 from test1") + + @override_settings( TEMPLATES=[ From 65930418d95b64d7f7804f3479d9b47a4f19089d Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 11 Apr 2022 13:16:52 -0300 Subject: [PATCH 4/7] Update render_block/django.py Co-authored-by: Patrick Cloke --- render_block/django.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render_block/django.py b/render_block/django.py index 04097f4..34c549a 100644 --- a/render_block/django.py +++ b/render_block/django.py @@ -56,7 +56,7 @@ def django_render_block(template, block_name, context, request=None): # Check the parent template for this block. node, render_context = _find_template_block(parent_template, block_name, context_instance) - if not settings.DEBUG: + if not template.engine.debug: _NODES_CACHE[cache_key] = node, render_context context_instance.render_context = render_context From 0a116ea599c4cd885cc9add5d0569a70c98cd9af Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 11 Apr 2022 13:17:53 -0300 Subject: [PATCH 5/7] lint --- render_block/django.py | 9 ++++++--- tests/tests.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/render_block/django.py b/render_block/django.py index 34c549a..9ca50cb 100644 --- a/render_block/django.py +++ b/render_block/django.py @@ -1,6 +1,5 @@ from copy import copy -from django.conf import settings from django.template import Context, RequestContext from django.template.base import TextNode from django.template.context import RenderContext @@ -45,7 +44,9 @@ def django_render_block(template, block_name, context, request=None): parent_template = _build_block_context(template, context_instance) try: - node, render_context = _find_template_block(template, block_name, context_instance) + node, render_context = _find_template_block( + template, block_name, context_instance + ) except BlockNotFound: # The block wasn't found in the current template. @@ -54,7 +55,9 @@ def django_render_block(template, block_name, context, request=None): raise # Check the parent template for this block. - node, render_context = _find_template_block(parent_template, block_name, context_instance) + node, render_context = _find_template_block( + parent_template, block_name, context_instance + ) if not template.engine.debug: _NODES_CACHE[cache_key] = node, render_context diff --git a/tests/tests.py b/tests/tests.py index 2045d83..96b11ed 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -151,7 +151,6 @@ def test_node_cache(self): self.assertEqual(result, "block1 from test1") - @override_settings( TEMPLATES=[ { From def43e83fb2a42622738d5f3acba3035a3cf8d61 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 11 Apr 2022 19:27:40 -0300 Subject: [PATCH 6/7] black --- render_block/django.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render_block/django.py b/render_block/django.py index 9ca50cb..3056e02 100644 --- a/render_block/django.py +++ b/render_block/django.py @@ -67,7 +67,7 @@ def django_render_block(template, block_name, context, request=None): def _make_node_cache_key(template, block_name): - return f'{template.name}@{block_name}' + return f"{template.name}@{block_name}" def _build_block_context(template, context): From 0aa0d05c7eb6c2da8ca704764e986615ebbad289 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Wed, 9 Nov 2022 16:14:26 -0300 Subject: [PATCH 7/7] Support cache for anonymous templates. --- render_block/django.py | 10 ++++++++-- tests/tests.py | 21 +++++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/render_block/django.py b/render_block/django.py index 3056e02..0aa2844 100644 --- a/render_block/django.py +++ b/render_block/django.py @@ -1,4 +1,5 @@ from copy import copy +import hashlib from django.template import Context, RequestContext from django.template.base import TextNode @@ -59,7 +60,7 @@ def django_render_block(template, block_name, context, request=None): parent_template, block_name, context_instance ) - if not template.engine.debug: + if cache_key and not template.engine.debug: _NODES_CACHE[cache_key] = node, render_context context_instance.render_context = render_context @@ -67,7 +68,12 @@ def django_render_block(template, block_name, context, request=None): def _make_node_cache_key(template, block_name): - return f"{template.name}@{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): diff --git a/tests/tests.py b/tests/tests.py index 96b11ed..9c2d958 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,10 +1,11 @@ +from collections import namedtuple from unittest import skip -from django.template import Context +from django.template import Context, Template from django.test import RequestFactory, TestCase, modify_settings, override_settings from render_block import BlockNotFound, UnsupportedEngine, render_block_to_string -from render_block.django import _NODES_CACHE +from render_block.django import _NODES_CACHE, django_render_block class TestDjango(TestCase): @@ -150,6 +151,22 @@ def test_node_cache(self): result = render_block_to_string("test1.html", "fakeblock") self.assertEqual(result, "block1 from test1") + def test_node_cache_anonymous_template(self): + """ + Test rendering from cache for anonymous templates. + + Cache key must be created from template source instead of template name + to avoid clashes. + """ + T = namedtuple("T", ["template"]) + template_1 = Template("{% block b %}foo {{ foo }}{% endblock %}") + template_2 = Template("{% block b %}bar {{ foo }}{% endblock %}") + + result = django_render_block(T(template_1), "b", {"foo": "1"}) + self.assertEqual(result, "foo 1") + result = django_render_block(T(template_2), "b", {"foo": "2"}) + self.assertEqual(result, "bar 2") + @override_settings( TEMPLATES=[