翻译:introduce to tornado - Extending Templates

在第二章中,咱们看到了如何使用tornado的template轻松地将数据从handler传送给web页面。让咱们在保持简洁的web tag结构的同时,轻松地向web页面插入动态数据,然而大部分网站都但愿使用一个高可用的响应模型,将内容按照页眉、页脚和布局框架的形式进行管理。在这一章咱们将让你了解到如何经过tornado的template或UI模块完成这种扩展。 javascript

块和替换 css

当你花费了大量的时间为你的web应用建立和制做模板时,你有没有发现,它们彷佛只是按照逻辑的形式进行布局,你但愿你的前端代码和后端代码同样可以尽量多的重用对吗?tornado提供了丰富的模板集成和扩展的块语句帮助你完成这一点,tornado能够按照你想要的方式灵活的控制和改变你现有的模板,提升它们的重用性。若是想要扩展示有的模板,你只须要将{% extends ”filename.html“%}放到你的模板中。例如使用你的父模板(main.html)去扩展一个新的模板,你只须要这么作: html

  1. {% extends ”main.html” %}  

它将会在新的web页面中继承并使用main.html,而后将main.html的内容插入到你但愿显示的地方。有了这个系统,你能够建立一个主模板,嵌入到其余有特殊需求的子模版中,在子模块中你可使用动态内容或效果快速地扩展你的应用。 前端

基本的块 java

除非你能够快速的使用和改变tornado中的模板,不然不建议你去改动扩展模板,你可使用块语句去完成你的改动。 python

一个块语句能够将一些元素封装到模板中,假如你想要改变它。例如为了实现一个动态的标题,你但愿这个效果能覆盖每个页面,那么你能够把这个效果写到你的父模板main.html中。 jquery

Code    View Copy Print
  1. <header>  
  2. {% block header %}{% end %}   
  3. </header>  

而后,去重写子模块中的{% block header%}{% end %}切分的内容。你能够参考下面的方式使用任何内容填充: web

Code    View Copy Print
  1. {% extends main.html %}   
  2.   
  3. {% block header %}   
  4.     <h1>Hello world!</h1>  
  5. {% end %}  

全部继承的模板均可以将{% block header %} 和 {% end %}的标签插入到任何地方。你能够经过一个简单的python脚本在web应用中调用一个已命名的子模板,就像这样: ajax

Code    View Copy Print
  1. class MainHandler(tornado.web.RequestHandler):   
  2.     def get(self):   
  3.         self.render(“index.html”)  

例如在这里,index.html会在页面显示一个main.html的内容:“hello world”,你能够在图片3-1中查看到效果。 mongodb

如今咱们能够看到,这是一个很是有用的功能,让咱们能够更快地去管理多个页面的总体页面结构,你还可使用不一样的block在同一个页面中,动态的元素如页眉、页脚均可以放在同一个页面中。

下面是一个例子,咱们添加了多个blocks到咱们父模板中:

Code    View Copy Print
  1. <html>  
  2.     <body>  
  3.     <header>  
  4.         {% block header %}{% end %}   
  5.     </header>  
  6.         <content>  
  7.             {% block body %}{% end %}   
  8.         </content>  
  9.     <footer>  
  10.         {% block footer %}{% end %}   
  11.     </footer>  
  12.     </body>  
  13. </html>  

图3-1 hello world

 

咱们能够参考这个子模板index.html的形式去扩展咱们的父模板main.html。

Code    View Copy Print
  1. {% extends ”main.html” %}   
  2. {% block header %}   
  3.     <h1>{{ header_text }}</h1>  
  4. {% end %}   
  5. {% block body %}   
  6.     <p>Hello from the child template!</p>  
  7. {% end %}   
  8. {% block footer %}   
  9.     <p>{{ footer_text }}</p>  
  10. {% end %}  

在python脚本和以前看上去的同样,只不过咱们如今经过添加一些变量将数据插入到template中,请查看效果图3-2:

Code    View Copy Print
  1. class MainHandler(tornado.web.RequestHandler):   
  2.     def get(self):   
  3.         self.render(   
  4.             ”index.html”,   
  5.             header_text = ”Header goes here”,   
  6.             footer_text = ”Footer goes here”   
  7.         )  

 

图3-2

 

