--- title: 'Django' disqus: 'For ID student' --- Django For ID student === ## Contents [TOC] ## Django ID學習指南 這份學習指南適合所有 Django 初學者,為了更好的學習效果,我們希望你能具備: * Web 的初步認識 * 了解如何使用 Command Line * 略懂 Python 基礎語法 * 看得懂簡單的 HTML / CSS ### 學習前準備 這此教學使用 * https://repl.it ### 學習範例 透過這份學習指南,你會學習到 Django 的程式架構,從創建一個專案,到最後將網站發佈到網路上,建立一個屬於自己的旅遊日記。 ## Django 介紹 Django (/ˈdʒæŋɡoʊ/ jang-goh) 可以說是 Python 最著名的 Web Framework,一些知名的網站如 Pinterest, Instagram, Disqus 等等都使用過它來開發。 它有以下的特色: * 免費開放原始碼 * 著重快速開發、高效能 * 遵從 DRY ( Don't Repeat Yourself ) 守則,致力於淺顯易懂和優雅的程式碼 * 使用類似 Model–view–controller (MVC) pattern 的架構 ### Web Framework Web framework,簡單來說就是當你開發 Web 應用程式時所用的框架。它通常會提供: 1. 一個既定的程式骨架 -- 你必須按照它的規範寫程式,例如把資料庫相關的程式與跟畫面溝通的程式分開,而不是全部寫在同一個檔案。這對於程式的開發速度、再利用性、和程式可讀性等等都有相當大的好處。 2. 強大且豐富的函式庫 ( Libraries ) -- 通常會提供一些開發網站所需要且常用的功能,例如使用者認證、安全機制、URL mapping、資料庫連接等等。讓你在開發網站時可以直接使用函式庫,然後專注在客製化自己的功能。 ### Django 的 架構 如同一些比較著名的 Web framework,Django 同樣也使用了類似 MVC 的架構,只是在定義和解釋上略為不同,稱為 MTV ( Model–Template–View ),我們可以透過下面這張圖來了解其運作方式: ![](https://i.imgur.com/1PdOgPA.png) ## Django 安裝 1. https://repl.it 2. 點選new repl 3. 選擇 Django ## Django 安裝確定 案F1 或 Ctrl + shift + P 選擇Open Shell 請在虛擬環境下指令輸入 python,進入互動式命令列環境 ``` >>>python ``` 輸入以下的指令取得 Django 版本資訊: ``` >>> import django >>> django.VERSION ``` 如果看見類似上面的訊息,就代表安裝成功囉! ## Django Project and apps 每一個 Django project 裡面可以有多個 Django apps,可以想成是類似模組的概念。在實務上,通常會依功能分成不同 app,方便未來的維護和重複使用。 例如,我們要做一個類似 Facebook 這種網站時,依功能可能會有以下 apps: * 使用者管理 -- accounts * 好友管理 -- friends * 塗鴉牆管理 -- timeline * 動態消息管理 -- news 若未來我們需要寫個購物網站,而需要會員功能時,accounts app(使用者管理)就可以被重複使用。 --- 這一章,你會學到如何使用 Django 命令列工具建立 Django project 和一個 Django app。 --- ### 建立 Django project(replt.it已經幫你弄好了) 首先,使用 django-admin.py 來建立第一個 Django project mysite: ``` ~/djangogirls$ django-admin.py startproject mysite ``` 此時會多了一個 mysite 資料夾。我們切換進去: ``` ~/djangogirls$ cd mysite ``` startproject 這個 Django 指令除了建立專案資料夾,也預設會建立一些常用檔案,你可以使用 ls 或 dir /w (Windows) 檢視檔案結構。 目前 project 的檔案結構如下: ![](https://i.imgur.com/4pVkj0P.png) --- ### 建立 Django application(app)(replt.it已經幫你弄好了) 讓我們利用 startapp 建立第一個 Django app -- trips: (djangogirls_venv) ~/djangogirls/mysite$ python manage.py startapp main startapp 會按照你的命名建立一個同名資料夾和 app 預設的檔案結構如下: ``` main ├── __init__.py ├── admin.py ├── migrations ├── models.py ├── tests.py └── views.py ``` 將新增的 Django app 加入設定檔 在前一個指令,我們透過 Django 命令列工具建立了 trips 這個 app。但若要讓 Django 知道要管理哪些 apps,還需再調整設定檔。 新增 app 打開 mysite/settings.py,找到 INSTALLED_APPS,調整如下: ``` # mysite/settings.py # Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'main', ) ``` 請注意 app 之間有時候需要特定先後順序。在此,我們將自訂的 trips 加在最後面。 --- #### 預設安裝的 Django app Django 已將常用的 app 設定為 INSTALLED_APPS 。例如,auth(使用者認證)、admin (管理後台)... 等等,我們可依需求自行增減。 --- ## Views and URLconfs ![](https://i.imgur.com/jIWeVHK.png) 在前面的介紹,我們有提到 Django 的 MTV 架構。其處理 request 的流程如下: 1. 瀏覽器送出 HTTP request 2. Django 依據 URL configuration 分配至對應的 View 3. View 進行資料庫的操作或其他運算,並回傳 HttpResponse 物件 4. 瀏覽器依據 HTTP response 顯示網頁畫面 --- 這一章,我們將透過 Hello World 範例 ,瞭解 Django 如何處理一個 request 的流程。 --- ### Django Views Django view 其實是一個 function,處理 HttpRequest 物件,並回傳 HttpResponse 物件,大致說明如下: * 會收到 HttpRequest 參數: Django 從網頁接收到 request 後,會將 request 中的資訊封裝產生一個 HttpRequest 物件,並當成第一個參數,傳入對應的 view function。 * 需要回傳 HttpResponse 物件: HttpResponse 物件裡面包含: * HttpResponse.contentHttp * Response.status_code …等 ### 建立第一個 View 首先建立一個名為 hello_world 的 view。 在 main/views.py 輸入下列程式碼: ``` # main/views.py from django.http import HttpResponse def hello_world(request): return HttpResponse("Hello World!") ``` 以上程式在做的事就是: 1. 從 django.http 模組中引用 HttpResponse 類別 2. 宣告 hello_world 這個 view 3. 當 hello_world 被呼叫時,回傳包含字串 Hello World! 的 HttpResponse 物件。 ### Django URL 設定 最後,Django 需要知道 URL 與 view 的對應關係。 例如: 有人瀏覽 http://127.0.0.1:8000/hello/ 時 ,hello_world() 這個 view function 需要被執行。 而這個對應關係就是 URL conf (URL configuration)。 --- ### URL Conf * 通常定義在 urls.py * 是一連串的規則 (URL patterns) * Django 收到 request 時,會一一比對 URL conf 中的規則,決定要執行哪個 view function --- 現在我們來設定 Hello World 範例的 URL conf 首先打開 main/urls.py,先 import 剛剛寫的 view function: ``` from main import views ``` 然後在 urlpatterns 中加入下面這行: ``` url('hello/',views.hello_world), ``` 現在 main/urls.py 的內容應該會像下面這樣: ``` # main/urls.py from django.conf.urls import url from django.contrib import admin from main import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', views.home, name='home'), url('hello/',views.hello_world), ] ``` 以上程式透過 url() function 傳入兩個參數 regex, view: ``` url(regex, view) ``` * regex -- 定義的 URL 規則 * 規則以 regular expression(正規表示式)來表達 * 'hello/' 代表的是 hello/ 這種 URL * view -- 對應的 view function * 指的是 hello_world 這個 view ### 測試 Hello World ## Templates 上一章的例子,只是很簡單的顯示一行字串。 現在,讓我們加上一些 HTML/CSS 美化網頁,並動態顯示每次進來這個頁面的時間。 ### 第一個 Template 實務上,我們會將前端的程式碼獨立出來,放在 templates 資料夾裡。不僅增加可讀性,也方便與設計師或前端工程師分工。 ### Template 資料夾(replt.it已經幫你弄好了) 首先建立 Template 資料夾。開啟終端機 (如果不想關閉 web server,可以再開新一個新的終端機視窗),並確認目前所在位置為 mysite/。 新增一個名為 templates 的資料夾`: ``` ~/djangogirls/mysite$ mkdir templates ``` ### 設定 Templates 資料夾的位置 (replt.it已經幫你弄好了) 建立好資料夾以後,我們需要修改 main/settings.py 中的 TEMPLATES 設定: ``` TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] ``` 宣告BASE_DIR路徑 ``` BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ``` 好讓 Django 找得到剛剛建立的 templates 資料夾。 ### 建立第一個 Template [html簡易教學](https://djangogirlstaipei.herokuapp.com/tutorials/html/?os=windows) 新增檔案 templates/hello_world.html: ![](https://i.imgur.com/1niJSRD.png) 並將下列的 HTML 複製到 hello_world.html: ``` <!-- hello_world.html --> <!DOCTYPE html> <html> <head> <title>I come from template!!</title> <style> body { background-color: lightyellow; } em { color: LightSeaGreen; } </style> </head> <body> <h1>Hello World!</h1> <em>{{ current_time }}</em> </body> </html> ``` ### 在 Template 中顯示變數 以上 Template 中,有個地方要特別注意: ``` <em>{{ current_time }}</em> ``` 在 Template 裡面.我們會使用兩個大括號,來顯示變數current_time。 --- {{<variable_name>}} 是在 Django Template 中顯示變數的語法。 其它 Django Template 語法,我們會在後面的章節陸續練習到。 --- ### 使用 render function 最後,將 view function hello_world 修改如下: ``` from django.shortcuts import render from datetime import datetime # Create your views here. def home(request): return render(request, 'main/index.html') def hello_world(request): return render(request, 'hello_world.html', { 'current_time': str(datetime.now()), }) ``` 1. 顯示目前時間: 為了顯示動態內容,我們 import datetime 時間模組,並用datetime.now()取得現在的時間。 2. render: 我們改成用 render 這個 function 產生要回傳的 HttpResponse 物件。 這次傳入的參數有: 1. request -- HttpRequest 物件 1. template_name -- 要使用的 template 1. dictionary -- 包含要新增至 template 的變數 --- render:產生 HttpResponse 物件。 render(request, template_name, dictionary) --- ### 大功告成 ## Models 現今的網站,都不再只是僅單純展示網頁內容的靜態網頁。大多數網站,都會加上一些與使用者互動的功能,如留言版、討論區、投票等等。而這些使用者產出的資料,往往會儲存於資料庫中。 --- 這一章,你會學到如何利用 Django Model 定義資料庫的結構(schema),並透過 Django 指令創建資料庫、資料表及欄位。 --- ### 使用 Django Model 的好處 為了開發方便,我們使用 Python 預設的資料庫引擎 - SQLite。打開 main/settings.py,看看 DATABASES 的設定。它應該長得像下面這樣: ``` DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } ``` 在這裡我們設定了資料庫連線的預設值: * ENGINE -- 你要使用的資料庫引擎。例如: * MySQL: django.db.backends.mysql * SQLite 3: django.db.backends.sqlite3 * PostgreSQL: django.db.backends.postgresql_psycopg2 * NAME -- 你的資料庫名稱 如果你使用 MySQL 或 PostgreSQL 等等資料庫,可能還要設定它的位置、名稱、使用者等等。不過我們這裡使用的 SQLite 3 不需要這些性質,所以可以省略。 ### Django Models 我們在 main/models.py 宣告一個 Post 類別,並定義裡面的屬性,而 Django 會依據這個建立資料表,以及資料表裡的欄位設定: ``` # main/models.py from django.db import models class Post(models.Model): title = models.CharField(max_length=100) content = models.TextField(blank=True) photo = models.URLField(blank=True) location = models.CharField(max_length=100) created_at = models.DateTimeField(auto_now_add=True) ``` * Django 預設會為每一個 Model 加上 id 欄位,並將這個欄位設成 primary key(主鍵),簡稱 pk,讓每一筆資料都會有一個獨一無二的 ID。 * 為 Post 定義以下屬性: ![](https://i.imgur.com/CryvxJL.png) Model fields 可為 Django Model 定義不同型態的屬性。 * CharField -- 字串欄位,適合像 title、location 這種有長度限制的字串。 * TextField -- 合放大量文字的欄位 * URLField -- URL 設計的欄位 * DateTimeField -- 日期與時間的欄位,使用時會轉成 Python datetime 型別。 更多 Model Field 與其參數,請參考 Django 文件 ### 同步資料庫 首先執行 makemigrations 指令: ``` python manage.py makemigrations ``` 這個指令會根據你對 Model 的修改刪除建立一個新的 migration 檔案,讓 migrate 指令執行時,可以照著這份紀錄更新資料庫。 接著用以下的指令,讓 Django 根據上面的紀錄,把 models.py 中的欄位寫入資料庫: ``` python manage.py migrate ``` migrate 指令會根據 INSTALLED_APPS 的設定,按照 app 順序建立或更新資料表,將你在 models.py 裡的更新跟資料庫同步。 ## Admin 大部份網站都設計有管理後台,讓管理者方便新增或異動網站內容。 而這樣的管理後台,Django 也有內建一個 App -- Django Admin 。只需要稍微設定,網站就能擁有管理後台功能。 前一章,我們學到如何使用 Django Model 抽象地表達資料庫結構。現在,我們要透過 Django Admin 看到實際的資料,並跟資料庫進行互動。 --- 完成本章後,你會瞭解如何設定 Django Admin,並使用 Django 管理後台,完成 Post 的新增、修改及刪除。 --- ### 設定管理後台 ### 將 Django Admin 加入 INSTALLED_APPS 後台管理的功能 Django 已預設開啟。因此,設定檔中的 INSTALLED_APPS 裡,已經有 django.contrib.admin 這個 app : ``` # main/settings.py INSTALLED_APPS = ( 'django.contrib.admin', ... ) ``` 當你在同步資料庫時,也會建立需要的資料表及欄位。 ### 設定管理後台的 URL 為了讓你可以從瀏覽器進入管理後台,我們需要設定對應的 urls 。 我們將管理後台的網址設定為 /admin/。確認 mysite/urls.py 中的 urlpatterns 包含下面這行: ``` url(r'^admin/', include(admin.site.urls)), ``` ### 建立 superuser 要使用 Django 的管理後台,需要一個管理員帳號。 使用 createsuperuser 這個指令,建立一個 superuser: ``` >>>python manage.py createsuperuser Username (leave blank to use 'YOUR_NAME'): Email address: your_name@yourmail.com Password: Password (again): Superuser created successfully. ``` 輸入帳號、Email、密碼等資訊,就完成 superuser 的新增了。 ### 註冊 Model class 最後,我們需要在讓 Django 知道,有哪些 Model 需要管理後台。 修改 main app 裡的 admin.py,並註冊 Post 這個 Model: ``` # main/admin.py from django.contrib import admin from .models import Post admin.site.register(Post) ``` ### 使用管理後台 ### 進入管理後台 連至 http://127.0.0.1:8000/admin,可以看到管理後台的登入頁面: ![](https://i.imgur.com/t6CUK42.png) 請輸入你剛創立的 superuser 帳號密碼,進入管理後台: 第一個區塊 Authentication and Authorization ,可以管理使用者(User)和 群組(Group);第二個 Trips 區塊裡,則可以看到剛剛設定的 Post model。在這裡可以執行 Post 的新增、修改、刪除等功能。 ![](https://i.imgur.com/RmXu939.png) 新增一個 Post 現在試著建立一個新的 Post 看看: ![](https://i.imgur.com/x6LL6mK.png) 建立成功後會回到 Posts 頁面,你會發現有一筆資料顯示為 Post object: ![](https://i.imgur.com/xgZV2GE.png) --- Django 通常以 Post object 來表示 Post 物件,但此種顯示不易辨別。我們可以透過 def __str__ 更改 Post 的表示方式。 修改 main/models.py: ``` # main/models.py from django.db import models class Post(models.Model): ... created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title ``` ### 小結 你現在己經學會: * 設定 Django Admin * 建立 superuser * 註冊 Model 至 Admin ## 使用 Django ORM 操作資料庫 在前一章,我們利用 Django Admin 新增、修改及刪除 Post 。而實際在寫程式時,我們會使用 Django 提供的 QuerySet API,來達成類似的資料庫操作。 --- 本章你會學到:如何使用 Django QuerySet API 與資料庫互動 (CRUD)。 CRUD 指的是,Create (新增)、Read (讀取)、Update (修改)、Delete (刪除) 等常見的資料庫操作。 --- ### 使用 Django Shell 使用 shell 指令,進入 Django Shell: ``` python manage.py shell ``` ### QuerySet API #### Create 首先,讓我們來試著新增幾筆資料: ``` >>> from main.models import Post >>> Post.objects.create(title='My First Trip', content='肚子好餓,吃什麼好呢?', location='台北火車站') <Post: My First Trip> >>> Post.objects.create(title='My Second Trip', content='去散散步吧', location='大安森林公園') <Post: My Second Trip> >>> Post.objects.create(title='Django 大冒險', content='從靜態到動態', location='台北市大安區復興南路一段293號') <Post: Django 大冒險> ``` #### Read 若想顯示所有的 Post ,可以使用 all(): ``` >>> from main.models import Post >>> Post.objects.all() [<Post: My First Trip>, <Post: My Second Trip>, <Post: Django 大冒險>] ``` 只想顯示部分資料時,則可以使用 get 或 filter: ``` >>> Post.objects.get(pk=1) <Post: My First Trip> >>> Post.objects.filter(location__contains='台北') [<Post: My First Trip>, <Post: Django 大冒險>] ``` * get:返回符合條件的唯一一筆資料。(注意:如果找不到符合條件的資料、或是有多筆資料符合條件,都會產生 exception) * filter:返回符合條件的陣列。如果找不到任何資料則會返回空陣列。 ### Update 當想修改資料時,可以使用 update 更新一筆或多筆資料: 首先,這裡使用 contains 針對title欄位,篩選出所有標題中包含 Trip 字眼的 Post ``` >>> posts = Post.objects.filter(title__contains='Trip') ``` --- 注意:Django ORM 會使用雙底線__,來區隔欄位title和篩選方法contains,如果只用一個底線,Django 會因為找不到欄位title_contains而出錯。 --- 共有 2 個 Post 符合上面的條件 ``` >>> posts [<Post: My First Trip>, <Post: My Second Trip>] ``` 我們將 location 的值印出 ``` >>> posts[0].location '台北火車站' >>> posts[1].location '大安森林公園' ``` 印出後發現,Post 的 location 分別為'台北火車站'和'大安森林公園'。現在我們試試用 update 指令,把它們改成 '象山親山步道' ``` >>> posts.update(location='象山親山步道') 2 ``` 回傳的數字 2 指的是已被更新的資料筆數。我們可以驗證一下 location 是否皆已被正確更新 ``` >>> posts[0].location '象山親山步道' >>> posts[1].location '象山親山步道' ``` ### Delete 我們也可以使用 delete 刪除資料: 我們試著使用 delete,將剛剛的那兩筆 Post 刪除。 ``` >>> posts.delete() ``` 最後確認一下,資料是否刪除 ``` >>> Post.objects.all() [<Post: Django 大冒險>] ``` ## Template tags 在先前的 Templates 章節中,我們已經學會基礎的 Django Template 用法 (在 Template 裡呈現變數內容)。但為了產生完整的網頁,我們會需要能在 Template 裡執行一些簡單的 Python 語法,例如: * 邏輯判斷 (if-else) -- 若使用者己經登入,則顯示使用者的暱稱;若未登入,則顯示登入按鈕 * 重覆 HTML 片段 (for loop) -- 列出所有好友的帳號和顯示圖片 * 格式化 Template 中的變數 -- 日期的格式化等等 Django template tags 讓你可以在 HTML 檔案裡使用類似 Python 的語法,動態存取從 view function 傳過來的變數,或是在顯示到瀏覽器之前幫你做簡單的資料判斷、轉換、計算等等。 --- 在這一章,我們將使用 Django ORM 存取資料庫,撈出旅遊日記全部的 posts 傳入 template,並使用 Django 的 template tags 與 filters,一步步產生旅遊日記的首頁。 --- ### 建立旅遊日記的首頁 #### 確認首頁需求 在開始動工之前,我們先確認需求。 旅遊日記的首頁應該會有: 1. 標題 2. 照片 3. 發佈日期 4. 部份的遊記內文 #### 建立首頁的 View 首先我們建立一個新的 view function - home(): ``` # main/views.py # ... from django.shortcuts import render from .models import Post def home(request): post_list = Post.objects.all().reverse()[::-1] return render(request, 'home.html', { 'post_list': post_list, }) ``` * 匯入所需的 model -- 記得 import 需要用到的 Model Post * 取得所有 posts -- 透過 Post.objects.all() 從資料庫取得全部的 posts,並傳入 home.html 這個 template。 #### 設定首頁的 URL 接下來,我們修改 urls.py ,將首頁(正規表達式 ^$)指向 home() 這個 view function: ``` url(r'^$', views.home, name='home'), ``` ### Template Tags #### 建立首頁的 Template 並印出 post_list 首先,在 templates 資料夾底下新增 home.html: ``` <!-- home.html --> {{ post_list }} ``` 打開瀏覽器進入首頁 http://127.0.0.1:8000/ ,可以看到 post_list 已呈現至網頁上了。 #### 顯示 Post 中的資料 仔細觀察印出的 post_list,會發現是以 list 的形式顯示。但我們希望的則是:存取每個 Post 中的資料,並印出來。 為了達成這個功能,我們會用到 for 這個 template tag。 ##### for 迴圈 在寫 Python 時,若想存取 list 裡的每一個元素,我們會使用 for 迴圈。而在 Django Template 中,也提供了類似的 template tags -- {% for %}。 --- ##### {% for %} 在 template 中使用類似 Python 的 for 迴圈,使用方法如下: ``` {% for <element> in <list> %} ... {% endfor %} ``` --- 瞭解了 for 的用法後,我們試著印出首頁所需的資訊。修改 home.html 如下: ``` <!-- home.html --> {% for post in post_list %} <div> {{ post.title }} {{ post.created_at }} {{ post.photo }} {{ post.content }} </div> {% endfor %} ``` * 開始標籤為 {% for %} 開始;結束標籤為 {% endfor %} * post_list 中有 3 個元素,所以 for 區塊中的內容會執行 3 次 * 迴圈中,使用標籤 {{ var }},反覆印出每個 post 中的標題、建立時間、照片網址和文章內容 重新整理瀏覽器,網頁上會有首頁所需的 post 資訊 #### 顯示照片 現在網頁已經有照片網址,我們稍微修改 template ,讓照片以圖片方式呈現。 把 home.html 的下面這一行: ``` {{ post.photo }} ``` 換成下面這樣: ``` <div class="thumbnail"> <img src="{{ post.photo }}" alt=""> </div> ``` #### 處理沒有照片的遊記 ##### if…else 另一個常用的 template tags 是 {% if %} 判斷式,用法如下: ``` {% if post.photo %} <div class="thumbnail"> <img src="{{ post.photo }}" alt=""> </div> {% else %} <div class="thumbnail thumbnail-default"></div> {% endif %} ``` * 符合條件所想要顯示的 HTML 放在 {% if<condition>%} 區塊裡 * 不符合的則放在 {% else %} 區塊裡面 * 最後跟 for 一樣,要加上 {% endif %} 作為判斷式結尾。 在這裡,我們判斷如果 post.photo 有值就顯示照片,否則就多加上一個 CSS class photo-default 另外處理。 #### Template Filter 除了 template tags ,Django 也內建也許多好用的 template filters。它能在變數顯示之前幫你做計算、設定預設值,置中、或是截斷過長的內容等等。使用方法如下: {{<variable_name>|<filter_name>:<filter_arguments>}} * <variable_name> -- 變數名稱 * <filter_name> -- filter 名稱,例如 add、cut 等等 * <filter_arguments> -- 要傳入 filter 的參數 #### 變更時間的顯示格式 在這裡,我們只練習一種很常用的 filter date。它可以將 datetime 型別的物件,以指定的時間格式輸出。 我們試著將 created_at 時間顯示成年 / 月 / 日: ``` {{ post.created_at|date:"Y / m / d" }} ``` ### 完整的 HTML 與 CSS ``` <!-- home.html --> <!DOCTYPE html> <!-- home.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>A Django Ncku's Adventure</title> <link href="//fonts.googleapis.com/css?family=Lemon" rel="stylesheet" type="text/css"> <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <link href="//djangogirlstaipei.github.io/assets/css/style.css" rel="stylesheet" type="text/css"> </head> <body> <div class="header"> <h1 class="site-title text-center"> <a href="/">A Django Ncku's Adventure</a> </h1> </div> <div class="container"> {% for post in post_list %} <div class="post-wrapper"> <div class="post"> <div class="post-heading"> <h2 class="title"> <a href="#">{{ post.title }}</a> </h2> <div class="date">{{ post.created_at|date:"Y / m / d" }}</div> </div> {% if post.photo %} <div class="thumbnail"> <img src="{{ post.photo }}" alt=""> </div> {% else %} <div class="thumbnail thumbnail-default"></div> {% endif %} <div class="post-content read-more-block"> {{ post.content }} </div> <div class="post-footer"> <a class="read-more" href="#"> Read More <i class="fa fa-arrow-right"></i> </a> </div> </div> </div> {% endfor %} </div> </body> </html> ``` ## Dynamic URL 除了在首頁顯示文章的摘要外,通常也會希望每篇文章能有獨立的網址與頁面。例如,我們可能會希望 http://127.0.0.1/post/3/ 能夠是 pk 為 3 那篇文章的網址,而頁面內容則是此篇日記的詳細資訊,而非摘要。 --- 在這個章節,我們會學到如何設定動態網址的 URL conf,讓每篇旅遊日記,擁有獨一無二的網址與頁面。 --- ### 建立單篇文章的 View 首先建立單篇文章所使用的 view function。在 main/views.py 中,新增 post_detail 這個 view 如下: ``` # main/views.py # ... from django.shortcuts import render from .models import Post def post_detail(request, pk): post = Post.objects.get(pk=pk) return render(request, 'post.html', {'post': post}) ``` 以訪客瀏覽 http://127.0.0.1:8000/post/3/ 的例子,來解釋以上程式: * 目前瀏覽文章的 pk 會傳入 view 中: 當訪客瀏覽 http://127.0.0.1/post/3/ 時,傳入 view 的 pk 會是 3。 * URL 與 pk 的對應,會在稍後設定。這裡只需知道 view 中傳入的,會是當前瀏覽文章 pk 即可。 * 取得傳入 pk 的那篇 Post 資料: 當傳入的 pk=3,代表訪客想看到 pk=3 那篇文章。我們可以利用之前學過的 ORM 語法 get, 取得該篇日記的 Post 物件: ``` post = Post.objects.get(pk=pk) # 此時 pk = 3 ``` * 回傳 HttpResponse: 將取得的 post(pk=3)傳入 Template post.html,並呈現 render 後的結果。 ### 設定動態網址的對應 日記單頁的 view function 完成後,我們來設定網址與 view 的對應。修改 main/urls.py ,加入以下內容: ``` from django.conf.urls import url from django.contrib import admin from main import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', views.home, name='home'), url('hello/',views.hello_world), url(r'^post/(?P<pk>\d+)/$', views.post_detail, name='post_detail'), ] ``` 上面的修改完成後,只要連至http://127.0.0.1/post/3/ 就會對應到 post_detail() 這個 view,並且傳入的 pk=3 。 ### 使用 Regex 提取部份 URL 為參數 我們前面提過,Django 的 URL 是一個 regular expression (regex)。Regular expression 語法可用來描述一個字串的樣式。 除了可以表示固定字串之外,還可以用來表示不確定的內容。我們一步一步解釋文章單頁所使用的 URL 設定: ``` (?P<pk>\d+) ``` 1. \d 代表一個阿拉伯數字。 1. +代表「一個以上」。 所以 \d+ 代表一個以上的阿拉伯數字,例如「0」、「99」、「12345」。可是像「8a」就不符合,因為「a」不是數字。 1. (?P<pk>) 代表「把這一串東西抓出來,命名為 pk。 所以 (?P<pk>\d+) 代表:抓出一個以上阿拉伯數字,並把抓出來的東西取名為 pk。 綜合以上的規則,r'^post/(?P<pk>\d+)/$' 會達成以下的效果: ![](https://i.imgur.com/WAq1drc.png) ### 建立單篇日記頁的 Template 回顧一下前面寫的 view function(post_detail)內容 ``` return render(request, 'post.html', {'post': post}) ``` 我們取得所需 post 物件後,傳入 post.html 這個 template 中 render。現在我們就來完成這個 template。建立 post.html 如下: ``` <!-- templates/post.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>{{ post.title }} | A Django Ncku’s Adventure</title> <link href="//fonts.googleapis.com/css?family=Lemon" rel="stylesheet" type="text/css"> <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <link href="//djangogirlstaipei.github.io/assets/css/style.css" rel="stylesheet" type="text/css"> </head> <body> <div class="header"> <h1 class="site-title text-center"> <a href="/">A Django Ncku’s Adventure</a> </h1> </div> <div class="container post post-detail"> <div class="post-heading"> <h1 class="title">{{ post.title }}</h1> <div class="date">{{ post.created_at|date:'Y / m / d' }}</div> </div> <div class="location"> <i class="fa fa-map-marker"></i> <span id="location-content">{{ post.location }}</span> </div> <div id="map-canvas" class="map"></div> <div class="post-content"> {{ post.content }} </div> <hr class="fancy-line"> <img class="photo" src="{{ post.photo }}" alt="Cover photo for {{ post.title }}"> </div> <script src="//maps.googleapis.com/maps/api/js?v=3.exp&libraries=places&sensor=false"></script> <script src="//djangogirlstaipei.github.io/assets/js/map.js"></script> </body> </html> ``` 這個 template 將 post 物件的屬性 (e.g. 標題、內文、時間......等),利用 {{ var }} 與 template filter 顯示並格式化於 HTML 中。若資料庫裡有 pk=3 的 Post,現在連至 http://127.0.0.1:8000/post/3/ 即可看到此日記的單頁。 --- #### {% url %} 連結到特定 view 的 template tag 使用方法: ![](https://i.imgur.com/cmlSLao.png) 也可以傳入參數,如: ``` {% url '<url_name>' arg1=<var1> arg2=<var2> ...%} ``` ### 加入到單篇日記頁的連結 最後,我們還需在首頁加上單篇日記的連結。我們可以使用 {% url %} 這個 template tag 達成。需要加入的地方有: 1. 每篇日記 1. 每篇日記的 Read More 按鈕 #### 設定標題連結 打開 home.html,找到下面的內容: ``` <!-- home.html --> <h2 class="title"> <a href="#">{{ post.title }}</a> </h2> ``` 將它改成 ``` <!-- home.html --> <h2 class="title"> <a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a> </h2> ``` #### 設定 Read More 按鈕的連結 在 home.html 中找到以下內容: ``` <!-- home.html --> <a class="read-more" href="#"> Read More <i class="fa fa-arrow-right"></i> </a> ``` 修改如下: ``` <!-- home.html --> <a class="read-more" href="{% url 'post_detail' pk=post.pk %}"> Read More <i class="fa fa-arrow-right"></i> </a> ``` ### 驗收成果