Django 2.1.3 文件-模型層-模型
模型
- 1.快速上手
- 2.使用模型
- 3.欄位
- 4.Meta選項
- 5.模型屬性
- 6.模型方法
- 7.模型繼承
- 8. 多重繼承
- 9.不允許欄位名稱“隱藏”
- 10.在包中組織模型
- - 譯自官方文件+自己的理解 --
模型是您的資料唯一而且準確的資訊來源。它包含您正在儲存的資料的重要欄位和行為。一般來說,每一個模型都對映一個數據庫表。
基礎:
- 每個模型都是一個 Python 的類,這些類繼承 django.db.models.Model
- 模型類的每個屬性都相當於一個數據庫的欄位。
- 綜上所說,Django 給你一個自動生成訪問資料庫的 API;請參閱 執行查詢。
1.快速上手
這個樣例模型定義了一個 Person類, 其擁有 first_name
last_name
屬性:first_name
和 last_name
是 模型的欄位。每個欄位都被指定為一個類屬性,並且每個屬性對映為一個數據庫列。上面的 Person 模型會建立一個如下的資料庫表:
一些技術上的說明:
- 該表的名稱 “myapp_person” 是自動從某些模型元資料中派生出來,但可以被改寫。有關更多詳細資訊,請參閱:表名。
- 一個 id 欄位會被自動新增,但是這種行為可以被改寫。請參閱:預設主鍵欄位。
- 此例中的建表語句使用了PostgreSQL語法,但是值得注意的是Django使用的SQL語句跟你在配置檔案中指定的資料庫有關。
2.使用模型
一旦你定義了你的模型,你需要告訴 Django 你準備使用這些模型。你需要修改設定檔案中的 INSTALLED_APPS ,在這個設定中新增包含你 models.py 檔案的模組的名字。
例如,如果模型位於你專案中的myapp.models
中( 此包結構使用:python manage.py startapp
命令建立),NSTALLED_APPS 應設定如下:
當你在INSTALLED_APPS配置新的app後,請記住按順序執行manage.py makemigrations
和manage.py migrate
這兩條命令。
3.欄位
模型中最重要的、並且也是唯一需要指定的應用於資料庫的欄位定義。欄位在類中定義。定義欄位名時應小心避免使用與 models API 衝突的名稱, 如 clean,save,delete
等。
舉例:
3.1欄位型別
模型中每一個欄位都應該是相應類的例項, Django 利用這些欄位類來實現下面這些功能。
- 欄位型別用以指定資料庫資料型別(如:INTEGER, VARCHAR, TEXT)
- 預設的HTML
widgets
,用來渲染form中的子部件(如:<input type="text">
<select>
) - 用於Django admin和自動生成表單的基本驗證。
Django內建了多種欄位型別;你可以在模型欄位參考model-field-types中看到完整列表。如果Django內建型別不能滿足你的需求,你可以很輕鬆地編寫自定義的欄位型別;見自定義模型欄位。
3.2欄位選項
每一種欄位都需要指定一些特定的引數(參考 model field reference ) 例如: CharField (以及它的子類)需要接收一個 max_length
引數,用以指定資料庫儲存資料時用的 VARCHAR 大小。
一些可選的引數是通用的,可以用於任何欄位型別,詳情請見 reference,下面介紹一部分經常用到的通用引數:
null
如果設定為 True , 當該欄位為空時,Django會將資料庫中該欄位設定為 NULL 。預設為 False 。
blank
如果設定為 True ,該欄位允許為空。預設為 False 。
注意該選項與null不同, null 選項僅僅是資料庫層面的設定,然而 blank 是涉及表單驗證方面。如果一個欄位設定為 blank=True ,在進行表單驗證時,接收的資料該欄位值允許為空,而設定為 blank=False 時,不允許為空。
choices
該引數接收一個可迭代的列表或元組(基本單位為二元組)。如果指定了該引數,在例項化該模型時,該欄位只能取選項列表中的值。
一個選項列表:
每個二元組的第一個值會儲存在資料庫中,而第二個值將只會用於顯示作用。
對於一個模型例項,要獲取該欄位二元組中相對應的第二個值,使用 get_FOO_display()
方法(FOO為欄位名字)。例如:
default
該欄位的預設值。可以是一個值或者是個可呼叫的物件,如果是個可呼叫物件,每次例項化模型時都會呼叫該物件。
help_text
針對某個form子部件的額外的提示資訊。
primary_key
如果設定為 True ,將該欄位設定為該模型的主鍵。
在一個模型中,如果你沒有對任何一個欄位設定 primary_key=True
選項。 Django 會自動新增一個 IntegerField 欄位,用於設定為主鍵,因此除非你想重寫 Django 預設的主鍵設定行為,你可以不手動設定主鍵。詳情請見 自動設定主鍵 。
主鍵欄位是隻可讀的,如果你修改一個模型例項該欄位的值並儲存,你將等同於建立了一個新的模型例項。例如:
unique
如果設定為True,則該欄位的值在整個表裡面唯一。
再次宣告,以上只是一些通用引數的簡略描述。你可以在 common model field option reference 中找到完整的介紹。
3.3自動設定主鍵
預設情況下, Django 會給每一個模型新增下面的欄位:
這是一個自增的主鍵。
如果你想指定某一欄位為主鍵, 在該欄位上設定primary_key=True
選項。如果 Django 看到你顯式的設定了 Field.primary_key ,將不會自動在表(模型)中新增 id 列。
每個模型都需要擁有一個設定了 primary_key=True 的欄位(無論是顯式的設定還是 Django 自動設定)。
3.4備註名
除了 ForeignKey
ManyToManyField
和 OneToOneField
,任何欄位型別都接收一個可選的引數 verbose_name
,如果未指定該引數值, Django 會自動使用該欄位的屬性名作為該引數值,並且把下劃線轉換為空格。
在該例中first_name屬性的備註名為person's first name
:
在該例中,備註名為first name
:
ForeignKey, ManyToManyField and OneToOneField 接收的第一個引數為關聯模型的類名,後面可以新增一個 verbose_name 引數:
一般情況下不需要將 verbose_name 值首字母大寫,必要時 Djanog 會自動把首字母轉換為大寫。
3.5關聯關係
顯然,關係型資料庫的強大之處在於各表之間的關聯關係。 Django 提供了定義三種最常見的資料庫關聯關係的方法:多對一,多對多,一對一。
3.5.1 多對一
定義一個多對一的關聯關係,使用 django.db.models.ForeignKey
類。就和其他 Field 欄位型別一樣,只需要在你模型中新增一個值為該類的屬性。
ForeignKey類需要一個必須的引數:和它關聯的類的名字。
例如,如果一個 Car 模型 有一個製造者 Manufacturer ,就是說一個 Manufacturer 製造許多輛車,但是每輛車都屬於某個特定的製造者。那麼使用下面的方法定義這個關係:
你也可以建立一個 遞迴關係(一個模型與它本身有多對一的關係[比如部落格的父評論和子評論])並且可以 對尚未定義的模型進行引用;詳情請見 the model field reference 。
建議設定 ForeignKey 欄位(上例中的 manufacturer )名為想要關聯的模型名,例如:
注意
ForeignKey 欄位還可以接收一些其他的引數,詳見 the model field reference ,這些可選的引數可以更深入的規定關聯關係的具體實現。
有關反向查詢(例如,一對多關係中,由一查多為反向查詢,由多查一為正向查詢),請見 Following relationships backward example.
如要檢視相關示例程式碼,詳見 Many-to-one relationship model example 。
3.5.2 多對多
定義一個多對多的關聯關係,使用 django.db.models.ManyToManyField
類。就和其他 Field 欄位型別一樣,只需要在你模型中新增一個值為該類的屬性。
ManyToManyField
類需要一個必須的引數:和它關聯的類的名字。
例如:如果 Pizza 含有多種 Topping(配料),也就是一種Topping 可能存在於多個 Pizza 中,並且每個 Pizza 含有多種 Topping。那麼可以這樣表示這種關係:
和 ForeignKey 類一樣,你也可以建立 遞迴關係(一個物件與他本身有著多對多的關係)並且可以對尚未定義的模型進行引用。
建議設定 ManyToManyField 欄位(上例中的 toppings )名為一個複數名詞,表示所要關聯的模型物件的集合。
對於多對多關聯關係的兩個模型,可以在任何一個模型中新增 ManyToManyField 欄位,但只能選擇一個模型設定該欄位,即不能同時在兩模型中新增該欄位。
通常來講,在表單中,一個ManyToManyField 屬性應該跟隨關聯的物件出現。上面的例子中,通常我們會這樣想,是Pizza中有toppings(多種配料),而不是一種topping屬於多個Pizzas。所以按照這種想法,在from表單中,應該是配料跟隨Pizza物件出現。
參見
如要檢視完整示例程式碼,詳見 Many-to-many relationship model example。
ManyToManyField欄位還有另一些引數,這些引數用來定義多對多關係該如何執行,都是可選的。
多對多關係中額外欄位
當您只處理簡單的多對多關係時,例如比薩餅和配料,ManyToManyField就可以完成了 。但是,有時您可能需要將資料與兩個模型之間的關係相關聯。
例如,考慮應用程式跟蹤音樂家所屬的音樂組的情況。一個人與他們所屬的團體之間存在多對多的關係,因此您可以使用ManyToManyField來表示這種關係。但是,您可能希望收整合員的更多資訊,例如此人加入該組的日期。
對於這些情況,Django允許您指定將用於管理多對多關係的模型。然後,您可以在中間模型(多對多關係中的第三張表)上新增額外的欄位。中間模型與ManyToManyField使用through
引數指向將充當中介的模型相關聯 。對於我們的音樂家示例,程式碼看起來像這樣:
譯者注:其實就是使用者通過through
關鍵字自定義多對多關係中的第三張表。
設定中間模型時,您明確指定多對多關係中涉及的模型的外來鍵。此顯式宣告定義了兩個模型的關聯方式。
中間模型有一些限制:
- 您的中間模型必須包含一個 且只有一個源模型的外來鍵(在我們的示例中是Group),或者您必須通過
ManyToManyField.through_fields
屬性顯式指定Django應該用於該關係的外來鍵。如果您有多個外來鍵而且through_fields
也沒有指定,則會引發驗證錯誤。類似的限制適用於另一個源模型外來鍵(Person)。 - 一個模型如果與自己有多對多關係,允許中間模型的有兩個相同的源模型外來鍵,但它們將被視為多對多關係的兩個(不同)列。如果不止兩個外來鍵,你還必須指定
through_fields
屬性如上所述,否則引發驗證錯誤。 - 在使用中間模型定義從模型到自身的多對多關係時,必須使用
symmetrical=False
(請參閱 the model field reference)。
假如,現在您已經設定了ManyToManyField使用中間模型(在本例中的Membership),您已準備好開始建立一些多對多關係。您可以通過建立中間模型的例項來完成此操作:
不同於一般的多對多欄位,你不能使用add(),create()或者set()建立關係:
為什麼?您不能只在Person和Group之間建立關係 - 您需要指定Membership模型所需關係的所有細節 。簡單的add,create呼叫不提供一種方式來指定這個額外的細節。因此,對於使用中間模型的多對多關係,它們被禁用。建立此類關係的唯一方法是建立中間模型的例項。
由於類似的原因remove()方法也被禁用。例如,如果中間模型中不強制在(model1, model2)上的唯一性,remove()將不能提供足夠的資訊,以使得中間模型例項應被刪除:
但是,clear() 方法可用於刪除一個例項的所有多對多關係:
通過建立中間模型的例項建立多對多關係後,您可以發出查詢。與普通的多對多關係一樣,您可以使用多對多相關模型的屬性進行查詢:
在使用中間模型時,您還可以查詢其屬性:
如果您需要訪問會員資格,可以直接查詢Membership模型:
訪問同一資訊的另一種方式是通過查詢一個 Person物件的 many-to-many reverse relationship :
3.5.3 一對一
要定義一對一的關係,請使用OneToOneField
。您可以像使用任何其他Field型別一樣使用它 :將其包含為模型的類屬性。
當物件以某種方式“擴充套件”另一個物件時,這對於物件的主鍵最有用。
OneToOneField 需要一個必須的引數:與模型相關的類的名字。
例如,如果您正在構建“place(地點)”資料庫,您將在資料庫中構建非常完整的內容,例如地址,電話號碼等。然後,如果你想在這些地點建立一個餐館資料庫,而不是在Restaurant模型中複製並重復這些欄位,你可以讓Restaurant有一個OneToOneField欄位並關聯到Place(因為一個餐館“是一個”地點";事實上,處理這通常使用 繼承,它涉及隱式的一對一關係)。
與ForeignKey相同,可以定義遞迴關係並且可以對尚未定義的模型進行引用。
參見
有關完整示例,請參閱一對一關係模型示例。
OneToOneField欄位也接受可選的 parent_link引數。
OneToOneField類在模型中會自動變成主鍵。這不再有效(儘管你可以手動傳入primary_key引數)。因此,現在單個模型上可以具有多個OneToOneField在型別的欄位 。
3.6 跨app引用模型
將模型與另一個應用程式中的模型相關聯是完全可以的。為此,請在定義模型的檔案頂部匯入相關模型。然後,只需在需要的地方引用其他模型類。例如:
3.7 欄位名稱限制
Django對模型欄位名稱只有兩個限制:
- 欄位名稱不能是Python保留字,因為這會導致Python語法錯誤。例如:
- 由於Django的查詢查詢語法的工作方式,欄位名稱不能在一行中包含多個下劃線。例如:
但是,這些限制可以解決,因為您的欄位名稱不一定必須與您的資料庫列名稱匹配。請參閱 db_column選項。
SQL保留字(例如join,where或select)被允許作為模型欄位名稱,因為Django會轉義每個基礎SQL查詢中的所有資料庫表名和列名。它使用特定資料庫引擎的引用語法。
3.8 自定義欄位型別
如果其中一個現有模型欄位不能用於滿足您的目的,或者您希望利用一些不太常見的資料庫列型別,則可以建立自己的欄位類。自定義模型欄位 提供了建立自己的欄位的全部內容。
4.Meta選項
在模型類內定義一個Meta類,可以模型類提供元資料,如下所示:
模型元資料是“任何不是欄位的東西”,例如排序選項(ordering),資料庫表名(db_table)或人類可讀的單數和複數名稱(verbose_name和 verbose_name_plural)。class Meta是一個可選項。
Meta所有可能選項的完整列表可以在模型選項參考中找到。
5.模型屬性
objects
模型最重要的屬性是 Manager
。它是為Django模型提供資料庫查詢操作的介面,用於 從資料庫中檢索例項。如果沒有自定義Manager,則預設名稱為 objects。Managers只能通過模型類訪問,而不能通過模型例項訪問。
6.模型方法
在模型上自定義方法,以向物件新增自定義“行級”功能。雖然Manager方法旨在執行“表內"的事情,但模型方法可以作用於特定的模型例項。
這是將業務邏輯儲存在一個地方的有價值的技術 - 模型。
例如,此模型有一些自定義方法:
此示例中的最後一個方法是屬性 (也就是python中的property方法)。
model instance reference中給出了 模型類自動新增的方法 的完整列表。您可以覆蓋其中的大多數 - 請參閱下面的 覆蓋預定義模型方法 - 但有幾個方法您幾乎總是要自定義:
__str__()
Python“魔術方法”,返回任何物件的字串表示形式。這是Python和Django在模型例項需要被強制並顯示為純字串時將使用的內容。最值得注意的是,當您在互動式控制檯或管理員中顯示物件時會發生這種情況。
你總是想要定義這個方法; 因為預設的此方法沒什麼實質性的作用。
get_absolute_url()
這告訴Django如何計算物件的URL。Django在其admin管理介面中使用它,並且只需要它找出物件的URL。
具有唯一標識它的URL的任何物件都應定義此方法。
6.1 覆蓋預定義模型方法
還有另一組 模型方法,它們封裝了一些您想要自定義的資料庫行為。特別是你經常想改變save()和 delete()的工作方式。
您可以自由地覆蓋這些方法(以及任何其他模型方法)來改變行為。
用於覆蓋內建方法的經典用例是,如果您希望在save物件時發生某些事情。例如(參見 save()方法接受的引數的文件):
您還可以阻止儲存:
重要的是要記住呼叫父類方法super().save(*args, **kwargs)
, 以確保物件仍然儲存到資料庫中。如果您忘記呼叫超類方法,則不會發生預設行為,也不會觸及資料庫。
傳遞可以傳遞給模型方法的引數也很重要,那就是*args和**kwargs
的作用。Django將不時擴充套件內建模型方法的功能,增加新的引數。如果在方法定義中使用*args, **kwargs,則可以保證程式碼在新增時自動支援這些引數。
在批量操作中不會呼叫重寫的模型方法
使用QuerySet在批量操作中刪除物件或者級聯刪除的時候,物件的delete()方法不一定要呼叫。為確保執行自定義delete()方法,您可以使用 pre_delete或者post_delete訊號。
不幸的是,在批量操作中建立或更新一個物件時沒辦法,因為不會呼叫save(), pre_save和 post_save。
6.2 執行自定義SQL
另一種常見模式是在模型方法和模組級方法中編寫自定義SQL語句。有關使用原始SQL的更多詳細資訊,請參閱有關使用原始SQL的文件。
7.模型繼承
在 Django 中模型繼承與在 Python 中普通類繼承的工作方式幾乎完全相同, 但也仍應遵循本頁開頭的內容. 這意味著其基類應該繼承django.db.models.Model
.
您必須做出的唯一決定是您希望父模型本身是模型(有自己的資料庫表),還是父類只儲存了一些通用資訊,且子模型可見。
Django中有三種可能的繼承方式。
- 通常,您只想使用父類來儲存您不希望為每個子模型鍵入的資訊。這個類不會被單獨使用,所以 抽象基類 就是你所追求的。
- 如果你是現有模型的子類(可能是完全來自另一個應用程式的模型),並希望每個模型都有自己的資料庫表,那麼 多表繼承 是最佳選擇。
最後,如果您只想修改模型的Python級行為,而不以任何方式更改模型欄位,則可以使用 代理模型。
7.1 抽象基類
當您想要將一些公共資訊放入許多其他模型時,抽象基類非常有用。你寫你的基類,並在Meta類裡放上abstract=True
。然後,此模型將不用於建立任何資料庫表。相反,當它用作其他模型的基類時,其欄位將新增到子類的欄位中。
一個例子:
Student模型將有三個屬性:name,age和 home_group。CommonInfo模型不能用作普通的Django模型,因為它是一個抽象基類。它不生成資料庫表或具有管理器,並且無法直接例項化或儲存。
從抽象基類繼承的欄位可以使用其他欄位或值覆蓋,也可以使用None來刪除。
對於許多用途,這種型別的模型繼承將完全符合您的要求。它提供了一種在Python級別分解公共資訊的方法,同時仍然只在資料庫級別為每個子模型建立一個數據庫表。
7.1.1 Meta繼承
當建立抽象基類時,基類中的內部類Meta被宣告為一個屬性。如果子類沒有宣告自己的Meta 類,它將繼承父類的Meta。如果孩子想要擴充套件父類的Meta類,它可以將其子類化。例如:
Django確實對抽象基類的Meta類進行了一次調整:在安裝Meta屬性之前,它設定了abstract=False。這意味著抽象基類的子類本身不會自動成為抽象類。當然,您可以建立一個繼承自另一個抽象基類的抽象基類。您只需要記住abstract=True每次都明確設定。
在抽象基類的Meta類中包含一些屬性是不會生效的。例如,包含db_table意味著所有子類(未指定自己的Meta)將使用相同的資料庫表,這幾乎肯定不是您想要的。
7.1.2 小心related_name和related_query_name
如果你在ForeignKey或 ManyToManyField中使用了related_name
或 related_query_name
屬性,則必須始終為該欄位指定唯一的反向名稱和查詢名稱。這通常會導致抽象基類出現問題,因為此類中的欄位包含在每個子類中,每次都具有完全相同的屬性值(包括 related_name和 related_query_name)。
若要解決此問題,當您使用 related_name或 related_query_name在抽象基類(僅)時,值的一部分應包含'%(app_label)s'
和 '%(class)s'
。
- ‘%(class)s’ 被替換為使用該欄位的子類的小寫名稱。
- ‘%(app_label)s’由包含在子類在內的應用程式的小寫名稱替換。每個安裝的應用程式名稱必須是唯一的,並且每個應用程式中的模型類名稱也必須是唯一的,因此生成的名稱最終會有所不同。
例如,給定一個應用程式common/models.py:
與另一個應用程式rare/models.py:
common.ChildA.m2m
欄位的反向名稱將是common_childa_related
,反向查詢名稱將是common_childas
。common.ChildB.m2m
欄位的反向名稱將是common_childb_related
,反向查詢名稱將是common_childbs
。最後,rare.ChildB.m2m
欄位rare_childb_related
的反向名稱將是反向查詢名稱rare_childbs
。由你如何使用’%(class)s’和 '%(app_label)s’部分來構造你的相關名稱或相關的查詢名稱,但如果你忘記使用它,Django會在你執行系統檢查(或執行migrate)時引發錯誤。
如果沒有related_name 為抽象基類中的欄位指定屬性,則預設反向名稱將是後跟子類的名稱’_set’,就像通常直接在子類上宣告欄位一樣。例如,在上面的程式碼中,如果related_name 省略了該屬性,對於ChildA來說,m2m欄位的反向名稱將是 childa_set,對於ChildB來說是childb_set。
7.2 多表繼承
Django支援的第二種模型繼承是當層次結構中的每個模型都是模型本身時。每個模型對應於自己的資料庫表,可以單獨查詢和建立。繼承關係引入子模型與其每個父模型之間的連結(通過自動建立OneToOneField)。例如:
Place表中的欄位在Restaurant表中也可以用到,儘管兩者的資料分在不同的表中存放:
譯者注: 自己寫了個例子有助於理解
(1)在place表中插入一條記錄name=Bob's Cafe address=china-z
(2)在restaurant表中插入一條記錄name=zb's CCx address=china-ccb
在兩張表中分別插入一條記錄,結果如下
(3)可以看到,父類中的資料儲存在父表中,子類中通過一個plact_ptr_id
來引用父表中的id
屬性。
(4)檢視restaurant表的定義
可以看到 Django為子類表自動生成了一個外來鍵引用,指向父類,這也解釋了開頭說的 繼承關係引入子模型與其每個父模型之間的連結(通過自動建立OneToOneField)
如果你有一個Restaurant物件例項,你可以使用小寫的模型名稱從 Place物件得到Restaurant物件:
但是,如果在上面的示例中p不是一個Restaurant例項(它已直接建立為Place物件或是其他類的父級),則引用p.restaurant會引發Restaurant.DoesNotExist
異常。
在Restaurant中自動建立的OneToOneField ,它連結到Place看起來像這樣:
你可以按照上面的例子在Restaurant中重寫這個一對一關係,並且指定parent_link=True
屬性。
7.2.1 Meta和多表繼承
在多表繼承情況下,子類從其父類繼承的Meta類是沒有意義的。所有的Meta選項都已經應用於父類,並且再次應用它們通常只會導致矛盾的行為(這與基類本身不存在的抽象基類情況形成對比)。
因此,子模型無法訪問其父級的Meta類。但是,有一些特殊的情況:如果子類沒有指定 ordering屬性或 get_latest_by屬性,它將從其父類繼承它們。
如果父類有一個ordering屬性而你不希望孩子有任何自然順序,你可以明確地禁用它:
7.2.2 繼承和反向關係
因為多表繼承使用隱式 OneToOneField連結子項和父項,所以可以從父類例項訪問到子類例項,如上例所示。但是,這會佔用ForeignKey和ManyToManyField中預設的related_name值 。如果要將這些型別的關係放在子類上,則 必須 在每個此類欄位上指定related_name屬性。如果您忘記了,Django將引發驗證錯誤。
例如,再次使用上面的Place類,讓我們建立另一個子類,其中包含ManyToManyField:
這會導致錯誤:
如下所述,新增related_name到該customers欄位將解決錯誤:models.ManyToManyField(Place, related_name='provider')
7.2.3 指定 parent_link 欄位
如上所述,Django將自動建立一個 OneToOneField,將您的子類連結回任何非抽象父模型。如果要控制連結回父級的屬性的名稱,可以建立自己的屬性OneToOneField並設定 parent_link=True 為指示您的欄位是返回父類的連結。
7.3 代理模型
使用多表繼承時,會為模型的每個子類建立一個新的資料庫表。這通常是所需的行為,因為子類需要一個位置來儲存基類上不存在的任何其他資料欄位。但是,有時您只想更改模型的Python行為 - 可能更改預設管理器或新增新方法。
這就是代理模型繼承的用途:為原始模型建立代理。您可以建立,刪除和更新代理模型的例項,並且將儲存所有資料,就像使用原始(非代理)模型一樣。不同之處在於您可以更改代理中的預設模型排序或預設管理器等內容,而無需更改原始內容。
代理模型宣告為普通模型。你通過設定模型的Meta類的proxy屬性為True,告訴Django它是一個代理模型。
例如,假設您要向Person模型新增方法。你可以這樣做:
MyPerson類和父類Person操作資料庫中的同一個表。特別是,任何新的例項Person也可以通過MyPerson來得到,反之亦然:
你仍然可以使用一個代理模型來定義模型的預設排序方法。你也許不會想一直對“Person”進行排序,但是通常情況下用代理模型根據“last_name”屬性進行排序。這很簡單:
正常的Person查詢將是無序的,OrderedPerson查詢將按順序排序last_name。
譯者投下一個例子
代理模型繼承“Meta”屬性:和普通模型使用同樣的方法
7.3.1 QuerySet仍然返回被請求的模型
每當你查詢MyPerson物件時,就是返回了Person物件。一個queryset返回這些Person型別的物件。代理物件的重點是依賴於原始程式碼,Person將使用這些程式碼,並且您自己的程式碼可以使用您包含的擴充套件(無論如何其他程式碼都不依賴)。它不是Person用你自己創造的東西替換任何地方(或任何其他)模型的方法。(翻譯不太準)
7.3.2 基類限制
一個代理模型必須僅能繼承一個非抽象模型類。你不能繼承多個非抽象模型類,因為代理模型無法提供不同資料表的任何行間連線。一個代理模型可以繼承任意數量的抽象模型類,假如他們沒有定義任何的模型欄位。一個代理模型也可以繼承任意數量的代理模型,只需他們共享同一個非抽象父類。
7.3.3 代理模型管理器
如果未在代理模型上指定任何模型管理器,它將從其父類模型繼承管理器。如果您在代理模型上定義管理器,它將成為預設管理器,儘管在父類上定義的任何管理器仍然可用。
繼續上面的示例,您可以更改查詢Person模型時使用的預設管理器,如下所示:
如果要在不更換預設管理器的情況下向代理新增新管理器,可以使用自定義管理器文件中描述的技術:建立包含新管理器的基類,並在主基類之後繼承:
譯者注:此處投下一個使用例項
通常情況下,你可能不需要這麼做。然而,你需要的時候,這也是可以的。
7.3.4 代理繼承和非託管模型之間的差異
代理模型繼承可能看起來與非託管模型(在模型類的Meta類中使用managed
屬性)非常相似。
通過仔細設定Meta.db_table
您可以建立一個非託管模型,該模型可以隱藏現有模型併為其新增Python方法。但是,如果您進行任何更改,則需要保持兩個副本同步,這將是非常重複和脆弱的。
另一方面,代理模型的行為與它們所代理的模型完全相同。它們始終與父模型同步,因為它們直接繼承其欄位和管理器。
一般規則是:
- 如果要映象現有模型或資料庫表,並且不想要所有原始資料庫表列,請使用
Meta.managed=False
。該選項通常用於建模不受Django控制的資料庫檢視和表。 - 如果您想要更改模型的Python行為,但保留與原始欄位相同的欄位,請使用
Meta.proxy=True
。這進行了設定,以便在儲存資料時代理模型是原始模型的儲存結構的精確副本。
8. 多重繼承
正如Python的子類化一樣,Django模型可以從多個父模型繼承。請記住,正常的Python名稱解析規則適用。第一個有特定名稱(例如Meta)的基類將是使用的基類; 例如,這意味著如果多個父類都包含一個Meta類,則只會使用第一個類,而將忽略所有其他類。
通常,您不需要從多個父類繼承。主要的使用場景是“混入”類:向每個繼承混合類的類新增特定的額外欄位或方法。儘量使您的繼承層次結構儘可能簡單明瞭,這樣您就不必費力去找出特定資訊來自哪裡。
請注意,從都具有id主鍵欄位的多個模型繼承將引發錯誤。要正確使用多重繼承,可以在基本模型中顯示地指定AutoField欄位:
或者使用讓共同的祖先來保持AutoField欄位。這需要使用OneToOneField從每個父模型到共同祖先的顯式來避免子項自動生成和繼承的欄位之間的衝突:
9.不允許欄位名稱“隱藏”
在普通的Python類繼承中,子類允許覆蓋父類的任何屬性。在Django中,模型欄位通常不允許這樣做。如果非抽象模型基類具有一個author欄位,則無法建立另一個author模型欄位,子類中也不能有author欄位。
此限制不適用於從抽象模型繼承的模型欄位。這些欄位可以用另一個欄位或值覆蓋,或者通過設定field_name = None來刪除。
警告
模型管理器繼承自抽象基類。覆蓋父類欄位 (此欄位由繼承的 Manager引用),可能會導致細微的錯誤。請參閱 自定義管理器和模型繼承。
註解
某些欄位在模型中定義額外的屬性,例如 ForeignKey定義了一個額外的屬性 _id附加到欄位名稱,以及related_name和 related_query_name屬性。
除非更改或刪除定義它的欄位以使其不再定義額外屬性,否則不能覆蓋這些額外屬性。↑(註解)
在父模型中覆蓋欄位會導致諸如初始化新例項(在Model.__init__
中指定正在初始化哪個欄位)和序列化等方面的困難 。這些是普通Python類繼承不必以完全相同的方式處理的特性,因此Django模型繼承和Python類繼承之間的區別不是隨意的。
此限制僅適用於作為Field例項的屬性 。如果您願意,可以覆蓋普通的Python屬性。它也只適用於Python看到的屬性名稱:如果您手動指定資料庫列名,則可以在子表和祖先模型中出現相同的列名,以用於多表繼承(它們是列在兩個不同的資料庫表中)。
如果你覆蓋任何祖先模型中的任何模型欄位,Django將引發一個FieldError。
10.在包中組織模型
manage.py startapp
命令建立包含models.py
檔案的應用程式結構。如果您有許多模型,則在分開的檔案中組織它們可能很有用。
為此,請建立一個models包。刪除models.py並建立一個myapp/models/
資料夾包含__init__.py
檔案和儲存模型的檔案。您必須在__init__.py
檔案中匯入模型。
例如,如果你在models 目錄中有organic.py
和synthetic.py
:
顯式匯入每個模型而不是使用from .models import *
,這樣做的好處是不會使名稱空間混亂,使程式碼更具可讀性以及保持程式碼分析工具更易理解。