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.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.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')
 
 
 
 
 __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.
 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')
        
        #: 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)
                """
                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."""
        
        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):
 
 
 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))
                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):
 
 
 class ContainerNode(template.Node):
@@ -38,26 +30,31 @@ class ContainerNode(template.Node):
                self.references = references
        
        def render(self, context):
                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 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:
                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:
                        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 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()
 
 
 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.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):
 
 
 def fattr(*args, **kwargs):
@@ -140,24 +138,4 @@ def paginate(objects, per_page=None, page_number=1):
        else:
                objects = page.object_list
        
        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 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.
 
 
 #: Tags which are considered insecure and are therefore always disallowed by secure :class:`TemplateValidator` instances.