Moved container finding code into philo/utils/templates, along with template utils...
authorStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 27 Jun 2011 15:23:28 +0000 (11:23 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 27 Jun 2011 15:23:28 +0000 (11:23 -0400)
philo/models/pages.py
philo/templatetags/containers.py
philo/templatetags/embed.py
philo/utils/__init__.py
philo/utils/templates.py [new file with mode: 0644]
philo/validators.py

index ea3bb64..350bce5 100644 (file)
 
 """
 
-import itertools
-
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes import generic
 from django.core.exceptions import ValidationError
 from django.db import models
 from django.http import HttpResponse
-from django.template import TemplateDoesNotExist, Context, RequestContext, Template as DjangoTemplate, TextNode, VariableNode
-from django.template.loader_tags import BlockNode, ExtendsNode, BlockContext
-from django.utils.datastructures import SortedDict
+from django.template import Context, RequestContext, Template as DjangoTemplate
 
 from philo.models.base import SlugTreeEntity, register_value_model
 from philo.models.fields import TemplateField
 from philo.models.nodes import View
 from philo.signals import page_about_to_render_to_string, page_finished_rendering_to_string
-from philo.templatetags.containers import ContainerNode
-from philo.utils import fattr
-from philo.validators import LOADED_TEMPLATE_ATTR
+from philo.utils import templates
 
 
 __all__ = ('Template', 'Page', 'Contentlet', 'ContentReference')
 
 
-class LazyContainerFinder(object):
-       def __init__(self, nodes, extends=False):
-               self.nodes = nodes
-               self.initialized = False
-               self.contentlet_specs = []
-               self.contentreference_specs = SortedDict()
-               self.blocks = {}
-               self.block_super = False
-               self.extends = extends
-       
-       def process(self, nodelist):
-               for node in nodelist:
-                       if self.extends:
-                               if isinstance(node, BlockNode):
-                                       self.blocks[node.name] = block = LazyContainerFinder(node.nodelist)
-                                       block.initialize()
-                                       self.blocks.update(block.blocks)
-                               continue
-                       
-                       if isinstance(node, ContainerNode):
-                               if not node.references:
-                                       self.contentlet_specs.append(node.name)
-                               else:
-                                       if node.name not in self.contentreference_specs.keys():
-                                               self.contentreference_specs[node.name] = node.references
-                               continue
-                       
-                       if isinstance(node, VariableNode):
-                               if node.filter_expression.var.lookups == (u'block', u'super'):
-                                       self.block_super = True
-                       
-                       if hasattr(node, 'child_nodelists'):
-                               for nodelist_name in node.child_nodelists:
-                                       if hasattr(node, nodelist_name):
-                                               nodelist = getattr(node, nodelist_name)
-                                               self.process(nodelist)
-                       
-                       # LOADED_TEMPLATE_ATTR contains the name of an attribute philo uses to declare a
-                       # node as rendering an additional template. Philo monkeypatches the attribute onto
-                       # the relevant default nodes and declares it on any native nodes.
-                       if hasattr(node, LOADED_TEMPLATE_ATTR):
-                               loaded_template = getattr(node, LOADED_TEMPLATE_ATTR)
-                               if loaded_template:
-                                       nodelist = loaded_template.nodelist
-                                       self.process(nodelist)
-       
-       def initialize(self):
-               if not self.initialized:
-                       self.process(self.nodes)
-                       self.initialized = True
-
-
-def build_extension_tree(nodelist):
-       nodelists = []
-       extends = None
-       for node in nodelist:
-               if not isinstance(node, TextNode):
-                       if isinstance(node, ExtendsNode):
-                               extends = node
-                       break
-       
-       if extends:
-               if extends.nodelist:
-                       nodelists.append(LazyContainerFinder(extends.nodelist, extends=True))
-               loaded_template = getattr(extends, LOADED_TEMPLATE_ATTR)
-               nodelists.extend(build_extension_tree(loaded_template.nodelist))
-       else:
-               # Base case: root.
-               nodelists.append(LazyContainerFinder(nodelist))
-       return nodelists
-
-
 class Template(SlugTreeEntity):
        """Represents a database-driven django template."""
        #: The name of the template. Used for organization and debugging.
@@ -111,38 +33,14 @@ class Template(SlugTreeEntity):
        #: An insecure :class:`~philo.models.fields.TemplateField` containing the django template code for this template.
        code = TemplateField(secure=False, verbose_name='django template code')
        
-       @property
-       def containers(self):
+       def get_containers(self):
                """
                Returns a tuple where the first item is a list of names of contentlets referenced by containers, and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers. This will break if there is a recursive extends or includes in the template code. Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
                
                """
                template = DjangoTemplate(self.code)
-               
-               # Build a tree of the templates we're using, placing the root template first.
-               levels = build_extension_tree(template.nodelist)
-               
-               contentlet_specs = []
-               contentreference_specs = SortedDict()
-               blocks = {}
-               
-               for level in reversed(levels):
-                       level.initialize()
-                       contentlet_specs.extend(itertools.ifilter(lambda x: x not in contentlet_specs, level.contentlet_specs))
-                       contentreference_specs.update(level.contentreference_specs)
-                       for name, block in level.blocks.items():
-                               if block.block_super:
-                                       blocks.setdefault(name, []).append(block)
-                               else:
-                                       blocks[name] = [block]
-               
-               for block_list in blocks.values():
-                       for block in block_list:
-                               block.initialize()
-                               contentlet_specs.extend(itertools.ifilter(lambda x: x not in contentlet_specs, block.contentlet_specs))
-                               contentreference_specs.update(block.contentreference_specs)
-               
-               return contentlet_specs, contentreference_specs
+               return templates.get_containers(template)
+       containers = property(get_containers)
        
        def __unicode__(self):
                """Returns the value of the :attr:`name` field."""
index 2c55034..11ccd88 100644 (file)
@@ -18,17 +18,9 @@ CONTAINER_CONTEXT_KEY = 'philo_container_context'
 
 
 class ContainerContext(object):
-       def __init__(self, page):
-               contentlet_specs, contentreference_specs = page.template.containers
-               
-               contentlets = page.contentlets.filter(name__in=contentlet_specs)
+       def __init__(self, contentlets, references):
                self.contentlets = dict(((c.name, c) for c in contentlets))
-               
-               q = Q()
-               for name, ct in contentreference_specs.items():
-                       q |= Q(name=name, content_type=ct)
-               references = page.contentreferences.filter(q)
-               self.references = dict(((c.name, c) for c in references))
+               self.references = dict((((c.name, ContentType.objects.get_for_id(c.content_type_id)), c) for c in references))
 
 
 class ContainerNode(template.Node):
@@ -38,26 +30,31 @@ class ContainerNode(template.Node):
                self.references = references
        
        def render(self, context):
-               content = settings.TEMPLATE_STRING_IF_INVALID
-               if 'page' in context:
-                       container_content = self.get_container_content(context)
-               else:
-                       container_content = None
+               container_content = self.get_container_content(context)
                
                if self.as_var:
                        context[self.as_var] = container_content
                        return ''
                
-               if not container_content:
-                       return ''
-               
                return container_content
        
        def get_container_content(self, context):
                try:
                        container_context = context.render_context[CONTAINER_CONTEXT_KEY]
                except KeyError:
-                       container_context = ContainerContext(context['page'])
+                       try:
+                               page = context['page']
+                       except KeyError:
+                               return settings.TEMPLATE_STRING_IF_INVALID
+                       
+                       contentlet_specs, contentreference_specs = page.template.containers
+                       contentlets = page.contentlets.filter(name__in=contentlet_specs)
+                       q = Q()
+                       for name, ct in contentreference_specs.items():
+                               q |= Q(name=name, content_type=ct)
+                       references = page.contentreferences.filter(q)
+                       
+                       container_context = ContainerContext(contentlets, references)
                        context.render_context[CONTAINER_CONTEXT_KEY] = container_context
                
                if self.references:
index 16f8092..b024b1b 100644 (file)
@@ -7,7 +7,7 @@ from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
 from django.template.loader_tags import ExtendsNode, BlockContext, BLOCK_CONTEXT_KEY, TextNode, BlockNode
 
-from philo.utils import LOADED_TEMPLATE_ATTR
+from philo.utils.templates import LOADED_TEMPLATE_ATTR
 
 
 register = template.Library()
index 83436a9..34ad1f0 100644 (file)
@@ -1,8 +1,6 @@
 from django.db import models
 from django.contrib.contenttypes.models import ContentType
 from django.core.paginator import Paginator, EmptyPage
-from django.template import Context
-from django.template.loader_tags import ExtendsNode, ConstantIncludeNode
 
 
 def fattr(*args, **kwargs):
@@ -140,24 +138,4 @@ def paginate(objects, per_page=None, page_number=1):
        else:
                objects = page.object_list
        
-       return paginator, page, objects
-
-
-### Facilitating template analysis.
-
-
-LOADED_TEMPLATE_ATTR = '_philo_loaded_template'
-BLANK_CONTEXT = Context()
-
-
-def get_extended(self):
-       return self.get_parent(BLANK_CONTEXT)
-
-
-def get_included(self):
-       return self.template
-
-
-# We ignore the IncludeNode because it will never work in a blank context.
-setattr(ExtendsNode, LOADED_TEMPLATE_ATTR, property(get_extended))
-setattr(ConstantIncludeNode, LOADED_TEMPLATE_ATTR, property(get_included))
\ No newline at end of file
+       return paginator, page, objects
\ No newline at end of file
diff --git a/philo/utils/templates.py b/philo/utils/templates.py
new file mode 100644 (file)
index 0000000..e0be31f
--- /dev/null
@@ -0,0 +1,123 @@
+import itertools
+
+from django.template import TextNode, VariableNode, Context
+from django.template.loader_tags import BlockNode, ExtendsNode, BlockContext, ConstantIncludeNode
+from django.utils.datastructures import SortedDict
+
+from philo.templatetags.containers import ContainerNode
+
+
+LOADED_TEMPLATE_ATTR = '_philo_loaded_template'
+BLANK_CONTEXT = Context()
+
+
+def get_extended(self):
+       return self.get_parent(BLANK_CONTEXT)
+
+
+def get_included(self):
+       return self.template
+
+
+# We ignore the IncludeNode because it will never work in a blank context.
+setattr(ExtendsNode, LOADED_TEMPLATE_ATTR, property(get_extended))
+setattr(ConstantIncludeNode, LOADED_TEMPLATE_ATTR, property(get_included))
+
+
+def get_containers(template):
+               # Build a tree of the templates we're using, placing the root template first.
+               levels = build_extension_tree(template.nodelist)
+               
+               contentlet_specs = []
+               contentreference_specs = SortedDict()
+               blocks = {}
+               
+               for level in reversed(levels):
+                       level.initialize()
+                       contentlet_specs.extend(itertools.ifilter(lambda x: x not in contentlet_specs, level.contentlet_specs))
+                       contentreference_specs.update(level.contentreference_specs)
+                       for name, block in level.blocks.items():
+                               if block.block_super:
+                                       blocks.setdefault(name, []).append(block)
+                               else:
+                                       blocks[name] = [block]
+               
+               for block_list in blocks.values():
+                       for block in block_list:
+                               block.initialize()
+                               contentlet_specs.extend(itertools.ifilter(lambda x: x not in contentlet_specs, block.contentlet_specs))
+                               contentreference_specs.update(block.contentreference_specs)
+               
+               return contentlet_specs, contentreference_specs
+
+
+class LazyContainerFinder(object):
+       def __init__(self, nodes, extends=False):
+               self.nodes = nodes
+               self.initialized = False
+               self.contentlet_specs = []
+               self.contentreference_specs = SortedDict()
+               self.blocks = {}
+               self.block_super = False
+               self.extends = extends
+       
+       def process(self, nodelist):
+               for node in nodelist:
+                       if self.extends:
+                               if isinstance(node, BlockNode):
+                                       self.blocks[node.name] = block = LazyContainerFinder(node.nodelist)
+                                       block.initialize()
+                                       self.blocks.update(block.blocks)
+                               continue
+                       
+                       if isinstance(node, ContainerNode):
+                               if not node.references:
+                                       self.contentlet_specs.append(node.name)
+                               else:
+                                       if node.name not in self.contentreference_specs.keys():
+                                               self.contentreference_specs[node.name] = node.references
+                               continue
+                       
+                       if isinstance(node, VariableNode):
+                               if node.filter_expression.var.lookups == (u'block', u'super'):
+                                       self.block_super = True
+                       
+                       if hasattr(node, 'child_nodelists'):
+                               for nodelist_name in node.child_nodelists:
+                                       if hasattr(node, nodelist_name):
+                                               nodelist = getattr(node, nodelist_name)
+                                               self.process(nodelist)
+                       
+                       # LOADED_TEMPLATE_ATTR contains the name of an attribute philo uses to declare a
+                       # node as rendering an additional template. Philo monkeypatches the attribute onto
+                       # the relevant default nodes and declares it on any native nodes.
+                       if hasattr(node, LOADED_TEMPLATE_ATTR):
+                               loaded_template = getattr(node, LOADED_TEMPLATE_ATTR)
+                               if loaded_template:
+                                       nodelist = loaded_template.nodelist
+                                       self.process(nodelist)
+       
+       def initialize(self):
+               if not self.initialized:
+                       self.process(self.nodes)
+                       self.initialized = True
+
+
+def build_extension_tree(nodelist):
+       nodelists = []
+       extends = None
+       for node in nodelist:
+               if not isinstance(node, TextNode):
+                       if isinstance(node, ExtendsNode):
+                               extends = node
+                       break
+       
+       if extends:
+               if extends.nodelist:
+                       nodelists.append(LazyContainerFinder(extends.nodelist, extends=True))
+               loaded_template = getattr(extends, LOADED_TEMPLATE_ATTR)
+               nodelists.extend(build_extension_tree(loaded_template.nodelist))
+       else:
+               # Base case: root.
+               nodelists.append(LazyContainerFinder(nodelist))
+       return nodelists
\ No newline at end of file
index 349dd56..4b43047 100644 (file)
@@ -6,7 +6,7 @@ from django.utils import simplejson as json
 from django.utils.html import escape, mark_safe
 from django.utils.translation import ugettext_lazy as _
 
-from philo.utils import LOADED_TEMPLATE_ATTR
+from philo.utils.templates import LOADED_TEMPLATE_ATTR
 
 
 #: Tags which are considered insecure and are therefore always disallowed by secure :class:`TemplateValidator` instances.