Merge branch 'fast_containers' into develop
[philo.git] / philo / templatetags / containers.py
1 """
2 The container template tags are automatically included as builtins if :mod:`philo` is an installed app.
3
4 """
5
6 from django import template
7 from django.conf import settings
8 from django.contrib.contenttypes.models import ContentType
9 from django.core.exceptions import ObjectDoesNotExist
10 from django.db.models import Q
11 from django.utils.safestring import SafeUnicode, mark_safe
12
13
14 register = template.Library()
15
16
17 CONTAINER_CONTEXT_KEY = 'philo_container_context'
18
19
20 class ContainerContext(object):
21         def __init__(self, page):
22                 self.page = page
23         
24         def get_contentlets(self):
25                 if not hasattr(self, '_contentlets'):
26                         self._contentlets = dict(((c.name, c) for c in self.page.contentlets.all()))
27                 return self._contentlets
28         
29         def get_references(self):
30                 if not hasattr(self, '_references'):
31                         references = self.page.contentreferences.all()
32                         self._references = {}
33                         contents = {}
34                         for c in references:
35                                 ct = ContentType.objects.get_for_id(c.content_type_id)
36                                 self.references[(c.name, ct)] = c
37                                 contents.setdefault(ct, {})[c.content_id] = c
38                         
39                         for ct in contents:
40                                 objs = ct.model_class().objects.filter(pk__in=contents[ct])
41                                 for obj in objs:
42                                         contents[ct][obj.pk].content = obj
43                 return self._references
44
45
46 class ContainerNode(template.Node):
47         def __init__(self, name, references=None, as_var=None):
48                 self.name = name
49                 self.as_var = as_var
50                 self.references = references
51         
52         def render(self, context):
53                 container_content = self.get_container_content(context)
54                 
55                 if self.as_var:
56                         context[self.as_var] = container_content
57                         return ''
58                 
59                 return container_content
60         
61         def get_container_content(self, context):
62                 try:
63                         container_context = context.render_context[CONTAINER_CONTEXT_KEY]
64                 except KeyError:
65                         try:
66                                 page = context['page']
67                         except KeyError:
68                                 return settings.TEMPLATE_STRING_IF_INVALID
69                         
70                         container_context = ContainerContext(page)
71                         context.render_context[CONTAINER_CONTEXT_KEY] = container_context
72                 
73                 if self.references:
74                         # Then it's a content reference.
75                         try:
76                                 contentreference = container_context.get_references()[(self.name, self.references)]
77                         except KeyError:
78                                 content = ''
79                         else:
80                                 content = contentreference.content
81                 else:
82                         # Otherwise it's a contentlet.
83                         try:
84                                 contentlet = container_context.get_contentlets()[self.name]
85                         except KeyError:
86                                 content = ''
87                         else:
88                                 content = contentlet.content
89                 return content
90
91
92 @register.tag
93 def container(parser, token):
94         """
95         If a template using this tag is used to render a :class:`.Page`, that :class:`.Page` will have associated content which can be set in the admin interface. If a content type is referenced, then a :class:`.ContentReference` object will be created; otherwise, a :class:`.Contentlet` object will be created.
96         
97         Usage::
98         
99                 {% container <name> [[references <app_label>.<model_name>] as <variable>] %}
100         
101         """
102         params = token.split_contents()
103         if len(params) >= 2:
104                 tag = params[0]
105                 name = params[1].strip('"')
106                 references = None
107                 as_var = None
108                 if len(params) > 2:
109                         remaining_tokens = params[2:]
110                         while remaining_tokens:
111                                 option_token = remaining_tokens.pop(0)
112                                 if option_token == 'references':
113                                         try:
114                                                 app_label, model = remaining_tokens.pop(0).strip('"').split('.')
115                                                 references = ContentType.objects.get_by_natural_key(app_label, model)
116                                         except IndexError:
117                                                 raise template.TemplateSyntaxError('"%s" template tag option "references" requires an argument specifying a content type' % tag)
118                                         except ValueError:
119                                                 raise template.TemplateSyntaxError('"%s" template tag option "references" requires an argument of the form app_label.model (see django.contrib.contenttypes)' % tag)
120                                         except ObjectDoesNotExist:
121                                                 raise template.TemplateSyntaxError('"%s" template tag option "references" requires an argument of the form app_label.model which refers to an installed content type (see django.contrib.contenttypes)' % tag)
122                                 elif option_token == 'as':
123                                         try:
124                                                 as_var = remaining_tokens.pop(0)
125                                         except IndexError:
126                                                 raise template.TemplateSyntaxError('"%s" template tag option "as" requires an argument specifying a variable name' % tag)
127                         if references and not as_var:
128                                 raise template.TemplateSyntaxError('"%s" template tags using "references" option require additional use of the "as" option specifying a variable name' % tag)
129                 return ContainerNode(name, references, as_var)
130                 
131         else: # error
132                 raise template.TemplateSyntaxError('"%s" template tag provided without arguments (at least one required)' % tag)