Python Django 概念與實作 - Django Model(二)
前言
目前小型以上的網站均會使用到資料庫,這節來介紹Django如何透過ORM的方式存取資料庫。
目前 Python Django 概念與實作 大致規劃為
- 佈置環境
- Django View(一)
- Django Model(二)
- Django Test(三)
- Django Forms(四)
- Django Admin(五)
- 登入功能(六)
- Blog功能(七)
Object Relational Mapping, ORM
一般來說,要存取資料庫通常會使用Structured Query Language, SQL來存取資料,類似SELECT * FROM order;這樣的語法。直接下SQL也不是不行,但大家仍會使用ORM作為操作資料庫的方式,雖然會消耗一些效能,但因有容易維護、跨不同資料庫、免於SQL Injection Attacks等優點,整體來說優點仍大於缺點。
ORM簡單來說是將資料庫不同的table定義成不同model,透過python操控已定義model來控制databse。
SQLite
要儲存資料庫必須要有一個database server, DB server(資料庫伺服器)來儲存data,常見的RDBMS(關聯式資料管理系統)有MySQL, PostgreSQL等,亦有非關聯式,但不在我們今天討論的範圍當中。
Django內建輕量級別的database(資料庫): SQLite,讓我們在學習、開發時免於架設額外DB server。settings.py中可設定資料庫連線與儲存位置,這裡我們用default就好。
1 | # settings.py |
Model
首先我們先來定義我們的model(模型),以商品來說,最少會有以下三個欄位
- 商品名稱
- 商品敘述
- 價錢
1 | # orders/models.py |
每一個欄位也對應不同的屬性,可以依照實際狀況使用models.Model下的不同Field。
例如:
- 商品是否顯示:
BooleanField - 商品數量:
IntegerField - 異動日期:
DateField - 異動時間:
TimeField
Tips
Decimal這種資料型態是用作固定精準度,若使用Float有可能導致0.1+0.2=0.3…1的狀況發生。
DecimalField中,max_digits(最大總位數)與decimal_places(小數位數)為必填的參數
e.g. price = 9999.99 則設定max_digits=6, decimal_places=2
Migration
模型完成後,要將DB對應的欄位設定轉成與模型一致,這一步驟我們稱作資料遷移。
而我們不必逐一調整DB資料型態,django可先將模型轉為資料遷移腳本,接著執行腳本就可成功調整資料型態。
Tips
Migration雖然稱作資料庫遷移,但並不是將A資料移至B位置,而是讓DB server知道每一個欄位的性質與大小。e.g.name(名字)這個欄位是儲存字串且通常不會太大,而item_num(物品數量)則是正整數,設為Integer較恰當。
1 | python manger.py makemigrations order # 生成遷移腳本 |
此時可以查看app下會產生遷移腳本
1 | orders/ |
1 | # 0001_initial.py |
執行migrate即可執行腳本
1 | python manage.py migrate |
Notices
‼️ 只要資料型態有異動(無論是長度修改、新增/刪除欄位),均須重新產生新的腳本,並執行資料庫遷移。否則會造成資料庫存取失敗的問題產生
QuerySet
模型、資料庫都ok後,可以正式使用ORM操作DB🎉
使用django內建互動式shell,直接操作model來控制DB
執行以下程式開啟互動式shell(ctrl+D可中斷)
1 | python manger.py shell |
透過控制model來CRUD(新增、讀取、修改、刪除)資料
1 | # Cmd click to launch VS Code Native REPL |
還有各種查詢方式
.all: 列出全部.filter,.exclude: 包含篩選, 除外篩選- 字串類
exact: 精準比較iexact: 鬆散比較(忽略大小寫)contains: 包含字元icontains: 包含字元(忽略大小寫)startswith: 起始字元istartswith: 起始字元(忽略大小寫)endswith: 結尾字元iendswith: 結尾字元(忽略大小寫)
- 邏輯類
gt: 大於gte: 大於等於lt: 小於lte: 小於等於in: 在List中range: 在範圍內isnull: 為null
- 字串類
.order_by: 排序.distinct: 排除重複值.exists: 是否存在, 返回True/False.get: 取QuerySet內的物件, 返回object/DoesNotExcist1
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
29
30
31
32
33
34
35
36
37p1 = Product(name="cake", description="This is a cake.", price = 50.0)
p1.save()
p2 = Product(name="apple", description="This is an apple.", price = 5.0)
p2.save()
p3 = Product(name="apple i17", description="This is a phone.", price = 999.99)
p3.save()
Product.objects.all() # <QuerySet [<Product: cake>, <Product: apple>, <Product: apple i17>]>
### filter(包含) & exclude(排除) ###
# 精準比較
Product.objects.filter(name='cake') # <QuerySet [<Product: cake>]>
Product.objects.filter(name__exact='cake') # <QuerySet [<Product: cake>]>
# 包含字元
Product.objects.filter(name__contains='apple') # <QuerySet [<Product: apple>], <Product: apple i17>]>
# 起始字元
Product.objects.filter(name__startswith='a') # <QuerySet [<Product: apple>], <Product: apple i17>]>
# 多重篩選
Product.objects.filter(name__contains='apple', price__gte=3) # <QuerySet [<Product: apple>], <Product: apple i17>]>
# 排除
Product.objects.exclude(name='cake') # <QuerySet [<Product: apple>], <Product: apple i17>]>
# 排序(降序)
Product.objects.order_by('-price') # <QuerySet [<Product: apple i17>, <Product: cake>, <Product: apple>]>
# OR
from django.db.models import Q
Product.objects.filter(Q(name='cake')|Q(name='apple')) # <QuerySet [<Product: cake>, <Product: apple>]>
# 限制篩選範圍
Product.objects.all()[0:2] # <QuerySet [<Product: cake>, <Product: apple>]>
# 取QuerySet內物件
Product.objects.all()[0] # <Product: cake>
Product.objects.all()[3:].get() # 取值失敗時, 拋出"DoesNotExist" Error
# raise self.model.DoesNotExist(
# orders.models.DoesNotExist: Product matching query does not exist.
MVT
在前一小節有介紹MVT,這裡我們實作/orders/products顯示當前所有商品資訊
- 修改
views.py1
2
3
4
5
6
7
8
9
10
11# orders/views.py
from django.shortcuts import render
from .models import Product
def product(request):
products = Product.objects.all()
# 透過context傳至templates
context = {
'products': products
}
return render(request, 'orders/product.html', context=context) - 新增
templates/product.html,並使用Jinja 2語法使用傳入的變數1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- orders/templates/product.html-->
<!-- 略... -->
<div class="row row-cols-1 row-cols-md-3">
{% for product in products %}
<div class="col mb-4">
<div class="card h-100">
<svg class="bd-placeholder-img card-img-top" width="100%" height="180" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Image cap" preserveAspectRatio="xMidYMid slice" focusable="false"><rect width="100%" height="100%" fill="#6c757d"></rect><text x="40%" y="50%" fill="#dee2e6">Image cap</text></svg>
<div class="card-body">
<h5 class="card-title">{{ product.name }}</h5>
<span>${{ product.price }}</span>
<p class="card-text">{{ product.description }}</p>
</div>
</div>
</div>
{% endfor %}
</div>Tips
關於Jinja 2語法的使用,可參考之前Flask的文章 - 修改
urls.py1
2
3
4
5
6
7
8# orders/urls.py
from django.urls import path
from . import views
app_name = 'orders'
urlpatterns = [
path("product", views.product, name="product"),
]
結論
- 定義資料庫model與使用
models.Model下不同的Field - 資料庫遷移
- QuertSet的CRUD與不同篩選條件
- 在Django中實作ORM