混合使用django模板和jinja模板的讨论及实现

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

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

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

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

Django 本身的观点:

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

台程序员的观点:

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

jinja2

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

Django中使用jinja2

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

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

view部分:

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

模板代码:

1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> 
2.   <html> 
3.   <head> 
4.   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
5.   <title>jinja_tag test</title> 
6.   </head> 
7.   <body> 
8.   {%load jinja_tag%} 
9.   {%jinja%} 
10. {% for item in seq - %} 
11. {{ item }} 
12. {% - endfor %} 
13. <br /> 
14. {{ 1+1*4 }} 
15. <br /> 
16. {{ sum(1, seq[2]) }} 
17. <br /> 
18. {%endjinja%} 
19. </body> 
20. </html>

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

现:

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

1. import jinja2 
2.   from django import template 
3.    
4.   register = template.Library() 
5.    
6.   def string_from_token(token): 
7.   """
8.   Converts a lexer token back into a string for use with Jinja.
9.   """ 
10. if token.token_type == template.TOKEN_TEXT: 
11. return token.contents 
12. elif token.token_type == template.TOKEN_VAR: 
13. return '%s %s %s' % ( 
14. template.VARIABLE_TAG_START, 
15. token.contents, 
16. template.VARIABLE_TAG_END, 
17. ) 
18. elif token.token_type == template.TOKEN_BLOCK: 
19. return '%s%s%s' % ( 
20. template.BLOCK_TAG_START, 
21. token.contents, 
22. template.BLOCK_TAG_END, 
23. ) 
24. elif token.token_type == template.TOKEN_COMMENT: 
25. return u'' # Django doesn't store the content of comments 
26.  
27. @register.tag 
28. def jinja(parser, token): 
29. """
30. Define a block that gets rendered by Jinja, rather than Django's templates.
31. """ 
32. bits = token.contents.split() 
33. if len(bits) != 1: 
34. raise template.TemplateSyntaxError, "'%s' tag doesn't take any arguments." % bits[0] 
35.  
36. # Manually collect tokens for the tag's content, so Django's template 
37. # parser doesn't try to make sense of it. 
38. contents = [] 
39. while 1: 
40. try: 
41. token = parser.next_token() 
42. except IndexError: 
43. # Reached the end of the template without finding the end tag 
44. raise template.TemplateSyntaxError("'endjinja' tag is required.") 
45. if token.token_type == template.TOKEN_BLOCK and token.contents == 'endjinja': 
46. break 
47. contents.append(string_from_token(token)) 
48. contents = ''.join(contents) 
49. return JinjaNode(contents) 
50.  
51. class JinjaNode(template.Node): 
52. def __init__(self, contents): 
53. self.template = jinja2.Template(contents) 
54.  
55. def render(self, context): 
56. # Jinja can't use Django's Context objects, so we have to 
57. # flatten it out to a single dictionary before using it. 
58. jinja_context = {} 
59. for layer in context: 
60. for key, value in layer.items(): 
61. if key not in jinja_context: 
62. jinja_context[key] = value 
63. return self.template.render(jinja_context)

好的集成:

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

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

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

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

demo 和源代码下载:

demo http://www.playdjango.cn/jinja/

源代码及使用参考: http://code.google.com/p/django-demo-apps/source/browse/#svn/trunk/demo/jinja_tag

原文链接:http://www.wumii.com/item/17ne2AxLr