Switched contentlets to use lists rather than sets to preserve ordering. Resolves...
[philo.git] / philo / models / pages.py
index 3ad05d8..0ce047e 100644 (file)
@@ -1,16 +1,18 @@
 # encoding: utf-8
 """
 # encoding: utf-8
 """
-:class:`Page`\ s are the most frequently used :class:`View` subclass. They define a basic HTML page and its associated content. Each :class:`Page` renders itself according to a :class:`Template`. The :class:`Template` may contain :ttag:`container <philo.templatetags.containers.do_container>` tags, which define related :class:`Contentlet`\ s and :class:`ContentReference`\ s for any page using that :class:`Template`.
+:class:`Page`\ s are the most frequently used :class:`.View` subclass. They define a basic HTML page and its associated content. Each :class:`Page` renders itself according to a :class:`Template`. The :class:`Template` may contain :ttag:`container` tags, which define related :class:`Contentlet`\ s and :class:`ContentReference`\ s for any page using that :class:`Template`.
 
 """
 
 
 """
 
+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, add_to_builtins as register_templatetags, TextNode, VariableNode
+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.loader_tags import BlockNode, ExtendsNode, BlockContext
 from django.utils.datastructures import SortedDict
 
@@ -23,11 +25,14 @@ from philo.utils import fattr
 from philo.validators import LOADED_TEMPLATE_ATTR
 
 
 from philo.validators import LOADED_TEMPLATE_ATTR
 
 
+__all__ = ('Template', 'Page', 'Contentlet', 'ContentReference')
+
+
 class LazyContainerFinder(object):
        def __init__(self, nodes, extends=False):
                self.nodes = nodes
                self.initialized = False
 class LazyContainerFinder(object):
        def __init__(self, nodes, extends=False):
                self.nodes = nodes
                self.initialized = False
-               self.contentlet_specs = set()
+               self.contentlet_specs = []
                self.contentreference_specs = SortedDict()
                self.blocks = {}
                self.block_super = False
                self.contentreference_specs = SortedDict()
                self.blocks = {}
                self.block_super = False
@@ -44,7 +49,7 @@ class LazyContainerFinder(object):
                        
                        if isinstance(node, ContainerNode):
                                if not node.references:
                        
                        if isinstance(node, ContainerNode):
                                if not node.references:
-                                       self.contentlet_specs.add(node.name)
+                                       self.contentlet_specs.append(node.name)
                                else:
                                        if node.name not in self.contentreference_specs.keys():
                                                self.contentreference_specs[node.name] = node.references
                                else:
                                        if node.name not in self.contentreference_specs.keys():
                                                self.contentreference_specs[node.name] = node.references
@@ -75,6 +80,26 @@ class LazyContainerFinder(object):
                        self.initialized = True
 
 
                        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(TreeModel):
        """Represents a database-driven django template."""
        #: The name of the template. Used for organization and debugging.
 class Template(TreeModel):
        """Represents a database-driven django template."""
        #: The name of the template. Used for organization and debugging.
@@ -94,35 +119,16 @@ class Template(TreeModel):
                """
                template = DjangoTemplate(self.code)
                
                """
                template = DjangoTemplate(self.code)
                
-               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
-               
                # Build a tree of the templates we're using, placing the root template first.
                # Build a tree of the templates we're using, placing the root template first.
-               levels = build_extension_tree(template.nodelist)[::-1]
+               levels = build_extension_tree(template.nodelist)
                
                
-               contentlet_specs = set()
+               contentlet_specs = []
                contentreference_specs = SortedDict()
                blocks = {}
                
                contentreference_specs = SortedDict()
                blocks = {}
                
-               for level in levels:
+               for level in reversed(levels):
                        level.initialize()
                        level.initialize()
-                       contentlet_specs |= level.contentlet_specs
+                       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:
                        contentreference_specs.update(level.contentreference_specs)
                        for name, block in level.blocks.items():
                                if block.block_super:
@@ -133,7 +139,7 @@ class Template(TreeModel):
                for block_list in blocks.values():
                        for block in block_list:
                                block.initialize()
                for block_list in blocks.values():
                        for block in block_list:
                                block.initialize()
-                               contentlet_specs |= block.contentlet_specs
+                               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
                                contentreference_specs.update(block.contentreference_specs)
                
                return contentlet_specs, contentreference_specs
@@ -158,7 +164,7 @@ class Page(View):
        
        def get_containers(self):
                """
        
        def get_containers(self):
                """
-               Returns the results :attr:`~Template.containers` for the related template. This is a tuple containing the specs of all :ttag:`containers <philo.templatetags.containers.do_container>` in the :class:`Template`'s code. The value will be cached on the instance so that multiple accesses will be less expensive.
+               Returns the results :attr:`~Template.containers` for the related template. This is a tuple containing the specs of all :ttag:`container`\ s in the :class:`Template`'s code. The value will be cached on the instance so that multiple accesses will be less expensive.
                
                """
                if not hasattr(self, '_containers'):
                
                """
                if not hasattr(self, '_containers'):
@@ -168,7 +174,9 @@ class Page(View):
        
        def render_to_string(self, request=None, extra_context=None):
                """
        
        def render_to_string(self, request=None, extra_context=None):
                """
-               In addition to rendering as an :class:`HttpResponse`, a :class:`Page` can also render as a string. This means, for example, that :class:`Page`\ s can be used to render emails or other non-HTML content with the same :ttag:`container <philo.templatetags.containers.do_container>`-based functionality as is used for HTML.
+               In addition to rendering as an :class:`HttpResponse`, a :class:`Page` can also render as a string. This means, for example, that :class:`Page`\ s can be used to render emails or other non-HTML content with the same :ttag:`container`-based functionality as is used for HTML.
+               
+               The :class:`Page` will add itself to the context as ``page`` and its :attr:`~.Entity.attributes` as ``attributes``. If a request is provided, then :class:`request.node <.Node>` will also be added to the context as ``node`` and ``attributes`` will be set to the result of calling :meth:`~.View.attributes_with_node` with that :class:`.Node`.
                
                """
                context = {}
                
                """
                context = {}
@@ -226,9 +234,9 @@ class Contentlet(models.Model):
        """Represents a piece of content on a page. This content is treated as a secure :class:`~philo.models.fields.TemplateField`."""
        #: The page which this :class:`Contentlet` is related to.
        page = models.ForeignKey(Page, related_name='contentlets')
        """Represents a piece of content on a page. This content is treated as a secure :class:`~philo.models.fields.TemplateField`."""
        #: The page which this :class:`Contentlet` is related to.
        page = models.ForeignKey(Page, related_name='contentlets')
-       #: This represents the name of the container as defined by a :ttag:`container <philo.templatetags.containers.do_container>` tag.
+       #: This represents the name of the container as defined by a :ttag:`container` tag.
        name = models.CharField(max_length=255, db_index=True)
        name = models.CharField(max_length=255, db_index=True)
-       #: A secure :class:`~philo.models.fields.TemplateField` holding the content for this :class:`Contentlet`. Note that actually using this field as a template requires use of the :ttag:`include_string <philo.templatetags.include_string.do_include_string>` template tag.
+       #: A secure :class:`~philo.models.fields.TemplateField` holding the content for this :class:`Contentlet`. Note that actually using this field as a template requires use of the :ttag:`include_string` template tag.
        content = TemplateField()
        
        def __unicode__(self):
        content = TemplateField()
        
        def __unicode__(self):
@@ -243,11 +251,11 @@ class ContentReference(models.Model):
        """Represents a model instance related to a page."""
        #: The page which this :class:`ContentReference` is related to.
        page = models.ForeignKey(Page, related_name='contentreferences')
        """Represents a model instance related to a page."""
        #: The page which this :class:`ContentReference` is related to.
        page = models.ForeignKey(Page, related_name='contentreferences')
-       #: This represents the name of the container as defined by a :ttag:`container <philo.templatetags.containers.do_container>` tag.
+       #: This represents the name of the container as defined by a :ttag:`container` tag.
        name = models.CharField(max_length=255, db_index=True)
        content_type = models.ForeignKey(ContentType, verbose_name='Content type')
        content_id = models.PositiveIntegerField(verbose_name='Content ID', blank=True, null=True)
        name = models.CharField(max_length=255, db_index=True)
        content_type = models.ForeignKey(ContentType, verbose_name='Content type')
        content_id = models.PositiveIntegerField(verbose_name='Content ID', blank=True, null=True)
-       #: A :class:`GenericForeignKey` to a model instance. The content type of this instance is defined by the :ttag:`container <philo.templatetags.containers.do_container>` tag which defines this :class:`ContentReference`.
+       #: A :class:`GenericForeignKey` to a model instance. The content type of this instance is defined by the :ttag:`container` tag which defines this :class:`ContentReference`.
        content = generic.GenericForeignKey('content_type', 'content_id')
        
        def __unicode__(self):
        content = generic.GenericForeignKey('content_type', 'content_id')
        
        def __unicode__(self):
@@ -258,8 +266,5 @@ class ContentReference(models.Model):
                app_label = 'philo'
 
 
                app_label = 'philo'
 
 
-register_templatetags('philo.templatetags.containers')
-
-
 register_value_model(Template)
 register_value_model(Page)
\ No newline at end of file
 register_value_model(Template)
 register_value_model(Page)
\ No newline at end of file