高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

Django的Flatpage

Django的功能很多,但假設其實你根本不需要這麼多資料庫相關的功能,你只是想做個簡單的個人簡介頁面,然後有個簡單的後台管理介面可以讓你更新內容就好,那你也許可以使用Django的Flatpage就行了。

使用方式很簡單,只要把一些module加上去就行了。請打開settings.py,在INSTALLED_APPS加上django.contrib.flatpages

1
2
3
4
5
6
7
8
9
10
11
12
13
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    'django.contrib.flatpages',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

並且在MIDDLEWARE_CLASSES加上django.contrib.flatpages.middleware.FlatpageFallbackMiddleware

1
2
3
4
5
6
7
8
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
)

然後,你可能需要改一下TEMPLATE_DIRS,設定一下template的路徑:

1
2
3
4
5
6
7
PROJECT_BASE_DIR = os.path.dirname(__file__).replace('\\', '/')
TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
    PROJECT_BASE_DIR + '/templates',
)

PS: 感謝 Lazkey 提醒,TEMPLATE_DIRS 裡請使用絕對路徑。

urls.py記得把Admin模組相關的設定打開:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.conf.urls.defaults import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'mysite.views.home', name='home'),
    # url(r'^mysite/', include('mysite.foo.urls')),

    # Uncomment the admin/doc line below to enable admin documentation:
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),
)

再來執行一下syncdb:

> python manage.py syncdb

這樣就設定完成了。進到Admin介面,你應該可以看到多了一個"簡平頁面"的功能(如果你沒改語系的話,應該會看到Flatpage):

image

然後你就可以開始新增頁面了。不過在新增頁面的最下面有個Sites選項要特別注意,預設是www.example.com,這邊要把它換成我們自己的網址。以本機為例,加入了127.0.0.1:8000。又,因為這個site是我們後來新加的,所以要讓它生效的話,需要再回頭來改一下settings.pySITE_ID

1
  SITE_ID = 2

最後,你還需要做個template給它用:

Flatpage HTML (flatpage.html) download
1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<title>{{ flatpage.title }}</title>
</head>
<body>
{{ flatpage.content }}
</body>
</html>

把上面這些內容存在 templates/flatpages/default.html,重新整理之後,應該就可以看得到內容了。

更多詳細內容及使用方式,請見Django官網 - Flatpage章節

從Model到Template

Model我們會建了,資料也可以從後台或Shell來做新增/修改/刪除/更新,網址會設定了,Template也大概知道怎麼回事了,其實整個Django的MTV(Model-Template-View)架構到這裡已經介紹得差不多了,接下來我們就是要把資料給串起來。

回來看一下先來看一下我們的book/views.py

1
2
3
4
5
6
7
8
from django.http import HttpResponse
from django.shortcuts import render_to_response

def index(request):
  return render_to_response('book/index.html')

def detail(request, book_id):
  return HttpResponse("Book ID = %s" % book_id)

接著,我們要來把資料給撈出來,並且把它餵給Template(我知道「撈」、「餵」這些詞有點台,不過我想大家比較聽得懂):

1
2
3
4
5
6
7
8
9
10
from django.http import HttpResponse
from django.shortcuts import render_to_response
from book.models import Book

def index(request):
  books = Book.objects.all()
  return render_to_response('book/index.html', {'books' : books})

def detail(request, book_id):
  return HttpResponse("Book ID = %s" % book_id)

我們在第6行把資料庫透過Book類別給取出來,然後在第7行再以dictionary型式傳給render_to_response,這樣待會我們在Template裡就可以取用得到books這個變數。

切換到Template templates/book/index.html

Book Index Template (book_index.html) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Book</title>
</head>
<body>
目前總共有{{ books.count }}本書
  <ul>
  {% for book in books %}
    <li>書名:{{ book.title }}({{ book.page }}頁,售價:{{ book.price }})</li>
  {% endfor %}
  </ul>
</body>
</html>

打開瀏覽器,網址輸入:http://127.0.0.1:8000/,應該可以看到類似的畫面:

image

如果沒有看到列表的話,就先到佛心的Admin模組去新增個兩本書,再回來看一下結果。

那書的內容呢?

剛剛只有列表,接下來要把連結跟內容頁給做起來:

templates/book/index.html,在書名上加上連結:

Book Index Template (book_index_2.html) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Book</title>
</head>
<body>
目前總共有{{ books.count }}本書
  <ul>
  {% for book in books %}
    <li>書名:<a href="/books/{{ book.id }}">{{ book.title }}</a>({{ book.page }}頁,售價:{{ book.price }})</li>
  {% endfor %}
  </ul>
</body>
</html>

再來做一下內容templates/book/detail.html

Book Index Template (book_detail.html) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Book</title>
</head>
<body>

<h1>書名:{{ book.title }}</h1>
<ul>
  <li>頁數:{{ book.page }}頁</li>
  <li>售價:{{ book.price }}元</li>
  <li>出版日期:{{ book.publish_date }}</li>
</ul>

<h1>作者:{{ book.author.name }}</h1>
聯絡資訊:
<ul>
  <li>電話:{{ book.author.tel }}</li>
  <li>Email:{{ book.author.email }}</li>
  <li>地址:{{ book.author.address }}</li>
</ul>

</body>
</html>

同時,book/views.py也要做些調整:

1
2
3
4
5
6
7
8
9
10
11
from django.http import HttpResponse
from django.shortcuts import render_to_response, get_object_or_404
from book.models import Book

def index(request):
  books = Book.objects.all()
  return render_to_response('book/index.html', {'books' : books})

def detail(request, book_id):
  book =  get_object_or_404(Book, id = book_id)
  return render_to_response('book/detail.html', {'book' : book})

這邊我又偷懶的用到了short_cuts模組的get_object_or_404,意思就是說如果資料庫裡面找不到這筆資料的話,就會直接丟出HTTP 404的畫面。

如果程式運作正常,你應該可以看到像這樣的畫面:

image

僅為展示效果,所以頁面很陽春,我相信你拿到設計師的HTML頁面應該都比這個漂亮許多倍。

Filters

到目前為止,程式看起來已經會動了!! 但長官可能看到畫面後,可能會神來一筆的說:「那個錢的單位,可以加上千位的逗點符號嗎?」,或是「那個出版日期..可以把年份放最前面,而且用斜線分開嗎?」。

你的第一個想法,可能會是資料撈出來之後,先每一筆都處理過之後再丟給Template。但事實上這種"資料呈現"的工作,應該只要交給Template的Filter來做就好。Template的Filter有點像在Linux系統上的pipe,就是可以透過|符號,把前面的資料丟給下一個執行,直到最後結束為止,來看一些範例:

Template Build-in Filter (filter_demo.html) download
1
2
3
4
5
6
7
8
9
10
{{ "eddie"|upper }}                         # EDDIE
{{ "eddie kao"|title }}                     # Eddie Kao
{{ "this is a book"|cut:" "}}               # thisisabook
{{ ""|default:"未填寫" }}                    # 未填寫
{{ "<b>hello</b>"|striptags }}              # hello
{{ "too long, don't read"|truncatewords:2}} # too long, ...
{{ "hello python"|wordcount }}              # 2

book{{ 1|pluralize }}                       # book
book{{ 10|pluralize }}                      # books

要注意的是,filter在用的時候,|的左右不可以有空白,不然會直接出現TemplateSyntaxError。其它更多的Filter,請參考:https://docs.djangoproject.com/en/1.3/ref/templates/builtins/

看完之後,認份的來完成剛剛老闆交待的修改吧!

出版日期的年份放前面,年、月、日各別用斜線分開

修改年份表示方式 (book_detail_2.html) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Book</title>
</head>
<body>
<a href="/">回列表頁</a>
<h1>書名:{{ book.title }}</h1>
<ul>
  <li>頁數:{{ book.page }}頁</li>
  <li>售價:{{ book.price }}元</li>
  <li>出版日期:{{ book.publish_date|date:"Y/m/d" }}</li>
</ul>
<h1>作者:{{ book.author.name }}</h1>
聯絡資訊:
<ul>
  <li>電話:{{ book.author.tel }}</li>
  <li>Email:{{ book.author.email }}</li>
  <li>地址:{{ book.author.address }}</li>
</ul>

</body>
</html>

只改了一行就搞定了!

數字加上千位逗點符號

Django也有幫我們寫好這個filter,但因為這個模組預設並沒有安裝/載入,所以在使用上會稍微麻煩一些:

settings.pyINSTALLED_APPS要加上django.contrib.humanize模組:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    'django.contrib.humanize',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'author',
    'book',
)

在使用之前需要用{% load humanize %}載入,然後就可以用了:

修改年份表示方式 (book_detail_3.html) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Book</title>
</head>
<body>
{% load humanize %}
<a href="/">回列表頁</a>
<h1>書名:{{ book.title }}</h1>
<ul>
  <li>頁數:{{ book.page }}頁</li>
  <li>售價:{{ book.price|intcomma }}元</li>
  <li>出版日期:{{ book.publish_date|date:"Y/m/d" }}</li>
