Initial extension of containers to support referencing objects of arbitrary type.
authorJoseph Spiros <joseph.spiros@ithinksw.com>
Wed, 19 May 2010 09:30:23 +0000 (05:30 -0400)
committerJoseph Spiros <joseph.spiros@ithinksw.com>
Wed, 19 May 2010 09:30:23 +0000 (05:30 -0400)
admin.py
models.py
templatetags/containers.py

index faf4b71..12b9183 100644 (file)
--- a/admin.py
+++ b/admin.py
@@ -82,12 +82,15 @@ class PageAdmin(EntityAdmin):
                if obj: # if no obj, creating a new page, thus no template set, thus no containers
                        page = obj
                        template = page.template
-                       containers = template.containers
-                       if len(containers) > 0:
-                               for container in containers:
-                                       fieldsets.append((('Container: %s' % container), {
-                                               'fields': (('container_content_%s' % container), ('container_dynamic_%s' % container))
-                                       }))
+                       contentlet_containers, contentreference_containers = template.containers
+                       for container_name in contentlet_containers:
+                               fieldsets.append((('Container: %s' % container_name), {
+                                       'fields': (('contentlet_container_content_%s' % container_name), ('contentlet_container_dynamic_%s' % container_name))
+                               }))
+                       for container_name, container_content_type in contentreference_containers:
+                               fieldsets.append((('Container: %s' % container_name), {
+                                       'fields': (('contentreference_container_%s' % container_name),)
+                               }))
                return fieldsets
        
        def get_form(self, request, obj=None, **kwargs):
@@ -95,34 +98,47 @@ class PageAdmin(EntityAdmin):
                if obj: # if no obj, creating a new page, thus no template set, thus no containers
                        page = obj
                        template = page.template
-                       containers = template.containers
-                       for container in containers:
+                       contentlet_containers, contentreference_containers = template.containers
+                       for container_name in contentlet_containers:
                                initial_content = None
                                initial_dynamic = False
                                try:
-                                       contentlet = page.contentlets.get(name__exact=container)
+                                       contentlet = page.contentlets.get(name__exact=container_name)
                                        initial_content = contentlet.content
                                        initial_dynamic = contentlet.dynamic
                                except Contentlet.DoesNotExist:
                                        pass
-                               form.base_fields[('container_content_%s' % container)] = forms.CharField(label='Content', widget=forms.Textarea(), initial=initial_content, required=False)
-                               form.base_fields[('container_dynamic_%s' % container)] = forms.BooleanField(label='Dynamic', help_text='Specify whether this content contains dynamic template code', initial=initial_dynamic, required=False)
+                               form.base_fields[('contentlet_container_content_%s' % container_name)] = forms.CharField(label='Content', widget=forms.Textarea(), initial=initial_content, required=False)
+                               form.base_fields[('contentlet_container_dynamic_%s' % container_name)] = forms.BooleanField(label='Dynamic', help_text='Specify whether this content contains dynamic template code', initial=initial_dynamic, required=False)
+                       for container_name, container_content_type in contentreference_containers:
+                               initial_content = None
+                               try:
+                                       initial_content = page.contentreferences.get(name__exact=container_name, content_type=container_content_type)
+                               except ContentReference.DoesNotExist:
+                                       pass
+                               form.base_fields[('contentreference_container_%s' % container_name)] = forms.ModelChoiceField(label='References', initial=initial_content, required=False, queryset=container_content_type.model_class().objects.all())
                return form
        
        def save_model(self, request, page, form, change):
                page.save()
-               
                template = page.template