你也能够在父模板中放入一些默认的文本到块语句标识符内。假如扩展的模板没有指定本身的替换块,将会显示默认的文本,这种方式让你能够根据须要灵活地修改其中一些页面的块语句,特别适合导入或替换:JavaScript、CSS文件和标识的块。

图3-3

一个模板的文档标识应该可以帮助咱们显示一些错误的语法或异常的关闭:“错误报告位于……”。有一些{% block %}声明的异常关闭或语法错误将会致使返回一个500:内部服务器错误的提示(若是你是在debug模式下运行,会显示一个完整的python堆栈跟踪表)到浏览器上。(请查看图3-3的内容)

总而言之,你本身须要保证模板的健壮性,并尽量地在错误出现前找到它。

一个模板的练习:Burt’s Books

你是否定为这些听起来很是有趣,可是又想象不出来如何在一个web应用中使用这些特性呢?让咱们来看一个例子吧:咱们的好朋友Burt经营了一家名为Burt’s Book的书店

Burt经过商店购买了不少书,他如今须要一个网站为来访者展现不一样的书籍介绍及更多的东西,Burt但愿有一个页面布局上保持一致,可是又能够很是方便地更新页面和内容的网站。

为此,Burt‘s Book使用了Tornado来搭建这个网站,使用一个主模板来定义全部的样式、布局、标题、页眉、页脚等细节,而后用一个很是轻量的子模板处理页面信息,有了这样一个应用系统,Burt就能够在一个页面完成发布图书信息、员工建议、事件安排等更多共享信息的操做。Burt’s Book的网站基于一个主模板main.html来完成网站的总体架构,它看起来是这样的:

Code    View Copy Print
  1. <html>  
  2.     <head>  
  3.         <title>{{ page_title }}</title>  
  4.         <link rel=“stylesheet” href=”{{ static_url(“css/style.css”) }}” />  
  5.     </head>  
  6.     <body>  
  7.         <div id=“container”>  
  8.             <header>  
  9.                 {% block header %}<h1>Burt’s Books</h1>{% end %}   
  10.             </header>  
  11.             <div id=“main”>  
  12.                 <div id=“content”>  
  13.                     {% block body %}{% end %}   
  14.                 </div>  
  15.             </div>  
  16.             <footer>  
  17.                 {% block footer %}   
  18.                     <p>  
  19.         For more information about our selection, hours or events, please email us at   
  20.         <a href=“mailto:contact@burtsbooks.com”>contact@burtsbooks.com</a>.   
  21.                     </p>  
  22.                 {% end %}   
  23.             </footer>  
  24.         </div>  
  25.         <script src=”{{ static_url(“js/script.js”) }}”></script>  
  26. <    /body>  
  27. </html>  

这个页面定义了整个结构,应用了一个CSS样式表,而且加载了主要的JavaScript文件。其它模板能够对这个主模板进行扩展,替换掉其中的页面、页脚、内容。

通过扩展main.html后,咱们只须要替换掉页眉、和内容的默认文本就能够实现一个index.html页面,这个网站的首页index.html向web访问的人提供了一些关于商店的信息。

Code    View Copy Print
  1. {% extends ”main.html” %}   
  2. {% block header %}   
  3.     <h1>{{ header_text }}</h1>  
  4. {% end %}   
  5. {% block body %}   
  6.     <div id=“hello”>  
  7.         <p>Welcome to Burt’s Books!<p>  
  8.         <p></p>  
  9.     </div>  
  10. {% end %}  

在这里咱们全部的子模板使用tornado模板继承main.html默认的页脚,将全部的信息传送给咱们的index.html模板以后,这个Burt’s Book 站点的python脚本 main.py就能够运行了。

Code    View Copy Print
  1. import tornado.web   
  2. import tornado.httpserver   
  3. import tornado.ioloop   
  4. import tornado.options   
  5. import os.path   
  6. from tornado.options import define, options   
  7. define(“port”, default=8000, help=”run on the given port”, type=int)   
  8. class Application(tornado.web.Application):   
  9.     def __init__(self):   
  10.         handlers = [   
  11.             (r"/", MainHandler),   
  12.         ]   
  13.         settings = dict(   
  14.             template_path=os.path.join(os.path.dirname(__file__), ”templates”),   
  15.             static_path=os.path.join(os.path.dirname(__file__), ”static”),   
  16.             debug=True,   
  17.         )   
  18.         tornado.web.Application.__init__(self, handlers, **settings)   
  19.   
  20. class MainHandler(tornado.web.RequestHandler):   
  21.     def get(self):   
  22.         self.render(   
  23.             ”index.html”,   
  24.             page_title = ”Burt’s Books | Home”,   
  25.             header_text = ”Welcome to Burt’s Books!”,   
  26.         )   
  27.   
  28. if __name__ == ”__main__“:   
  29.     tornado.options.parse_command_line()   
  30.     http_server = tornado.httpserver.HTTPServer(Application())   
  31.     http_server.listen(options.port)   
  32.     tornado.ioloop.IOLoop.instance().start()  

这个例子的结构和咱们以前见到的彷佛有些不一样,可是这没什么好怕的,咱们不是经过调用tornado.web.application构造函数列表的实例的形式来实现,而是经过和其它参数来定义咱们本身的应用类,咱们经过一个很简单的方式去初始化和调用咱们本身实现的方法,咱们建立了一个handlers的列表和一个字典去传递对应的数值,而且使用这些值去调用并初始化咱们的父类。像这样

tornado.web.Application.__init__(self, handlers, **settings)

当这个应用系统完成以后,Burt’s Book就能够很轻松地改变索引页面,而且保证main.html模板能够正常地被其它子页面调用。此外他们还能够发挥tornado框架的优点,经过python脚本让网站使用动态的内容或者数据库,咱们将在后续的部分看到更多细节的实现。

转义

在默认状况下,tornado将会对HTML模板开启自动转义,将其转换为关联的HTML实体,这有助于防止恶意脚本对网站数据库的攻击,假如你的网站有一个讨论的功能,用户能够添加任何他们喜欢的文章并对此进行讨论。虽然大部分的HTML tag并不可以给网站带来危险,可是一些未转义的<script>标记可让攻击者加载外部的JavaScript文件,开启一些后门、跨站脚本、XSS漏洞等等。

让咱们来思考一下这个例子,Burt’s Book有一个用户反馈的页面, Melvin今天在评论表单中提交了一个恶意攻击的文本:

  1. Totally hacked your site lulz »   
  2. &lt;script&gt;alert(‘RUNNING EVIL H4CKS AND SPL01TS NOW…’)&lt;/script&gt;  

如今当Alice登陆这个网站的时候,他的网页将会显示图3-4的内容。

图3-4

 

在tornado1.x中,template没有提供自动转移的功能,因此咱们须要讨论一下,如何经过调用escape()方法清除用户的危险输入。

在这里咱们能够看到怎么经过转义去保护你的用户不被恶意代码攻击。可是它也一样会拦截你的一些动态的HTML模板和模块。

例如,Burt想要经过模板的变量添加一个邮箱的链接信息在页脚,Burt添加的链接将会被拦截。让咱们看看Burt的代码:

Code    View Copy Print
  1. {% set mailLink = ”<a href=\”mailto:contact@burtsbooks.com\”>Contact Us</a>“ %}   
  2. {{ mailLink }}  

这段代码在页面中将会被调整成下面的内容:

Code    View Copy Print
  1. &lt;a href=&quot;mailto:contact@burtsbooks.com&quot;&gt;Contact Us&lt;/a&gt;  

图3-5

这就是转义autoescaping()致使的,很明显,用户将没办法联系到Burt。

为了处理这样的状况,你能够经过设置autoescape = None禁用autoescaping功能,或者像下面这样在每一页修改autoescape的功能。

  1. {% autoescape None %}   
  2. {{ mailLink }}  

这些autoescape 不须要使用{end} tag标记结束,固然咱们也能够经过设置xhtml_escape去启用autoescaping(这是默认开启的),或者将其关闭。

实际上,你不管如何都要一直启用autoescaping对网站进行保护,固然也能够在标签中使用{%raw %}去禁用自动转义对模板的渲染。

这里有一个特别重要的事情,当你使用tornado的linkify()和xsrf_form_html()功能时,会影响autoescaping的设置。例如:你想要使用linkify()在页脚中插入一个连接(autoescaping已经启用了),你可使用{%raw%}来实现关闭autoescaping功能:

