之前有在用Django寫一些小網站,現在暑假想說再來複習一下之前買的這本書
於是我就把它寫成一系列的文章,也方便查語法
而且因為這本書大概是2014年出的,如今Django也已經出到2.多版
有些內容也變得不再支援或適用,而且語法或許也改變了
所以我會以最新版的Python和Django來修正這本書的內容跟程式碼
DRY(don't repeat yourself,不要重複你自己)
WET(write everything twice,什麼事都寫兩次)
這小節要用django好好地來重複利用已經寫過的模板
使用{% include %}
index.html
<html>
<head>
<title>Index</title>
<meta charset="utf-8">
</head>
<body>
<h2>歡迎來到餐廳王</h2>
<p><a href="/accounts/register/">註冊</a></p>
{% if request.user.is_authenticated %}
<p>
{{request.user}} 您已經登入囉~
<a href="/accounts/logout/">登出</a>
</p>
<a href="/restaurants_list/">餐廳列表</a>
{% else %}
<p>您尚未登入喔~<a href="/accounts/login/">登入</a></p>
{% endif %}
</body>
</html>
可以拆解為
index.html
<html>
<head>
<title>Index</title>
<meta charset="utf-8">
</head>
<body>
{% include 'body.html' %}
</body>
</html>
與
body.html
<h2>歡迎來到餐廳王</h2>
<p><a href="/accounts/register/">註冊</a></p>
{% if request.user.is_authenticated %}
<p>
{{request.user}} 您已經登入囉~
<a href="/accounts/logout/">登出</a>
</p>
<a href="/restaurants_list/">餐廳列表</a>
{% else %}
<p>您尚未登入喔~<a href="/accounts/login/">登入</a></p>
{% endif %}
include的用法{% include '模版名稱(路徑)' %}
不過其實include有時候沒那麼好用
一個頁面可能需要花上好幾個html檔,且檔案零碎
這時會非常痛苦!
那麼就要改用模板的繼承了
首先制定一個基礎的模板
base.html
<!doctype html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
網頁選單
<h1>{% block pagetitle %}{% endblock %}</h1>
<h1>說明</h1>
<p>{% block content %}{% endblock %}</p>
頁尾
</body>
</html>
在每個需要自定義的地方,使用模板區塊
一個模板區塊以{% block BLOCKNAME %}
為開頭,以{% endblock %}
為結尾
可以填入內容也可以留白,這個區塊的內容會讓繼承此基礎模板的子模板覆寫,這與物件導向中關於類別的繼承與覆寫很類似
接著使用{% extends %}
標籤讓子模板去繼承基礎模板
final.html
{% extends 'base.html' %}
{% block title %}(頁首標題){% endblock %}
{% block pagetitle %}(頁面標題){% endblock %}
{% block content %}(內容){% endblock %}
而{% extends TEMPLATENAME %}
標籤的參數TEMPLATENAME
可以是一個字面的字串,也可以是一個變量,當使用變量的時候,可以動態地更換繼承的模板
mysite/templates/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock title %}</title>
</head>
<body>
<h2>{% block content %}{% endblock content %}</h2>
{% if request.user.is_authenticated %}
<p>
{{request.user}} 您已經登入囉~
<a href="/accounts/logout/">登出</a>
</p>
<a href="/restaurants_list/">餐廳列表</a>
{% else %}
<p>您尚未登入喔~<a href="/accounts/login/">登入</a></p>
{% endif %}
<p>若您還沒有帳號,請<a href="/accounts/register/">註冊</a></p>
</body>
</html>
mysite/templates/index.html
{% extends 'base.html' %}
{% block title %} 首頁 {% endblock%}
{% block content %}
<p>歡迎來到餐廳王</p>
{% endblock %}
這邊大部分的用法在新版的django中已經不再支援
如果有人知道該如何繼續沿用的話麻煩指教一下!
所以只介紹
從from django.shortcuts import render
匯入
用法 render(HttpRequest, TEMPLATENAME, DICTIONARY)
第一個參數放request
物件
第二個參數放模板
第三個參數放字典
與render_to_response
來比較
從from django.shortcuts import render_to_response
匯入
用法 render_to_response(TEMPLATENAME, DICTIONARY)
第一個參數放模板
第二個參數放字典
不能放request參數
所以如果要取得request資訊只能用render
在restaurants目錄下建一個資料夾templatetags
在templatetags資料夾中新增一個__init__.py
這樣才會被python視為一個套件
接著再新增一個myfilters.py
之前的menu.html模板
如果想要自定義一個過濾器yes_no
...
<tr>
<td> {{ food.name }} </td>
<td> {{ food.price }} </td>
<td> {{ food.comment }} </td>
<td> {{ food.is_spicy|yes_no:"辣/不辣" }} </td>
</tr>
...
這個過濾器接受一個額外的字串參數Y/N,如果is_spicy為真則輸出Y,否則輸出N
mysite/restaurants/templatetags/myfilters.py
def yes_no(bool_value, show_str):
if bool_value:
return show_str.partition('/')[0]
else:
return show_str.partition('/')[1]
函式的名字不需要跟我們自定義的過濾器名稱一樣,但是選擇相同名稱通常是比較好的作法,bool_value是過濾器的第一個參數,他負責接收模板中pipe符號(|)左邊的值,而show_str是第二個參數,用來接收過濾器中的額外參數
對照圖:
{{ food.is_spicy|yes_no:"辣/不辣" }}
------------- ------ --------
2. 1. 3.
def yes_no(bool_value, show_str):
------ ---------- --------
1. 2. 3.
要注意的是,過濾器函式我們總是要保證它是對的,也就是說,我們不允許該函式能夠拋出例外
因為任何的例外都會導致網站顯示錯誤,我們應該想辦法對於不可避免的例外採行捕捉,並且回傳一個空白字串
寫好過濾器函式後接著要註冊給Django知道
myfilters.py
from django import template
def yes_no(bool_value, show_str):
if bool_value:
return show_str.partition('/')[0]
else:
return show_str.partition('/')[1]
register = template.Library()
register.filter('yes_no', yes_no)
使用register的filter函式來註冊,filter函式的第一個參數是過濾器的名稱,第二個參數是它對應的過濾器函式
這就是為什麼過濾器函式名稱不需要跟過濾器的名稱一樣
要注意的是,不要這樣寫:
template.Library().filter('yes_no',yes_no)
或是:
reg = template.Library()
reg.filter('yes_no',yes_no)
沒有把register(名稱也要一樣)給建立出來就會出現錯誤
也可以用裝飾器來註冊
from django import template
register = template.Library()
@register.filter(name='yes_no')
def yes_no(bool_value, show_str):
if bool_value:
return show_str.partition('/')[0]
else:
return show_str.partition('/')[1]
該裝飾器的參數name
是個可選的參數,用來定義過濾器的名稱,如果不提供該參數,Django預設會使用過濾器函式的名稱作為過濾器名稱
必須在模板裡載入它
menu.html
{% load myfilters %}
...
<tr>
<td> {{ food.name }} </td>
<td> {{ food.price }} </td>
<td> {{ food.comment }} </td>
<td> {{ food.is_spicy|yes_no:"辣/不辣" }} </td>
</tr>
...
在這裡只能載入restaurants app的過濾器
要注意的是前面的步驟必須一模一樣
網站中會用到的圖片、css、js檔等被稱為靜態檔(static file)
在上層mysite底下新增兩個資料夾static和assets
static資料夾是開發時用來放置靜態檔的目錄
該目錄底下可以新增數個子目錄來放置不同種類的靜態檔
比如設置img資料夾來放置圖片,或是設置css資料夾來放置css檔
而assets是網站真正上線時放置靜態檔的目錄
這兩個目錄之所以分開,是因為在上線時我們需要將靜態檔的管理權交給網頁伺服器
設定settings.py
...
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
STATIC_ROOT = os.path.join(BASE_DIR, 'assets')
...
三個參數的說明:
參數 | 說明 |
---|---|
STATIC_URL |
靜態檔的URL pattern,設為/static/ 代表在網頁路徑中以/static/ 開始的便會被視為靜態檔,如127.0.0.1/static/hello.png、127.0.0.1/static/hi.js,但如果是127.0.0.1/restaurants/static/則會匹配urls.py中的路徑而非靜態檔 |
STATIC_DIRS |
開發時放置靜態檔的資料夾,允許底下設置多個資料夾來指示靜態檔的位置 |
STATIC_ROOT |
上線時放置靜態檔的資料夾,在部署時Django藉由python manage.py collectstatic 將STATIC_DIRS 下發現的靜態檔複製至STATIC_ROOT 。由於當DEBUG設為False後,Django預設便不會處理回傳靜態檔了,藉由指令將這些檔案集合到一個資料夾下,方便網頁伺服器管理及讀取 |
假設網站要放一張圖片logo.png在mysite/static/img底下 | |
那麼在模板上要這樣寫: |
...
<img src='/static/img/logo.png'>
...
如果有一天STATIC_URL被更改為/static_file/
就得一個一個更改模板中的路徑,這樣會超累
解決辦法是:
{% load staticfiles %}
...
<img src="{% static 'img/logo.png' %}">
...
這裡的{% static %}
標籤就會自動幫我們找到靜態檔的路徑了
記得要{% load staticfiles %}
載入靜態檔