高見龍

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

更多關於Model的使用

在上一個章節的Shell操作過程中,如果你有注意到的話,你會發現一段長得像這樣的東西:

>>> Profile.objects.all()
[<Profile: Profile object>]

但如果你想讓這個object能夠一眼就看得出來它是什麼內容的話,只要在Model的Profile類別裡覆寫一些method,我們再次打開author/models.py,原來的程式碼長這樣:

1
2
3
4
5
6
7
8
9
10
11
12
from django.db import models

# Create your models here.
class Profile(models.Model):
  name     = models.CharField(max_length = 50)
  age      = models.IntegerField()
  tel      = models.CharField(max_length = 30)
  address  = models.CharField(max_length = 100)
  email    = models.EmailField()

  class Meta(object):
    db_table = "profile"

我們來幫它加一個__unicode__的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.db import models

# Create your models here.
class Profile(models.Model):
  name     = models.CharField(max_length = 50)
  age      = models.IntegerField()
  tel      = models.CharField(max_length = 30)
  address  = models.CharField(max_length = 100)
  email    = models.EmailField()

  def __unicode__(self):
    return self.name

  class Meta(object):
    db_table = "profile"

然後我們再到shell裡操作的時候,就會發現秀出來的資料變得不一樣了:

>>> from author.models import Profile
>>> eddie = Profile(name = "eddie kao", age = 30, tel = "0928123123", address = "Taipei, Taiwan", email = "eddie@digik.com.tw")
>>> eddie
<Profile: eddie kao>

看到了嗎? 它由原本的object變成會秀出名字了。其實原理就是當你在Python用print方法把物件給印出來的時候,它會呼叫這個類別裡實作的__str____repr__以及__unicode__,其實這三個有一些些微的差異,細節可參考Python的文件說明,這裡我們直接覆寫Profile類別__unicode__方法,讓它回傳name欄位的值。這樣的修改,稍候你在後台看資料的時候,也會發現它會由原本的object,變成秀出你設定的name欄位。

事實上,你還可以定義更多的方法,把一些程式邏輯給包在Model裡,這樣一來,你在View(Controller)的地方就可以讓程式碼變得更簡潔,也更容易維護。例如我們來簡單寫個檢查該名作者是否已經成年(年齡 > 18):

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

# Create your models here.
class Profile(models.Model):
  name     = models.CharField(max_length = 50)
  age      = models.IntegerField()
  tel      = models.CharField(max_length = 30)
  address  = models.CharField(max_length = 100)
  email    = models.EmailField()

  def is_adult(self):
    return self.age >= 18

  def __unicode__(self):
    return self.name

  class Meta(object):
    db_table = "profile"

再來進到shell裡看看:

> bookstore  python manage.py shell
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from author.models import Profile
>>> author = Profile.objects.get(id = 1)
>>> author
<Profile: eddie>
>>> author.is_adult()
True

原本你可能需要在View(Controller)層寫個if .. else ..來判斷,現在可以把這個判斷包在Model層裡,一來程式碼容易除錯,而且因為都集中在Model的話,萬一今天假設我們的法律把成年人條件改成滿20歲,你只要修改一下Model的邏輯判斷,其它程式碼不用修改就可以下班了,這樣感覺不是很歡樂嗎?

那個超佛心的管理後台呢?

記得我們在前面有提到Django有送我們一個很好用的後台管理系統嗎? 但那時候登入之後空空的什麼都沒有,接下來我們要來讓這個Profile類別在後台也可以管理。要讓Model在Admin模組可以看得到,有兩種做法,第一種是在App裡面新增一個admin.py,內容長得像這樣:

1
2
3
4
from django.contrib import admin
from author.models import Profile

admin.site.register(Profile)

這段程式碼的大意就是我們要把Profile類別給"註冊"到Admin模組裡,讓它也感覺得到它的存在。登入後台後,你應該可以看到Profile類別出現了:

image

你可以試著玩看看它的一些管理功能,試試從Admin模組去新增/修改/刪除/更新一些資料。除了剛剛在App裡新增admin.py之外,另一種做法就是把這個"註冊"的動作寫在Model裡,原來的Profile類別看起來會像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from django.db import models
from django.contrib import admin

# Create your models here.
class Profile(models.Model):
  name     = models.CharField(max_length = 50)
  age      = models.IntegerField()
  tel      = models.CharField(max_length = 30)
  address  = models.CharField(max_length = 100)
  email    = models.EmailField()

  def is_adult(self):
    return self.age >= 18

  def __unicode__(self):
    return self.name

  class Meta(object):
    db_table = "profile"