Code    View Copy Print
  1. {% block footer %}   
  2.     <p>  
  3.         For more information about our selection, hours or events, please email us at   
  4.         <a href=“mailto:contact@burtsbooks.com”>contact@burtsbooks.com</a>.   
  5.     </p>  
  6.     <p class=“small”>  
  7.         Follow us on Facebook at   
  8.         {% raw linkify(“https://fb.me/burtsbooks”, extra_params=‘ref=website’) %}.   
  9.     </p>  
  10. {% end %}  

这样你就能够快速的在这里使用linkify()功能,可是在其余地方autoescaping仍然起做用

 

UI模块

正如咱们看到的,template系统很是轻量级但功能又很是强大,在实际使用中,咱们还须要听从一些软件工程的原则:DRY原则(Don’t Repeat Yourself)。不要在项目中出现重复的代码,咱们能够经过template 模块尽量地去掉冗余代码。例如,咱们能够为显示物品清单定义一个列表模板,在每个须要使用的地方经过render去调用它,此外还能够定义一个导航模板放到共享模板中。tornado的UI模块能够有效的解决这些问题。

UI模块将不少可重用的组件:设计风格、样式、特效放到了一个模板中。这些页面元素一般都被多个模板重用或在一个模板中反复使用。module模块自己就是一个简单的python类,它继承了tornado的UImodule类中定义的render方法。当一个模模板经过{%module Foo(…)%} tag 去引用另外一个模板时 ,tornado的template 引擎将会去调用module类中的render方法进行渲染,而后返回一个替换模板的字符串给template。UI模块能够嵌入本身的JavaScript和CSS到渲染的页面中,固然你也能够定义一些可选的embedded_javascript , embedded_css,javascript_file,css_file等文件到页面中。

使用基本模块

想要在你的模板中引入一个模块,你必需要在应用程序中声明它。这个UI 模块将会把maps模块做为参数导入到模板中,请查看例子3-1:

Code    View Copy Print
  1. import tornado.web   
  2. import tornado.httpserver   
  3. import tornado.ioloop   
  4. import tornado.options   
  5. import os.path   
  6. from tornado.options import define, options   
  7. define(“port”, default=8000, help=”run on the given port”, type=int)   
  8.   
  9. class HelloHandler(tornado.web.RequestHandler):   
  10.     def get(self):   
  11.         self.render(‘hello.html’)   
  12.   
  13. class HelloModule(tornado.web.UIModule):   
  14.     def render(self):   
  15.         return ’<h1>Hello, world!</h1>’   
  16.   
  17. if __name__ == ’__main__‘:   
  18.     tornado.options.parse_command_line()   
  19.     app = tornado.web.Application(   
  20.         handlers=[(r'/', HelloHandler)],   
  21.         template_path=os.path.join(os.path.dirname(__file__), ’templates’),   
  22.         ui_modules={‘Hello’, HelloModule}   
  23.     )   
  24.     server = tornado.httpserver.HTTPServer(app)   
  25.     server.listen(options.port)   
  26.     tornado.ioloop.IOLoop.instance().start()  

在这个例子中只有一个项用到了UI_module字典。同时咱们在HelloModule类中已经定义了一个名字为Hello的模块,如今当咱们调用HelloHandler 时将会显示hello.html,咱们能够经过{%module Hello()%} 这个模板标签导入HelloModule类渲染后生成的字符串:

Code    View Copy Print
  1. <html>  
  2.     <head><title>UI Module Example</title></head>  
  3.     <body>  
  4.         {% module Hello() %}   
  5.     </body>  
  6. </html>  

这个hello.html模板将会用调用HelloModule类返回的字符串,去替换掉module标签。这个例子在接下来的部分将会展现如何扩展UI模块,导入JavaScript脚本和样式表来渲染咱们的模板。

深刻模块

一般,咱们会将模板中的module 标签用module类中渲染的字符串替换掉。这些模板将会被咱们当成一个总体。

在一个应用程序中,UI模块一般用来对数据库查询或者API查询的结果进行迭代,在一个独立的项目中展示带有相同表示的数据。例如Burt但愿可以在网站中建立一个推荐阅读的模块,在下面的代码中咱们能够看到,他将会建立一个recommended.html模板,并经过{%module Book(book)%} tag来插入数据:

Code    View Copy Print
  1. {% module Book(book) %} tag.   
  2.     {% extends ”main.html” %}   
  3.   
  4.     {% block body %}   
  5.     <h2>Recommended Reading</h2>   
  6.         {% for book in books %}   
  7.             {% module Book(book) %}   
  8.         {% end %}       
  9.     {% end %}  

Burt将会在book.html模板中建立一个Book模块,它被存放在templates/modules目录中。一个简单的book模板看起来是这样的:

Code    View Copy Print
  1. <div class=“book”>  
  2.     <h3 class=“book_title”>{{ book["title"] }}</h3>  
  3.     <img src=”{{ book["image"] }}” class=“book_image”/>  
  4. </div>  

如今咱们定义一个BookModule类,它将会继承UIModule中的render_string方法,这个方法会将模板和它的关键字提取出来渲染而后做为字符串返回给调用者。

Code    View Copy Print
  1. class BookModule(tornado.web.UIModule):   
  2.     def render(self, book):   
  3.         return self.render_string(‘modules/book.html’, book=book)  

在整个完整的示例中,咱们将会使用如下模板去格式化有推荐书籍的属性,并替换掉book.html模板中的标签。

Code    View Copy Print
  1. <div class=“book”>  
  2.      <h3 class=“book_title”>{{ book["title"] }}</h3>  
  3.      {% if book["subtitle"] != ”" %}   
  4.           <h4 class=“book_subtitle”>{{ book["subtitle"] }}</h4>  
  5.      {% end %}   
  6.      <img src=”{{ book["image"] }}” class=“book_image”/>  
  7.      <div class=“book_details”>  
  8.           <div class=“book_date_released”>Released: {{ book["date_released"]}}</div>  
  9.           <div class=“book_date_added”>Added: {{ >>  
  10. locale.format_date(book["date_added"], relative=False) }}</div>  
  11.           <h5>Description:</h5>  
  12.           <div class=“book_body”>{% raw book["description"] %}</div>  
  13.      </div>  
  14. </div>  

根据这样的排列,这个模块将会调用每一本书籍的参数并传递给recommended.html模板,每一次调用都会生成一个新的书籍参数,这个模块(还有book.html模块)能够按照恰当的格式引用每一本书籍的参数(请查看效果图3-6)

如今咱们能够定义一个RecommendedHandler,它将会按照Book 模块返回的推荐书籍列表来渲染一个模板。

Code    View Copy Print
  1. class RecommendedHandler(tornado.web.RequestHandler):   
  2.     def get(self):   
  3.         self.render(   
  4.             ”recommended.html”,   
  5.             page_title=”Burt’s Books | Recommended Reading”,   
  6.             header_text=”Recommended Reading”,   
  7.             books=[   
  8.                 {   
  9.                     "title":"Programming Collective Intelligence",   
  10.                     "subtitle": "Building Smart Web 2.0 Applications",   
  11.                     "image":"/static/images/collective_intelligence.gif",   
  12.                     "author": "Toby Segaran",   
  13.                     "date_added":1310248056,   
  14.                     "date_released": "August 2007",   
  15.                     "isbn":"978-0-596-52932-1",   
  16.                     "description":"<p>This fascinating book demonstrates how you »   
  17. can build web applications to mine the enormous amount of data created by people »   
  18. on the Internet. With the sophisticated algorithms in this book, you can write »   
  19. smart programs to access interesting datasets from other web sites, collect data »   
  20. from users of your own applications, and analyze and understand the data once »   
  21. you've found it.</p>"   
  22.                 },   
  23.                 ...   
  24.             ]   
  25.         )  

经过添加ui_modules 参数的映射就可使用附加的模块,由于templates能够调用任何模块中定义的ui_modules映射,轻松地将特殊功能插入到咱们的模板中。

图片3-6

在这个例子中,你可能已经注意到了咱们使用了locale.format_date()。它调用tornado.locale.module中的datahandling方法。这是一个拥有许多国际化选项的方法format_data(),在默认状况下,它使用GMT的Unix时间戳来显示时间,固然咱们也能够经过这样的方式{{locale.format_date(book["data"])}} relative = False的方式得到一个绝对时间(小时和分钟),或者经过full_format=True的方式让它显示一个完整的时间(例如 July 9, 2011 at 9:47pm),还能够经过shorter = True的方式让它只显示月日年。

这个模块能够有效地帮助咱们处理时间和日期的格式

嵌入 JavaScript 和CSS

为了在模块中引入更多特性,tornado容许你在模块中嵌入单独的CSS和JavaScript到embedded_css()和embedded_javascript()方法中,例如假如你想要在调用模块时添加一行文字到DOM中,你能够经过嵌入JavaScript到模块中实现这个需求。

Code    View Copy Print
  1. class BookModule(tornado.web.UIModule):   
  2.     def render(self, book):   
  3.         return self.render_string(   
  4.             ”modules/book.html”,   
  5.             book=book,   
  6.         )   
  7.     def embedded_javascript(self):   
  8.         return ”document.write(\”hi!\”)”  

当模块被调用的时候,在靠近<body>标签的地方,将会添加一个document.write(\”hi!\”)到<script> 标签中。

很明显,仅仅添加内容到文档中不是最有效率事情,咱们能够在调用的时候根据不一样的模块灵活地定制导入不一样的JavaScript和CSS:

Code    View Copy Print
  1. def embedded_css(self):   
  2.     return ”.book {background-color:#F5F5F5}”  

在这个例子中,则将会在<head>标签的附件插入<style>标签引入.book{background-color:#555} CSS 规则:

Code    View Copy Print
  1. <style type=“text/css”>  
  2. .book {background-color:#F5F5F5}   
  3. </style>  

若是但愿获得更多的特性,你可使用html_body()在靠近</body>的地方插入更多html标签

Code    View Copy Print
  1. def html_body(self):   
  2.     return ”<script>document.write(\”Hello!\”)</script>”  

很明显,它可以帮助咱们在简洁的代码风格下有效地管理复杂的关联文件(样式表、脚本文件),你还能够经过使用javascript_file()和css_files()去导入一些本地或外部的支持文件。例如你能够像这样导入一个独立的CSS文件:

Code    View Copy Print
  1. def css_files(self):   
  2.     return ”/static/css/newreleases.css”  

或者去获取一个外部的JavaScript文件:

Code    View Copy Print
  1. def javascript_files(self):   
  2.     return ”https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js”  

这样能够高效的管理模块额外导入的库。假如你有一个模块须要使用jQuery UI 库(其它模块不须要使用),你能够只在这个模块加载jquery-ui.min.js文件,而其它模块则不须要加载。

由于JavaScript-embedding, HTML-embedding嵌入功能在对</body>插入替换字符串时,html_body(), javascript_files(),embedded_javascript()将会按照倒序的方式插入到页面的底部,因此若是你有一个模块,你应该像这样去指定嵌入元素:

Code    View Copy Print
  1. class SampleModule(tornado.web.UIModule):   
  2.     def render(self, sample):   
  3.         return self.render_string(   
  4.             ”modules/sample.html”,   
  5.             sample=sample   
  6.         )   
  7.   
  8. def html_body(self):   
  9.     return ”<div class=\”addition\”><p>html_body()</p></div>”   
  10.   
  11. def embedded_javascript(self):   
  12.     return ”document.write(\”<p>embedded_javascript()</p>\”)”   
  13.   
  14. def embedded_css(self):   
  15.     return ”.addition {color: #A1CAF1}”   
  16.   
  17. def css_files(self):   
  18.     return ”/static/css/sample.css”   
  19.   
  20. def javascript_files(self):   
  21.     return ”/static/js/sample.js”  

 这个html_body()将会做为</body>以前的元素第一个写入,接下来会由embedded_javascript()去渲染他,最后执行的是javascript_files(),你能够在图3-7中看到它是如何实现的,请注意,你若是在其它地方没有导入这些请求的方法(好比使用JavaScript功能去替换一些文件),那么你的页面可能显示效果会与你指望的不一样

总之,tornado容许你灵活地使用规范的格式去渲染模板,也容许你对某一个模块引入外部的样式表或功能规则进行渲染,经过使用module的一些特殊功能,你能够有效的增强代码的可重用性,让你的网站开发更简单更快速。

总结

正如咱们看到的,tornado让你能够很轻松的对模板进行扩展,经过添加模块,你能够更精确地操做调用的文件、样式表和脚本。然而目前在咱们的例子中常常用到python风格的数据类型,这会给程序带来许多硬编码的数据结构。接下来让我带你去看看如何经过动态的方式去处理数据持久化、存储服务等内容。

原创翻译,首发: http://blog.xihuan.de/tech/web/tornado/tornado_extending_templates.html

上一篇:翻译:introduce to tornado - form and template

下一篇:翻译:introduce to tornado - Databases

相关文章
相关标签/搜索