mirror of
https://github.com/django-components/django-components.git
synced 2025-09-03 12:47:20 +00:00
Caching templates to allow for dynamic template generation
Co-authored-by: rbeard0330 <@dul2k3BKW6m>
This commit is contained in:
parent
3919943cbd
commit
07986c5216
9 changed files with 36 additions and 17 deletions
|
@ -15,6 +15,10 @@ class AppSettings:
|
||||||
def LIBRARIES(self):
|
def LIBRARIES(self):
|
||||||
return self.settings.setdefault("libraries", [])
|
return self.settings.setdefault("libraries", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def TEMPLATE_CACHE_SIZE(self):
|
||||||
|
return self.settings.setdefault("template_cache_size", 128)
|
||||||
|
|
||||||
|
|
||||||
app_settings = AppSettings()
|
app_settings = AppSettings()
|
||||||
app_settings.__name__ = __name__
|
app_settings.__name__ = __name__
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import warnings
|
import warnings
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
from functools import lru_cache
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -11,12 +12,15 @@ from django.utils.safestring import mark_safe
|
||||||
# Allow "component.AlreadyRegistered" instead of having to import these everywhere
|
# Allow "component.AlreadyRegistered" instead of having to import these everywhere
|
||||||
from django_components.component_registry import AlreadyRegistered, ComponentRegistry, NotRegistered # noqa
|
from django_components.component_registry import AlreadyRegistered, ComponentRegistry, NotRegistered # noqa
|
||||||
|
|
||||||
|
TEMPLATE_CACHE_SIZE = getattr(settings, "COMPONENTS", {}).get('TEMPLATE_CACHE_SIZE', 128)
|
||||||
|
|
||||||
|
|
||||||
class Component(metaclass=MediaDefiningClass):
|
class Component(metaclass=MediaDefiningClass):
|
||||||
|
|
||||||
def __init__(self, component_name):
|
def __init__(self, component_name):
|
||||||
self.__component_name = component_name
|
self.__component_name = component_name
|
||||||
self.instance_template = None
|
self.instance_template = None
|
||||||
|
self.slots = {}
|
||||||
|
|
||||||
def context(self):
|
def context(self):
|
||||||
return {}
|
return {}
|
||||||
|
@ -43,15 +47,16 @@ class Component(metaclass=MediaDefiningClass):
|
||||||
def slots_in_template(template):
|
def slots_in_template(template):
|
||||||
return {node.name: node.nodelist for node in template.template.nodelist if is_slot_node(node)}
|
return {node.name: node.nodelist for node in template.template.nodelist if is_slot_node(node)}
|
||||||
|
|
||||||
def compile_instance_template(self, slots_for_instance):
|
@lru_cache(maxsize=TEMPLATE_CACHE_SIZE)
|
||||||
|
def compile_instance_template(self, template_name):
|
||||||
"""Use component's base template and the slots used for this instance to compile
|
"""Use component's base template and the slots used for this instance to compile
|
||||||
a unified template for this instance."""
|
a unified template for this instance."""
|
||||||
|
|
||||||
component_template = get_template(self.template({}))
|
component_template = get_template(template_name)
|
||||||
slots_in_template = self.slots_in_template(component_template)
|
slots_in_template = self.slots_in_template(component_template)
|
||||||
|
|
||||||
defined_slot_names = set(slots_in_template.keys())
|
defined_slot_names = set(slots_in_template.keys())
|
||||||
filled_slot_names = set(slots_for_instance.keys())
|
filled_slot_names = set(self.slots.keys())
|
||||||
unexpected_slots = filled_slot_names - defined_slot_names
|
unexpected_slots = filled_slot_names - defined_slot_names
|
||||||
if unexpected_slots:
|
if unexpected_slots:
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
@ -61,21 +66,25 @@ class Component(metaclass=MediaDefiningClass):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for unexpected_slot in unexpected_slots:
|
for unexpected_slot in unexpected_slots:
|
||||||
del slots_for_instance[unexpected_slot]
|
del self.slots[unexpected_slot]
|
||||||
|
|
||||||
combined_slots = dict(slots_in_template, **slots_for_instance)
|
combined_slots = dict(slots_in_template, **self.slots)
|
||||||
if combined_slots:
|
if combined_slots:
|
||||||
# Replace slot nodes with their nodelists, then combine into a single, flat nodelist
|
# Replace slot nodes with their nodelists, then combine into a single, flat nodelist
|
||||||
node_iterator = ([node] if not is_slot_node(node) else combined_slots[node.name]
|
node_iterator = ([node] if not is_slot_node(node) else combined_slots[node.name]
|
||||||
for node in component_template.template.nodelist)
|
for node in component_template.template.nodelist)
|
||||||
|
|
||||||
self.instance_template = copy(component_template.template)
|
instance_template = copy(component_template.template)
|
||||||
self.instance_template.nodelist = NodeList(chain.from_iterable(node_iterator))
|
instance_template.nodelist = NodeList(chain.from_iterable(node_iterator))
|
||||||
else:
|
else:
|
||||||
self.instance_template = component_template.template
|
instance_template = component_template.template
|
||||||
|
|
||||||
|
return instance_template
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
return self.instance_template.render(context)
|
template_name = self.template(context)
|
||||||
|
instance_template = self.compile_instance_template(template_name)
|
||||||
|
return instance_template.render(context)
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
css = {}
|
css = {}
|
||||||
|
|
|
@ -100,7 +100,7 @@ class ComponentNode(Node):
|
||||||
if slots:
|
if slots:
|
||||||
for slot in slots:
|
for slot in slots:
|
||||||
slot_dict[slot.name].extend(slot.nodelist)
|
slot_dict[slot.name].extend(slot.nodelist)
|
||||||
self.component.compile_instance_template(slot_dict)
|
self.component.slots = slot_dict
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Component Node: %s. Contents: %r>" % (self.component, self.component.instance_template.nodelist)
|
return "<Component Node: %s. Contents: %r>" % (self.component, self.component.instance_template.nodelist)
|
||||||
|
|
|
@ -10,7 +10,10 @@ if not settings.configured:
|
||||||
TEMPLATES=[{
|
TEMPLATES=[{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': ["tests/templates/"],
|
'DIRS': ["tests/templates/"],
|
||||||
}]
|
}],
|
||||||
|
COMPONENTS={
|
||||||
|
'TEMPLATE_CACHE_SIZE': 128
|
||||||
|
},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
settings.configure(
|
settings.configure(
|
||||||
|
|
|
@ -2,11 +2,11 @@ from textwrap import dedent
|
||||||
|
|
||||||
from django.template import Context
|
from django.template import Context
|
||||||
|
|
||||||
from django_components import component
|
|
||||||
|
|
||||||
from .django_test_setup import * # NOQA
|
from .django_test_setup import * # NOQA
|
||||||
from .testutils import Django111CompatibleSimpleTestCase as SimpleTestCase
|
from .testutils import Django111CompatibleSimpleTestCase as SimpleTestCase
|
||||||
|
|
||||||
|
from django_components import component
|
||||||
|
|
||||||
|
|
||||||
class ComponentRegistryTest(SimpleTestCase):
|
class ComponentRegistryTest(SimpleTestCase):
|
||||||
def test_empty_component(self):
|
def test_empty_component(self):
|
||||||
|
@ -32,7 +32,6 @@ class ComponentRegistryTest(SimpleTestCase):
|
||||||
|
|
||||||
comp = SimpleComponent("simple_component")
|
comp = SimpleComponent("simple_component")
|
||||||
context = Context(comp.context(variable="test"))
|
context = Context(comp.context(variable="test"))
|
||||||
comp.compile_instance_template({})
|
|
||||||
|
|
||||||
self.assertHTMLEqual(comp.render_dependencies(), dedent("""
|
self.assertHTMLEqual(comp.render_dependencies(), dedent("""
|
||||||
<link href="style.css" type="text/css" media="all" rel="stylesheet">
|
<link href="style.css" type="text/css" media="all" rel="stylesheet">
|
||||||
|
@ -71,7 +70,6 @@ class ComponentRegistryTest(SimpleTestCase):
|
||||||
|
|
||||||
comp = FilteredComponent("filtered_component")
|
comp = FilteredComponent("filtered_component")
|
||||||
context = Context(comp.context(var1="test1", var2="test2"))
|
context = Context(comp.context(var1="test1", var2="test2"))
|
||||||
comp.compile_instance_template({})
|
|
||||||
|
|
||||||
self.assertHTMLEqual(comp.render(context), dedent("""
|
self.assertHTMLEqual(comp.render(context), dedent("""
|
||||||
Var1: <strong>test1</strong>
|
Var1: <strong>test1</strong>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
|
|
||||||
|
from .django_test_setup import * # NOQA
|
||||||
|
|
||||||
from django_components import component
|
from django_components import component
|
||||||
|
|
||||||
from .django_test_setup import * # NOQA
|
|
||||||
from .testutils import Django111CompatibleSimpleTestCase as SimpleTestCase
|
from .testutils import Django111CompatibleSimpleTestCase as SimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from .django_test_setup import * # NOQA
|
||||||
from django_components import component
|
from django_components import component
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@ from textwrap import dedent
|
||||||
|
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
|
|
||||||
|
from .django_test_setup import * # NOQA
|
||||||
from django_components import component
|
from django_components import component
|
||||||
|
|
||||||
from .django_test_setup import * # NOQA
|
|
||||||
from .testutils import Django111CompatibleSimpleTestCase as SimpleTestCase
|
from .testutils import Django111CompatibleSimpleTestCase as SimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django import setup
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,3 +6,5 @@ class Django111CompatibleSimpleTestCase(SimpleTestCase):
|
||||||
def assertHTMLEqual(self, left, right):
|
def assertHTMLEqual(self, left, right):
|
||||||
left = left.replace(' type="text/javascript"', '')
|
left = left.replace(' type="text/javascript"', '')
|
||||||
super(Django111CompatibleSimpleTestCase, self).assertHTMLEqual(left, right)
|
super(Django111CompatibleSimpleTestCase, self).assertHTMLEqual(left, right)
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue