---
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 ),我們可以透過下面這張圖來了解其運作方式:

## 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 的檔案結構如下:

---
### 建立 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

在前面的介紹,我們有提到 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:

並將下列的 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 定義以下屬性:

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,可以看到管理後台的登入頁面:

請輸入你剛創立的 superuser 帳號密碼,進入管理後台:
第一個區塊 Authentication and Authorization ,可以管理使用者(User)和 群組(Group);第二個 Trips 區塊裡,則可以看到剛剛設定的 Post model。在這裡可以執行 Post 的新增、修改、刪除等功能。

新增一個 Post
現在試著建立一個新的 Post 看看:

建立成功後會回到 Posts 頁面,你會發現有一筆資料顯示為 Post object:

---
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+)/$' 會達成以下的效果:

### 建立單篇日記頁的 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
使用方法:

也可以傳入參數,如:
```
{% 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>
```
### 驗收成果