</ul>
<h1>作者:{{ book.author.name }}</h1>
聯絡資訊:
<ul>
  <li>電話:{{ book.author.tel }}</li>
  <li>Email:{{ book.author.email }}</li>
  <li>地址:{{ book.author.address }}</li>
</ul>

</body>
</html>

沒問題的話,應該是這個樣子:

image

打完收工,下班去了!

寫自己的Filter

有時候就是沒有現成的filter可以用(其實是不常發生..),例如老闆又說「那個日期的年份,可以改成民國年份表示嗎?」。大概猜也知道老外不太可能會幫我們寫這種filter,當然要硬改也不是不行,但就失去了我們用這個framework的原意了。我們就來寫個可以產生民國年份的filter吧(其實不過就是把年份減掉1911而已..)

filter本身其實也就是Python的程式碼而已,所以第一個問題應該會是:「filter要寫在哪裡?」

filter有固定的寫法跟位置,你可以把它寫在適合的App底下,它必須放在某個App的templatetags資料夾裡,而且因為它是一個標準的Python模組,所以在templatetags資料夾裡,它必須也要放一個__init__.py。在我們目前這個範例,我把它放在book這個App裡,模組的檔名我給它取做chinese_date.py,整個看起來的資料夾結構大概會長得像這樣:

image

再來,為了讓Django認得你寫的filter,你會需要有一個叫做registertemplate.Library實體變數,所以它的固定寫法會是長得像這樣:

1
2
3
4
5
6
7
8
# encoding: utf-8
from django import template
register = template.Library()

def chinese_date(value):
  return "民國%i%i%i日" % (value.year - 1911, value.month, value.day)

register.filter("chinese_date", chinese_date)

第1行加上encoding: utf-8是因為我們在程式裡面有用到中文字元,如果沒加這行的話會出現SyntaxError。第2、3行就是Django規定filter的固定寫的filter method,最後第8行再把它註冊起來,讓Django看得到它。第8行的註冊方式,也可以用method decorator的寫法:

1
2
3
4
5
6
7
# encoding: utf-8
from django import template
register = template.Library()

@register.filter
def chinese_date(value):
  return "民國%i%i%i日" % (value.year - 1911, value.month, value.day)

效果一樣,不過我個人比較偏好這種寫法。filter寫好了,最後回來原來的Template:

Custom Filter (book_detail_4.html) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Book</title>
</head>
<body>
{% load humanize %}
{% load chinese_date %}
<a href="/">回列表頁</a>
<h1>書名:{{ book.title }}</h1>
<ul>
  <li>頁數:{{ book.page }}頁</li>
  <li>售價:{{ book.price|intcomma }}元</li>
  <li>出版日期:{{ book.publish_date|chinese_date }}</li>
</ul>
<h1>作者:{{ book.author.name }}</h1>
聯絡資訊:
<ul>
  <li>電話:{{ book.author.tel }}</li>
  <li>Email:{{ book.author.email }}</li>
  <li>地址:{{ book.author.address }}</li>
</ul>

</body>
</html>

{% load chinese_date %}載入剛剛寫好的模組,修改一下filter,最後得樣子應該會長這樣:

image

搞定,下班啦!

更多相關細節,請參考 https://docs.djangoproject.com/en/1.3/howto/custom-template-tags/

還有嗎?

到這裡,其實我們已經將Django的MTV架構跟流程介紹的差不多了,接下來請趁你還有記憶的時候,把官網的文件全部讀一遍吧!

Template

上一章我們學到了怎麼設定網址跟對應的action,但畫面的呈現不可能永遠只有一個Hello BookStore!這麼美好。很多時候程式設計師是需要跟美術設計師合作的,通常的流程是工程師會從設計手上拿到HTML + CSS的檔案,如果你曾經用PHP開發過網站,我猜大概你可能是把拿到的.html改成.php,然後開始HTML裡面塞PHP程式碼。Django的MTV架構也差不多,而且Django雖然是Python寫的,但不同的是你不可以在Template裡寫Python的程式碼,你在Template只能用Template提供的API,以及一些filter跟tag(不管是內建或是自己寫的)。

主要的目的就是希望大家不要把以前那種寫義大利程式碼(Spaghetti code)的不良習慣帶來Django。

回來看一下我們原來的book/views.py

1
2
3
4
5
6
7
from django.http import HttpResponse

def index(request):
  return HttpResponse("Hello BookStore!")

def detail(request, book_id):
  return HttpResponse("Book ID = %s" % book_id)

接著我們把原本的HttpResponse改寫成:

