買了許多新模型當然也要買個模型櫃,買櫃子時也要記得貨比三家,買貴退差價已經是現在常見的消費者福利了。
本章節我們將延續上一節學習到的內容,建立電商網站的第一個應用程式來實作商品模型。
讓我們來建立第一個 App 吧,電商網站首先會想到的是需要一個商品列表的呈現頁面。
這裡我們以 products 來做為我們的 App 名稱,透過 docker 執行 startapp 指令來建立一個新的 app:
docker exec --workdir /opt/app/web example_tenant_web python3.10 manage.py startapp products
現在 web 目錄結構如下:
main 為 django 主要應用程式
customers 為多租戶應用程式
products 為剛剛建立的商品應用程式
.
├── customers
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── main
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── product
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
建立好 App 後我們要在 main/settings.py 進行註冊,每個租戶的商品資料是會各自管理的,所以 products 屬於租戶獨自擁有的應用程式 TENANT_APPS
# main/settings.py
TENANT_APPS = (
# ...
'products',
)
接下來我們就開始撰寫 products 的模型吧!
定義商品模型 Product ,商品模型的欄位定義了商品名稱、商品描述、商品價格、建立日期、修改日期,使用的欄位與參數皆為『認識模型,常用欄位與參數介紹』中說明過的。
在 Meta 中定義了模型的單複數名稱與使用建立時間進行的排序方式,__str__
則是調用 Product 物件時返回的物件名稱,要注意的是返回值一定要是 Str 類型,為了避免有時候純數字會被誤判為 Int 類型,所以會使用 f-string 來進行字串格式化。
# products/models.py
from django.db import models
# Create your models here.
class Product(models.Model):
'''
商品模型
'''
name = models.CharField('商品名稱', max_length=50)
description = models.TextField('商品描述', max_length=500, null=True, blank=True)
price = models.PositiveIntegerField('商品價格', default=0)
created = models.DateTimeField('建立日期', auto_now_add=True)
modified = models.DateTimeField('修改日期', auto_now=True)
class Meta:
verbose_name = '商品'
verbose_name_plural = '商品'
ordering = ['-created']
def __str__(self):
return f'{self.name}'
定義商品分類模型 ProductCategory ,商品分類模型的欄位定義了商品分類名稱、商品分類描述、建立日期、修改日期,Meta 與 __str__
與 Product 設定方式一致。
# products/models.py
# ...
class ProductCategory(models.Model):
'''
商品分類模型
'''
name = models.CharField('商品分類名稱', max_length=50)
description = models.TextField('商品分類描述', max_length=500, null=True, blank=True)
created = models.DateTimeField('建立日期', auto_now_add=True)
modified = models.DateTimeField('修改日期', auto_now=True)
class Meta:
verbose_name = '商品分類'
verbose_name_plural = '商品分類'
ordering = ['-created']
def __str__(self):
return f'{self.name}'
要將 Product 與 ProductCategory 進行關聯,就要使用到之前介紹過的 Foreignkey 欄位。
我們在 Product 模型加入一個名為 category 的關聯欄位:
# products/models.py
# ...
class Product(models.Model):
'''
商品模型
'''
name = models.CharField('商品名稱', max_length=50)
description = models.TextField('商品描述', max_length=500, null=True, blank=True)
price = models.PositiveIntegerField('商品價格', default=0)
created = models.DateTimeField('建立日期', auto_now_add=True)
modified = models.DateTimeField('修改日期', auto_now=True)
category = models.ForeignKey(
'products.ProductCategory', blank=True, null=True,
on_delete=models.RESTRICT, verbose_name='商品分類', related_name='product_set'
) # 新增 category 欄位
class Meta:
verbose_name = '商品'
verbose_name_plural = '商品'
ordering = ['-created']
def __str__(self):
return f'{self.name}'
# ...
定義好模型後,一定要進行資料庫遷移 makemigrations 與 migrate
使用 makemigrations 指令生成資料庫遷移檔案
docker exec --workdir /opt/app/web example_tenant_web \
python3.10 manage.py makemigrations products
...
Migrations for 'products':
products/migrations/0001_initial.py
- Create model ProductCategory
- Create model Product
使用 migrate 指令執行資料庫遷移,這裡會將所有租戶都進行遷移
docker exec --workdir /opt/app/web example_tenant_web \
python3.10 manage.py migrate products
...
=== Starting migration
Operations to perform:
Apply all migrations: products
Running migrations:
Applying products.0001_initial...
OK
=== Starting migration
Operations to perform:
Apply all migrations: products
Running migrations:
Applying products.0001_initial...
OK
=== Starting migration
Operations to perform:
Apply all migrations: products
Running migrations:
Applying products.0001_initial...
OK
查看 schema 結構,可以看到租戶獨立 schema 都新增了 products_product 與 products_productcategory 資料表
List of relations
Schema | Name | Type | Owner
--------------------+----------------------------+-------------+---------
public | customers_client | table | db_user
public | customers_domain | table | db_user
public | django_content_type | table | db_user
public | django_migrations | table | db_user
example01 | auth_group | table | db_user
example01 | auth_group_permissions | table | db_user
example01 | auth_permission | table | db_user
example01 | auth_user | table | db_user
example01 | auth_user_groups | table | db_user
example01 | auth_user_user_permissions | table | db_user
example01 | django_admin_log | table | db_user
example01 | django_migrations | table | db_user
example01 | django_session | table | db_user
example01 | django_site | table | db_user
example01 | products_product | table | db_user
example01 | products_productcategory | table | db_user
example02 | auth_group | table | db_user
example02 | auth_group_permissions | table | db_user
example02 | auth_permission | table | db_user
example02 | auth_user | table | db_user
example02 | auth_user_groups | table | db_user
example02 | auth_user_user_permissions | table | db_user
example02 | django_admin_log | table | db_user
example02 | django_migrations | table | db_user
example02 | django_session | table | db_user
example02 | django_site | table | db_user
example02 | products_product | table | db_user
example02 | products_productcategory | table | db_user
實作模型,任務完成!
makemigrate 與 migrate 將會一直扮演與資料庫溝通的重要角色,也會一直是使用 Django 的一個課題,有些棘手的情況本來就不好解,所以遇到挫折也不要氣餒。筆者在反覆練習與使用後現在已經可以運用自如,不慌不忙地解決各種資料不同步的問題,相信每個人也都一定辦的到!
下一回『Django Admin,管理室不收管理費』就要來來認識 Django 強大的自帶管理端,來為我們今天建立的模型鋪好地磚。