2 The embed template tags are automatically included as builtins if :mod:`philo` is an installed app.
9 The {% embed %} tag can be used in two ways.
11 First, to set which template will be used to render a particular model. This declaration can be placed in a base template and will propagate into all templates that extend that template.
15 {% embed <app_label>.<model_name> with <template> %}
17 Second, to embed a specific model instance in the document with a template specified earlier in the template or in a parent template using the first syntax. The instance can be specified as a content type and pk or as a context variable. Any kwargs provided will be passed into the context of the template.
21 {% embed (<app_label>.<model_name> <object_pk> || <instance>) [<argname>=<value> ...] %}
24 from django import template
25 from django.conf import settings
26 from django.contrib.contenttypes.models import ContentType
27 from django.template.loader_tags import ExtendsNode, BlockContext, BLOCK_CONTEXT_KEY, TextNode, BlockNode
29 from philo.utils import LOADED_TEMPLATE_ATTR
32 register = template.Library()
33 EMBED_CONTEXT_KEY = 'embed_context'
36 class EmbedContext(object):
37 "Inspired by django.template.loader_tags.BlockContext."
42 def add_embeds(self, embeds):
43 for content_type, embed_list in embeds.iteritems():
44 if content_type in self.embeds:
45 self.embeds[content_type] = embed_list + self.embeds[content_type]
47 self.embeds[content_type] = embed_list
49 def get_embed_template(self, embed, context):
50 """To return a template for an embed node, find the node's position in the stack
51 and then progress up the stack until a template-defining node is found
53 ct = embed.get_content_type(context)
54 embeds = self.embeds[ct]
55 embeds = embeds[:embeds.index(embed)][::-1]
57 template = e.get_template(context)
61 # No template was found in the current render_context - but perhaps one level up? Or more?
62 # We may be in an inclusion tag.
64 for context_dict in context.render_context.dicts[::-1]:
66 if self in context_dict.values():
69 elif EMBED_CONTEXT_KEY not in context_dict:
72 embed_context = context_dict[EMBED_CONTEXT_KEY]
73 # We can tell where we are in the list of embeds by which have already been rendered.
74 embeds = embed_context.embeds[ct][:len(embed_context.rendered)][::-1]
76 template = e.get_template(context)
83 # Override ExtendsNode render method to have it handle EmbedNodes
84 # similarly to BlockNodes.
85 old_extends_node_init = ExtendsNode.__init__
88 def get_embed_dict(embed_list, context):
91 ct = e.get_content_type(context)
93 # Then the embed doesn't exist for this context.
102 def extends_node_init(self, nodelist, *args, **kwargs):
103 self.embed_list = nodelist.get_nodes_by_type(ConstantEmbedNode)
104 old_extends_node_init(self, nodelist, *args, **kwargs)
107 def render_extends_node(self, context):
108 compiled_parent = self.get_parent(context)
109 embeds = get_embed_dict(self.embed_list, context)
111 if BLOCK_CONTEXT_KEY not in context.render_context:
112 context.render_context[BLOCK_CONTEXT_KEY] = BlockContext()
113 block_context = context.render_context[BLOCK_CONTEXT_KEY]
115 if EMBED_CONTEXT_KEY not in context.render_context:
116 context.render_context[EMBED_CONTEXT_KEY] = EmbedContext()
117 embed_context = context.render_context[EMBED_CONTEXT_KEY]
119 # Add the block nodes from this node to the block context
120 # Do the equivalent for embed nodes
121 block_context.add_blocks(self.blocks)
122 embed_context.add_embeds(embeds)
124 # If this block's parent doesn't have an extends node it is the root,
125 # and its block nodes also need to be added to the block context.
126 for node in compiled_parent.nodelist:
127 # The ExtendsNode has to be the first non-text node.
128 if not isinstance(node, TextNode):
129 if not isinstance(node, ExtendsNode):
130 blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
131 block_context.add_blocks(blocks)
132 embeds = get_embed_dict(compiled_parent.nodelist.get_nodes_by_type(ConstantEmbedNode), context)
133 embed_context.add_embeds(embeds)
136 # Explicitly render all direct embed children of this node.
138 for node in self.nodelist:
139 if isinstance(node, ConstantEmbedNode):
142 # Call Template._render explicitly so the parser context stays
144 return compiled_parent._render(context)
147 ExtendsNode.__init__ = extends_node_init
148 ExtendsNode.render = render_extends_node
151 class ConstantEmbedNode(template.Node):
152 """Analogous to the ConstantIncludeNode, this node precompiles several variables necessary for correct rendering - namely the referenced instance or the included template."""
153 def __init__(self, content_type, object_pk=None, template_name=None, kwargs=None):
154 assert template_name is not None or object_pk is not None
155 self.content_type = content_type
157 kwargs = kwargs or {}
158 for k, v in kwargs.items():
162 if object_pk is not None:
163 self.instance = self.compile_instance(object_pk)
167 if template_name is not None:
168 self.template = self.compile_template(template_name[1:-1])
172 def compile_instance(self, object_pk):
173 model = self.content_type.model_class()
175 return model.objects.get(pk=object_pk)
176 except model.DoesNotExist:
177 if not hasattr(self, 'object_pk') and settings.TEMPLATE_DEBUG:
178 # Then it's a constant node.
182 def get_instance(self, context):
185 def compile_template(self, template_name):
187 return template.loader.get_template(template_name)
188 except template.TemplateDoesNotExist:
189 if hasattr(self, 'template') and settings.TEMPLATE_DEBUG:
190 # Then it's a constant node.
194 def get_template(self, context):
197 def get_content_type(self, context):
198 return self.content_type
200 def check_context(self, context):
201 if EMBED_CONTEXT_KEY not in context.render_context:
202 context.render_context[EMBED_CONTEXT_KEY] = EmbedContext()
203 embed_context = context.render_context[EMBED_CONTEXT_KEY]
205 ct = self.get_content_type(context)
206 if ct not in embed_context.embeds:
207 embed_context.embeds[ct] = [self]
208 elif self not in embed_context.embeds[ct]:
209 embed_context.embeds[ct].append(self)
211 def mark_rendered_for(self, context):
212 context.render_context[EMBED_CONTEXT_KEY].rendered.append(self)
214 def render(self, context):
215 self.check_context(context)
217 template = self.get_template(context)
218 if template is not None:
219 self.mark_rendered_for(context)
220 if template is False:
221 return settings.TEMPLATE_STRING_IF_INVALID
224 # Otherwise an instance should be available. Render the instance with the appropriate template!
225 instance = self.get_instance(context)
226 if instance is None or instance is False:
227 self.mark_rendered_for(context)
228 return settings.TEMPLATE_STRING_IF_INVALID
230 return self.render_instance(context, instance)
232 def render_instance(self, context, instance):
234 t = context.render_context[EMBED_CONTEXT_KEY].get_embed_template(self, context)
235 except (KeyError, IndexError):
236 self.mark_rendered_for(context)
237 return settings.TEMPLATE_STRING_IF_INVALID
240 context['embedded'] = instance
241 for k, v in self.kwargs.items():
242 context[k] = v.resolve(context)
243 t_rendered = t.render(context)
245 self.mark_rendered_for(context)
249 class EmbedNode(ConstantEmbedNode):
250 def __init__(self, content_type, object_pk=None, template_name=None, kwargs=None):
251 assert template_name is not None or object_pk is not None
252 self.content_type = content_type
253 self.kwargs = kwargs or {}
255 if object_pk is not None:
256 self.object_pk = object_pk
258 self.object_pk = None
260 if template_name is not None:
261 self.template_name = template_name
263 self.template_name = None
265 def get_instance(self, context):
266 if self.object_pk is None:
268 return self.compile_instance(self.object_pk.resolve(context))
270 def get_template(self, context):
271 if self.template_name is None:
273 return self.compile_template(self.template_name.resolve(context))
276 class InstanceEmbedNode(EmbedNode):
277 def __init__(self, instance, kwargs=None):
278 self.instance = instance
279 self.kwargs = kwargs or {}
281 def get_template(self, context):
284 def get_instance(self, context):
285 return self.instance.resolve(context)
287 def get_content_type(self, context):
288 instance = self.get_instance(context)
291 return ContentType.objects.get_for_model(instance)
294 def get_embedded(self):
298 setattr(ConstantEmbedNode, LOADED_TEMPLATE_ATTR, property(get_embedded))
301 def parse_content_type(bit, tagname):
303 app_label, model = bit.split('.')
305 raise template.TemplateSyntaxError('"%s" template tag expects the first argument to be of the form app_label.model' % tagname)
307 ct = ContentType.objects.get(app_label=app_label, model=model)
308 except ContentType.DoesNotExist:
309 raise template.TemplateSyntaxError('"%s" template tag requires an argument of the form app_label.model which refers to an installed content type (see django.contrib.contenttypes)' % tagname)
313 def do_embed(parser, token):
315 {% embed <app_label>.<model_name> with <template> %}
316 {% embed (<app_label>.<model_name> <object_pk> || <instance>) [<argname>=<value> ...] %}
319 bits = token.split_contents()
323 raise template.TemplateSyntaxError('"%s" template tag must have at least two arguments.' % tag)
325 if len(bits) == 3 and bits[-2] == 'with':
326 ct = parse_content_type(bits[0], tag)
328 if bits[2][0] in ['"', "'"] and bits[2][0] == bits[2][-1]:
329 return ConstantEmbedNode(ct, template_name=bits[2])
330 return EmbedNode(ct, template_name=bits[2])
332 # Otherwise they're trying to embed a certain instance.
337 k, v = bit.split('=')
338 kwargs[k] = parser.compile_filter(v)
342 raise template.TemplateSyntaxError('"%s" template tag expects at least one non-keyword argument when embedding instances.')
345 instance = parser.compile_filter(bits[0])
346 return InstanceEmbedNode(instance, kwargs)
348 raise template.TemplateSyntaxError('"%s" template tag expects at most 2 non-keyword arguments when embedding instances.')
349 ct = parse_content_type(bits[0], tag)
355 return EmbedNode(ct, object_pk=parser.compile_filter(pk), kwargs=kwargs)
357 return ConstantEmbedNode(ct, object_pk=pk, kwargs=kwargs)
360 register.tag('embed', do_embed)