📚 Django - Data Modelling
Create Model
- 會自動產生 primary key,因為繼承自 models.Model。若想要自訂
primary key
,可以參考 sku 的寫法
# playground/models.py
from django.db import models
# Create your models here.
class Product(models.Model):
#sku = models.CharField(max_length=10, primary_key=True) # use this primary key
title = models.CharField(max_length=300)
description = models.TextField()
price = models.DecimalField(max_digits=6, decimal_places=2)
inventory = models.IntegerField()
last_updated = models.DateTimeField(auto_now=True) # update every time
Choices
field
class Customer(models.Model):
# best practice to prevent hardcode
MEMBERSHIP_BRONZE = 'B'
MEMBERSHIP_SILVER = 'S'
MEMBERSHIP_GOLD = 'G'
MEMBERSHIP_CHOICES = [
(MEMBERSHIP_BRONZE, 'Bronze'),
(MEMBERSHIP_SILVER, 'Silver'),
(MEMBERSHIP_GOLD, 'Gold'),
]
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
email = models.EmailField(max_length=100,unique=True)
phone = models.CharField(max_length=15, unique=True)
birth_date = models.DateTimeField(null=True, blank=True)
membership = models.CharField(max_length=1, choices=MEMBERSHIP_CHOICES, default=MEMBERSHIP_BRONZE)
One-to-One
relationship
class Address(models.Model):
street = models.CharField(max_length=255)
city = models.CharField(max_length=255)
# address is the child, customer is the parent
customr = models.OneToOneField(
Customer, on_delete=models.CASCADE, primary_key=True)
on_delete=models.CASCADE
(層級刪除): 當父模型被刪除時,與之相關聯的子模型也會被刪除on_delete=models.PROTECT
(保護模式): 防止刪除父模型,除非先刪除所有與之相關的子模型。on_delete=models.SET_NULL
(設為空值): 當父模型被刪除時,與之相關聯的子模型的外鍵欄位(即OneToOneField
)會被設為 NULL。- 範例:一個簡單的部落格系統,如果一篇文章被作者刪除,該文章的作者欄位會被設為 NULL。
on_delete=models.SET_DEFAULT
(設為預設值): 當父模型被刪除時,與之相關聯的子模型的外鍵欄位會被設為預設值。- 範例:一個簡單的部落格系統,如果一篇文章被作者刪除,該文章的作者欄位會被設為 NULL。
on_delete=models.SET()
(設定為特定值): 當父模型被刪除時,與之相關聯的子模型的外鍵欄位會被設為特定的值(通常是另一個存在的模型實例)。- 範例:一個論壇系統,如果一個用戶帳號被刪除,與之相關聯的文章的作者欄位會被設為論壇的預設用戶,這個預設用戶的 ID 為 1。
class Child(models.Model):
parent = models.OneToOneField(Parent, on_delete=models.CASCADE)
...
class Child(models.Model):
parent = models.OneToOneField(Parent, on_delete=models.PROTECT)
...
class Child(models.Model):
parent = models.OneToOneField(Parent, on_delete=models.SET_NULL, null=True)
...
class Child(models.Model):
parent = models.OneToOneField(Parent, on_delete=models.SET_DEFAULT, default=1)
...
class Child(models.Model):
parent = models.OneToOneField(Parent, on_delete=models.SET(DefaultParent.objects.get(pk=1)))
...
One-to-many
relationship
one collection can have many products
-
Use
foreignkey
-
if you can not modify the order of declaration for the
collection
model, you can employ astring argument
within theForeignKey
field:collection = models.ForeignKey('Collection', on_delete=models.PROTECT)
class Collection(models.Model):
title = models.CharField(max_length=300)
class Product(models.Model):
...
collection = models.ForeignKey(Collection, on_delete=models.PROTECT)
Many-to-Many
relationship
- Use
ManyToManyField
- to customize the name of the reverse relation. Instead of the default
product_set
, you can use therelated_name
field
class Promotion(models.Model):
description = models.CharField(max_length=255)
discount = models.FloatField()
# product_set
class Product(models.Model):
...
collection = models.ForeignKey('Collection', on_delete=models.PROTECT)
#promotions = models.ManyToManyField(Promotion, related_name='products')
promotions = models.ManyToManyField(Promotion)
Generic
Relationship
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.auth.models import User
class LikeItem(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
# type (Table)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
# ID (record)
object_id = models.PositiveSmallIntegerField()
# Pointer
content_object = GenericForeignKey()
-
ContentType
(內容類型): 在 Django 中,ContentType
是一個模型,它用來表示其他模型的類型。在這個程式碼中,content_type
欄位是一個外鍵,關聯到ContentType
模型。這麼做的好處是,它允許你動態地參照其他模型,而不是在程式碼中直接寫死模型的名稱。這樣的設計可以增加彈性,讓你的程式碼更容易擴展和修改,而不需要改動太多程式碼。 -
GenericForeignKey
(通用外鍵):GenericForeignKey
是一個用於關聯任意模型的特殊欄位。在這個例子中,content_object
欄位使用了GenericForeignKey
。這意味著LikeItem
模型可以與任何其他模型建立關聯,而不僅僅是固定的一個或幾個模型。通過content_type
和object_id
這兩個欄位,你可以動態地建立與不同模型的關聯。 -
LikeItem
模型:LikeItem
是一個用戶點贊的模型,它關聯到了使用者 (user
) 和其他模型 (content_type
和object_id
確定的模型)。這樣的設計允許你在不確定具體模型的情況下,保存用戶對不同類型物件的喜愛。
在 LikeItem
模型中,content_type
是一個外鍵欄位,關聯到 ContentType
模型,用來指定被點贊物件的類型(例如,是一篇文章還是一張圖片等等)。而 object_id
是一個正整數欄位,用來指定具體的被點贊物件的 ID。
GenericForeignKey
的作用就是把 content_type
和 object_id
這兩個欄位關聯到一個具體的模型實例上,這樣你就可以在 LikeItem
中使用 content_object
這個屬性,直接訪問被點贊的具體物件,而不需要關心它是哪種模型。
例如,如果 content_type
指定為文章,object_id
指定為某篇文章的 ID,那麼 content_object
就可以直接訪問該篇文章的所有屬性和方法。這樣,你可以在不確定具體模型的情況下,動態地建立關聯,使得程式碼更加靈活和可擴展。
你可以將 content_object
想像成程式語言中的指標 pointer
。在這個上下文中,content_object
實際上是一個通用指標,指向了應用中的任何一個模型的特定實例。這樣的設計使得你可以動態地指向和訪問不同模型的物件,而不需要知道具體是哪個類型的物件。
就像指標在程式語言中可以指向不同的變數或數據結構,content_object
允許你在 LikeItem
模型中指向任何類型的物件,從而實現彈性的資料模型。這樣的抽象設計使得程式碼更具通用性,可以應對多種不同的情況,同時也使得程式碼更容易擴展和維護。
Generic Relationship - Example
假設你的對象是 Article
(文章)模型,你可以使用 GenericForeignKey
來建立 LikeItem
與 Article
之間的關聯。以下是一個簡單的範例:
首先,定義你的 Article
模型:
from django.db import models
class Article(models.Model):
# 文章的屬性,例如標題、內容等等
title = models.CharField(max_length=100)
content = models.TextField()
def __str__(self):
return self.title
然後,定義你的 LikeItem
模型,使用 GenericForeignKey
來建立通用外鍵關聯:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.db import models
class LikeItem(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
在這個例子中,LikeItem
模型可以關聯到任何模型的實例,包括 Article
。當你想要為某篇文章建立一個 LikeItem
時,你可以這樣做:
# 創建一篇文章
article = Article.objects.create(title="標題", content="文章內容")
# 創建一個 LikeItem 關聯到這篇文章
like_item = LikeItem.objects.create(user=user, content_type=ContentType.objects.get_for_model(article), object_id=article.id)
# 使用 GenericForeignKey 訪問被點贊的物件
liked_article = like_item.content_object
print(liked_article.title) # 輸出: 標題
在這個例子中,content_type
指定為 Article
模型的類型,object_id
指定為具體文章的 ID。通過 GenericForeignKey
,你可以使用 content_object
直接訪問到被點贊的文章的屬性。這樣,你可以動態地建立與不同模型的關聯,而不需要為每種模型都定義一個單獨的外鍵。
Reverse Relationship - Example
在 Django 中,反向關係(reverse relationship) 通常是自動產生的,無論是在一對一(One-to-One)、一對多(One-to-Many)還是多對多(Many-to-Many)的關係中,Django 都會自動為你建立反向關係。這意味著你可以從一個模型對象訪問與之相關聯的其他模型對象。
假設你有兩個模型,一個是Author
,另一個是Book
。Book
模型有一個外鍵欄位指向Author
模型:
from django.db import models
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)
在這個例子中,Book
模型有一個外鍵欄位author
指向Author
模型。Django 會自動為Author
模型生成一個反向關係。這意味著你可以透過Author
模型來訪問所有與之相關聯的書籍,即使你並沒有在Author
模型中定義相關的欄位。
例如,如果你有一個Author
的實例author
,你可以這樣獲取所有與這個作者相關的書籍:
books = author.book_set.all()
在這裡,book_set
就是 Django 自動為Author
模型生成的反向關係。這個名稱的格式是小寫的模型名稱_set
,它可以讓你訪問到所有與這個模型相關的對象。這種反向關係的自動生成使得在 Django 中進行模型之間的查詢和操作更加方便。
然而,有一個特殊的情況是如果你已經使用了 related_name
參數來定義了自定義的反向關係名稱,反向關係的名稱就不再是默認的 小寫的模型名稱_set
格式。在這種情況下,你必須通過自定義的名稱來訪問反向關係。
舉例來說:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
class Passport(models.Model):
number = models.CharField(max_length=20)
person = models.OneToOneField(Person, on_delete=models.CASCADE, related_name='passport')
在這個例子中,Passport
模型有一個一對一的外鍵關係,並且使用了 related_name='passport'
參數。這樣,在 Person
模型中就不會自動生成默認的反向關係。要訪問這個關係,你需要使用 passport
這個自定義的名稱:
person = Person.objects.get(pk=1)
passport = person.passport # 訪問一對一關係的反向關係
在其他情況下(包括一對多和多對多關係),Django 都會自動生成默認的反向關係,你可以直接使用模型的名稱(小寫)作為反向關係的名稱。
related_name
field in ForeignKey
在一對多關係中,related_name
可以被用來定義反向關係的名稱,使你能夠更具意義地訪問相關對象。在多對多關係中,related_name
可以被用來定義通過中間表訪問相關對象時的名稱。
以下是一個一對多和多對多關係中 related_name
的使用示例:
一對多(One-to-Many)關係:
from django.db import models
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='books')
在這個例子中,Book
模型的 author
欄位定義了外鍵關係,並且使用了 related_name='books'
參數。這樣,你就可以透過 author.books.all()
來訪問一位作者的所有書籍。
多對多(Many-to-Many)關係:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
groups = models.ManyToManyField('Group', related_name='memberships')
class Group(models.Model):
name = models.CharField(max_length=100)
# 其他欄位和方法
在這個例子中,Person
模型和 Group
模型之間建立了多對多的關係。Person
模型的 groups
欄位使用了 related_name='memberships'
參數。這樣,你可以透過 group.memberships.all()
來訪問一個群組中的所有成員。