用tornado做網(wǎng)站(4)

2018-02-24 15:48 更新

模板

已經(jīng)基本了解前端向和后端如何傳遞數(shù)據(jù),以及后端如何接收數(shù)據(jù)的過程和方法之后。我突然發(fā)現(xiàn),前端頁面寫的太難看了。俗話說“外行看熱鬧,內(nèi)行看門道”。程序員寫的網(wǎng)站,在更多時(shí)候是給“外行”看的,他們可沒有耐心來看代碼,他們看的就是界面,因此界面是否做的漂亮一點(diǎn)點(diǎn),是直觀重要的。

其實(shí),也不僅僅是漂亮的原因,因?yàn)榍岸隧撁妫€要顯示從后端讀取出來的數(shù)據(jù)呢。

恰好,tornado提供比較好用的前端模板(tornado.template)。通過這個(gè)模板,能夠讓前端編寫更方便。

render()

render()方法能夠告訴tornado讀入哪個(gè)模板,插入其中的模板代碼,并返回結(jié)果給瀏覽器。比如在IndexHandler類中g(shù)et()方法里面的self.render("index.html"),就是讓tornado到templates目中找到名為index.html的文件,讀出它的內(nèi)容,返回給瀏覽器。這樣用戶就能看到index.html所規(guī)定的頁面了。當(dāng)然,在前面所寫的index.html還僅僅是html標(biāo)記,沒有顯示出所謂“模板”的作用。為此,將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ù)庫中讀取用戶名,并且提出一個(gè)用戶(one_user),然后通過self.render("index.html", user=one_user)將這個(gè)用戶名放到index.html中,其中user=one_user的作用就是傳遞對(duì)象到模板。

提醒讀者注意的是,在上面的代碼中,我使用了mrd.select_columns(table="users",column="username"),也就是說必須要在methods目錄中的readdb.py文件中有一個(gè)名為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="https://atts.w3cschool.cn/attachments/image/cimg/jquery.min.js")}}"></script>
    <script src="https://atts.w3cschool.cn/attachments/image/cimg/script.js")}}"></script>
</body>

<p>用用戶名為:{{user}}登錄</p>,這里用了{{ }}方式,接受對(duì)應(yīng)的變量引導(dǎo)來的對(duì)象。也就是在首頁打開之后,用戶應(yīng)當(dāng)看到有一行提示。如下圖一樣。

圖中箭頭是我為了強(qiáng)調(diào)后來加上去的,箭頭所指的,就是從數(shù)據(jù)庫中讀取出來的用戶名,借助于模板中的雙大括號(hào){{ }}顯示出來。

{{ }}本質(zhì)上是占位符。當(dāng)這個(gè)html被執(zhí)行的時(shí)候,這個(gè)位置會(huì)被一個(gè)具體的對(duì)象(例如上面就是字符串qiwsir)所替代。具體是哪個(gè)具體對(duì)象替代這個(gè)占位符,完全是由render()方法中關(guān)鍵詞來指定,也就是render()中的關(guān)鍵詞與模板中的占位符包裹著的關(guān)鍵詞一致。

用這種方式,修改一下用戶正確登錄之后的效果。要求用戶正確登錄之后,跳轉(zhuǎn)到另外一個(gè)頁面,并且在那個(gè)頁面中顯示出用戶的完整信息。

先修改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的值。因此,當(dāng)用戶登錄后,得到正確返回值,那么js應(yīng)該用這樣的方式載入新的頁面。

注意:上述的user.py代碼為了簡(jiǎn)單突出本將要說明的,沒有對(duì)user_infos的結(jié)果進(jìn)行判斷。在實(shí)際的編程中,這要進(jìn)行判斷或者使用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引用的對(duì)象不是一個(gè)字符串了,也就是傳入模板的不是一個(gè)字符串,是一個(gè)元組。對(duì)此,模板這樣來處理它。

<!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>

顯示的效果是:

在上面的模板中,其實(shí)用到了模板語法。

模板語法

在模板的雙大括號(hào)中,可以寫類似python的語句或者表達(dá)式。比如:

>>> 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

意即如果在模板中,某個(gè)地方寫上{{ 3+4 }},當(dāng)那個(gè)模板被render()讀入之后,在頁面上該占位符的地方就顯示7。這說明tornado自動(dòng)將雙大括號(hào)內(nèi)的表達(dá)式進(jìn)行計(jì)算,并將其結(jié)果以字符串的形式返回到瀏覽器輸出。

除了表達(dá)式之外,python的語句也可以在表達(dá)式中使用,包括if、for、while和try。只不過要有一個(gè)語句開始和結(jié)束的標(biāo)記,用以區(qū)分那里是語句、哪里是HTML標(biāo)記符。

語句的形式:{{% 語句 %}}

例如:

{{% if user=='qiwsir' %}}
    {{ user }}
{{% end %}}

上面的舉例中,第一行雖然是if語句,但是不要在后面寫冒號(hào)了。最后一行一定不能缺少,表示語句塊結(jié)束。將這一個(gè)語句塊放到模板中,當(dāng)被render讀取此模板的時(shí)候,tornado將執(zhí)行結(jié)果返回給瀏覽器顯示,跟前面的表達(dá)式一樣。實(shí)際的例子可以看上圖輸出結(jié)果和對(duì)應(yīng)的循環(huán)語句。

