新手指南:Service 模型與 related_name 的作用

更新日期: 2024 年 12 月 24 日

在開發過程中,資料模型設計是構建系統的基石,而 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

操作方向

  1. 正向操作:從 Service 訪問分類
service = Service.objects.get(id=1)
print(service.category.name)  # 輸出:教育
  1. 反向操作:從分類訪問服務
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 的實際應用有了更清晰的理解。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *