新手指南:Service 模型與 related_name 的作用
更新日期: 2024 年 12 月 24 日
本文為 Django 分類功能系列教學,第 3 篇:
在開發過程中,資料模型設計是構建系統的基石,而 related_name
是一個提升數據關聯操作靈活性的重要參數。
本文將專注於講解 Service 模型 的設計,以及如何通過 related_name
實現反向關聯操作,讓您能快速掌握這一概念。
Service 模型設計
以下是 Service 模型的定義,展示了如何將服務與分類關聯:
class Service(models.Model):
name = models.CharField(max_length=200, verbose_name="服務名稱")
description = models.TextField(blank=True, null=True, verbose_name="服務描述")
category = models.ForeignKey(
'Category',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='services',
verbose_name="分類"
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="建立時間")
def __str__(self):
return self.name
核心欄位解釋
name
:服務名稱,使用CharField
儲存簡短的文字描述。description
:服務描述,允許空值,適合存儲長文本資訊。category
(外鍵欄位):將服務與分類進行關聯,並包含關鍵參數related_name
。created_at
:記錄服務建立的時間,便於排序與歷史追蹤。
related_name
的作用
正向與反向關聯
- 正向關聯:從 Service 模型訪問分類:
service = Service.objects.get(id=1)
print(service.category) # 輸出該服務的分類物件
- 反向關聯:從分類訪問服務:
- 透過
related_name='services'
,可以清晰地表達分類下包含的服務。
- 透過
category = Category.objects.get(id=1)
services = category.services.all()
for service in services:
print(service.name) # 輸出該分類下所有服務名稱
related_name
命名的重要性
- 語義清晰:
related_name='services'
明確表達「分類下的服務集合」,符合業務邏輯。 - 避免衝突:
若未指定related_name
,Django 預設會自動生成名稱,但可能與其他屬性發生衝突或難以理解。
補充:Django 如何自動生成 related_name
:
在 Django 中,related_name
是用於反向查詢關聯對象的屬性名稱。如果你在設計模型時沒有明確設置 related_name
,Django 會根據一些規則自動生成反向關聯的名稱。
以下是 Django 自動生成
related_name
的規則和原理:基本規則
使用模型名稱的複數形式
- 如果沒有指定
related_name
,Django 會自動使用模型名稱的複數形式作為反向關聯的名稱。- 使用的名稱基於
Meta.verbose_name_plural
(如果未設置,則根據模型名稱自動生成)。範例:
# models.py class Author(models.Model): name = models.CharField(max_length=100) class Book(models.Model): title = models.CharField(max_length=100) author = models.ForeignKey(Author, on_delete=models.CASCADE)
- 自動生成的
related_name
是模型名稱的複數形式,默認為book_set
。- 查詢時可以這樣操作:
author = Author.objects.get(id=1) books = author.book_set.all()
當有多個字段引用同一模型
規則:
- 如果同一個模型中有多個字段引用同一個外鍵目標模型,Django 在生成
related_name
時會加上字段名稱。範例:
class Author(models.Model): name = models.CharField(max_length=100) class Book(models.Model): title = models.CharField(max_length=100) primary_author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='primary_books') secondary_author = models.ForeignKey(Author, on_delete=models.CASCADE)
primary_author
的反向查詢名稱是primary_books
(因為指定了related_name
)。secondary_author
的反向查詢名稱是book_set
(因為沒有指定related_name
,且是默認值)。- 使用方法:
primary_author = Author.objects.get(id=1) primary_books = primary_author.primary_books.all() # 使用自定義的 related_name secondary_author = Author.objects.get(id=2) books = secondary_author.book_set.all() # 使用默認的 related_name
related_query_name
的影響
- 如果在設計模型時指定了
related_query_name
,Django 會使用它來生成查詢語句中的名稱。- 如果未設置,Django 默認使用模型名稱的小寫形式。
範例:
class Author(models.Model): name = models.CharField(max_length=100) class Book(models.Model): title = models.CharField(max_length=100) author = models.ForeignKey(Author, on_delete=models.CASCADE, related_query_name='written_by')
- 反向關聯的查詢可以這樣進行:
books_by_author = Book.objects.filter(written_by=1)
如果出現名稱衝突
當模型中存在多個外鍵指向同一目標模型且未指定
related_name
,Django 會報錯,要求顯式設置related_name
。範例:
class Author(models.Model): name = models.CharField(max_length=100) class Book(models.Model): primary_author = models.ForeignKey(Author, on_delete=models.CASCADE) secondary_author = models.ForeignKey(Author, on_delete=models.CASCADE)
- 上述代碼會報錯:
django.core.exceptions.FieldError: Reverse accessor for 'Book.primary_author' clashes with reverse accessor for 'Book.secondary_author'.
解決方法:
設置
related_name
:class Book(models.Model): primary_author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='primary_books') secondary_author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='secondary_books')
related_name='+'
的特例如果將
related_name='+'
設置為特殊值,Django 不會為該關聯生成反向關聯屬性。範例:
class Author(models.Model): name = models.CharField(max_length=100) class Book(models.Model): author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='+')
- 在這種情況下,無法從
Author
模型反向查詢到Book
模型。- 無法執行:
author.book_set.all() # 這將會失敗,因為沒有生成反向關聯名稱。
代碼運作示例
假設資料如下:
- Service 表:
id | name | category_id
----|--------------|------------
1 | 線上課程 | 1
2 | 面授課程 | 1
3 | 外賣服務 | 2
操作方向:
- 正向操作:從 Service 訪問分類
service = Service.objects.get(id=1)
print(service.category.name) # 輸出:教育
- 反向操作:從分類訪問服務
category = Category.objects.get(id=1)
services = category.services.all() # 使用 related_name 訪問服務
for service in services:
print(service.name) # 輸出:線上課程、面授課程
related_name
設計的常見誤區
誤用不符合語義的名稱
- 如果將
related_name
設為categories
,會導致語義混亂,因為一個分類下應包含多個服務,而非多個分類。
忽略 related_name
的設置
- 若未指定
related_name
,Django 預設會使用modelname_set
(如service_set
),但這種命名方式不直觀,會降低代碼可讀性。
總結
- Service 模型的核心設計:
- 使用外鍵欄位
category
將服務與分類關聯。 - 設置
related_name='services'
,實現從分類訪問服務的反向操作。
- 使用外鍵欄位
related_name
的作用:- 提高代碼可讀性與操作靈活性,避免預設名稱的語義模糊。
- 設計要點:
始終選擇能反映業務邏輯的名稱,如services
,確保代碼結構清晰易懂。
透過這篇指南,您應該對 Service 模型的設計和 related_name
的實際應用有了更清晰的理解。