over 4 years ago

  • 變量下層查找
  • 標籤
    • for標籤
      • forloop變量及其屬性
      • 選用的empty標籤
      • 倒轉元素
      • for標籤的限制
    • if/else標籤
      • 使用邏輯運算
      • if標籤的限制
    • 註解與註解標籤
  • 過濾器

接續上一篇,我們要來深入了解模版的變量與標籤,我們將拋開之前的URL計算機(太愚蠢了,我們還是快跳過吧),以一個新的example開始說明跟介紹。

我們想要有一個頁面,他會列出一家餐廳的menu,包括菜名、價格、說明以及會不會辣。

為了說明方便,我們先從秀出一道菜的資料開始,依據前面的經驗,我們要設定一下urls.py(我們省略與本篇無關的部份,你當然可以用之前開啟的專案繼續做練習):

mysite/mysite/urls.py
from views import menu

urlpatterns = patterns('',
    url(r'^menu/$', menu),
)

接著我們可以來寫一下視圖函數:

mysite/mysite/views.py
from django.shortcuts import render_to_response

def menu(request):
    food = { 'name':'番茄炒蛋', 'price':60, 'comment':'好吃', 'is_spicy':False }
    return render_to_response('menu.html',locals())

在這個範例碼中,我們使用了一個字典來保存該食物的各項資訊(通常我們不會想要搞出一堆變數),於是藉由locals()我們將food當做填寫模版的材料之一調用menu.html模版來完成html。可是我們要如何分別輸出food中的各值到模版上呢?

變量下層查找

使用.這個符號便可以讓我們往變量的下層開始找尋,以本例來說,撰寫模版如下(這次我們就不要偷懶了,寫一個完整的html)

mysite/templates/menu.html
<!doctype html>
<html>
    <head>
        <title> Menu </title>
        <meta charset='utf-8'>
    </head>
    <body>
        <ul>
            <li> 食物的名字是:{{ food.name }} </li>
            <li> 食物的價格是:{{ food.price }} </li>
            <li> 食物說明: {{ food.comment }} </li>
            <li> 食物會辣? {{ food.is_spicy }} </li>
        </ul>
    </body>
</html>

本例筆者遵循html5的寫法,若有寫法讀者不太明白,可以忽略或改成自己習慣的寫法