1
2
3
4
5
6
7
8
from django.http import HttpResponse
from django.shortcuts import render_to_response

def index(request):
  return render_to_response('book/index.html')

def detail(request, book_id):
  return HttpResponse("Book ID = %s" % book_id)

如果你有注意到shortcuts字樣,應該不難猜得出來因為這個功能太常用了,所以本來其實要寫一堆東西(例如要用loader把template抓出來,再把結果render出來),變成一行render_to_response就搞定,我很偷懶,所以平常幾乎都是用render_to_response就夠了。

你可能會好奇,這個book/index.html要放在哪裡? 先等等,我們先改個設定,請打開專案根目錄的settings.py,在TEMPLATE_DIRS裡加一些東西:

1
2
3
4
5
6
TEMPLATE_DIRS = (
    'templates',
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

設定這個的目的是要告訴Django要去哪邊取得template的檔案,這裡我設定了templates。接著,我們要來自己在專案根目錄底下建立一個templates的資料夾,然後結構像這樣:

image

原來的這行程式碼:

return render_to_response('book/index.html')

就會知道去哪邊找index.html這個檔案了。

這樣一來,你從設計那邊拿到的HTML檔案,就可以依照像index.html這樣的規則來擺放了。

Template API

前面提到,在Template裡不可以放Python的程式碼,只能放Template提到的API。什麼是Template API? 我直接從Django官網文件偷一小段程式碼來看:

Template API Demo (template_api_demo.py) download
1
2
3
4
5
6
7
8
9
{% if latest_poll_list %}
    <ul>
    {% for poll in latest_poll_list %}
        <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

你看到的這些程式碼,是可以寫在.html裡的,反正到時候Django會把.html當做Template讀出來之後再解析它,最後把內容render給使用者看。

if .. else ..來說,因為在HTML裡沒辦法對齊(就算對齊了萬一被設計師拿回去改過回來可能又不齊了),所以Template API要用一個結束標記,告訴Django說這個if已經在這裡結尾了。

在Template裡,如果看到{% .. %}的,表示它是一個邏輯運算或是語法,通常不會有輸出值;而看到{{ .. }}的話,就是會把裡面的值給吐出來餵在HTML裡的。

Template API很多,在繼續往下之後,請你先看一下https://docs.djangoproject.com/en/1.3/ref/templates/api/這份資料,知道一下Template API是怎麼一回事,又跟一般的Python程式碼有什麼不同?

大概看過之後,接著我們來看看怎麼樣把資料給丟到Template裡呈現出來。

網址設定

Model的部份看過之後,接著介紹Django很重要的一個單元,就是怎麼在Django裡設定或設計你網站內的網址。

以前你可以會看到像這樣的網址:

http://www.eddie.com.tw/book/index.php?id=100

網址後面接問號又接了一些參數,從網址大概猜得出來這應該是編號(id)第100號的書。這樣的做法不是不好,只是近年來有更好的做法,而且對SEO(Search Engine Optimization)有一些加分的效果:

http://www.eddie.com.tw/book/100

這該怎麼做呢? 在Django的網址設定是交給一個叫做urls.py的檔案在管理(其實不限定只有一個,也可以多個/模組化的設定),先看一下它原來的樣子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.conf.urls.defaults import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
  # Examples:
  # url(r'^$', 'bookstore.views.home', name='home'),
  # url(r'^bookstore/', include('bookstore.foo.urls')),

  # Uncomment the admin/doc line below to enable admin documentation:
  # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

  # Uncomment the next line to enable the admin:
  url(r'^admin/', include(admin.site.urls)),
)

urls.py的主要功能,就是設定"當遇到某個網址的request的時候,請找指定的App裡的View的action處理"。設定網址的對應,可能會需要學一些簡單的Regular Expression語法。

我們先來設定一下整個網站的首頁,讓它交給books/views.py裡的index方法處理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.conf.urls.defaults import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    url(r'^$', 'bookstore.book.views.index'),
    url(r'^books/(?P<book_id>\d+)/$', 'bookstore.book.views.detail'),
    # url(r'^bookstore/', include('bookstore.foo.urls')),

    # Uncomment the admin/doc line below to enable admin documentation:
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),
)

其中這行:

url(r'^$', 'bookstore.book.views.index'),

表示「當遇到網址後面沒接東西的時候(也就是大家俗稱的"首頁"),請找book/views.py裡面的index方法處理」,前面的bookstore是整個專案的名稱。設定完之後如果你直接連網址http://127.0.0.1:8000/的話,會看到這個畫面:

image

