高見龍

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

搬到Octopress了

image

我也換到OctoPress了!

跟流行嗎? 也算是啦,不過這種Markdown + Static Page的寫法我的確比較喜歡,特別是我寫的東西幾乎都會有一些程式碼,在原來的WordPress得花不少時間在處理程式碼範例變色的問題。

WP的plugin是很方便,但Octopress要寫plugin看起來也不是很難寫(我自己仿著內建的幾個plugin,就可以簡單的寫出給自己用的Vimeo跟Youtube的plugin了);反倒是我PHP的程度比較差,要在WP裡面改什麼東西對我來說實在難度太高了。

超便利的Github Page加上可以很簡單的自定domain name,於是我就花了一、二天的時間把文章手動的轉成markdown。還好我本來在WP就是用permalink,所以轉換過來別人還是連得到。

為什麼不用工具自動轉? 因為我想再從頭一篇一篇看自己寫的東西,而且其實也沒幾篇(算了一下才171篇)。用這個寫之後,感覺會不知不覺的就一直寫下去。

如果大家有興趣試看看的話,Heroku跟Github Page都是不錯的hosting空間,要在Heroku安裝的話可以參考這篇

Learn Python and Django from the Beginning

Python

  1. Python簡介
  2. 安裝Python
  3. 如果你是使用Windows作業系統的話..
  4. 邏輯及流程控制
  5. Method
  6. 數字
  7. Boolean
  8. 字串
  9. List
  10. Dictionary
  11. Tuple
  12. 例外處理
  13. 模組
  14. 物件導向程式設計

Django

  1. Django簡介
  2. 安裝Django
  3. 開始你的第一個專案
  4. 建立App
  5. Django Shell
  6. 更多關於Model的使用
  7. 在Django裡做Database Migration
  8. 資料表的關連
  9. 網址設定
  10. Template
  11. 從Model到Template
  12. Flatpage
  13. 小結

我另外在http://python-and-django.heroku.com/也放了一份。

這次是用Octopress + Markdown寫的,感覺相當順手,不知不覺就會一直打字下去。內容還沒很完整,我會再找時間把它繼續完成,希望內容對大家有幫助。

如果有寫錯的地方再請不吝告知,感謝!

小結

官網的文件一定要讀!!

如果你之後會把Django當做你主力的開發工具的話,目前坊間的書因為版本幾乎都沒跟上,所以大概可以先放旁邊,不過至少要把官網的資料讀過一遍。那如果文件看完怎麼辦? 閱讀原始碼啊!!

Django本身是open source的,而且是用純Python寫的,所以閱讀Django的原始碼,保證可以讓你的Python增加不少,而且當遇到一些文件上沒提到的問題的時候,通常也只有原始碼可以救你了。

養成閱讀文件及原始碼的習慣,可以讓你在這條路上走的更順利!

還有哪些主題沒講到的?

其實還滿多的,例如怎麼寄Email表單的處理資料驗證Admin模組的客製化等等,都是很常用而且很重要的主題。

這還沒有結束!!

如果時間許可,我會繼續把這個系列寫完的。

如果內容有哪邊寫錯了,還請歡迎來信跟我說 :)

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架構跟流程介紹的差不多了,接下來請趁你還有記憶的時候,把官網的文件全部讀一遍吧!