user178884
user178884

Reputation:

Django inclusion tag with configurable template

I've created an inclusion tag, however I'd like to be able to make the template optionally configurable. There doesn't seem to be support for this out of the box, so I'd like to see how people did this - maybe a method search the templates directory first for a specific template name and then falling back to the default template.

@register.inclusion_tag('foo.html', takes_context=True)

Upvotes: 24

Views: 9594

Answers (6)

geogeo
geogeo

Reputation: 154

A variation of the @cvng's answer that doesn't require creating an include_tag.html template:

@register.inclusion_tag(template.Template('{% include dynamic_template %}'),
                        takes_context=True)
def dynamic_tag(context):
    context.update({
        'dynamic_template': 'path/to/your/template.html',
    })
    return context

Upvotes: 0

cmaluenda
cmaluenda

Reputation: 2469

This post saved my life: http://djangosnippets.org/snippets/1329/

The key is to add to a "dummy template":

{% extends template %}

Upvotes: 14

cvng
cvng

Reputation: 1878

A solution could be a regular inclusion_tag which pass dynamic template name to context.

Like this :

# templatetags/tags.py

@register.inclusion_tag('include_tag.html', takes_context=True)
def tag_manager(context):
    context.update({
        'dynamic_template': resolve_template(context),
    })
    return context

And the template:

<!-- include_tag.html -->

{% include dynamic_template %}

The tricks here is, when I call {% tag_manager %}, it includes include_tag.html which in turn includes the template returned by resolve_template() (not included for brevity).

Hope this helps...

Upvotes: 0

bbrik
bbrik

Reputation: 3036

I use simple_tag when i need to do that:

from django.template import Library, loader, Context

@register.simple_tag(takes_context=True)
def my_tag(context, template_name):

    var1 = ...

    t = loader.get_template(template_name)
    return t.render(Context({
        'var1': var1,
        ...
    }))

Upvotes: 38

gonz
gonz

Reputation: 5276

I had to do something like this for a project and since we needed more than one of this kind of inclusion tag I made a decorator based on django inclusion_tag decorator. This is the code:

# -*- coding: utf-8 -*-
from django import template
from inspect import getargspec
from django.template.context import Context
from django.template import Node, generic_tag_compiler, Variable
from django.utils.functional import curry


def inclusion_tag(register, context_class=Context, takes_context=False):
    def dec(func):
        params, xx, xxx, defaults = getargspec(func)
        if takes_context:
            if params[0] == 'context':
                params = params[1:]
            else:
                raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")

        class InclusionNode(Node):
            def __init__(self, vars_to_resolve):
                self.vars_to_resolve = map(Variable, vars_to_resolve)

            def render(self, context):
                resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
                if takes_context:
                    args = [context] + resolved_vars
                else:
                    args = resolved_vars

                file_name, extra_context = func(*args)

                from django.template.loader import get_template, select_template
                if not isinstance(file_name, basestring) and is_iterable(file_name):
                    t = select_template(file_name)
                else:
                    t = get_template(file_name)
                self.nodelist = t.nodelist
                new_context = context_class(extra_context, autoescape=context.autoescape)
                # Copy across the CSRF token, if present, because inclusion
                # tags are often used for forms, and we need instructions
                # for using CSRF protection to be as simple as possible.
                csrf_token = context.get('csrf_token', None)
                if csrf_token is not None:
                    new_context['csrf_token'] = csrf_token
                return self.nodelist.render(new_context)

        compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
        compile_func.__doc__ = func.__doc__
        register.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
        return func
    return dec

You have to return a tuple with the template (or template list) and the context dict. Note that you have to pass the register (Library instance) in the decorator call:

from somewhere import inclusion_tag
@inclusion_tag(register)
def display_formset(formset):
    template_name = FORMSET_TEMPLATES.get(formset.model,
        'includes/inline_formset.html')
    return (template_name, {'formset': formset})

Hope this helps

Upvotes: 4

Daniel Roseman
Daniel Roseman

Reputation: 599610

The inclusion_tag decorator is just a shortcut - it's meant as a simple way of rendering a specific template with a specific context. As soon as you want to move outside of that, it can no longer help you. But that just means you'll have to write the tag the long way, as explained in the documentation, and pass the template you want as a parameter.

Upvotes: 8

Related Questions