-               containers = template.containers
-               for container in containers:
-                       if (("container_content_%s" % container) in form.cleaned_data) and (("container_dynamic_%s" % container) in form.cleaned_data):
-                               content = form.cleaned_data[('container_content_%s' % container)]
-                               dynamic = form.cleaned_data[('container_dynamic_%s' % container)]
-                               contentlet, created = page.contentlets.get_or_create(name=container, defaults={'content': content, 'dynamic': dynamic})
+               contentlet_containers, contentreference_containers = template.containers
+               for container_name in contentlet_containers:
+                       if (('contentlet_container_content_%s' % container_name) in form.cleaned_data) and (('contentlet_container_dynamic_%s' % container_name) in form.cleaned_data):
+                               content = form.cleaned_data[('contentlet_container_content_%s' % container_name)]
+                               dynamic = form.cleaned_data[('contentlet_container_dynamic_%s' % container_name)]
+                               contentlet, created = page.contentlets.get_or_create(name=container_name, defaults={'content': content, 'dynamic': dynamic})
                                if not created:
                                        contentlet.content = content
                                        contentlet.dynamic = dynamic
                                        contentlet.save()
+               for container_name, container_content_type in contentreference_containers:
+                       if ('contentreference_container_%s' % container_name) in form.cleaned_data:
+                               content = form.cleaned_data[('contentreference_container_%s' % container_name)]
+                               contentreference, created = page.contentreferences.get_or_create(name=container_name, defaults={'content': content})
+                               if not created:
+                                       contentreference.content = content
+                                       contentreference.save()
 
 
 admin.site.register(Collection, CollectionAdmin)
index 1d255c7..41e9a15 100644 (file)
--- a/models.py
+++ b/models.py
@@ -282,37 +282,46 @@ class Template(TreeModel):
        @property
        def containers(self):
                """
-               Returns a list of names of contentlets referenced by containers. 
+               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.
                """
-               def container_node_names(template):
-                       def nodelist_container_node_names(nodelist):
-                               names = []
+               def container_nodes(template):
+                       def nodelist_container_nodes(nodelist):
+                               nodes = []
                                for node in nodelist:
                                        try:
                                                for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
                                                        if hasattr(node, nodelist_name):
-                                                               names.extend(nodelist_container_node_names(getattr(node, nodelist_name)))
+                                                               nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
                                                if isinstance(node, ContainerNode):
-                                                       names.append(node.name)
+                                                       nodes.append(node)
                                                elif isinstance(node, ExtendsNode):
                                                        extended_template = node.get_parent(Context())
                                                        if extended_template:
-                                                               names.extend(container_node_names(extended_template))
+                                                               nodes.extend(container_nodes(extended_template))
                                                elif isinstance(node, ConstantIncludeNode):
                                                        included_template = node.template
                                                        if included_template:
-                                                               names.extend(container_node_names(included_template))
+                                                               nodes.extend(container_nodes(included_template))
                                                elif isinstance(node, IncludeNode):
                                                        included_template = get_template(node.template_name.resolve(Context()))
                                                        if included_template:
-                                                               names.extend(container_node_names(included_template))
+                                                               nodes.extend(container_nodes(included_template))
                                        except:
                                                pass # fail for this node
-                               return names
-                       return nodelist_container_node_names(template.nodelist)
-               return set(container_node_names(self.django_template))
+                               return nodes
+                       return nodelist_container_nodes(template.nodelist)
+               all_nodes = container_nodes(self.django_template)
+               contentlet_node_names = set([node.name for node in all_nodes if not node.references])
+               contentreference_node_names = []
+               contentreference_node_specs = []
+               for node in all_nodes:
+                       if node.references and node.name not in contentreference_node_names:
+                               contentreference_node_specs.append((node.name, node.references))
+                               contentreference_node_names.append(node.name)
+               return contentlet_node_names, contentreference_node_specs
        
        def __unicode__(self):
                return self.get_path(u' › ', 'name')
@@ -349,6 +358,14 @@ class Contentlet(models.Model):
        dynamic = models.BooleanField(default=False)
 
 
+class ContentReference(models.Model):
+       page = models.ForeignKey(Page, related_name='contentreferences')
+       name = models.CharField(max_length=255)
+       content_type = models.ForeignKey(ContentType, verbose_name='Content type')
+       content_id = models.PositiveIntegerField(verbose_name='Content ID')
+       content = generic.GenericForeignKey('content_type', 'content_id')
+
+
 register_templatetags('philo.templatetags.containers')
 
 
index 9bf056f..cc6b313 100644 (file)
@@ -2,51 +2,77 @@ from django import template
 from django.conf import settings
 from django.utils.safestring import SafeUnicode, mark_safe
 from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.contenttypes.models import ContentType
+
 
 register = template.Library()
 
 
 class ContainerNode(template.Node):
-       def __init__(self, name, as_var=None):
+       def __init__(self, name, references=None, as_var=None):
                self.name = name
                self.as_var = as_var
+               self.references = references
        def render(self, context):
-               page = None
+               content = settings.TEMPLATE_STRING_IF_INVALID
                if 'page' in context:
                        page = context['page']
-               if page:
-                       contentlet = None
-                       try:
-                               contentlet = page.contentlets.get(name__exact=self.name)
-                       except ObjectDoesNotExist:
-                               pass
-                       if contentlet:
-                               content = contentlet.content
-                               if contentlet.dynamic:
-                                       try:
-                                               content = mark_safe(template.Template(content, name=contentlet.name).render(context))
-                                       except template.TemplateSyntaxError, error:
-                                               content = ''
-                                               if settings.DEBUG:
-                                                       content = ('[Error parsing contentlet \'%s\': %s]' % self.name, error)
-                               if self.as_var:
-                                       context[self.as_var] = content
-                                       content = ''
-                               return content
-               return ''
+                       if self.references:
+                               try:
+                                       contentreference = page.contentreferences.get(name__exact=self.name, content_type=self.references)
+                                       content = contentreference.content
+                               except ObjectDoesNotExist:
+                                       pass
+                       else:
+                               try:
+                                       contentlet = page.contentlets.get(name__exact=self.name)
+                                       if contentlet.dynamic:
+                                               try:
+                                                       content = mark_safe(template.Template(contentlet.content, name=contentlet.name).render(context))
+                                               except template.TemplateSyntaxError, error:
+                                                       if settings.DEBUG:
+                                                               content = ('[Error parsing contentlet \'%s\': %s]' % self.name, error)
+                                       else:
+                                               content = contentlet.content
+                               except ObjectDoesNotExist:
+                                       pass
+               if content and self.as_var:
+                       context[self.as_var] = content
+                       return ''
+               return content
 
 
 def do_container(parser, token):
        """
-       {% container <name> [as <variable>] %}
+       {% container <name> [[references <type>] as <variable>] %}
        """
        params = token.split_contents()
-       if len(params) >= 2: # without as_var
+       if len(params) >= 2:
                name = params[1].strip('"')
+               references = None
                as_var = None
-               if len(params) == 4:
-                       as_var = params[3]
-               return ContainerNode(name, as_var)
+               if len(params) > 2:
+                       remaining_tokens = params[2:]
+                       while remaining_tokens:
+                               option_token = remaining_tokens.pop(0)
+                               if option_token == 'references':
+                                       try:
+                                               app_label, model = remaining_tokens.pop(0).strip('"').split('.')
+                                               references = ContentType.objects.get(app_label=app_label, model=model)
+                                       except IndexError:
+                                               raise template.TemplateSyntaxError('"container" template tag option "references" requires an argument specifying a content type')
+                                       except ValueError:
+                                               raise template.TemplateSyntaxError('"container" template tag option "references" requires an argument of the form app_label.model (see django.contrib.contenttypes)')
+                                       except ObjectDoesNotExist:
+                                               raise template.TemplateSyntaxError('"container" template tag option "references" requires an argument of the form app_label.model which refers to an installed content type (see django.contrib.contenttypes)')
+                               elif option_token == 'as':
+                                       try:
+                                               as_var = remaining_tokens.pop(0)
+                                       except IndexError:
+                                               raise template.TemplateSyntaxError('"container" template tag option "as" requires an argument specifying a variable name')
+                       if references and not as_var:
+                               raise template.TemplateSyntaxError('"container" template tags using "references" option require additional use of the "as" option specifying a variable name')
+               return ContainerNode(name, references, as_var)
        else: # error
-               raise template.TemplateSyntaxError('do_container template tag provided with invalid arguments')
-register.tag('container', do_container)
+               raise template.TemplateSyntaxError('"container" template tag provided without arguments (at least one required)')
+register.tag('container', do_container)
\ No newline at end of file