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