跳出了ViewDoesNotExist的錯誤訊息了。看到錯誤訊息不用擔心,通常錯誤訊息裡都會跟你說哪邊有錯。原來是我們剛剛把首頁指向之後,但並沒有在對應的地方加上index方法,所以請打開book/views.py,加上index方法:

1
2
3
4
5
6
7
from django.http import HttpResponse

def index(request):
  return HttpResponse("Hello BookStore!")

def detail(request, book_id):
  return HttpResponse("Book ID = %s" % book_id)

存檔後再回來看,你應該就可以看到這個畫面:

image

如果你這樣設定:

url(r'books\.php', 'bookstore.book.views.index'),

這表示當你連到http://127.0.0.1:8000/books.php的時候,會把request丟給book/views.pyindex方法處理。看起來好像是PHP,但其實不是。二、三年前我就曾經用Django做過幫Microsoft做過某個網路行銷活動案,客戶沒有規定要用什麼程式語言(只要會動就好),不過為了讓客戶覺得我是用ASP.NET開發的,我還特地在urls.py加上.aspx的附檔名。

是有點蠢,不過也挺有趣的不是嗎? :)

接下來,我們再來看看這行:

url(r'^books/(?P<book_id>\d+)/$', 'bookstore.book.views.detail'),

意思就是說當遇到http://127.0.0.1:8000/books/100的網址的時候,那個100會被當做book_id傳給book/views.pydetail方法當做參數。

views.py裡的方法,通常我們會稱它叫做action,每個action方法的第一個參數,一定都是http request,每個action的回傳值,一定要是一個HttpResponseviews.py在一般的MVC架構裡,大概就是Controller之類的角色。

如果你有注意到在urls.py裡的這行註解,長得跟我們寫的不太一樣:

url(r'^bookstore/', include('bookstore.foo.urls')),

這其實是另一種url的設定,雞蛋不一定要放在同一個籠子裡。當你的專案如果小的時候,全部放在專案根目錄的urls.py是沒問題的(中央集權),但隨著功能越加越多,你的網址對應可能會越來越多、越來越複雜,這時候你可以在App裡自己建一個urls.py,然後把所有跟這個App有關的url"委任"給它管理(地方自治),不過這部份的細節就不再多說明,還請再參考官網文件URL dispatcher

即使我們會設定網址了,會寫對應的action了,但還是少了什麼.. 如果設計師拿一個切好版的HTML + CSS給我,難到我要像這樣用HttpResponse整個把它印出來? 當然不是,接著我們來看看在Django的MTV架構裡的T,Template!

資料表的關連

我們在開發資料庫相關的網站的時候,通常會用到許多資料表,而且這些資料表之間彼此都有關連性,接著我們就來看看在Django的Model要怎麼實作。

我們現在再來新增一個App:

> python manage.py startapp book

book/models.py的內容長得像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.db import models
from author.models import Profile
from django.contrib import admin

class Book(models.Model):
  title        = models.CharField(max_length = 150)
  page         = models.IntegerField()
  price        = models.IntegerField()
  author       = models.ForeignKey(Profile)
  publish_date = models.DateField()

  class Meta(object):
    db_table = "book"

class BookAdmin(admin.ModelAdmin):
  pass

admin.site.register(Book, BookAdmin)

這裡用到了ForeignKey,對應到了我們之前建的Profile類別,表示這本書的作者資料是指向profile的某一筆資料,書對作者來說是一個一對一的關係,但是一個作者可能會寫多本書,作者對書來說就是多對一的關係(雖然現實上一本書可能有超過一個以上的作者,不過我們就把它單純化,讓生活暫時美好一下..)

別忘了把book這個App也設定到settings.pyINSTALLED_APP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'author',
    'book',
)

執行python manage.py sql book,看一下它轉換出來的SQL語法:

1
2
3
4
5
6
7
8
9
10
11
BEGIN;
CREATE TABLE "book" (
    "id" integer NOT NULL PRIMARY KEY,
    "title" varchar(150) NOT NULL,
    "page" integer NOT NULL,
    "price" integer NOT NULL,
    "author_id" integer NOT NULL REFERENCES "profile" ("id"),
    "publish_date" date NOT NULL
)
;
COMMIT;

執行python manage.py syncdb,把這個Book類別也轉成資料表。因為我們剛才也把Book類別也註冊到Admin模組了,所以我們到後台也可以看得到它。你可以在新增Book資料時候發現它的Author欄位在後台變成下拉選單了:

image

方便吧 :)

Model其實還有很多東西可以介紹的,不過因為我們的目的是為了快速看過一遍Django這個web framework大概是怎麼回事,所以細節還請各位參考官網文件(一定要看!!)。