Django閱讀之模型
模型
模型是您的資料唯一而且準確的資訊來源。它包含您正在儲存的資料的重要欄位和行為。一般來說,每一個模型都對映一個數據庫表。
基礎:
- 每個模型都是一個 Python 的類,這些類繼承
django.db.models.Model
- 模型類的每個屬性都相當於一個數據庫的欄位。每個屬性對映為一個數據庫列。
- 綜上訴說,Django 給你一個自動生成訪問資料庫的 API
預設情況下表名是appname_classname,由app名和你建立的模型名組成,可以自定義。
預設情況下會在表中自動建立一個'id'欄位,它是預設主鍵欄位,可以自己手動設定
一旦你定義了你的模型,你需要告訴Django你準備*使用*這些模型。你需要修改設定檔案中的INSTALLED_APPS
,在這個設定中新增包含你 models.py
檔案的模組的名字。
欄位
模型中的每個欄位都應該是相應Field
類的例項 。Django使用欄位類型別來確定一些事情:
- 欄位型別用以指定資料庫資料型別(如:
INTEGER
,VARCHAR
,TEXT
) - 預設的HTML表單輸入框</ ref / forms / widgets>(如:<input type =“text”> <select>)
- 用於Django admin和自動生成表單的基本驗證。
每個欄位都採用一組特定於欄位的引數。例如, CharField
(及其子類)需要一個max_length
引數,該 引數指定VARCHAR
用於儲存資料的資料庫欄位的大小。還有一些通用引數:
null
如果True
,Django將NULL
在資料庫中儲存空值。預設是False
。
blank如果True
,該欄位允許為空。預設是False
。
請注意,這不同於null
。 null
純粹與資料庫相關,而 blank
與驗證相關。如果欄位有blank=True
,則表單驗證將允許輸入空值。如果欄位有blank=False
,則需要該欄位。
choices
2元組的可迭代(例如,列表或元組),用作此欄位的選項。如果給出了這個,則預設表單小部件將是一個選擇框而不是標準文字欄位,並將限制對給定選項的選擇。
選項列表如下所示:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), )
每個元組中的第一個元素是將儲存在資料庫中的值。第二個元素由欄位的窗體小部件顯示。
給定模型例項,choices
可以使用該get_FOO_display()
方法訪問欄位的顯示值。
default
欄位的預設值。這可以是值或可呼叫物件。如果可呼叫,則每次建立新物件時都會呼叫它。help_text
使用表單小部件顯示額外的“幫助”文字。即使您的欄位未在表單上使用,它也對文件很有用。
primary_key如果True
,此欄位是模型的主鍵。
如果沒有primary_key=True
為模型中的任何欄位指定,Django將自動新增一個 IntegerField
來儲存主鍵,因此primary_key=True
除非要覆蓋預設的主鍵行為,否則不需要設定 任何欄位。主鍵欄位是隻讀的。如果更改現有物件上主鍵的值然後儲存它,則將建立一個與舊物件並排的新物件。
unique
如果True
,該欄位在整個表格中必須是唯一的。
自動主鍵欄位
預設情況下,Django為每個模型提供以下欄位:
id = models.AutoField(primary_key=True)
這是一個自動遞增的主鍵。
如果您要指定自定義主鍵,只需primary_key=True
在其中一個欄位中指定即可 。如果Django看到你明確設定Field.primary_key
,它將不會新增自動 id
列。
每個模型只需要一個欄位primary_key=True
(顯式宣告或自動新增)。
詳細欄位名
除了和 之外ForeignKey
, 每個欄位型別都採用可選的第一個位置引數 - 一個詳細的名稱。如果沒有給出詳細名稱,Django將使用欄位的屬性名稱自動建立它,將下劃線轉換為空格。ManyToManyField
OneToOneField
在此示例中,詳細名稱為:"person's first name"
first_name = models.CharField("person's first name", max_length=30)
在此示例中,詳細名稱為:"first name"
first_name = models.CharField(max_length=30)
ForeignKey
, ManyToManyField
並 OneToOneField
要求第一個引數是一個模型類,所以使用verbose_name
關鍵字引數:
poll = models.ForeignKey( Poll, on_delete=models.CASCADE, verbose_name="the related poll", ) sites = models.ManyToManyField(Site, verbose_name="list of sites") place = models.OneToOneField( Place, on_delete=models.CASCADE, verbose_name="related place", )
關係
顯然,關係資料庫的力量在於將表相互關聯。Django提供了定義三種最常見資料庫關係型別的方法:多對一,多對多和一對一。
要定義多對一關係,請使用django.db.models.ForeignKey
。您可以像使用任何其他Field
型別一樣使用它:將其包含為模型的類屬性。
ForeignKey
需要一個位置引數:與模型相關的類。
from django.db import models
class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) # ...
多對多關係
要定義多對多關係,請使用 ManyToManyField
。您可以像使用任何其他Field
型別一樣使用它 :將其包含為模型的類屬性。
ManyToManyField
需要一個位置引數:與模型相關的類。
例如,如果a Pizza
有多個Topping
物件 - 也就是說,a Topping
可以在多個比薩餅上,每個Pizza
都有多個澆頭 - 這就是你如何表示:
from django.db import models
class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
建議(但不要求)a的名稱 ManyToManyField
(toppings
在上面的示例中)是描述相關模型物件集的複數。
哪種型號有什麼關係並不重要 ManyToManyField
,但您應該只將其放在其中一種型號中 - 而不是兩種型號。
通常,ManyToManyField
例項應該放在要在表單上編輯的物件中。在上面的例子中, toppings
是Pizza
(而不是Topping
擁有pizzas
ManyToManyField
),因為考慮披薩的配料比在多個披薩上打頂更自然。在上面設定的方式,Pizza
表單將允許使用者選擇澆頭。
多對多關係中的額外欄位
當您只處理簡單的多對多關係時,例如混合和匹配比薩餅和澆頭,ManyToManyField
您只需要一個標準 。但是,有時您可能需要將資料與兩個模型之間的關係相關聯。
例如,考慮應用程式跟蹤音樂家所屬的音樂組的情況。一個人與他們所屬的團體之間存在多對多的關係,因此您可以使用a ManyToManyField
來表示這種關係。但是,您可能希望收集的成員資格有很多詳細資訊,例如此人加入該組的日期。
對於這些情況,Django允許您指定將用於管理多對多關係的模型。然後,您可以在中間模型上新增額外的欄位。中間模型與ManyToManyField
使用 through
引數指向將充當中介的模型相關聯 。對於我們的音樂家示例,程式碼看起來像這樣:
from django.db import models
class Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __str__(self): return self.name class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) date_joined = models.DateField() invite_reason = models.CharField(max_length=64)
設定中間模型時,您明確指定多對多關係中涉及的模型的外來鍵。此顯式宣告定義了兩個模型的關聯方式。
一對一的關係
要定義一對一的關係,請使用 OneToOneField
。您可以像使用任何其他Field
型別一樣使用它 :將其包含為模型的類屬性。
當物件以某種方式“擴充套件”另一個物件時,這對於物件的主鍵最有用。
OneToOneField
需要一個位置引數:與模型相關的類。
例如,如果您正在構建“地點”資料庫,您將在資料庫中構建非常標準的內容,例如地址,電話號碼等。然後,如果你想在這些地方建立一個餐館資料庫,而不是重複自己並在Restaurant
模型中複製這些欄位,你可以做Restaurant
一個OneToOneField
to Place
(因為一個餐館“是一個”地方;事實上,處理這通常使用 繼承,它涉及隱式的一對一關係)。
OneToOneField
欄位也接受可選 parent_link
引數。
OneToOneField
用於自動成為模型主鍵的類。這不再是真的(儘管你可以手動傳入primary_key
引數)。因此,現在可以OneToOneField
在單個模型上具有多個型別的欄位 。
注意:
由於Django的查詢查詢語法的工作方式,欄位名稱不能在一行中包含多個下劃線。例如:
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
Meta
選項
使用內部提供模型元資料,如下所示:class Meta
from django.db import models
class Ox(models.Model): horn_length = models.IntegerField() class Meta: ordering = ["horn_length"] verbose_name_plural = "oxen"
模型元資料是“任何不是欄位的東西”,例如排序選項(ordering
),資料庫表名(db_table
)或人類可讀的單數和複數名稱(verbose_name
和 verbose_name_plural
)。不是必要,新增到模型是完全可選的。
模型屬性
-
objects
模型最重要的屬性是Manager
。它是為Django模型提供資料庫查詢操作的介面,用於 從資料庫中 檢索例項。如果Manager
未定義自定義,則預設名稱為objects
。
模型方法
在模型上定義自定義方法,以向物件新增自定義“行級”功能。雖然Manager
方法旨在執行“表格範圍”的事情,但模型方法應該作用於特定的模型例項。
__str__()
Python“魔術方法”,返回任何物件的字串表示形式。這是Python和Django在模型例項需要被強制並顯示為純字串時將使用的內容。最值得注意的是,當您在互動式控制檯或管理員中顯示物件時會發生這種情況。
你總是想要定義這個方法; 預設情況下根本沒有用。
get_absolute_url()
這告訴Django如何計算物件的URL。Django在其管理介面中使用它,並且只要它需要找出物件的URL。
具有唯一標識它的URL的任何物件都應定義此方法。
覆蓋預定義的模型方法
還有另一組模型方法,它們封裝了一些您想要自定義的資料庫行為。特別是你經常想改變方式save()
和 delete()
工作方式。
您可以自由地覆蓋這些方法(以及任何其他模型方法)來改變行為。
用於覆蓋內建方法的經典用例是,如果您希望在儲存物件時發生某些事情。例如(參見 save()
它接受的引數的文件):
from django.db import models
class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def save(self, *args, **kwargs): do_something() super().save(*args, **kwargs) # Call the "real" save() method. do_something_else()
重要的是要記住呼叫超類方法 - 那就是業務 - 以確保物件仍然儲存到資料庫中。如果您忘記呼叫超類方法,則不會發生預設行為,也不會觸及資料庫。super().save(*args, **kwargs)
傳遞可以傳遞給模型方法的引數也很重要 - 這就是位的作用。Django將不時擴充套件內建模型方法的功能,增加新的引數。如果在方法定義中使用,則可以保證程式碼在新增時自動支援這些引數。*args,**kwargs
*args, **kwargs
繼承模型
模型繼承在Django中與普通類繼承在Python中的工作方式幾乎完全相同,但也仍有遵循本頁開頭的內容。這意味著其基類應該繼承自django.db.models.Model
。
您必須做出的唯一決定是,您是希望父模型本身是模型(使用自己的資料庫表),還是父母只是通過子模型可見的公共資訊的持有者。
Django中有三種可能的繼承方式。
- 通常,您只想使用父類來儲存您不希望為每個子模型鍵入的資訊。這個類不會被孤立使用,所以抽象基類就是你所追求的。
- 如果你是現有模型的子類(可能是完全來自另一個應用程式的東西),並希望每個模型都有自己的資料庫表,那麼 多表繼承是最佳選擇。
- 最後,如果您只想修改模型的Python級行為,而不以任何方式更改模型欄位,則可以使用 代理模型。
抽象基類
當您想要將一些公共資訊放入許多其他模型時,抽象基類非常有用。你寫你的基類,並把abstract=True
在元 類。然後,此模型將不用於建立任何資料庫表。相反,當它用作其他模型的基類時,其欄位將新增到子類的欄位中。
一個例子:
from django.db import models
class CommonInfo(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() class Meta: abstract = True class Student(CommonInfo): home_group = models.CharField(max_length=5)
該Student
模型將有三個領域:name
,age
和 home_group
。該CommonInfo
模型不能用作普通的Django模型,因為它是一個抽象基類。它不生成資料庫表或具有管理器,並且無法直接例項化或儲存。
從抽象基類繼承的欄位可以使用其他欄位或值覆蓋,也可以使用刪除None
。
對於許多用途,這種型別的模型繼承將完全符合您的要求。它提供了一種在Python級別分解公共資訊的方法,同時仍然只在資料庫級別為每個子模型建立一個數據庫表。
Meta
繼承
當建立抽象基類時,Django使 您在基類中宣告的任何Meta內部類可用作屬性。如果子類沒有宣告自己的Meta 類,它將繼承父類的Meta。如果孩子想要擴充套件父類的Meta類,它可以將其子類化。例如:
from django.db import models
class CommonInfo(models.Model): # ... class Meta: abstract = True ordering = ['name'] class Student(CommonInfo): # ... class Meta(CommonInfo.Meta): db_table = 'student_info'
Django確實對抽象基類的Meta類進行了一次調整:在安裝Meta屬性之前,它設定了abstract=False
。這意味著抽象基類的子節點本身不會自動成為抽象類。當然,您可以建立一個繼承自另一個抽象基類的抽象基類。您只需要記住abstract=True
每次都明確設定。
多表繼承
Django支援的第二種模型繼承是當層次結構中的每個模型都是模型本身時。每個模型對應於自己的資料庫表,可以單獨查詢和建立。繼承關係引入子模型與其每個父模型之間的連結(通過自動建立OneToOneField
)。例如:
from django.db import models
class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(Place): serves_hot_dogs = models.BooleanField(default=False) serves_pizza = models.BooleanField(default=False)
儘管資料將駐留在不同的資料庫表Place
中Restaurant
,但所有欄位都將可用。所以這些都是可能的:
>>> Place.objects.filter(name="Bob's Cafe") >>> Restaurant.objects.filter(name="Bob's Cafe")
Meta
和多表繼承
在多表繼承情況下,子類從其父類的Meta類繼承是沒有意義的。所有的Meta選項都已經應用於父類,並且再次應用它們通常只會導致矛盾的行為(這與基類本身不存在的抽象基類情況形成對比)。
因此,子模型無法訪問其父級的Meta類。但是,有一些有限的情況,子程序從父程序繼承行為:如果子程序沒有指定 ordering
屬性或 get_latest_by
屬性,它將從其父程序繼承它們。
如果父級有一個排序而你不希望孩子有任何自然順序,你可以明確地禁用它:
class ChildModel(ParentModel):
# ... class Meta: # Remove parent's ordering effect ordering = []
繼承和反向關係
因為多表繼承使用隱式 OneToOneField
連結子項和父項,所以可以從父項向下移動到子項,如上例所示。但是,這會佔用名稱和 關係的預設related_name
值 。如果要將這些型別的關係放在父模型的子類上,則 必須 在每個此類欄位上指定該屬性。
class Supplier(Place):
customers = models.ManyToManyField(Place)
這會導致錯誤,新增related_name
到該customers
欄位將解決錯誤:models.ManyToManyField(Place,related_name='provider')
代理模型
使用多表繼承時,會為模型的每個子類建立一個新的資料庫表。這通常是所需的行為,因為子類需要一個位置來儲存基類上不存在的任何其他資料欄位。但是,有時您只想更改模型的Python行為 - 可能更改預設管理器或新增新方法。
這就是代理模型繼承的用途:為原始模型建立代理。您可以建立,刪除和更新代理模型的例項,並且將儲存所有資料,就像使用原始(非代理)模型一樣。不同之處在於您可以更改代理中的預設模型排序或預設管理器等內容,而無需更改原始內容。
代理模型宣告為普通模型。你通過設定類的proxy
屬性告訴Django它是一個代理模型。Meta
True
例如,假設您要向Person
模型新增方法。你可以這樣做:
from django.db import models
class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) class MyPerson(Person): class Meta: proxy = True def do_something(self): # ... pass
該MyPerson
班在同一個資料庫表作為它的父工作 Person
類。特別是,任何新的例項Person
也可以通過MyPerson
,反之亦然:
>>> p = Person.objects.create(first_name="foobar") >>> MyPerson.objects.get(first_name="foobar") <MyPerson: foobar>
你仍然可以使用一個代理模型來定義模型的預設排序方法你也許不會想一直對“Persion”進行排序,但是通常情況下用代理模型根據“姓氏”屬性進行排序這很簡單。:
class OrderedPerson(Person):
class Meta: ordering = ["last_name"] proxy = True
現在,正常Person
查詢將是無序的,OrderedPerson
查詢將按順序排序last_name
。
代理繼承和非託管模型之間的差異¶
代理模型繼承可能看起來與使用managed
模型Meta
類的屬性建立非託管模型非常相似。
通過仔細設定,Meta.db_table
您可以建立一個非託管模型,該模型可以隱藏現有模型併為其新增Python方法。但是,如果您進行任何更改,則需要保持兩個副本同步,這將是非常重複和脆弱的。
另一方面,代理模型的行為與它們所代表的模型完全相同。它們始終與父模型同步,因為它們直接繼承其欄位和管理器。
一般規則是:
- 如果要映象現有模型或資料庫表,並且不想要所有原始資料庫表列,請使用
Meta.managed=False
。該選項通常用於建模不受Django控制的資料庫檢視和表。 - 如果您想要更改模型的僅Python行為,但保留與原始欄位相同的欄位,請使用
Meta.proxy=True
。這進行了設定,以便在儲存資料時代理模型是原始模型的儲存結構的精確副本。