用tornado做網站(4)

2018-02-24 15:48 更新

模板

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

其實,也不僅僅是漂亮的原因,因為前端頁面,還要顯示從后端讀取出來的數(shù)據呢。

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

render()

render()方法能夠告訴tornado讀入哪個模板,插入其中的模板代碼,并返回結果給瀏覽器。比如在IndexHandler類中get()方法里面的self.render("index.html"),就是讓tornado到templates目中找到名為index.html的文件,讀出它的內容,返回給瀏覽器。這樣用戶就能看到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ù)據庫中讀取用戶名,并且提出一個用戶(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ù)。為了使讀者能夠理解,貼出已經修改之后的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>,這里用了{{ }}方式,接受對應的變量引導來的對象。也就是在首頁打開之后,用戶應當看到有一行提示。如下圖一樣。

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

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

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

先修改url.py文件,在其中增加一些內容。完整代碼如下:

#!/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文件,內容如下:

#!/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的結果進行判斷。在實際的編程中,這要進行判斷或者使用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>

顯示的效果是:

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

模板語法

在模板的雙大括號中,可以寫類似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自動將雙大括號內的表達式進行計算,并將其結果以字符串的形式返回到瀏覽器輸出。

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

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

例如:

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

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

轉義字符

雖然讀者現(xiàn)在已經對字符轉義問題不陌生了,但是在網站開發(fā)中,它還將是一個令人感到麻煩的問題。所謂轉義字符(Escape Sequence)也稱字符實體(Character Entity),它的存在是因為在網頁中<, >之類的符號,是不能直接被輸出的,因為它們已經被用作了HTML標記符了,如果在網頁上用到它們,就要轉義。另外,也有一些字符在ASCII字符集中沒有定義(如版權符號“?”),這樣的符號要在HTML中出現(xiàn),也需要轉義字符(如“?”對應的轉義字符是“&copy;”)。

上述是指前端頁面的字符轉義,其實不僅前端,在后端程序中,因為要讀寫數(shù)據庫,也會遇到字符轉義問題。

比如一個簡單的查詢語句: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" />,結果出現(xiàn)了一個輸入框,如果是<form action="...",會造成跨站攻擊了。這方面的問題還不少呢,讀者有空可以到網上搜一下所謂sql注入問題,能了解更多。

所以,后端也要轉義。

轉義是不是很麻煩呢?

Tornado為你著想了,因為存在以上轉義問題,而且會有粗心的程序員忘記了,于是Tornado中,模板默認為自動轉義。這是多么好的設計呀。于是所有表單輸入的,你就不用擔心會遇到上述問題了。

為了能夠體會自動轉義,不妨在登錄框中輸入上面那樣字符,然后可以用print語句看一看,后臺得到了什么。

print語句,在python3中是print()函數(shù),在進行程序調試的時候非常有用。經常用它把要看個究竟的東西打印出來。

自動轉義是一個好事情,但是,有時候會不需要轉義,比如想在模板中這樣做:

<!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>" %},作用是設置一個變量,名字是website,它對應的內容是一個做了超鏈接的文字。然后在下面使用這個變量{{ website }},本希望能夠出現(xiàn)的是有一行字“welcome to my website”,點擊這行字,就可以打開對應鏈接的網站??墒?,看到了這個:

下面那一行,把整個源碼都顯示出來了。這就是因為自動轉義的結果。這里需要的是不轉義。于是可以將{{ website }}修改為:

{% raw website %}

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

如果你要全轉義,可以使用:

{% autoescape None %}
{{ website }}

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

幾個備查函數(shù)

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

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

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

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號