終於來到 Django 的重頭戲~資料庫了, 這是建立資料庫驅動網站的基礎, 我之前學習 Django 2/3 時就是停在這裡, 參考 :
本次複習的前三篇筆記參考 :
一. 透過 ORM 間接操作資料庫 :
Django 使用 ORM (Object Relational Mapper) 物件關聯對映機制來操作資料庫, 開發者並非直接使用 SQL 來存取資料庫, 而是使用 Python 模型物件的方法來存取, 具體而言, 就是利用 django.db.models.Model 類別來定義資料表欄位的模型, 以模型做為程式與資料庫之間的中介層, 然後透過 ORM 機制將對 Model 物件之存取轉換為 SQL 指令, 如下圖所示 :
ORM 模型 | 關聯式資料庫 |
類別 (Class) | 資料表 (Table) |
物件 (Object) | 紀錄 (Row) |
屬性 (Attribute) | 欄位 (Field) |
方法 (Method) | CRUD 操作 |
這種作法最大的好處是更換資料庫系統時只要編輯專案設定檔 settings, 將 DATABASE 變數 (字典) default 鍵的值改為要使用的資料庫即可, 預設是 SQLite :
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
連線 MySQL 資料庫的設定範例如下 :
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydatabase',
'USER': 'root',
'PASSWORD': 'mypassword',
'HOST': 'localhost',
'PORT': '3306',
}
}
連線 Postgre 資料庫的設定範例如下 :
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'mydatabase',
'USER': 'tony1966',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
這些正式運營使用的伺服器型資料庫系統主要差異是為 ENGINE 與 PORT 這兩個鍵.
參考 :
Django 預設使用單檔案的 SQLite 資料庫, 用 Python 存取 SQLite 資料庫必須學習 SQL 語法, 參考 :
二. 建立資料庫驅動的網站 :
要在 Django 專案中使用資料庫必須先建立 App, 在每個 App 目錄下都會自動建立與資料庫操作相關的檔案與資料夾.
1. 建立網站 :
首先以下列指令建立一個專案 mysite :
django-admin startproject mysite
D:\django\test4>django-admin startproject mysite
用 tree /f 指令檢視檔案目錄結構 :
D:\django\test4\mysite>cd ..
D:\django\test4>tree /f
mysite
│ manage.py
│
└─mysite
asgi.py
settings.py
urls.py
wsgi.py
__init__.py
可見這時並無資料庫相關檔案.
2. 建立應用程式 :
接下來建立第一個應用程式 myapp1 為例 :
python manage.py startapp myapp1
D:\django\test4\mysite>python manage.py startapp myapp1
用 tree /f 指令檢視檔案目錄結構 :
D:\django\test4\mysite>cd ..
D:\django\test4>tree /f
mysite
│ manage.py
│
├─mysite
│ │ asgi.py
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ │ __init__.py
│ │
│ └─__pycache__
│ __init__.cpython-310.pyc
│ settings.cpython-310.pyc
│
└─myapp1
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ views.py
│ __init__.py
│
└─migrations
__init__.py
可見已經新增了應用程式目錄 myapp1, 裡面與資料庫有關的是 admin.py, models.py, 以及 migrations 子目錄, 其中 admin.py 用來把 App 註冊到內建的後台資料庫管理網站 admin 中; models.py 用來定義資料表的模型; 而 migrations 子目錄則用來記錄 migration (模型異動) 的資料檔.
建立應用程式後首先必須開啟專案設定檔 settings.py, 在 INSTALLED_APPS 變數中註冊此 App, 這動作很重要, 因為後續要生成 migration (模型異動) 資料檔時, Django 才會知道要去哪些 App 底下找 models.py 模型檔來比對資料表的變化情形 :
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp1',
]
3. 定義資料表的模型 :
App 目錄下的 models.py 用來定義與實體資料表對映的資料模型, 其預設內容只有兩列, 已預先匯入 django.db.models 模組, 只要在底下編輯各資料表的模型類別定義即可 :
from django.db import models
# Create your models here.
建立資料表模型須繼承 django.db.models.Model 類別, 然後呼叫 models 模組的欄位定義函式 models.xxxField() 建立欄位變數即可, 語法結構如下 :
from django.db import models
class table_name(models.Model):
field_name_1=models.xxxField(param=value)
field_name_2=models.xxxField(param=value)
...
class Meta:
ordering=('-field_name_x', )
...
def __str__(self):
return self.field_name_x
其中方法 __str__() 為必須, 用來傳回要在內建的資料庫後台應用程式 admin 中顯示的資料表欄位; 子類別 Meta 則可有可無, 用來指定與資料庫相關之屬性, 常用的 Meta 類別屬性如下表 :
Meta 類別屬性 | 說明 |
ordering | 用 tuple 指定排序欄位 (預設為升冪, 冠 - 為降冪) |
db_table | 指定映射之資料表名稱, 預設自動映射至 '應用名_模型名' 資料表' |
app_label | 指定此模型類別所屬之 App 名稱 |
default_permissions | 指定允許之模型操作許可權, 預設為 ('add', 'change', 'delete') |
get_latest_by | 依指定之欄位排序以獲得模型之開始或結束紀錄 (日期或整數欄位) |
unique_together | 將多個欄位設定成不重複的欄位組合 (雙層串列, 例如 [['欄1', '欄3']]) |
index_together | 設定聯合索引之欄位 (雙層串列, 例如 [['欄1', '欄3']]) |
required_db_vendor | 限定模型維護之底層資料庫類型, 例如 ['SQLite', 'MySQL'] |
db_tablespace | 映射之表格空間名稱 (僅適用於如 Oracle 等有表格空間之資料庫) |
required_db_features | 指定底層資料庫必須具備之特性, 例如 ['gis_enabled'] |
order_with_respect_to | 設定此模型可依據指定之外鍵引用關係排序 |
其中最常用的是 ordering 屬性, 用來指定資料庫傳回紀錄時要依據那些欄位來排序, 其值為一個欄位名稱組成的 tuple, 預設為升冪, 欄位名稱前面冠 '-' 則為降冪, 例如 ordering=(name, -birthday) 表示要先按欄位 name 升冪排序, 再按 birthday 欄位降冪排序.
django.db.models 模組提供了許多欄位定義函式, 其名稱格式為 xxxField(), 如下表所示 :
欄位型態函式 | 說明 |
BooleanField() | 儲存布林值=True/False, 必要參數 : 無, checkbox 輸入用 |
CharField() | 儲存單行文字輸入內容,必要參數 : max_length=最大字元數 (上限 254) |
SlugField | 與 CharField() 相同, 但用來儲存 URL 的一部分 |
TextField() | 儲存多行文字輸入 textarea 內容, 必要參數 : 無 |
IntegerField() | 儲存整數 (-2147483648 ~ 2147483647), 必要參數 : 無 |
BigInteger() | 儲存長度 64 位元的大整數 |
PositiveIntegerField() | 儲存正整數 (0 ~ 2147483647), 必要參數 : 無 |
DecimalField() | 儲存固定精度之十進位數 (Decimal 物件), 必要參數 : max_digits=最大位數 decimal_places=整數位數 |
FloatField() | 儲存浮點數, 參數 : 無 |
DateField() | 儲存日期 (即 datetime.date), 可選參數 : auto_now=物件儲存時自動儲存今日日期 auto_now_add=只在建立時儲存今日日期 |
DateTimeField() | 儲存日期時間 (即 datetime.datetime), 可選參數 : auto_now=物件儲存時自動儲存今日日期時間 auto_now_add=只在建立時儲存今日日期時間 |
EmailField() | 儲存有效之電子郵件, 可選參數 : max_length=最大字元數 (上限 254) |
FileField() | 檔案上傳欄位, 必要參數 : 無 |
ImageField() | 圖檔欄位 (繼承自 FileField, 須配合使用 Pillow 套件) |
URLField() | 儲存完整的 URL (繼承自 CharField), 可選參數 : max_length=最大字元數 (預設 200) |
AutoField() | 自動增量主鍵欄位, 必要參數 primary_key=True |
ForeignKey() | 關聯欄位, 用來指向其他資料表的主鍵 (預設=id) : 第一參數=所指之資料表類別名稱 第二參數 : on_delete=models.CASCADE |
呼叫上表中的欄位定義函式時除了必要參數外, 還可以傳入選項參數, 這些參數都是對映到 SQL 語法的欄位屬性, 常用選項參數如下表 :
欄位選項參數 | 說明 |
null | 欄位值是否可為 null, 值=True/False (預設) |
blank | 欄位值是否可為空白, 值=True/False (預設) |
default | 欄位預設值 (或可呼叫物件) |
unique | 欄位值是否為唯一, 值=True/False (預設) |
primary_key | 欄位是否為主鍵, 值=True/False (預設) |
editable | 欄位是否顯示於 admin 後台, 值=True (預設) /False |
choices | 設定 select 欄位之選項 (可用 list 或 tuple) |
help_text | 顯示於表單元件上的額外資訊 |
verbose_name | 欄位之人類可讀名稱, 未指定以欄位名稱代替 (底線變空白) |
例如定義一個會員資料表 Members 的模型 :
from django.db import models
class Members(models.Model):
name=models.CharField(max_length=20, null=False)
gender=models.CharField(max_length=2, default='男', null=False)
birthday=models.DateField(null=False)
email=models.CharField(max_length=100, blank=True, default='')
phone=models.CharField(max_length=50, blank=True, default='')
address=models.CharField(max_length=255, blank=True, default='')
def __str__(self):
return self.name
此例定義了一個包含 6 個欄位的資料表模型 Members, 除了 birthday 欄位是日期欄位外, 其餘都是長度不等之單行文字欄位.
models.py 編輯完成存檔後可先用 check 指令檢查模型設定, 如果顯示 "System check identified no issues" 表示設定正確 :
python manage.py check
D:\django\test4\mysite>python manage.py check
System check identified no issues (0 silenced).
4. 建立 migration 資料檔與資料庫同步 :
models.py 建立在上層專案目錄下先用 makemigrations 指令產生模型遷移檔 :
python manage.py makemigrations (對全部 App)
也可以指定要對哪個 App 建立遷移檔 :
python manage.py makemigrations myapp1 (只對指定之 App)
migrations 指令會依據 App 的 models.py 內容在該 App 的 migrations 資料夾下產生遷移檔, 主要是紀錄資料表的結構與版本, 並與 migrations 資料夾下面的前一版本的遷移檔比較模型是否有異動. 如果未指名 App, 則會去專案設定檔找尋全部已註冊的 App 逐一建立遷移檔.
D:\django\test4\mysite>python manage.py makemigrations
Migrations for 'myapp1':
myapp1\migrations\0001_initial.py
- Create model Members
可見此指令新增了一個建立 Members 模型的 0001_initial.py 遷移檔, 其中 0001 表示版本 (第二次更改模型時版本就會變成 0002), initial 表示異動說明 (初次建立模型).
這時用 tree /f 觀察網站檔案目錄結構, 會發現上層專案目錄下新增了一個 SQLite 資料庫檔db.sqlite3. 注意, 這是所有 App 共用的資料庫, 只有在建立第一個 App 時會產生, 之後的 App 不會再產生 :
mysite
│ manage.py
│ db.sqlite3
│
├─mysite
│ │ asgi.py
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ │ __init__.py
│ │
│ └─__pycache__
│ __init__.cpython-310.pyc
│ urls.cpython-310.pyc
│ settings.cpython-310.pyc
│
└─myapp1
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ views.py
│ __init__.py
│
├─migrations
│ │ __init__.py
│ │ 0001_initial.py
│ │
│ └─__pycache__
│ __init__.cpython-310.pyc
│
└─__pycache__
__init__.cpython-310.pyc
apps.cpython-310.pyc
models.cpython-310.pyc
admin.cpython-310.pyc
開啟 migration 資料檔 0001_initial.py 可知其內容就是 models.py 的資料表欄位定義, 它只是將 models.Model 類別轉成了 Migration 類別, 以便後續執行 migrate 指令時與實體資料庫同步, 其內容如下 :
# Generated by Django 4.2.4 on 2023-10-22 16:12
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Members',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20)),
('gender', models.CharField(default='男', max_length=2)),
('birthday', models.DateField()),
('email', models.CharField(blank=True, default='', max_length=100)),
('phone', models.CharField(blank=True, default='', max_length=50)),
('address', models.CharField(blank=True, default='', max_length=255)),
],
),
]
然後在上層專案目錄下用下列指令讓模型與資料庫同步 :
python manage.py migrate
此指令會到每一個 App 目錄的 migrations 子目錄下的遷移紀錄檔, 比較前後版本差異後去更改資料表, 這樣便達成模型定義與資料庫同步的目的. 也可以在 migrate 後面指定要同步的 App, 這樣就只會同步該 App :
python manage.py migrate myapp1
注意, 每次修改 models.py 後都要重新執行 makemigrations 與 migrate 指令才能維持模型與資料庫的同步. 例如 :
D:\django\test4\mysite>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, myapp1, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying myapp1.0001_initial... OK
Applying sessions.0001_initial... OK
可見 migrate 指令會依照 makemigrations 產生的遷移資料檔 0001_initial.py 去同步資料庫.
如果將 email 欄位長度由 100 改成 120, 再次執行 makemigrations 與 migrate 指令重新進行遷移與同步 :
D:\django\test4>cd mysite
D:\django\test4\mysite>python manage.py makemigrations
Migrations for 'myapp1':
myapp1\migrations\0002_alter_members_email.py
- Alter field email on members
D:\django\test4\mysite>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, myapp1, sessions
Running migrations:
Applying myapp1.0002_alter_members_email... OK
可見在 myapp1/migrations 資料夾下多出了 0002_alter_members_email.py 這個中介檔, 從名稱可知此次是要異動模型中的 email 欄位, 內容如下 :
# Generated by Django 4.2.5 on 2023-10-26 03:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myapp1', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='members',
name='email',
field=models.CharField(blank=True, default='', max_length=120),
),
]
Django 會比較 models.py 與最近一版的 migrations 資料檔 (即 dependencies 變數中的 '0001_initial'), 得出要如何做異動 (即 operations 變數之值). 經過 migrate 後, 模型的最新異動就與資料庫 db.sqlite3 同步了.
5. 使用資料庫後台管理程式 admin :
Django 內建了一個用來管理後台資料庫的應用程式 admin, 專案路由模組 urls.py 中預設路由 path('admin/', admin.site.urls) 即用來存取此 App, 在瀏覽器輸入http://127.0.0.1:8000/admin 會顯示登入畫面 :
欲進入 admin 後台必須用 createsuperuser 指令設定登入帳密, 先按 Ctrl + C 停止測試伺服器, 然後輸入下列指令進行設定 :
python manage.py createsuperuser
D:\django\test4\mysite>python manage.py createsuperuser
Username (leave blank to use 'user'):
Email address: abc@gmail.com
Password:
Password (again):
Superuser created successfully.
用 python manage.py runserver 重新開啟測試伺服器, 然後用上面申請的帳密即可登入 :
可見後台並未出現 Members 模型的資料表, 這是因為沒有向 admin 註冊此模型之故, 須編輯 App 目錄下的 admin.py 將此模型註冊到 admin 應用程式中才行. admin.py 預設只有一列匯入了 admin 應用程式的指令 :
from django.contrib import admin
# Register your models here.
在 admin.py 加入如下指令, 先匯入 myapp1 模型檔 models.py 中的模型類別 Members, 然後呼叫 admin.site.register() 函式註冊此 Members 模型 :
# admin.py
from django.contrib import admin
from myapp1.models import Members # 匯入 Members 模型
admin.site.register(Members) # 向 admin 註冊 Members 模型
存檔後重新整理網頁就會看到 Members 模型所對應的資料表了 :
可見實體資料庫中的資料表名稱是 Memberss, 與模型名稱 Members 有所不同, 不過因 ORM 作為中介, 開發者不會直接存取實體資料庫, 故實際資料表名稱為何無關緊要.
按 +Add 可新增資料 :
按 Save 儲存後顯示使用者列表 :
此處顯示人名是因為 models.py 中的模型裡 __str__() 方法傳回的是 name 欄位之故 :
def __str__(self):
return self.name
如果要顯示更多欄位, 必須在 admin.py 中定義一個 ModelAdmin 類別的子類別, 在其中定義一個名為 list_display 的顯示欄位 tuple, 而且必須向 admin 註冊, 例如 :
# admin.py
from django.contrib import admin
from myapp1.models import Members # 匯入 Members 模型
class MembersAdmin(admin.ModelAdmin): # 顯示欄位類別
list_display=('id', 'name', 'gender', 'birthday', 'email', 'phone', 'address')
# 向 admin 註冊 Members, MembersAdmin
admin.site.register(Members, MembersAdmin) # 顯示欄位類別也要註冊
存檔後重新整理網頁就會取代 __str__() 的顯示單一欄位功能而顯示所指定的多欄位了 :
可見資料預設是以 id 倒序 (降冪) 排列的, 如果要用升冪排序, 須於 MembersAdmin 類別中定義一個名為 ordering 的 tuple 變數, 其元素為預設排序欄位例如 'id', 如果要降冪排序, 則欄位名稱前面冠 "-" 即可, 例如 '-id' :
from django.contrib import admin
from myapp1.models import Members # 匯入 Members 模型
class MembersAdmin(admin.ModelAdmin):
list_display=('id', 'name', 'gender', 'birthday', 'email', 'phone', 'address')
ordering=('id', ) # 依照 id 升冪排序
# 向 admin 註冊 Members, MembersAdmin
admin.site.register(Members, MembersAdmin)
重新整理網頁就會按照 id 升冪排序了 :
注意, 指定排序欄位 id 後面有個向上的小三角形, 表示是升冪排序欄位. 其實點按每個顯示欄位都會按照該欄位升冪或降冪排序, ordering 只是指定預設之排序欄位而已.
除了 ordering 與 list_display 變數外, ModelAdmin 類別內還可以用 search_fields 變數指定搜尋欄位; 也可以用 list_filter 指定資料過濾欄位, 它們都是 tuple 變數, 例如 :
from django.contrib import admin
from myapp1.models import Members # 匯入 Members 模型
class MembersAdmin(admin.ModelAdmin):
list_display=('id', 'name', 'gender', 'birthday', 'email', 'phone', 'address')
ordering=('id', )
search_fields=('name', 'email', 'phone', 'address',)
list_filter=('name', 'gender')
# 向 admin 註冊 Members, MembersAdmin
admin.site.register(Members, MembersAdmin)
注意, 並非所有欄位都可以可以放在 list_filter 過濾, 可以過濾的欄位類型只有下面幾種 :
- CharField
- BooleanField
- DateField
- DateTimeField
- IntegerField
- ForeignKey
重新整理網頁, 在上方會出現搜尋控件, 可搜尋 search_fields 變數所指定之欄位; 其次右方會出現 list_filter 變數指定之欄位值, 點擊就會顯示依據該值過濾之紀錄列表 :
以上測試範例檔放在 GitHub :
# https://github.com/tony1966/tony1966.github.io/blob/master/test/django/django-review-database-1.zip
沒有留言:
張貼留言