玩 Django — Part 3 (MTV 架構)

Andy Lu
12 min readMar 23, 2018

第一篇文章中,我們這樣說:

而Django採用的是類似MVC的MTV(Model-Template-View)架構,一般在MVC架構的View是把資料呈現出來,而MTV架構下的View是要呈現哪一個資料,並交由Template呈現出來。也就是說Django的View並不包含如何把資料輸出至畫面上,取而代之的是將資料傳給Template並由其輸出資料。

MTV 架構圖

如上圖可以發現,當使用者使用瀏覽器輸入URL時,URL 分發器(URL dispatcher)便會分析此輸入之URL該對應至什麼View,而該View就會把要呈現的資料找出來。純網頁或是需要讀取資料庫。

當此View並不包含model的存取時,View將會直接把對應之template畫出來;反之則會去資料庫尋找相對應的資料,並交由template去顯示。

URL dispatcher

一個高效能的網站,必須要有簡單及優雅的URL scheme。Django讓你可以沒有框架的限制輕鬆地設計URL。

為了設計出專屬於我們app的URL,我們建立了一個由純Python 編碼的Python module 稱為URLconf (URL configuration),可以簡單的將URL 模式(URL patterns)對應至Python functions(Views)。

範例1:

urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
]

範例1解釋:

  • 我們可以發現,URL dispatcher提供了Regular expression(正規表示式)來分析輸入之URL。
  • url(r'^articles/2003/$', views.special_case_2003): 顯示2003年份的文章,此為固定頁面。
    輸入 http://your.domain.name/articles/2003/ ,Django會呼叫 views.special_case_2003(request,'2018'),並將views定義的template畫出。
  • url(r'^articles/([0-9]{4})/$', views.year_archive): 依照輸入年份的不同,顯示不同年份的文章。
    例如: http://your.domain.name/articles/2018/ : 顯示2018年的文章,此文章格式或許會跟2003的不同,因為在2003對應的view是special_case_2003中,而這邊是views.year_archive。
    Django會呼叫 views.year_archive(request,'2018'),將2018當作參數輸入進view function內。
  • url(r'articles/([0-9]{4})/([0-9]{2})/$', views.month_archive) : 可以同時輸入多個參數。
    例如: http://your.domain.name/articles/2018/05/ 顯示2018年五月的文章,Django會呼叫 views.month_archive(request, '2018', '05')

範例2:

urlpatterns = [
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
]

範例2解釋:
在此URL中,加入了含有名稱的正規表示式。
(?P<year>[0-9]{4})表示會將正規表示法中的值代入變數year中。

  • 例如: http://your.domain.name/articles/2018/,
    Django將會呼叫views.year_archive(request, year='2018')

含有名稱的正規表示式與不含名稱的正規表示式在使用上有兩點要注意:

  1. 當採用含有名稱的正規表示式,則所有的正規表示式都是以含有名稱的正規表示式處裡,部含名稱的正規表示式將被忽略
  2. 若沒有包含名稱的正規表示式,則所有的正規表示式以順序輸入至function。

在使用URL dipatcher有兩點須注意,

  1. URLConf並不會看到get所帶的參數
    https://www.example.com/myapp/,
    → URLConf會看到/myapp/
    https://www.example.com/myapp/?page=3,
    — > URLConf 也是會看到/myapp/,並且把page=3當作http get的參數代入request中。

2. 利用正規表示式所代入的參數全部都是字串。

View

View是什麼呢? View是一種在Django應用程式中的網頁的類型,通常服務一個特殊的功能而且有一個特定的模板(Template)。

在Django中, 網頁和其他內容都是被View所分送。每一個view都代表一個簡單的Python function。

當此Python function接收到一個http請求(Http request),則此Python function會回傳一個網站響應(Web response)給使用者,這個Web response可能是一個網頁,一個重新導向(redirect),或是一個404 error,或是圖片等等…

範例1:

from django.http import Http404
from django.shortcuts import render
from polls.models import Poll
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404("Poll does not exist")
return render(request, 'polls/detail.html', {'poll': p})

範例1解釋: 當URL dispatcher 將輸入之URL導到此View(detail)時,Django會將資料庫內的資料取出,並交由template去呈現該資料;若資料庫沒有資料或是沒有此Model時,則會顯示Http404的錯誤訊息。

