Skip to content

Latest commit

 

History

History
138 lines (93 loc) · 8.38 KB

07-django-models.md

File metadata and controls

138 lines (93 loc) · 8.38 KB

我們的 stores app 負責管理店家資訊,所以顯然我們需要有一個 Store model。這個 model 裡面要可以存店家名稱與菜單的圖片。菜單上的項目當然不止一個,所以它與店家之間是多對一關聯。我們知道,多對一需要一個 foreign key,所以我們還需要另一個 model,用來放菜單項目。它們之間的關聯會像下面這樣:

┌───────────────┐           ┌───────────────┐
│     Store     │           │   MenuItem    │
├───────────────┤           ├───────────────┤
│ name          │           │ name          │
│ note          │ 1       n │ price         │
│ menu_items    │ <──────── │ store         │
└───────────────┘           └───────────────┘

Store 有兩個資料欄位,分別儲存店家名稱與其他資訊;MenuItem 也有兩個資料欄位,一個用來存圖片檔案位置,另一個則是圖片標題(用在 HTML alt tag)。MenuItem 另外有一個指向 Store 的 foreign key,叫做 store;這個 FK 在 Store 的 reverse attribute 則叫做 menu_items

好了,現在我們要把上面的結構轉換為 Django model 定義。打開 stores/models.py,加入以下的內容(記得保留第一行的 import):

class Store(models.Model):

    name = models.CharField(max_length=20)
    notes = models.TextField(blank=True, default='')

    def __str__(self):
        return self.name


class MenuItem(models.Model):

    store = models.ForeignKey('Store', related_name='menu_items')
    name = models.CharField(max_length=20)
    price = models.IntegerField()

    def __str__(self):
        return self.name

在 Django 中,一個 model 就是一個 class。所有的 Django models 都必須繼承 django.db.models.Model。我們在兩個 models 中依照上面的結構,加入合適的欄位。各欄位形態的意義如下:

  • CharField 對應到資料庫的 VARCHARmax_length 參數代表 VARCHAR 的長度。[註 1]
  • TextField 對應到 TEXTblank=True 代表本欄位可空白(注意不是可為 NULL!可為 NULL 的欄位是使用 null=True,我們這裡沒有用到);default 代表如果你在建立 Store 物件時沒有輸入本欄位,預設會使用的值。
  • IntegerField 對應到 INTEGER
  • ForeignKey 是 Django 專門用來管理 foreign key 的 model field。我們來詳細討論一下它的原理。

當你建立一個 model 時,Django 會自動幫它加入一個名為 idAutoField——對應到資料庫的 INTEGER,加上 primary key(和 auto increment)屬性。[註 2] ForeignKey 同樣對應到 INTEGER,但當它被使用時,Django 會自動用裡面的值執行一個 SELECT,把該 ID 對應的物件取出來。ForeignKey 必須擁有一個參數,指明這個 FK 指向的 model class 名稱。[註 3]

你可能已經注意到,我們並沒有為 Storemenu_items 建立 attribute,但這裡 related_name 的值就是它。Django 會自動建立 foreign key 的 reverse relation,所以你不需要自行建立。

ForeignKey 的狀況中,Django 預設會用 model 的名稱後面加 _set 來當作 reverse relation 的名稱,所以 MenuItem.store 的預設 reverse relation key 會是 Store.menuitem_set。你當然可以直接使用這個值,不過如果狀況允許,我個人推薦盡量還是手動設定這個值。即使設成和預設一樣,也比沒有設定好,因為 explicit is better than implicit 是 Python 的中心思想之一。

我們另外在兩個 models 都各加上了一個 __str__ 函式。這是 Python 用來把物件轉換成 str 的 hook;因為做網站時,常常需要把東西變成字串,所以這會很方便。

現在程式已經可以認得這兩種 models 了。但如果要儲存它們,還需要在資料庫裡建立對應的 tables。確保資料庫與程式中的定義同步,是件很麻煩的工作;幸好,Django 提供了一個自動同步資料表的工具,可以協助我們完成這個工作。

