iT邦幫忙

0

Django學習紀錄 15.模板進階技巧[含static靜態檔使用方式]

之前有在用Django寫一些小網站,現在暑假想說再來複習一下之前買的這本書
https://ithelp.ithome.com.tw/upload/images/20190724/20118889bj9fH1vhuR.jpg
於是我就把它寫成一系列的文章,也方便查語法
而且因為這本書大概是2014年出的,如今Django也已經出到2.多版
有些內容也變得不再支援或適用,而且語法或許也改變了
所以我會以最新版的Python和Django來修正這本書的內容跟程式碼

目錄:django系列文章-Django學習紀錄

15. 模板進階技巧

15.1 主題1-重複利用模板

DRY(don't repeat yourself,不要重複你自己)
WET(write everything twice,什麼事都寫兩次)
這小節要用django好好地來重複利用已經寫過的模板

15.1.1 匯入模板

使用{% 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 '模版名稱(路徑)' %}

15.1.2 模板繼承

不過其實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可以是一個字面的字串,也可以是一個變量,當使用變量的時候,可以動態地更換繼承的模板

15.1.3 利用模板繼承更新我們的網站

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

15.2 主題2-RequestContext與Context處理器

這邊大部分的用法在新版的django中已經不再支援
如果有人知道該如何繼續沿用的話麻煩指教一下!
所以只介紹

15.2.3 使用render

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

15.3 主題3-自定義過濾器

15.3.1 前置作業

在restaurants目錄下建一個資料夾templatetags
在templatetags資料夾中新增一個__init__.py
這樣才會被python視為一個套件
接著再新增一個myfilters.py

15.3.2 過濾器函式

之前的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.

要注意的是,過濾器函式我們總是要保證它是對的,也就是說,我們不允許該函式能夠拋出例外
因為任何的例外都會導致網站顯示錯誤,我們應該想辦法對於不可避免的例外採行捕捉,並且回傳一個空白字串

15.3.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預設會使用過濾器函式的名稱作為過濾器名稱

15.3.4 載入過濾器

必須在模板裡載入它
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的過濾器
要注意的是前面的步驟必須一模一樣

15.4 主題4-使用靜態檔

網站中會用到的圖片、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 collectstaticSTATIC_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 %}載入靜態檔

上一篇:Django學習紀錄 14.權限與註冊

下一篇:Django學習紀錄 16.URL配置與視圖進階技巧


尚未有邦友留言

立即登入留言