已經(jīng)基本了解前端向和后端如何傳遞數(shù)據(jù),以及后端如何接收數(shù)據(jù)的過程和方法之后。我突然發(fā)現(xiàn),前端頁面寫的太難看了。俗話說“外行看熱鬧,內(nèi)行看門道”。程序員寫的網(wǎng)站,在更多時候是給“外行”看的,他們可沒有耐心來看代碼,他們看的就是界面,因此界面是否做的漂亮一點點,是直觀重要的。
其實,也不僅僅是漂亮的原因,因為前端頁面,還要顯示從后端讀取出來的數(shù)據(jù)呢。
恰好,tornado 提供比較好用的前端模板(tornado.template)。通過這個模板,能夠讓前端編寫更方便。
render() 方法能夠告訴 tornado 讀入哪個模板,插入其中的模板代碼,并返回結(jié)果給瀏覽器。比如在 IndexHandler 類中 get() 方法里面的 self.render("index.html")
,就是讓 tornado 到 templates 目中找到名為 index.html 的文件,讀出它的內(nèi)容,返回給瀏覽器。這樣用戶就能看到 index.html 所規(guī)定的頁面了。當然,在前面所寫的 index.html 還僅僅是 html 標記,沒有顯示出所謂“模板”的作用。為此,將 index.html 和 index.py 文件做如下改造。
#!/usr/bin/env Python
# coding=utf-8
import tornado.web
import methods.readdb as mrd
class IndexHandler(tornado.web.RequestHandler):
def get(self):
usernames = mrd.select_columns(table="users",column="username")
one_user = usernames[0][0]
self.render("index.html", user=one_user)
index.py 文件中,只修改了 get() 方法,從數(shù)據(jù)庫中讀取用戶名,并且提出一個用戶(one_user),然后通過 self.render("index.html", user=one_user)
將這個用戶名放到 index.html 中,其中 user=one_user
的作用就是傳遞對象到模板。
提醒讀者注意的是,在上面的代碼中,我使用了 mrd.select_columns(table="users",column="username")
,也就是說必須要在 methods 目錄中的 readdb.py 文件中有一個名為 select_columns 的函數(shù)。為了使讀者能夠理解,貼出已經(jīng)修改之后的 readdb.py 文件代碼,比上一節(jié)多了函數(shù) select_columns:
#!/usr/bin/env Python
# coding=utf-8
from db import *
def select_table(table, column, condition, value ):
sql = "select " + column + " from " + table + " where " + condition + "='" + value + "'"
cur.execute(sql)
lines = cur.fetchall()
return lines
def select_columns(table, column ):
sql = "select " + column + " from " + table
cur.execute(sql)
lines = cur.fetchall()
return lines
下面是 index.html 修改后的代碼:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Learning Python</title>
</head>
<body>
<h2> 登錄頁面</h2>
<p>用用戶名為:{{user}}登錄</p>
<form method="POST">
<p><span>UserName:</span><input type="text" id="username"/></p>
<p><span>Password:</span><input type="password" id="password" /></p>
<p><input type="BUTTON" value="登錄" id="login" /></p>
</form>
<script src="{{static_url("js/jquery.min.js")}}"></script>
<script src="{{static_url("js/script.js")}}"></script>
</body>
<p> 用用戶名為:{{user}}登錄</p>
,這里用了{{ }}
方式,接受對應的變量引導來的對象。也就是在首頁打開之后,用戶應當看到有一行提示。如下圖一樣。
http://wiki.jikexueyuan.com/project/start-learning-python/images/30601.png" alt="" />
圖中箭頭是我為了強調(diào)后來加上去的,箭頭所指的,就是從數(shù)據(jù)庫中讀取出來的用戶名,借助于模板中的雙大括號{{ }}
顯示出來。
{{ }}
本質(zhì)上是占位符。當這個 html 被執(zhí)行的時候,這個位置會被一個具體的對象(例如上面就是字符串 qiwsir)所替代。具體是哪個具體對象替代這個占位符,完全是由 render() 方法中關鍵詞來指定,也就是 render() 中的關鍵詞與模板中的占位符包裹著的關鍵詞一致。
用這種方式,修改一下用戶正確登錄之后的效果。要求用戶正確登錄之后,跳轉(zhuǎn)到另外一個頁面,并且在那個頁面中顯示出用戶的完整信息。
先修改 url.py 文件,在其中增加一些內(nèi)容。完整代碼如下:
#!/usr/bin/env Python
# coding=utf-8
"""
the url structure of website
"""
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
from handlers.index import IndexHandler
from handlers.user import UserHandler
url = [
(r'/', IndexHandler),
(r'/user', UserHandler),
]
然后就建立 handlers/user.py 文件,內(nèi)容如下:
#!/usr/bin/env Python
# coding=utf-8
import tornado.web
import methods.readdb as mrd
class UserHandler(tornado.web.RequestHandler):
def get(self):
username = self.get_argument("user")
user_infos = mrd.select_table(table="users",column="*",condition="username",value=username)
self.render("user.html", users = user_infos)
在 get() 中使用 self.get_argument("user")
,目的是要通過 url 獲取參數(shù) user 的值。因此,當用戶登錄后,得到正確返回值,那么 js 應該用這樣的方式載入新的頁面。
注意:上述的 user.py 代碼為了簡單突出本將要說明的,沒有對 user_infos 的結(jié)果進行判斷。在實際的編程中,這要進行判斷或者使用 try...except。
$(document).ready(function(){
$("#login").click(function(){
var user = $("#username").val();
var pwd = $("#password").val();
var pd = {"username":user, "password":pwd};
$.ajax({
type:"post",
url:"/",
data:pd,
cache:false,
success:function(data){
window.location.href = "/user?user="+data;
},
error:function(){
alert("error!");
},
});
});
});
接下來是 user.html 模板。注意上面的代碼中,user_infos 引用的對象不是一個字符串了,也就是傳入模板的不是一個字符串,是一個元組。對此,模板這樣來處理它。
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Learning Python</title>
</head>
<body>
<h2>Your informations are:</h2>
<ul>
{% for one in users %}
<li>username:{{one[1]}}</li>
<li>password:{{one[2]}}</li>
<li>email:{{one[3]}}</li>
{% end %}
</ul>
</body>
顯示的效果是:
http://wiki.jikexueyuan.com/project/start-learning-python/images/30602.png" alt="" />
在上面的模板中,其實用到了模板語法。
在模板的雙大括號中,可以寫類似 Python 的語句或者表達式。比如:
>>> from tornado.template import Template
>>> print Template("{{ 3+4 }}").generate()
7
>>> print Template("{{ 'python'[0:2] }}").generate()
py
>>> print Template("{{ '-'.join(str(i) for i in range(10)) }}").generate()
0-1-2-3-4-5-6-7-8-9
意即如果在模板中,某個地方寫上{{ 3+4 }}
,當那個模板被 render() 讀入之后,在頁面上該占位符的地方就顯示 7
。這說明 tornado 自動將雙大括號內(nèi)的表達式進行計算,并將其結(jié)果以字符串的形式返回到瀏覽器輸出。
除了表達式之外,Python 的語句也可以在表達式中使用,包括 if、for、while 和 try。只不過要有一個語句開始和結(jié)束的標記,用以區(qū)分那里是語句、哪里是 HTML 標記符。
語句的形式:{{% 語句 %}}
例如:
{{% if user=='qiwsir' %}}
{{ user }}
{{% end %}}
上面的舉例中,第一行雖然是 if 語句,但是不要在后面寫冒號了。最后一行一定不能缺少,表示語句塊結(jié)束。將這一個語句塊放到模板中,當被 render 讀取此模板的時候,tornado 將執(zhí)行結(jié)果返回給瀏覽器顯示,跟前面的表達式一樣。實際的例子可以看上圖輸出結(jié)果和對應的循環(huán)語句。
雖然讀者現(xiàn)在已經(jīng)對字符轉(zhuǎn)義問題不陌生了,但是在網(wǎng)站開發(fā)中,它還將是一個令人感到麻煩的問題。所謂轉(zhuǎn)義字符(Escape Sequence)也稱字符實體(Character Entity),它的存在是因為在網(wǎng)頁中 <, >
之類的符號,是不能直接被輸出的,因為它們已經(jīng)被用作了 HTML 標記符了,如果在網(wǎng)頁上用到它們,就要轉(zhuǎn)義。另外,也有一些字符在 ASCII 字符集中沒有定義(如版權符號“?”),這樣的符號要在 HTML 中出現(xiàn),也需要轉(zhuǎn)義字符(如“?”對應的轉(zhuǎn)義字符是“&copy;”)。
上述是指前端頁面的字符轉(zhuǎn)義,其實不僅前端,在后端程序中,因為要讀寫數(shù)據(jù)庫,也會遇到字符轉(zhuǎn)義問題。
比如一個簡單的查詢語句:select username, password from usertable where username='qiwsir'
,如果在登錄框中沒有輸入 qiwsir,而是輸入了 a;drop database;
,這個查詢語句就變成了 select username, password from usertable where username=a; drop database;
,如果后端程序執(zhí)行了這條語句會怎么樣呢?后果很嚴重,因為會 drop database
,屆時真的是欲哭無淚了。類似的情況還很多,比如還可以輸入 <input type="text" />
,結(jié)果出現(xiàn)了一個輸入框,如果是 <form action="..."
,會造成跨站攻擊了。這方面的問題還不少呢,讀者有空可以到網(wǎng)上搜一下所謂 sql 注入問題,能了解更多。
所以,后端也要轉(zhuǎn)義。
轉(zhuǎn)義是不是很麻煩呢?
Tornado 為你著想了,因為存在以上轉(zhuǎn)義問題,而且會有粗心的程序員忘記了,于是 Tornado 中,模板默認為自動轉(zhuǎn)義。這是多么好的設計呀。于是所有表單輸入的,你就不用擔心會遇到上述問題了。
為了能夠體會自動轉(zhuǎn)義,不妨在登錄框中輸入上面那樣字符,然后可以用 print 語句看一看,后臺得到了什么。
print 語句,在 Python3 中是 print() 函數(shù),在進行程序調(diào)試的時候非常有用。經(jīng)常用它把要看個究竟的東西打印出來。
自動轉(zhuǎn)義是一個好事情,但是,有時候會不需要轉(zhuǎn)義,比如想在模板中這樣做:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Learning Python</title>
</head>
<body>
<h2>登錄頁面</h2>
<p>用用戶名為:{{user}}登錄</p>
<form method="POST">
<p><span>UserName:</span><input type="text" id="username"/></p>
<p><span>Password:</span><input type="password" id="password" /></p>
<p><input type="BUTTON" value="登錄" id="login" /></p>
</form>
{% set website = "<a >welcome to my website</a>" %}
{{ website }}
<script src="{{static_url("js/jquery.min.js")}}"></script>
<script src="{{static_url("js/script.js")}}"></script>
</body>
這是 index.html 的代碼,我增加了 {% set website = "<a >welcome to my website</a>" %}
,作用是設置一個變量,名字是 website,它對應的內(nèi)容是一個做了超鏈接的文字。然后在下面使用這個變量{{ website }}
,本希望能夠出現(xiàn)的是有一行字“welcome to my website”,點擊這行字,就可以打開對應鏈接的網(wǎng)站??墒牵吹搅诉@個:
http://wiki.jikexueyuan.com/project/start-learning-python/images/30603.png" alt="" />
下面那一行,把整個源碼都顯示出來了。這就是因為自動轉(zhuǎn)義的結(jié)果。這里需要的是不轉(zhuǎn)義。于是可以將{{ website }}
修改為:
{% raw website %}
表示這一行不轉(zhuǎn)義。但是別的地方還是轉(zhuǎn)義的。這是一種最推薦的方法。
http://wiki.jikexueyuan.com/project/start-learning-python/images/30604.png" alt="" />
如果你要全轉(zhuǎn)義,可以使用:
{% autoescape None %}
{{ website }}
貌似省事,但是我不推薦。
下面幾個函數(shù),放在這里備查,或許在某些時候用到。都是可以使用在模板中的。
此外,在模板中也可以使用自己編寫的函數(shù)。但不常用。所以本教程就不啰嗦這個了。
總目錄 | 上節(jié):用 tornado 做網(wǎng)站 (3) | 下節(jié):用 tornado 做網(wǎng)站 (5)
如果你認為有必要打賞我,請通過支付寶:qiwsir@126.com,不勝感激。