Created
January 22, 2014 06:50
-
-
Save boekkooi/8554447 to your computer and use it in GitHub Desktop.
Django - A hack to apply custom templates to tags registered using inclusion_tag, this is related to https://code.djangoproject.com/ticket/9093
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import ctypes | |
import types | |
from django.template import TemplateSyntaxError, Token, TOKEN_BLOCK | |
from django.template.defaulttags import register | |
if sys.version_info < (3,): | |
cb_func_code = 'func_code' | |
cb_func_closure = 'func_closure' | |
else: | |
cb_func_code = '__code__' | |
cb_func_closure = '__closure__' | |
@register.tag('using') | |
def do_using(parser, token): | |
""" | |
Adds custom template for template tags defined with inclusion_tag | |
For example:: | |
{% using "my_template.html" for some_tag %} | |
""" | |
bits = token.split_contents() | |
if len(bits) < 4: | |
raise TemplateSyntaxError("using takes at least 4 arguments") | |
if bits[2] != 'for': | |
raise TemplateSyntaxError("Invalid syntax in using tag. Expecting 'for' keyword") | |
template_name = str(bits[1]).strip('"').strip("'") | |
tag_name = bits[3] | |
tag_content = token.contents[token.contents.find(tag_name):] | |
if tag_name not in parser.tags: | |
raise TemplateSyntaxError("using received an invalid tag: %s" % tag_name) | |
tag_compile = parser.tags[tag_name] | |
def render_injector(self, context): | |
""" | |
This method injects a custom file_name into the render function of the bound object | |
""" | |
func = types.MethodType(self.__class__.render, self) | |
func_closure = getattr(func, cb_func_closure) | |
template_idx = getattr(func, cb_func_code).co_freevars.index('file_name') | |
func_var = func_closure[template_idx] | |
# Prepare for c injection | |
c_target = ctypes.py_object(func_var) | |
c_org_value = ctypes.py_object(func_var.cell_contents) | |
c_new_value = ctypes.py_object(template_name) | |
# Do a very dirty HACK! to override the file_name | |
ctypes.pythonapi.PyCell_Set(c_target, c_new_value) | |
try: | |
return func(context) | |
finally: | |
ctypes.pythonapi.PyCell_Set(c_target, c_org_value) | |
# Compile a tag node | |
tag_node = tag_compile(parser, Token(TOKEN_BLOCK, tag_content)) | |
# Validate render method | |
if not hasattr(tag_node.render, cb_func_closure) or \ | |
not hasattr(tag_node.render, cb_func_code) or \ | |
'file_name' not in getattr(tag_node.render, cb_func_code).co_freevars: | |
raise RuntimeError('using is unable to use callback invalid closure/function specified.') | |
# Set the render_injector as the render method | |
tag_node.render = types.MethodType(render_injector, tag_node) | |
return tag_node |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment