Rework slot management to avoid nodelist copying (fixes #64)

Co-authored-by: rbeard0330 <@dul2k3BKW6m>
This commit is contained in:
Emil Stenström 2021-05-30 18:46:11 +02:00 committed by GitHub
parent 5e8ae9d27b
commit a5bd0cf2e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 40 deletions

View file

@ -1,10 +1,9 @@
import warnings
from copy import copy, deepcopy
from functools import lru_cache
from django.conf import settings
from django.forms.widgets import MediaDefiningClass
from django.template.base import Node, NodeList, TokenType
from django.template.base import Node, TokenType
from django.template.loader import get_template
from django.utils.safestring import mark_safe
@ -12,6 +11,7 @@ from django.utils.safestring import mark_safe
from django_components.component_registry import AlreadyRegistered, ComponentRegistry, NotRegistered # noqa
TEMPLATE_CACHE_SIZE = getattr(settings, "COMPONENTS", {}).get('TEMPLATE_CACHE_SIZE', 128)
ACTIVE_SLOT_CONTEXT_KEY = '_DJANGO_COMPONENTS_ACTIVE_SLOTS'
class SimplifiedInterfaceMediaDefiningClass(MediaDefiningClass):
@ -80,18 +80,11 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
@lru_cache(maxsize=TEMPLATE_CACHE_SIZE)
def get_processed_template(self, template_name):
"""Retrieve the requested template and add a link to this component to each SlotNode in the template."""
"""Retrieve the requested template and check for unused slots."""
source_template = get_template(template_name)
component_template = get_template(template_name).template
# The template may be shared with another component (e.g., due to caching). To ensure that each
# SlotNode is unique between components, we have to copy the nodes in the template nodelist and
# any contained nodelists.
component_template = copy(source_template.template)
cloned_nodelist = [duplicate_node(node) for node in component_template.nodelist]
component_template.nodelist = NodeList(cloned_nodelist)
# Traverse template nodes and descendants, and give each slot node a reference to this component.
# Traverse template nodes and descendants
visited_nodes = set()
nodes_to_visit = list(component_template.nodelist)
slots_seen = set()
@ -104,7 +97,6 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
nodes_to_visit.extend(getattr(current_node, nodelist_name, []))
if self.is_slot_node(current_node):
slots_seen.add(current_node.name)
current_node.parent_component = self
# Check and warn for unknown slots
if settings.DEBUG:
@ -122,7 +114,9 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
def render(self, context):
template_name = self.template(context)
instance_template = self.get_processed_template(template_name)
return instance_template.render(context)
active_slots = {**context.get(ACTIVE_SLOT_CONTEXT_KEY, {}), **self.slots}
with context.update({ACTIVE_SLOT_CONTEXT_KEY: active_slots}):
return instance_template.render(context)
class Media:
css = {}
@ -133,24 +127,6 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
registry = ComponentRegistry()
def duplicate_node(source_node):
"""Perform a shallow copy of source_node and then recursively copy over each of source_node's nodelists.
If a nodelist is a dynamic property that cannot be set, fall back to a deepcopy of source_node."""
try:
clone = copy(source_node)
for nodelist_name in source_node.child_nodelists:
nodelist = getattr(source_node, nodelist_name, NodeList())
nodelist_contents = [duplicate_node(n) for n in nodelist]
setattr(clone, nodelist_name, type(nodelist)(nodelist_contents))
return clone
except AttributeError:
# AttributeError is raised if an attribute cannot be set (e.g., IfNode's nodelist,
# which is a read-only property).
return deepcopy(source_node)
def register(name):
"""Class decorator to register a component.