原來透過.就可以查找字典中的值,這讓我們領悟到,原來用來填寫模版的不一定要是字串或是整數,也可以是清單或是字典,甚至是物件!(廢話,前面哪個東西不是物件),不過大家可能要注意一件事,模版是模版,python是python,模版語言並不是Python語言,所以請不要出現{{{ food['name'] }}這種神奇的變量寫法,要小心諸多bug都會在這裡出現。

下表快速的展示了不同類型的變量查找

優先序 變量類型 範例 在python中的對應 備註
1 字典 {{ food.name }} food['name'] 字典的鍵是整數時,也通,如{{ food.1 }}
2 屬性 {{ foo.bar }} foo.bar 在這裡bar是foo的物件屬性
3 方法 {{ foo.bar }} foo.bar() 在這裡bar是foo的物件方法
4 清單 {{ foo.0 }} foo[0] 在這裡foo是清單,可能foo=['a','b','c']

這邊採用的是短路邏輯,Django依序查找到第一個match的項目後便結束查詢。另外值得提的是,變量的寫法有誤會導致錯誤的頁面,但是變量無法查找則會以該變量不顯示的方式(就是留白啦)回覆正常頁面。

標籤

模版語言雖然受到重重限制,但是若只能顯示變量那在view logic上可能就顯得大大的不足啦,所以標籤就應運而生了,Django內建的標籤有許多種,再加上我們能自定標籤,使得模版語言有著更大的彈性空間與表述能力,但是大家一定要注意之前的原則,別把business logic帶到模版來,講白話一點就是,該在views.py裡面處理好的事情不要帶到模版來處理,尤其當你發現模版怎麼不能夠搞定你需要的功能的時候,就該退一步回views.py中進行處理。

以下我們只介紹幾個最常用的重要標籤

for標籤

剛剛我們只列了一道菜,現在我們要加緊腳步多增加幾道了!請看

mysite/mysite/views.py
def menu(request):
    food1 = { 'name':'番茄炒蛋', 'price':60, 'comment':'好吃', 'is_spicy':False }
    food2 = { 'name':'蒜泥白肉', 'price':100, 'comment':'人氣推薦', 'is_spicy':True }
    return render_to_response('menu.html',locals())
mysite/templates/menu.html
<!doctype html>
<html>
    <head>
        <title> Menu </title>
        <meta charset='utf-8'>
    </head>
    <body>
        <h3>1道食物</h3>
        <ul>
            <li> 食物的名字是:{{ food1.name }} </li>
            <li> 食物的價格是:{{ food1.price }} </li>
            <li> 食物說明: {{ food1.comment }} </li>
            <li> 食物會辣? {{ food1.is_spicy }} </li>
        </ul>
        <h3>2道食物</h3>
        <ul>
            <li> 食物的名字是:{{ food2.name }} </li>
            <li> 食物的價格是:{{ food2.price }} </li>
            <li> 食物說明: {{ food2.comment }} </li>
            <li> 食物會辣? {{ food2.is_spicy }} </li>
        </ul>
    </body>
</html>

等等,這不是在開玩笑吧,照這樣寫的話,我有一百道菜不就完蛋了!不但在views.py裡面資料準備上困難(還好有locals,不然會更慘),在模版中重複寫code(而且每道菜的變量名還不一樣)根本是侮辱designer的智商,還好還好我們有for標籤,怎麼用呢?首先我們先來整備一下views.py中的menu函數:

mysite/mysite/views.py
def menu(request):
    food1 = { 'name':'番茄炒蛋', 'price':60, 'comment':'好吃', 'is_spicy':False }
    food2 = { 'name':'蒜泥白肉', 'price':100, 'comment':'人氣推薦', 'is_spicy':True }
    foods = [food1,food2]
    return render_to_response('menu.html',locals())

在這裡我們利用foods這個list將兩道菜的字典當做清單的元素,這有助於搭配for標籤一起使用。接著,我們可以簡化模版

mysite/templates/menu.html
<!doctype html>
<html>
    <head>
        <title> Menu </title>
        <meta charset='utf-8'>
    </head>
    <body>
        {% for food in foods %}
            <ul>
                <li> 食物的名字是:{{ food.name }} </li>
                <li> 食物的價格是:{{ food.price }} </li>
                <li> 食物說明: {{ food.comment }} </li>
                <li> 食物會辣? {{ food.is_spicy }} </li>
            </ul>
        {% endfor %}
    </body>
</html>

啟動server後我們發現for標籤幫我們對foods中的元素進行了迭代,使得<ul>元素被產生了兩次,且分別填入兩道菜的資料。其中,變量food是依據for標籤和變量foods產生的(不是由views.py送來的),他會依次對應到foods中的兩個元素。

注意,再次強調,模版語言並非python語言(即使相似程度很高),我們應當注意到for標籤的子區塊不以:開始,且必須得以{% endfor %}結束。

其實就算利用了for標籤的幫助也不能幫助解決views.py中的問題,資料還是跟business logic混雜在一起,而且我們不可能真的將資料寫在code中,這都有賴資料庫以及Model的幫助,之後我們會陸續介紹到

另外還有一點,提醒大家,在這幾篇筆記中提到的視圖函數指的是views.py裡面的負責處理business logic的函數,至於view logic反而是交給模版處理,千萬不要弄混

forloop變量及其屬性

其實這還不夠,眼尖的讀者會發現新版的模版中略去了第n道食物的標題,為了解決這個問題我,我們使用forloop.counter這個會伴隨for標籤的變量屬性來完成:

mysite/templates/menu.html
<!doctype html>
<html>
    <head>
        <title> Menu </title>
        <meta charset='utf-8'>
    </head>
    <body>
        {% for food in foods %}
            <h3>第{{ forloop.counter }}道食物</h3>
            <ul>
                <li> 食物的名字是:{{ food.name }} </li>
# 以下略...

forloop.counter會標示出這是第幾次的迭代(迴圈),他跟一般程式計數由零開始的慣例不同,是由1開始的。其他有用的一些forloop屬性,列在下表供讀者參考:

forloop屬性 說明
forloop.counter forloop的計數器,由1開始遞增到迭代總數
forloop.counter0 forloop的計數器,由0開始遞增到迭代總數減1
forloop.revcounter forloop的倒數器,由迭代總數遞減到1
forloop.revcounter0 forloop的倒數器,由迭代總數減1遞減到0
forloop.first 真假值,若是第一次for迴圈,此值為真,否則為假
forloop.last 真假值,若是最後一次for迴圈,此值為真,否則為假
forloop.parentloop 父迴圈(上一層迴圈)的forloop變量

針對forloop.paraentloop來做個說明,for標籤是允許巢狀結構的,但是為了避免forloop的混用,每一層的forloop都會是屬於自己該層的forloop,若要取用上一層的forloop變量及其屬性,必須利用forloop.parentloop來取得。

選用的empty標籤

另外,還有一個可以跟for標籤一同選用的標籤{% empty %},當forloop所要迭代的項目元素量為0時,則改為輸出本區塊的內容。我們可以進一步改善menu.html:

mysite/templates/menu.html
# 以上省略...
        {% for food in foods %}
            <h3>第{{ forloop.counter }}道食物</h3>
                <ul>
                    <li> 食物的名字是:{{ food.name }} </li>
                    <li> 食物的價格是:{{ food.price }} </li>
                    <li> 食物說明: {{ food.comment }} </li>
                    <li> 食物會辣? {{ food.is_spicy }} </li>
                </ul>
        {% empty %}
            <p>本餐廳啥都沒賣!</p>
        {% endfor %}
# 以下省略...

倒轉元素

一個序列(清單)想要利用for標籤反序輸出時,我們可以使用下列兩個方法來解決

1. 在視圖函式中便使用清單的reverse方法來逆轉
2. 為for標籤加上補充說明reversed,如:{% for item in items reversed %}

for標籤的限制

由於MTV分工的關係,標籤語言受到了種種限制(筆者太囉嗦了),所以對於for標籤來說,break或是continue的方法是不存在的,我們只能透過在視圖函式中整理出一個最貼切的清單。另外特別要注意的是,for標籤雖然能夠迭代字典的鍵,但要由鍵取值是相當容易犯錯的,請看以下例子(假定我們的視圖函數給定了一個字典 dic={'1':'a','2':'b'} ):

{% for key in dic%}
    {{ key }} = {{ dic.key }}
{% endfor %}

讀者將會發現,鍵會被輸出而對應的值不會,理由很簡單,變量的使用只會以變量的值取代變量的名稱(變量內的第一個名字),往後的各種名稱都會以字面的意思解讀。有點難懂,以上例來說{{ key }}在兩次迴圈中會分別被代換成1跟2,但是{{ dic.key }}卻會被試著解讀成dic['key']dic.keydic.key()dic[key],也就是說變量中第二個以後的名字便不會被對應到值了,因為他不是變量,而是變量的屬性、方法或鍵(字面上!不會被真的代換),dic當然沒有叫做'key'的屬性。那你問我該怎麼辦,可以考慮在視圖函式提供items = dic.items(),以下有範例:

{% for key, value in items%}
    {{ key }} = {{ value }}
{% endfor %}

{% for item in items%}
    {{ item.0 }} = {{ item.1 }}
{% endfor %}

if/else標籤

在剛剛的menu範例中,大家有沒有覺得會不會辣的地方輸出boolean值很不友善呢,我們試著使用if/else標籤來改善吧:

mysite/templates/menu.html
上略...
        {% for food in foods %}
            <h3>第{{ forloop.counter }}道食物</h3>
            <ul>
                <li> 食物的名字是:{{ food.name }} </li>
                <li> 食物的價格是:{{ food.price }} </li>
                <li> 食物說明: {{ food.comment }} </li>
                <li> 食物會辣? {% if food.is_spicy %} 辣 {% else %} 不辣 {% endif %} </li>
            </ul>
        {% empty %}
            <p>本餐廳啥都沒賣!</p>
        {% endfor %}
下略...

這邊一共用到{% if XXX %}{% else %}{% endif %}三種標籤,當然{% else %}標籤是可選的,我們也可以只單獨使用if標籤。至於變量的真假值判斷方式就跟python一樣,除了None和所有的空元素之外都為真,自定義的物件也可以透過設定物件屬性讓他有自己的定義。

使用邏輯運算

當然{% if %}標籤中我們可以使用andornot等邏輯運算子,使用的方法跟python也不有太大的不同,不過if標籤還是屬於弱弱的(才不是咧!)模版語言,邏輯運算式的能力有些許被封印這是我們可以預測的。請看下節(本節也太短了...)

if標籤的限制

如下表,請參閱!

限制 解決辦法
沒有{% elif%}標籤 那就只能用巢狀結構的{% if %}標籤了
andor不能混用 想辦法在視圖函數裡面解決
()是不能使用的,比如說{% if (a and b) %} 想辦法在視圖函數裡面解決

註解與註解標籤

前篇提過,註解會以{# here is comment #}的方式出現,但其實Django模版還提供了一個可以撰寫多行註解的標籤{% comment %},記得搭配{% endcomment% },如範例

{% comment %}
採用
多行的註解!!!
{% endcomment% }

過濾器

終於到了本篇的最後了,要介紹過濾器,過濾器會搭配著變量來使用,可以對變量做出處理獲取取得變量的一些資訊,比如說我們menu頁面想要在最一開始列出食物的總項目數量的話,可以借助過濾器length:

mysite/templates/menu.html
# 上略...
        <p>本餐廳共有{{ foods|length }}道菜</p>
        {% for food in foods %}
            <h3>第{{ forloop.counter }}道食物</h3>
            <ul>
# 下略...

過濾器會使用|管道符號(pipe)來接收前面的資料並做處理,過濾器可以串連如:{{ restaurants|first|length }},將會列出第一家餐廳的菜單數目,有些過濾器還需要接受參數如{{ word|truncatewords:"30" }},這會輸出word變量的前三十個詞。

Django內建的過濾器可以參照:

Django內建的過濾器可以參照:

到此為止基本的模版概念已經說明的差不多了,在接下去談到更進階的技術之前,我們先來回顧一下本篇提到的問題,即便使用了模版成功地將view logic移出視圖函數(別被搞混了,雖然視圖函式處理的不是視圖邏輯真的很詭異 = =)了,但是關於資料的部分竟然還留在view logic中,這既不符合MTV分工的架構,也無法將資料妥善的做管理,總之就是太不科學了,所以我們有必要得先跟各位介紹模型與資料庫。請看下篇!

← Django筆記(3) - 模版初探 Django筆記(5) - 模型與資料庫 →
 
comments powered by Disqus