class ProfileAdmin(admin.ModelAdmin):
  pass

admin.site.register(Profile, ProfileAdmin)

兩種做法都可以,我個人比較偏第二種,因為可以少寫一個檔案(懶!),程式碼只要在Model找就行了。

到目前為止,我們很單純的都只有一個表格,但現實生活不可能有這麼美好的事,所以我們再來新增一些資料表,並且讓這些資料表彼此有一些關連。

Django Shell

有互動才是王道!!

學過Python的朋友應該都知道,Python有個很方便的互動shell,可以讓你在互動的指令模式底下,試驗一下程式碼是不是可以正確執行,是個相當方便的工具。在Django也提供了類似的工具,只要輸入python manage.py shell,就會進入shell模式,而且還有把該專案的一些設定也一起加進來,也就是說你可以在shell裡面直接就可以操作資料庫。

接著來看看怎麼樣在shell裡面新增/修改/刪除資料:

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
> python manage.py shell
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from author.models import Profile
>>> Profile.objects.all()
[]
>>>
>>> eddie = Profile(name = "eddie kao", age = 30, tel = "0928123123", address = "Taipei, Taiwan", email = "eddie@digik.com.tw")
>>> eddie.save()
>>>
>>> Profile.objects.all()
[<Profile: Profile object>]
>>>
>>> eddie.id
1
>>>
>>> eddie.name
'eddie kao'
>>> someone = Profile.objects.get(id = 1)
>>> someone.email
u'eddie@digik.com.tw'
>>>
>>> someone.email = 'eddie@anotheremail.com'
>>> someone.save()
>>>
>>> someone.delete()

從上面的程式碼可以看到,我們都是透過Profile類別來做操作,例如我們在第10行建立了一個Profile類別的實體eddie,並在建立的時候傳入了一些參數(其實這些參數就是每個欄位的對應名稱),接著我們在第11行執行eddie.save(),這樣就新增了一筆資料到profile的資料表裡了;第21行,透過get方法取得一個id = 1的物件出來,第25行設定新值之後,第26行一樣執行someone.save()進行資料的更新;如果你要刪除該筆資料,在第28行執行delete(),該筆資料就會被刪除。

透過類別跟物件的操作,讓你可以不用寫SQL語法也能操作資料庫。

要提醒大家,不用寫SQL語法不代表你可以不用懂SQL語法,事實上像這樣的ORM(Object-Relational Mapping)只是讓你用比較抽象的方式來操作資料,但如果你不知道實際上它到底幫你做了什麼事,當你要組合比較複雜的查詢的時候就會踢到鐵板了。

建立App

在上一章節我們介紹如何建立一個Django的專案,以及快速的建立起後台管理系統。但事實上它還是什麼都沒有,接下來我們要開始來幫這個空的專案加功能上去了。

建立App

這裡這個App跟現在我們常聽到手機的App是不一樣的東西,一個Django專案裡,可以有好幾個App,每個App通常代表的是一個功能。建立App的方法跟之前建立專案的方式有點像,只是關鍵字有些不同,例如我想加一個跟作者(author)有關的功能:

> python manage.py startapp author

這個指令會幫你在你的專案裡建立一個叫做author的資料夾,跟專案很像,裡面的有4個檔案,不過稍微有些些的不同:

> ls -l author
total 24
-rw-r--r--  1 eddie  wheel    0 10 18 16:05 __init__.py
-rw-r--r--  1 eddie  wheel   57 10 18 16:05 models.py
-rw-r--r--  1 eddie  wheel  383 10 18 16:05 tests.py
-rw-r--r--  1 eddie  wheel   26 10 18 16:05 views.py

__init__.py的目的跟之前專案一樣,是要告訴Python這裡是一個module,其它的看名字大概猜得出來,models.py是給Model用的,views.py則是給View用的,tests.py則是寫測試用的。

接著我們打開models.py,準備來寫一點程式碼了。

Model

在Django的Model是負責處理跟資料庫相關的事情,透過Python的類別、屬性跟方法,可以讓你像在操作物件一樣的操作資料表,而且可以避開一些可能的危險,例如SQL Injection。接下來我們來看看在Django的Model要怎麼寫。例如我們要來建一個作者的基本資料,假設一個作者的基本資料可能會有姓名電話地址Email 等欄位,我們在Model裡就這樣寫:

1
2
3
4
5
6
7
8
9
from django.db import models

# Create your models here.
class Profile(models.Model):
  name     = models.CharField(max_length = 50)
  age      = models.IntegerField()
  tel      = models.CharField(max_length = 30)
  address  = models.CharField(max_length = 100)
  email    = models.EmailField()

