Python Tornado初學筆記之表單與模板(一)
Tornado中的表單和HTML5中的表單具有相同的用途,同樣是用於內容的填寫。只是不同的是Tornado中的表單需要傳入到後臺,然後通過後臺進行對模板填充。
模板:是一個允許嵌入Python代碼片段的HTML文件。
一、簡單模板示例:
Python主程序:
import os.path import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web from tornado.options import define, options define("port",default=8000,help="run on the given port",type=int) class IndexHandler(tornado.web.RequestHandler): def get(self): self.render(‘index.html‘) class PoemPageHandler(tornado.web.RequestHandler): def post(self): noun1 = self.get_argument(‘roads‘) noun2 = self.get_argument(‘wood‘) verb = self.get_argument(‘made‘) noun3 = self.get_argument(‘difference‘) self.render(‘poem.html‘,roads=roads,wood=wood,made=made,difference=difference) if __name__ == "__main__": tornado.options.parse_command_line() app = tornado.web.Application( handlers=[(r‘/‘,IndexHandler),(r‘/poem‘,PoemPageHandler)], template_path=os.path.join(os.path.dirname(__file__),‘templates‘) ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start()
template_path為頁面文件存放位置,之後還有static文件夾,在static文件夾中存放靜態文件如css文件、圖片等。此處的template_path是告訴服務器“我的index.html和poem.html文件就放在了當前程序所在文件夾中的template文件夾中,不要去其他地方找了。”,其實在此處調用了Application的__init__函數。當在Application函數中添加一個debug=True參數時,它調用了一個便利的測試模式:tornado.autoreload模塊,此時,一旦主要的Python文件被修改,Tornado將會嘗試重啟服務器,並且在模板改變時會進行刷新。對於快速改變和實時更新這非常棒,但不要再生產上使用它,因為它將防止Tornado緩存模板!
Index.html內容:
<!DOCTYPE html> <html> <head><title>Poem Maker Pro</title></head> <body> <h1>Enter terms below.</h1> <form method="post" action="/poem"> <p>Plural noun<br><input type="text" name="roads"></p> <p>Singular noun<br><input type="text" name="wood"></p> <p>Verb (past tense)<br><input type="text" name="made"></p> <p>Noun<br><input type="text" name="difference"></p> <input type="submit"> </form> </body> </html>
poem.html文件內容:
<!DOCTYPE html> <html> <head><title>Poem Maker Pro</title></head> <body> <h1>Your poem</h1> <p>Two {{roads}} diverged in a {{wood}}, and I--<br />
I took the one less travelled by,<br />
And that has {{made}} all the {{difference}}.</p> </body> </html>
註:此處的模板和Flask中的模板是很相似的。
首先先看一段簡單的模板代碼:
<p>Two {{roads}} diverged in a {{wood}}, and I—<br/> I took the one less travelled by,<br> And that has {{made}} all the {{difference}}.</p>
在代碼中{{ }}包裹的部分為占位符,它是渲染模板時的實際值(即傳入的值)。
其實在程序中最關鍵的部分在於主程序中的get_argument()函數,該函數可以從表單中獲取用戶輸入的值,然後通過render函數將用戶輸入的值傳遞到模板中,然後在模板中渲染顯示。
noun1 = self.get_argument(‘roads‘) noun2 = self.get_argument(‘wood‘) verb = self.get_argument(‘made‘) noun3 = self.get_argument(‘difference‘) self.render(‘poem.html‘, roads=roads, wood=wood, made=made, difference=difference)
假如用戶在表單中輸入的roads、wood、made、defference分別為pineapples、grandfather clock、irradiated和supernovae。那麽當用戶點擊提交後,看到的內容便為:
Two pineapples diverged in a grandfather clock, and I— I took the one less travelled by, And that has irradiated all the supernovae.
二、填充表達式和控制流語句
在模板中使用表達式,需要將Python表達式放入{{ }}中,Tornado將插入一個包含任何表達式計算結果值的字符串到輸出中。
例如:
>>> from tornado.template import Template >>> print(Template(‘{{ 1+2 }}‘).generate()) b‘3‘ >>> print(Template("{{ ‘scrambled eggs‘[-4:] }}").generate()) b‘eggs‘ >>> print(Template("{{ ‘,‘.join([str(x*x) for x in range(10)]) }}").generate()) b‘0,1,4,9,16,25,36,49,64,81‘
在使用python的控制流語句時,需要將控制流語句放入{% %}中,以{% end %}結尾,它同樣支持Python的if、while、for、try。Tornado模板語言的一個最好的東西是在if和for語句塊中可以使用的表達式沒有限制,同時你也可以在你的控制語句塊中間使用{% set foo=‘bar‘ %}來設置變量。
例如:
{% if a is None %}xxx{% end %}
當然在Tornado在所有模板中默認提供了一些便利的函數:
escape(s):替換字符串s中的&、為他們對應的HTML字符。
url_escape(s):使用urllib.quote_plus替換字符串s中的字符為URL編碼形式。
json_encode(s):將val編碼成JSON格式。(在系統底層,這是一個對json庫的dumps函數的調用。)
squeeze(s):過濾字符串s,把連續的多個空白字符替換成一個空格。
註:在Tornado 2.0中,模板被默認為自動轉義(並且可以在Application構造函數中使用autoscaping=None關閉)
三、模板擴展
Tornado中的塊替換可以實現前端代碼的重用,Tornado通過extends和block語句支持模板繼承,這就讓你擁有了編寫能夠在合適的地方復用的流體模板的控制權和靈活性。
在使用{% extends “xxx.html”%}時,實際上是從xxx.html文件中繼承了xxx.html的內容,當然前提是,xxx.html文件中必須定義了相關的塊內容。此語句需要在新的模板文件的頭部進行提前聲明。
{% block xxx %}語句壓縮了一些當你擴展時可能想要改變的模板元素。在新的模板文件中,直接使用該語句,可以實現對該塊內容的覆蓋,並將自己需要顯示的內容放入相對應的模板塊中進行顯示。
例如:
父模板(部分):
<header>{% block header %}{% end %}</header>
子模版(部分):
<header> {% block header %} <p>Hello World!</p> {% end %} </header>
在父模板中可以使用多個塊,然後在子模版中直接進行使用就可以,節省了網站開發的時間。此處給出一個簡單的header、body、footer塊。
<html> <body> <header> {% block header %}{% end %} </header> <content> {% block body %}{% end %} </content> <footer> {% block footer %}{% end %} </footer> </body> </html>
註:此處容易忘記{% end %},一定要記得添加該結束語句,否則會ParseError的
當用戶自定義一個Application函數時,在我們定義的__init__方法中,需要創建網站的處理類列表以及一個設置的字典,然後在初始化子類中調用中傳遞這些值,
tornado.web.Application.__init__(self,handlers,**settings)),說明:handlers中主要寫訪問頁面時需要調用哪個函數,settings中主要為靜態文件、網頁文件路徑以及其他調試的參數。當然在運行時就需要更改應用方式了。
用戶自定的Application函數:
class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", xxxHandler),#xxxHandler根據自己實際的類名稱進行填寫 ] settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), debug=True, ) tornado.web.Application.__init__(self, handlers, **settings)
原:
http_server = tornado.web.Application(app) #app中為網站的配置
改:
http_server = tornado.web.Application(Application())#Application中為我們自定義個網站配置,此處將網站的配置寫入了一個函數中。
註:此處不給出一個簡單頁面的代碼是因為,在此處,每個人都可以按照自己的想法仿照前面的例子進行寫個簡單的代碼,然後將上文的內容進行使用,這種方法我個人覺得是一個很好的鍛煉方式。
前文說過,在tornado中會自動轉義模板中的內容,把標簽轉換為相應的HTML實體。這樣可以防止後端為數據庫的網站被惡意腳本攻擊。
例如設置一個郵箱鏈接:
{% set mailLink = "<a href="mailto:[email protected]">Contact Us</a>" %} {{ mailLink }}‘
在tornado中會將該內容在頁面源碼中顯示為:
<a href="mailto:[email protected]">Contact Us</a>
此時自動轉義被運行了,很明顯,這無法讓人們聯系該用戶。
為了處理這種情況,你可以禁用自動轉義,一種方法是在Application構造函數中傳遞autoescape=None,另一種方法是在每頁的基礎上修改自動轉義行為,如下所示:
{% autoescape None %}
{{ mailLink }}
註:這些autoescape塊不需要結束標簽,並且可以設置xhtml_escape來開啟自動轉義(默認行為),或None來關閉
tornado.escape.linkify():
tornado.escape.linkify(text, shorten=False, extra_params=‘‘, require_protocol=False, permitted_protocols=[‘http‘, ‘https‘])[source] Converts plain text into HTML with links. For example: linkify("Hello http://tornadoweb.org!") would returnHello <a href="http://tornadoweb.org">http://tornadoweb.org</a>! Parameters: shorten: Long urls will be shortened for display. extra_params: Extra text to include in the link tag, or a callable taking the link as an argument and returning the extra text e.g. linkify(text, extra_params=‘rel="nofollow" class="external"‘), or: def extra_params_cb(url): if url.startswith("http://example.com"): return ‘class="internal"‘ else: return ‘class="external" rel="nofollow"‘ linkify(text, extra_params=extra_params_cb) require_protocol: Only linkify urls which include a protocol. If this is False, urls such as www.facebook.com will also be linkified. permitted_protocols: List (or set) of protocols which should be linkified, e.g. linkify(text, permitted_protocols=["http", "ftp", "mailto"]). It is very unsafe to include protocols such as javascript.
四、Tornado中的UI Module
UI模塊是封裝模板中包含的標記、樣式以及行為的可復用組件。它所定義的元素通常用於多個模板交叉復用或在同一個模板中重復使用。模塊本身是一個繼承自Tornado的UIModule類的簡單Python類,並定義了一個render方法。當一個模板使用{% module Foo(...) %}標簽引用一個模塊時,Tornado的模板引擎調用模塊的render方法,然後返回一個字符串來替換模板中的模塊標簽。UI模塊也可以在渲染後的頁面中嵌入自己的JavaScript和CSS文件,或指定額外包含的JavaScript或CSS文件。你可以定義可選的embedded_javascript、embedded_css、javascript_files和css_files方法來實現這一方法。
在模板中引入模塊時,需要在應用的設置中進行聲明,ui_moudles參數期望一個模塊名為鍵、類為值的字典輸入來渲染它們。
簡單例子:
import tornado.web import tornado.httpserver import tornado.ioloop import tornado.options import os.path from tornado.options import define, options define("port", default=8000, help="run on the given port", type=int) class HelloHandler(tornado.web.RequestHandler): def get(self): self.render(‘hello.html‘) class HelloModule(tornado.web.UIModule): def render(self): return ‘<h1>Hello, world!</h1>‘ if __name__ == ‘__main__‘: tornado.options.parse_command_line() app = tornado.web.Application( handlers=[(r‘/‘, HelloHandler)], template_path=os.path.join(os.path.dirname(__file__), ‘templates‘), ui_modules={‘Hello‘: HelloModule} ) server = tornado.httpserver.HTTPServer(app) server.listen(options.port) tornado.ioloop.IOLoop.instance().start()
當用戶訪問時,程序首先會先調用HelloHandler類,然後進行跳轉至hello.html頁面,然後再hello.html頁面使用{% module Hello() %}進行顯示需要顯示的內容。當使用這個module時,會調用ui_modules中的Hello對應的類,然後進行內容反饋。
hello.html文件內容:
<html> <head><title>UI Module Example</title></head> <body> {% module Hello() %} </body> </html>
這個hello.html模板通過在模塊標簽自身的位置調用HelloModule返回的字符串進行填充
如果要用更多的模塊,只需要簡單地在ui_modules參數中添加映射值。因為模板可以指向任何定義在ui_modules字典中的模塊。
五、嵌入JavaScript和CSS
為了給這些模塊提供更高的靈活性,Tornado允許你使用embedded_css和embedded_javascript方法嵌入其他的CSS和JavaScript文件。
添加本地CSS文件:
def css_files(self): return "/static/css/newreleases.css"
添加外部的JavaScript文件:
def javascript_files(self): return "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"
當一個模塊需要額外的庫而應用的其他地方不是必需的時候,這種方式非常有用。比如,你有一個使用JQuery UI庫的模塊(而在應用的其他地方都不會被使用),你可以只在這個樣本模塊中加載jquery-ui.min.js文件,減少那些不需要它的頁面的加載時間。
因為模塊的內嵌JavaScript和內嵌HTML函數的目標都是緊鄰標簽,html_body()、javascript_files()和embedded_javascript()都會將內容渲染後插到頁面底部,那麽它們出現的順序正好是你指定它們的順序的倒序。
如果你有一個模塊如下面的代碼所示:
class SampleModule(tornado.web.UIModule): def render(self, sample): return self.render_string( "modules/sample.html", sample=sample ) def html_body(self): return "<div class=\"addition\"><p>html_body()</p></div>" def embedded_javascript(self): return "document.write(\"<p>embedded_javascript()</p>\")" def embedded_css(self): return ".addition {color: #A1CAF1}" def css_files(self): return "/static/css/sample.css" def javascript_files(self): return "/static/js/sample.js"
加載方式為:html_body()最先被編寫,它緊挨著出現在標簽的上面。embedded_javascript()接著被渲染,最後是javascript_files()
Python Tornado初學筆記之表單與模板(一)