在 console 執行以下指令:

python manage.py makemigrations stores

這告訴 Django 我們更新了 stores 中的 models。這個指令應該會輸出下面的內容:

Migrations for 'stores':
  0001_initial.py:
    - Create model MenuItem
    - Create model Store
    - Add field store to menuitem

這代表 Django 已經偵測到你新增了 MenuItemStore 兩個 models,以及它們之間的關聯。這些資訊被放在 0001_initial.py 中。這個檔案會被放在 store/migrations 目錄裡。檢查看看!

我們昨天提到,每個 Django app 都可以包含 migrations 目錄,用來存放 database migration 記錄。這些記錄是用來告訴 Django,這個 app 中的 models 在某個時間點長什麼樣子。你可以自己寫(同樣要放在 migrations 目錄裡),但是在大多數的情況下,只要用 makemigrations 這個指令,Django 就可以自動偵測。Django 自動產生的檔案會以以個四位數字開頭,後面接一個底線,然後是 Django 偵測到這個記錄與前一個記錄間相異處的描述

接著我們要實際執行這個檔案,讓資料庫發生改變:

python manage.py migrate stores

輸出:

Operations to perform:
  Apply all migrations: stores
Running migrations:
  Rendering model states... DONE
  Applying stores.0001_initial... OK

Django 偵測到剛剛的 0001_initial 尚未與資料庫同步,並成功執行它。所以現在資料庫裡會多出兩個 tables。

不信?證明給你看。輸入以下的指令:

python manage.py dbshell

這會你連上 Django 目前設定的資料庫(就是你在 DATABASES 裡面設定的那個)。列出目前的資料庫看看(SQLite:.tables、PostgreSQL:\d、MySQL:SHOW TABLES;;這裡以 SQLite 為例):

sqlite> .tables
auth_group                  django_admin_log          
auth_group_permissions      django_content_type       
auth_permission             django_migrations         
auth_user                   django_session            
auth_user_groups            stores_menuitem           
auth_user_user_permissions  stores_store

其中 stores_storestores_menuitem 就是我們剛剛建立的 models!

不過其他的是什麼?這些是 Django 預設啟用功能所需要的資料。還記得我們剛建立 project 時有執行一次 migrate 嗎?這就是用來 migrate Django 內建的資料。那時我們後面沒有加 app name,所以會 migrate 所有 apps。

在這些資料表中,auth 開頭的是用來管理使用者註冊與權限——所以我們之前才會先跳過使用者認證,因為 Django 內建就有。其他還有一些內部用的資料,這裡先略過。不過我們特別來看一下 django_migrations

sqlite> select * from django_migrations;
1|contenttypes|0001_initial|2014-09-19 09:46:37.731368
2|auth|0001_initial|2014-09-19 09:46:37.758253
3|admin|0001_initial|2014-09-19 09:46:37.792646
4|sessions|0001_initial|2014-09-19 09:46:37.797104
5|stores|0001_initial|2014-09-24 03:15:42.455255

這就是 Django 用來記錄 migration 的表!這裡記錄了目前各 app 被 migrate 到哪個階段,以及進行的時間。這個表格搭配 migrations 裡面的資料,就能讓 Django 偵測 models 的修改,幫我們產生合適的 migration record,並對資料庫進行合適的設定。

呼!這篇好像有點長。明天我們會實際使用這些 models 看看。

對了,剛剛那個資料庫介面可以用 control-d 退出。

更多關於 Django model 欄位的資訊,請參照官網文件


註 1:SQLite 沒有固定長度字串型別,所以 CharField 其實是對應到 TEXT。不過 Django 會在程式內部檢查輸入長度,所以 max_length 還是會有用(除非你手動直接把值插入資料庫)。

註 2:這個行為可以被覆寫,不過我們這裡不管。

註 3:你可以能會想知道如果需要 refer 其他 app 中的 model 該怎麼辦。之後會講到,請耐心!