這裡的CharFieldIntegerField以及EmailField方法到時候會依照不同的資料庫,各別被轉換成適當的型態。這樣的好處就是,即使原本你的專案用的資料庫是SQLite,今天突然接到老闆或主管的命令說要換成MySQL,因為SQLite跟MySQL在某些欄位的定義可能不太一樣,在以前你可能需要重新調整資料庫存取的程式碼,但現在透過Django的Model來幫我們做這個型態的對應,程式碼不用幾乎不用修改就可以無痛的把資料庫整個換掉了。

接下來,我們要把這個App給安裝到INSTALLED_APPS裡,所以打開專案的settings.py,加上我們剛剛建立的App:

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',
  # Uncomment the next line to enable admin documentation:
  # 'django.contrib.admindocs',
  'author',
)

這樣Django專案就知道我們有這個App了,然後執行python manage.py sql author,可以看到剛剛我們寫的那個Profile類別轉換成成SQL語法:

1
2
3
4
5
6
7
8
9
10
BEGIN;
CREATE TABLE "author_profile" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(50) NOT NULL,
    "age" integer NOT NULL,
    "tel" varchar(30) NOT NULL,
    "address" varchar(100) NOT NULL,
    "email" varchar(75) NOT NULL
);
COMMIT;

原來的CharField會轉換成varchar,參數max_length = 50則被轉換成它的欄位長度,IntegerField轉換成integer,而且即使沒有特別註明,Django也預設在整個表格裡自動的加上了一個id的primary key(如果在MySQL就是autoincrement的欄位)。那EmailField是什麼? 因為Email欄位在開發網站的時候太常用到了,所以Django的Model就直接內建了一個叫做EmailField的方法,它會轉成換varchar(75),並同時還送了一些欄位驗證的方法,這個我們之後在表單處理的單元會再詳細討論。

當然這個Model不是只有讓你轉換成SQL這麼單純,它還有許多好用的方法讓你在開發網站可以少寫不少程式碼。

到這裡,我們其實還沒真正的把Profile類別轉換成資料庫的表格,你需要執行python manage.py syncdb,如果你寫的程式碼沒問題的話,應該會看到這個結果:

> bookstore  python manage.py syncdb
Creating tables ...
Creating table author_profile
Installing custom SQL ...
Installing indexes ...
No fixtures found.

執行syncdb之後,Django幫你建立了一個叫做author_profile的表格。Django的Model在建table的時候,預設會依照App名稱 + 類別名稱來建立,如果你想要改掉這個預設的設定,只要在Profile類別裡加上一個meta class,原來的程式碼就會變成這樣:

1
2
3
4
5
6
7
8
9
10
11
12
from django.db import models

# Create your models here.
class Profile(models.Model):
  name     = models.CharField(max_length = 50)
  age      = models.IntegerField()
  tel      = models.CharField(max_length = 30)
  address  = models.CharField(max_length = 100)
  email    = models.EmailField()

  class Meta(object):
    db_table = "profile"

要注意這個meta class是在原來的class再放一個class。這樣一來,轉出來的表格名稱就會是profile了。在Django的Model裡,Meta Class還有許多可以設定的屬性,更多細節請參考 https://docs.djangoproject.com/en/1.3/ref/models/options/

其實這個表格的名稱對Django的Model來說並不是那麼重要,因為我們在存取資料表的時候,還多透過了Django的ORM幫我們做表格及欄位對照的工作,所以即使使用預設有點囉嗦的表格名稱,或是用你自己設定的名稱,對Django的Model來說都是一樣的。

Django也有提供一個跟Python很像的shell,可以讓你直接透過指令來操作物件,不用寫SQL語法,一樣可以做到資料的新增/修改/查詢/刪除功能,接下來我們先停一下,來看看shell怎麼操作。

開始你的第一個專案

Start Your Engine!

Django安裝好之後,接下來就是要開始你的第一個專案啦。使用方法:

> django-admin.py startproject bookstore

請注意,在某些系統安裝Django,並不一定會有django-admin.py,而是只有django-admin,沒有.py的附檔名,為避免找不到檔案以及節省打字時間,建議在輸入django之後就可以按一下TAB鍵,讓系統幫你自動完成。

如果你在執行django-admin的時候出現command not found的錯誤訊息,表示你可能還沒安裝Django,或是安裝的過程有哪邊出了問題,請再回"安裝Django“章節,看看是不是哪邊有漏了。