範例2:

def detail(request, poll_id):
if request.method == 'GET':
do_something()
elif request.method == 'POST':
do_something_else()

範例2解釋: 利用同一個URL可以發送HTTP GET以及HTTP POST的請求給同一個view,我們可以直接利用request.method來取得輸入之方法(method),來針對不同的方法去做不同的事。
應用: HTTP GET把一個表格(form)畫出來,再利用HTTP POST將輸入之資料輸入到資料庫內。

Model

Model明確定義你的資料內的資訊,這裡面只包含著必要的欄位以及儲存資料的行為。一般來說,一個Model對應到一個單一的資料庫表。

Model的基本概念:

  1. 每一個Model都是django.db.models.Model的子類別
  2. 每一個Model的屬性代表著資料庫的一個欄位。
  3. Django提供自動建立資料庫存取的API。

範例:

from django.db import modelsclass Coffee(models.Model):
name = models.CharField(max_length=60)
price = models.IntegerField()

範例解釋:

建立一個名稱為Coffee的Model,並且在裡面定義了兩個屬性,name以及price。

利用 python manage.py makemigrations 可以將此model轉換成預計migrate的格式, 再次輸入 python mange.py migrate Django就會依照此migration 檔將database對應之資料庫建立出來。

Hint: 利用python manage.py makemigrations 若發現migration檔並未如預期的產生,有兩個地方需要注意。

  1. settings.py中的 INSTALL_APPS是否已經加入目標之APP
*settings.py*INSTALL_APPS = [ 
#----
'mycafe',
#----]

2. 若在model內的改動不需要修改至database中,則migration檔不會產生。

第一次利用 python manage.py migrate 存取資料庫時,django會自動在資料庫中產生一個新的table。其預設的命名方式為:appname_modelname。
如上面的範例 mycafe_coffee 將會被django建立於資料庫內。

小試身手

題目: 有一個咖啡廳需要一個網頁內含兩頁,一頁是歡迎頁、另一頁是產品內容。

  • 我們首先定義兩組URL,
*** urls.py ***from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^/$', views.home),
url(r'^menu/$', views.menu),
]
  • 接者定義兩個View,
*** views.py ***
from django.shortcuts import render
from .models import Coffee
def home(request):
template='mycafe/home.html'
return render(request, template)
def menu(request):
coffee = Coffee.objects.all()
template = 'mycafe/menu.html'
return render(request, template, {'coffee':coffee})
  • 接著我們來建立Coffee的model。
*** models.py ***from django.db import modelsclass Coffee(models.Model):
name = models.CharField(max_length=60)
price = models.IntegerField()
  • 建立完model後,別忘了migrate到database.
python manage.py makemigrations
python manage.py migrate
  • 將Coffee model註冊到Django admin頁面,並於admin頁面來輸入資料。
*** admin.py ***from django.contrib import admin
from models import Coffee
admin.site.register(Coffee)
  • 登入admin頁面之前,必須要先有帳號才可以登入,利用createsuperuser指令可以直接建立一個管理者的帳號
  • 開啟測試網站 python manage.py startserver ,打開瀏覽器,輸入網址

http://your.test.ip/admin/ 即可進入admin頁面。

進入Coffees,任意輸入幾筆資料。

  • 最後,我們需要兩個template(home.html以及menu.html)
*** home.html ***<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cafe Home Page</title>
</head>
<body>
<h1>Welcome my cafe shop.</h1>
</body>
</html>
*** menu.html ***
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Coffee menu</title>
</head>
<body>
<h1>Coffee menu</h1>
<table>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
{% for c in coffee %}
<tr>
<td>{{ c.name }}</a></td>
<td>{{ c.price }}</td>
</tr>
{% empty %}
<li>No menu item</li>
{% endfor %}
</table>
</body>
</html>
  • 在瀏覽器輸入指定的網址,即可產生相對應的頁面

小結

在Django的MTV架構下,我們可以將各個部分的邏輯拆開,再搭配不同功能的app,如此網站的架構就會清楚又優雅。

--

--

Andy Lu

Android/Flutter developer, Kotlin Expert, like to learn and share.