Merge branch 'master' of git://github.com/melinath/philo
[philo.git] / templatetags / embed.py
1 from django import template
2 from django.contrib.contenttypes.models import ContentType
3 from django.conf import settings
4 from philo.utils import LOADED_TEMPLATE_ATTR
5
6
7 register = template.Library()
8
9
10 class ConstantEmbedNode(template.Node):
11         """Analogous to the ConstantIncludeNode, this node precompiles several variables necessary for correct rendering - namely the referenced instance or the included template."""
12         def __init__(self, content_type, varname, object_pk=None, template_name=None, kwargs=None):
13                 assert template_name is not None or object_pk is not None
14                 self.content_type = content_type
15                 self.varname = varname
16                 
17                 kwargs = kwargs or {}
18                 for k, v in kwargs.items():
19                         kwargs[k] = template.Variable(v)
20                 self.kwargs = kwargs
21                 
22                 if object_pk is not None:
23                         self.compile_instance(object_pk)
24                 else:
25                         self.instance = None
26                 
27                 if template_name is not None:
28                         self.compile_template(template_name[1:-1])
29                 else:
30                         self.template = None
31         
32         def compile_instance(self, object_pk):
33                 self.object_pk = object_pk
34                 model = self.content_type.model_class()
35                 try:
36                         self.instance = model.objects.get(pk=object_pk)
37                 except model.DoesNotExist:
38                         if not hasattr(self, 'object_pk') and settings.TEMPLATE_DEBUG:
39                                 # Then it's a constant node.
40                                 raise
41                         self.instance = False
42         
43         def compile_template(self, template_name):
44                 try:
45                         self.template = template.loader.get_template(template_name)
46                 except template.TemplateDoesNotExist:
47                         if not hasattr(self, 'template_name') and settings.TEMPLATE_DEBUG:
48                                 # Then it's a constant node.
49                                 raise
50                         self.template = False
51         
52         def render(self, context):
53                 if self.template is not None:
54                         if self.template is False:
55                                 return settings.TEMPLATE_STRING_IF_INVALID
56                         
57                         if self.varname not in context:
58                                 context[self.varname] = {}
59                         context[self.varname][self.content_type] = self.template
60                         
61                         return ''
62                 
63                 # Otherwise self.instance should be set. Render the instance with the appropriate template!
64                 if self.instance is None or self.instance is False:
65                         return settings.TEMPLATE_STRING_IF_INVALID
66                 
67                 return self.render_template(context, self.instance)
68         
69         def render_template(self, context, instance):
70                 try:
71                         t = context[self.varname][self.content_type]
72                 except KeyError:
73                         return settings.TEMPLATE_STRING_IF_INVALID
74                 
75                 context.push()
76                 context['embedded'] = instance
77                 kwargs = {}
78                 for k, v in self.kwargs.items():
79                         kwargs[k] = v.resolve(context)
80                 context.update(kwargs)
81                 t_rendered = t.render(context)
82                 context.pop()
83                 return t_rendered
84
85
86 class EmbedNode(ConstantEmbedNode):
87         def __init__(self, content_type, varname, object_pk=None, template_name=None, kwargs=None):
88                 assert template_name is not None or object_pk is not None
89                 self.content_type = content_type
90                 self.varname = varname
91                 
92                 kwargs = kwargs or {}
93                 for k, v in kwargs.items():
94                         kwargs[k] = template.Variable(v)
95                 self.kwargs = kwargs
96                 
97                 if object_pk is not None:
98                         self.object_pk = template.Variable(object_pk)
99                 else:
100                         self.object_pk = None
101                         self.instance = None
102                 
103                 if template_name is not None:
104                         self.template_name = template.Variable(template_name)
105                 else:
106                         self.template_name = None
107                         self.template = None
108         
109         def render(self, context):
110                 if self.template_name is not None:
111                         template_name = self.template_name.resolve(context)
112                         self.compile_template(template_name)
113                 
114                 if self.object_pk is not None:
115                         object_pk = self.object_pk.resolve(context)
116                         self.compile_instance(object_pk)
117                 
118                 return super(EmbedNode, self).render(context)
119
120
121 def get_embedded(self):
122         return self.template
123
124
125 setattr(ConstantEmbedNode, LOADED_TEMPLATE_ATTR, property(get_embedded))
126
127
128 def do_embed(parser, token):
129         """
130         The {% embed %} tag can be used in three ways:
131         {% embed as <varname> %} :: This sets which variable will be used to track embedding template names for the current context. Default: "embed"
132         {% embed <app_label>.<model_name> with <template> %} :: Sets which template will be used to render a particular model.
133         {% embed <app_label>.<model_name> <object_pk> [<argname>=<value> ...]%} :: Embeds the instance specified by the given parameters in the document with the previously-specified template. Any kwargs provided will be passed into the context of the template.
134         """
135         args = token.split_contents()
136         tag = args[0]
137         
138         if len(args) < 2:
139                 raise template.TemplateSyntaxError('"%s" template tag must have at least three arguments.' % tag)
140         elif len(args) == 3 and args[1] == "as":
141                 parser._embedNodeVarName = args[2]
142                 return template.defaulttags.CommentNode()
143         else:
144                 if '.' not in args[1]:
145                         raise template.TemplateSyntaxError('"%s" template tag expects the first argument to be of the form app_label.model' % tag)
146                 
147                 app_label, model = args[1].split('.')
148                 try:
149                         ct = ContentType.objects.get(app_label=app_label, model=model)
150                 except ContentType.DoesNotExist:
151                         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)
152                 
153                 varname = getattr(parser, '_embedNodeVarName', 'embed')
154                 
155                 if args[2] == "with":
156                         if len(args) > 4:
157                                 raise template.TemplateSyntaxError('"%s" template tag may have no more than four arguments.' % tag)
158                         
159                         if args[3][0] in ['"', "'"] and args[3][0] == args[3][-1]:
160                                 return ConstantEmbedNode(ct, template_name=args[3], varname=varname)
161                         
162                         return EmbedNode(ct, template_name=args[3], varname=varname)
163                 
164                 object_pk = args[2]
165                 remaining_args = args[3:]
166                 kwargs = {}
167                 for arg in remaining_args:
168                         if '=' not in arg:
169                                 raise template.TemplateSyntaxError("Invalid keyword argument for '%s' template tag: %s" % (tag, arg))
170                         k, v = arg.split('=')
171                         kwargs[k] = v
172                 
173                 return EmbedNode(ct, object_pk=object_pk, varname=varname, kwargs=kwargs)
174
175
176 register.tag('embed', do_embed)