沒問題的話,它就會幫你產生一個叫做bookstore的資料夾,內容大概會長得像這樣:

> bookstore  ls -al
total 32
-rw-r--r--  1 eddie  wheel     0 10 18 13:35 __init__.py
-rw-r--r--  1 eddie  wheel   503 10 18 13:35 manage.py
-rw-r--r--  1 eddie  wheel  5037 10 18 13:35 settings.py
-rw-r--r--  1 eddie  wheel   574 10 18 13:35 urls.py

結構很簡單,只有4個檔案:

  • __init__.py 用來告訴Python這個資料夾是一個模組,裡面通常是空的,不過也可以寫一些程式碼在裡面。
  • manage.py 用來操作整個Django專案的小工具,例如啟動伺服器python manage.py runserver,或是同步資料庫python manage.py syncdb
  • settings.py 設定檔
  • urls.py 負責網站的路由。

新專案開好之後,直接在專案的資料夾裡執行python manage.py runserver

> python manage.py runserver
Validating models...

0 errors found
Django version 1.3.1, using settings 'bookstore.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

它會在你的本機開一個port 8000的web server,打開你的瀏覽器,輸入網址http://127.0.0.1:8000/,你應該可以看到這個畫面: image

資料庫設定

在上面的4個檔案裡有個settings.py,裡面有一段是用來填寫資料庫設定的:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': '',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

這邊我們就以展示目的,僅使用SQLite做為資料庫,所以請將ENGINE的欄位改成django.db.backends.sqlite3,並將NAME設定為bookstore。另外一樣在settings.py裡,有幾項設定可以一起修改:

TIME_ZONE = 'Asia/Taipei'

修改你的時區,我真希望可以寫Asia/Taiwan!!

LANGUAGE_CODE = 'zh-TW'

語系改成繁體中文,這樣到時候後台的語言就會整個變繁體中文版了。

然後回到終端機視窗執行python manage.py syncdb

> python manage.py syncdb

Creating tables ...
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_user_permissions
Creating table auth_user_groups
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use 'eddie'):
E-mail address: eddie@digik.com.tw
Password:
Password (again):
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
No fixtures found.

過程中會問你是不是要建立帳號密碼,這個是給內建的Admin模組用的,之後就可以用這組帳號密碼登入Django超佛心的Admin模組後台。

你可能會好奇為什麼明明什麼程式都還沒寫,然後就幫我建立了一堆的表格了。這是因為Django預設幫你安裝了一些模組了(你一樣可以在settings.pyINSTALLED_APPS段落找到它幫你預設裝了哪些東西。

內建的Admin模組

為什麼說是超佛心的Admin後台模組? 因為你不用寫到任何一行程式就可以有個漂亮而且功能完整的後台可以用了。我們要做的只有幾件事:

  1. 打開settings.py,把INSTALLED_APPSdjango.contrib.admin前面的#註解拿掉
  2. 打開urls.py,把from django.contrib import admin以及admin.autodiscover()兩行的註解拿掉,然後在urlpatterns的最後一行url(r'^admin/', include(admin.site.urls))的註解也拿掉
  3. 執行python manage.py syncdb

這樣就完成了,你做的應該只有拿掉幾個註解,以及syncdb的動作而已。然後請打開瀏覽器,網址輸入http://127.0.0.1:8000/admin/,應該可以看到一個登入畫面: image

輸入你剛剛設定的帳號密碼之後:

image

因為我們還沒有建立任何的Model,所以在這裡除了網站的一些設定以及帳號設定之外,還看不到任何東西。不過想想你還沒寫到一行程式碼就可以有這樣的東西可以用,而且還有中文版的,真的是相當佛心啊!

當然,後台的客製化又是另一個主題了。

安裝Django

取得Django

要注意的是,目前的Django支援Python 2.4到2.7的版本,Python3.x目前並不支援。

請到Django網站取得最新版stable版本的程式碼(目前是1.3.1版)。如果你對Django的發展有興趣,想要提早體驗發展中的功能,也可以透過svn去取得開發版本。

安裝

> wget --no-check-certificate http://www.djangoproject.com/download/1.3.1/tarball/ -O Django-1.3.1.tar.gz
> tar xzvf Django-1.3.1.tar.gz
> cd Django-1.3.1
> sudo python setup.py install

如果過程中沒出現錯誤訊息,應該這樣就安裝完成了,你可以進到Python shell裡看一下版本對不對:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.get_version()
'1.3.1'

如果秀出來的結果跟你安裝版本不一樣,請檢查看看是不是裝到舊的版本囉。