轉(zhuǎn)義字符

雖然讀者現(xiàn)在已經(jīng)對(duì)字符轉(zhuǎn)義問題不陌生了,但是在網(wǎng)站開發(fā)中,它還將是一個(gè)令人感到麻煩的問題。所謂轉(zhuǎn)義字符(Escape Sequence)也稱字符實(shí)體(Character Entity),它的存在是因?yàn)樵诰W(wǎng)頁中<, >之類的符號(hào),是不能直接被輸出的,因?yàn)樗鼈円呀?jīng)被用作了HTML標(biāo)記符了,如果在網(wǎng)頁上用到它們,就要轉(zhuǎn)義。另外,也有一些字符在ASCII字符集中沒有定義(如版權(quán)符號(hào)“?”),這樣的符號(hào)要在HTML中出現(xiàn),也需要轉(zhuǎn)義字符(如“?”對(duì)應(yīng)的轉(zhuǎn)義字符是“&copy;”)。

上述是指前端頁面的字符轉(zhuǎn)義,其實(shí)不僅前端,在后端程序中,因?yàn)橐x寫數(shù)據(jù)庫,也會(huì)遇到字符轉(zhuǎn)義問題。

比如一個(gè)簡(jiǎn)單的查詢語句:select username, password from usertable where username='qiwsir',如果在登錄框中沒有輸入qiwsir,而是輸入了a;drop database;,這個(gè)查詢語句就變成了select username, password from usertable where username=a; drop database;,如果后端程序執(zhí)行了這條語句會(huì)怎么樣呢?后果很嚴(yán)重,因?yàn)闀?huì)drop database,屆時(shí)真的是欲哭無淚了。類似的情況還很多,比如還可以輸入<input type="text" />,結(jié)果出現(xiàn)了一個(gè)輸入框,如果是<form action="...",會(huì)造成跨站攻擊了。這方面的問題還不少呢,讀者有空可以到網(wǎng)上搜一下所謂sql注入問題,能了解更多。

所以,后端也要轉(zhuǎn)義。

轉(zhuǎn)義是不是很麻煩呢?

Tornado為你著想了,因?yàn)榇嬖谝陨限D(zhuǎn)義問題,而且會(huì)有粗心的程序員忘記了,于是Tornado中,模板默認(rèn)為自動(dòng)轉(zhuǎn)義。這是多么好的設(shè)計(jì)呀。于是所有表單輸入的,你就不用擔(dān)心會(huì)遇到上述問題了。

為了能夠體會(huì)自動(dòng)轉(zhuǎn)義,不妨在登錄框中輸入上面那樣字符,然后可以用print語句看一看,后臺(tái)得到了什么。

print語句,在python3中是print()函數(shù),在進(jìn)行程序調(diào)試的時(shí)候非常有用。經(jīng)常用它把要看個(gè)究竟的東西打印出來。

自動(dòng)轉(zhuǎn)義是一個(gè)好事情,但是,有時(shí)候會(huì)不需要轉(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="https://atts.w3cschool.cn/attachments/image/cimg/jquery.min.js")}}"></script>
    <script src="https://atts.w3cschool.cn/attachments/image/cimg/script.js")}}"></script>
</body>

這是index.html的代碼,我增加了{% set website = "<a >welcome to my website</a>" %},作用是設(shè)置一個(gè)變量,名字是website,它對(duì)應(yīng)的內(nèi)容是一個(gè)做了超鏈接的文字。然后在下面使用這個(gè)變量{{ website }},本希望能夠出現(xiàn)的是有一行字“welcome to my website”,點(diǎn)擊這行字,就可以打開對(duì)應(yīng)鏈接的網(wǎng)站??墒?,看到了這個(gè):

下面那一行,把整個(gè)源碼都顯示出來了。這就是因?yàn)樽詣?dòng)轉(zhuǎn)義的結(jié)果。這里需要的是不轉(zhuǎn)義。于是可以將{{ website }}修改為:

{% raw website %}

表示這一行不轉(zhuǎn)義。但是別的地方還是轉(zhuǎn)義的。這是一種最推薦的方法。

如果你要全轉(zhuǎn)義,可以使用:

{% autoescape None %}
{{ website }}

貌似省事,但是我不推薦。

幾個(gè)備查函數(shù)

下面幾個(gè)函數(shù),放在這里備查,或許在某些時(shí)候用到。都是可以使用在模板中的。

  • escape(s):替換字符串s中的&、為他們對(duì)應(yīng)的HTML字符。
  • url_escape(s):使用urllib.quote_plus替換字符串s中的字符為URL編碼形式。
  • json_encode(val):將val編碼成JSON格式。
  • squeeze(s):過濾字符串s,把連續(xù)的多個(gè)空白字符替換成一個(gè)空格。

此外,在模板中也可以使用自己編寫的函數(shù)。但不常用。所以本教程就不啰嗦這個(gè)了。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)