用户工具

站点工具


template:jinja-django

混合使用django模板和jinja模板

Django一直广受争论的地方就是它的模板功能,其中印象最深的一次是在python-cn上最初由一个与模板不太相关的主题引起的大讨论。

见 《听一个turbogears的家伙讲django该向zope学­什么》

http://groups.google.com/group/python-cn/browse_thread/thread/c32a8ba1b2e1f5f3

争论的焦点主要集中在django的模板功能太弱,扩展的filter,tag难写,是否应该在模板中直接允许执行更多的python代码等。

Django本身的观点

Django模板本身从设计之初就更多的考虑到模板的使用者是 页面设计人员 而非 后台程序员 , 所以设计的尽可能简单,从设计上去限制模板的不规范使用,以便更好的区分工作责任,这点从它的模板部分文档从最初就直接分为两份,分别适合以上两种人员进行细读就可以看出来。

后台程序员的观点

或许Django的使用者以后台程序员为主,所以很多人都强烈要求改进Django的模板,以便可以更方便的直接调用python代码。但是Django的开发团队对此要求始终无动于衷。于是出现了其他的一些 模板引擎,比如jinja 。

jinja2

jinja的使用上和Django及其相似,都主要通过 {{ }} 和{% %} 这两个东西里进行模板渲染,但是jinja允许你在模板中更多的使用python形式的代码。这在某些时候是的确是非常方便的。同时jinja自己宣称它比django的模板引擎拥有更好的性能。

在Django中使用jinja2

目前介绍在Django中使用jinja2的方式主要都是通过各种方式替换Django原有模板。最近看 《Pro Django》,其中第6章介绍模板的时候,提出了另一种在django中使用jinja2的方式。

这种方式是通过自定义一个需要有相应end的tag,然后在render的时候,将此对tag中的原始内容直接传给jinja2进行处理,因此此对 tag之间的内容就可以使用jinja的语法,而其他部分仍需符合django的模板语法。这对于只需要少量使用jinja2的情况下,相对于完整替换,这种方式更省时省力,也显得更干净利落。下面就是混合使用django和jinja2的代码示例:

view部分Python代码

# Create your views here.  
from django.http import HttpResponse  
from django.shortcuts import render_to_response  
from django.template import RequestContext  
 
def test(request):  
    user = 'myuser'  
    seq = [1,2,3]  
    def sum(a,b):  
         return a + b  
     return render_to_response('jinja_test.html',   
                               {'user':user, 'seq':seq, 'sum':sum,},   
                               context_instance=RequestContext(request))  

模板代码

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">  
<html>  
    <head>  
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
        <title>jinja_tag test</title>  
    </head>  
    <body>  
        {%load jinja_tag%}  
        {%jinja%}  
            {% for item in seq - %}  
                {{ item }}  
            {% - endfor %}  
            <br />  
            {{ 1+1*4 }}  
            <br />  
            {{ sum(1, seq[2]) }}  
            <br />  
        {%endjinja%}  
    </body>  
</html>  

从上面可以看到,包围在{%jinja%}和{%endjinja%}之间的代码使用jinja语法,而其他部分仍然限制在django的模板语法。

实现

其实实现这样一个jinja tag是非常容易的,具体原理可以看《pro django》原书,这里只贴下经过修改,修复了一些小bug的代码:

 import jinja2  
 from django import template  
 
 register = template.Library()  
 
 def string_from_token(token):  
     """ 
     Converts a lexer token back into a string for use with Jinja. 
     """  
     if token.token_type == template.TOKEN_TEXT:  
         return token.contents  
     elif token.token_type == template.TOKEN_VAR:  
         return '%s %s %s' % (  
             template.VARIABLE_TAG_START,  
             token.contents,  
             template.VARIABLE_TAG_END,  
         )  
     elif token.token_type == template.TOKEN_BLOCK:  
         return '%s%s%s' % (  
             template.BLOCK_TAG_START,  
             token.contents,  
             template.BLOCK_TAG_END,  
         )  
     elif token.token_type == template.TOKEN_COMMENT:  
         return u'' # Django doesn't store the content of comments  
 
 @register.tag  
 def jinja(parser, token):  
     """ 
     Define a block that gets rendered by Jinja, rather than Django's templates. 
     """  
     bits = token.contents.split()  
     if len(bits) != 1:  
         raise template.TemplateSyntaxError, "'%s' tag doesn't take any arguments." % bits[0]  
 
     # Manually collect tokens for the tag's content, so Django's template  
     # parser doesn't try to make sense of it.  
     contents = []  
     while 1:  
         try:  
             token = parser.next_token()  
         except IndexError:  
             # Reached the end of the template without finding the end tag  
             raise template.TemplateSyntaxError("'endjinja' tag is required.")  
         if token.token_type == template.TOKEN_BLOCK and token.contents == 'endjinja':  
             break  
         contents.append(string_from_token(token))  
     contents = ''.join(contents)  
     return JinjaNode(contents)  
 
 class JinjaNode(template.Node):  
     def __init__(self, contents):  
         self.template = jinja2.Template(contents)  
 
     def render(self, context):  
         # Jinja can't use Django's Context objects, so we have to  
         # flatten it out to a single dictionary before using it.  
         jinja_context = {}  
         for layer in context:  
             for key, value in layer.items():  
                 if key not in jinja_context:  
                     jinja_context[key] = value  
         return self.template.render(jinja_context)  

更好的集成

上面的HTML模板代码中,每次需要使用该 tag 的时候,都要经过 {%load jinja_tag%} 这个步骤,显得很麻烦。

其实可以将该 tag 添加到和django同样的builtin中,那样就可以像使用内置tag一样使用该tag了。

只需要 在 某个app目录下的 init.py 文件中添加以下代码就可以实现:

from django.template import add_to_builtins  
# Uncomment the next line to enable the jinja_tag as if defaulttags  
add_to_builtins('jinja_tag.templatetags.jinja_tag')  

源代码及使用参考

template/jinja-django.txt · 最后更改: 2011/02/11 04:24 (外部编辑)