W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
模型繼承在 Django 中與普通類繼承在 Python 中的工作方式幾乎完全相同,但也仍應(yīng)遵循本頁開頭的內(nèi)容。這意味著其基類應(yīng)該繼承自 ??django.db.models.Model?
?。你只需要決定父類模型是否需要擁有它們的權(quán)利(擁有它們的數(shù)據(jù)表),或者父類僅作為承載僅子類中可見的公共信息的載體。Django 有三種可用的繼承風格。
抽象基類在你要將公共信息放入很多模型時會很有用。編寫你的基類,并在 ??Meta
??類中填入? ?abstract=True
??。該模型將不會創(chuàng)建任何數(shù)據(jù)表。當其用作其它模型類的基類時,它的字段會自動添加至子類。
一個例子:
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
??模型擁有3個字段: ??name
??, ??age
??和 ??home_group
??。 ??CommonInfo
??模型不能用作普通的 Django 模型,因為它是一個抽象基類。它不會生成數(shù)據(jù)表,也沒有管理器,也不能被實例化和保存。從抽象基類繼承來的字段可被其它字段或值重寫,或用 ??None
??刪除。對很多用戶來說,這種繼承可能就是你想要的。它提供了一種在 Python 級抽出公共信息的方法,但仍會在子類模型中創(chuàng)建數(shù)據(jù)表。
當一個抽象基類被建立,Django 將所有你在基類中申明的 ??Meta
??內(nèi)部類以屬性的形式提供。若子類未定義自己的 ??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
??做了一個調(diào)整——設(shè)置 ??abstract=False
??。這意味著抽象基類的子類不會自動地變成抽象類。為了繼承一個抽象基類創(chuàng)建另一個抽象基類,你需要在子類上顯式地設(shè)置 ??abstract=True
??。抽象基類的某些 ??Meta
??屬性對子類是沒用的。比如,包含 ??db_table
??意味著所有的子類(你并未在子類中指定它們的 ??Meta
??)會使用同一張數(shù)據(jù)表,這肯定不是你想要的。由于Python繼承的工作方式,如果子類從多個抽象基類繼承,則默認情況下僅繼承第一個列出的類的 ??Meta
??選項。為了從多個抽象類中繼承 ??Meta
??選項,必須顯式地聲明 ??Meta
??繼承。例如:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
ordering = ['name']
class Unmanaged(models.Model):
class Meta:
abstract = True
managed = False
class Student(CommonInfo, Unmanaged):
home_group = models.CharField(max_length=5)
class Meta(CommonInfo.Meta, Unmanaged.Meta):
pass
若你在 外鍵 或 多對多字段 使用了 ??related_name
??或 ??related_query_name
??,你必須為該字段提供一個 獨一無二 的反向名字和查詢名字。這在抽象基類中一般會引發(fā)問題,因為基類中的字段都被子類繼承,且保持了同樣的值(包括 ??related_name
??和 ??related_query_name
??)。為了解決此問題,當你在抽象基類中(也只能是在抽象基類中)使用 ??related_name
??和 ??related_query_name
??,部分值需要包含 ??'%(app_label)s'?
? 和 ??'%(class)s'
??。
?'%(class)s'
??用使用了該字段的子類的小寫類名替換。?'%(app_label)s'
?? 用小寫的包含子類的應(yīng)用名替換。每個安裝的應(yīng)用名必須是唯一的,應(yīng)用內(nèi)的每個模型類名也必須是唯一的。因此,替換后的名字也是唯一的。舉個例子,有個應(yīng)用? ?common/models.py
??:
from django.db import models
class Base(models.Model):
m2m = models.ManyToManyField(
OtherModel,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",
)
class Meta:
abstract = True
class ChildA(Base):
pass
class ChildB(Base):
pass
附帶另一個應(yīng)用 ??rare/models.py?
?:
from common.models import Base
class ChildB(Base):
pass
??common.ChildA.m2m
?? 字段的反轉(zhuǎn)名是 ??common_childa_related
??,反轉(zhuǎn)查詢名是 ??common_childas
??。 ??common.ChildB.m2m
?? 字段的反轉(zhuǎn)名是 ??common_childb_related
??, 反轉(zhuǎn)查詢名是 ??common_childbs
??。 ??rare.ChildB.m2m
?? 字段的反轉(zhuǎn)名是 ??rare_childb_related
??,反轉(zhuǎn)查詢名是 ??rare_childbs
??。這決定于你如何使用??'%(class)s'?
? 和??'%(app_label)s'?
?構(gòu)建關(guān)聯(lián)名字和關(guān)聯(lián)查詢名。但是,若你忘了使用它們,Django 會在你執(zhí)行系統(tǒng)檢查(或運行 ??migrate
??)時拋出錯誤。如果你未指定抽象基類中的 ??related_name
??屬性,默認的反轉(zhuǎn)名會是子類名,后接 ??'_set'
?? 。這名字看起來就像你在子類中定義的一樣。比如,在上述代碼中,若省略了 ??related_name
??屬性, ??ChildA
??的 ??m2m
??字段的反轉(zhuǎn)名會是 ??childa_set
??, ??ChildB
??的是 ??childb_set
??。
Django 支持的第二種模型繼承方式是層次結(jié)構(gòu)中的每個模型都是一個單獨的模型。每個模型都指向分離的數(shù)據(jù)表,且可被獨立查詢和創(chuàng)建。繼承關(guān)系介紹了子類和父類之間的連接(通過一個自動創(chuàng)建的 ??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
??中可用,雖然數(shù)據(jù)分別存在不同的表中。所有,以下操作均可:
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
若有一個 ??Place
??同時也是 ??Restaurant
??,你可以通過小寫的模型名將 ??Place
??對象轉(zhuǎn)為 ??Restaurant
??對象。
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
然而,若上述例子中的 ??p
?? 不是 一個 ??Restaurant
??(它僅是個 ??Place
??對象或是其它類的父類),指向 ??p.restaurant
?? 會拋出一個 ??Restaurant.DoesNotExist?
? 異常。??Restaurant
??中自動創(chuàng)建的連接至 ??Place
??的 ??OneToOneField
??看起來像這樣:
place_ptr = models.OneToOneField(
Place, on_delete=models.CASCADE,
parent_link=True,
primary_key=True,
)
你可以在 ??Restaurant
??中重寫該字段,通過申明你自己的 ??OneToOneField
??,并設(shè)置 ??parent_link=True
??。
多表繼承情況下,子類不會繼承父類的 ??Meta
??。所以的 ??Meta
??類選項已被應(yīng)用至父類,在子類中再次應(yīng)用會導致行為沖突(與抽象基類中應(yīng)用場景對比,這種情況下,基類并不存在)。故子類模型無法訪問父類的 ??Meta
??類。不過,有限的幾種情況下:若子類未指定 ??ordering
??屬性或 ??get_latest_by
??屬性,子類會從父類繼承這些。如果父類有排序,而你并不期望子類有排序,你可以顯示的禁止它:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
由于多表繼承使用隱式的 ??OneToOneField
??連接子類和父類,所以直接從父類訪問子類是可能的,就像上述例子展示的那樣。然而,使用的名字是 ??ForeignKey
??和 ??ManyToManyField
??關(guān)系的默認值。如果你在繼承父類模型的子類中添加了這些關(guān)聯(lián),你 必須 指定 ??related_name
??屬性。假如你忘了,Django 會拋出一個合法性錯誤。比如,讓我們用上面的 ??Place
??類創(chuàng)建另一個子類,包含一個 ??ManyToManyField
??:
class Supplier(Place):
customers = models.ManyToManyField(Place)
這會導致以下錯誤:
Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.
HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.
將 ??related_name
??像下面這樣加至 ??customers
??字段能解決此錯誤:?models.ManyToManyField(Place, related_name='provider')?
?。
如上所述,Django 會自動創(chuàng)建一個 ??OneToOneField
??,將子類連接回非抽象的父類。如果你想修改連接回父類的屬性名,你可以自己創(chuàng)建 ??OneToOneField
??,并設(shè)置 ??parent_link=True?
?,表明該屬性用于連接回父類。
使用 多表繼承 時,每個子類模型都會創(chuàng)建一張新表。這一般是期望的行為,因為子類需要一個地方存儲基類中不存在的額外數(shù)據(jù)字段。不過,有時候你只想修改模型的 Python 級行為——可能是修改默認管理器,或添加一個方法。這是代理模型繼承的目的:為原模型創(chuàng)建一個 代理。你可以創(chuàng)建,刪除和更新代理模型的實例,所以的數(shù)據(jù)都會存儲的像你使用原模型(未代理的)一樣。不同點是你可以修改代理默認的模型排序和默認管理器,而不需要修改原模型。代理模型就像普通模型一樣申明。你需要告訴 Django 這是一個代理模型,通過將 ??Meta
??類的 ??proxy
??屬性設(shè)置為 ??True
??。例如,假設(shè)你想為 ??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
?? 操作同一張數(shù)據(jù)表。特別提醒, ??Person
??的實例能通過 ??MyPerson
??訪問,反之亦然。
>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>
你也可以用代理模型定義模型的另一種不同的默認排序方法。你也許不期望總對 ??“Persion”
?? 進行排序,但是在使用代理時,總是依據(jù) ??“l(fā)ast_name”?
? 屬性進行排序:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
現(xiàn)在,普通的 ??Person
??查詢結(jié)果不會被排序,但 ??OrderdPerson
??查詢結(jié)果會按 ??last_name
??排序。代理模型繼承?“?Meta?”
?屬性 和普通模型一樣。
當你用 ??Person
??對象查詢時,Django 永遠不會返回 ??MyPerson
??對象。??Person
??對象的查詢結(jié)果集總是返回對應(yīng)類型。代理對象存在的全部意義是幫你復用原 ??Person
??提供的代碼和自定義的功能代碼(并未依賴其它代碼)。不存在什么方法能在你創(chuàng)建完代理后,幫你替換所有 ??Person
??或其它模型。
一個代理模型必須繼承自一個非抽象模型類。你不能繼承多個非抽象模型類,因為代理模型無法在不同數(shù)據(jù)表之間提供任何行間連接。一個代理模型可以繼承任意數(shù)量的抽象模型類,假如他們 沒有 定義任何的模型字段。一個代理模型也可以繼承任意數(shù)量的代理模型,只需他們共享同一個非抽象父類。
若你未在代理模型中指定模型管理器,它會從父類模型中繼承。如果你在代理模型中指定了管理器,它會成為默認管理器,但父類中定義的管理器仍是可用的。隨著上面的例子一路走下來,你可以在查詢 ??Person
??模型時這樣修改默認管理器:
from django.db import models
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
若你在不替換已存在的默認管理器的情況下,為代理添加新管理器,你可以創(chuàng)建一個包含新管理器的基類,在繼承列表中,主類后追加這個基類:
# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
secondary = NewManager()
class Meta:
abstract = True
class MyPerson(Person, ExtraManagers):
class Meta:
proxy = True
代理模型繼承可能看起來和創(chuàng)建未托管的模型很類似,通過在模型的 ??Meta
??類中定義 ??managed
??屬性。通過小心地配置 ??Meta.db_table
??,你將創(chuàng)建一個未托管的模型,該模型將對現(xiàn)有模型進行陰影處理,并添加一些 Python 方法。然而,這會是個經(jīng)常重復的且容易出錯的過程,因為你要在做任何修改時保持兩個副本的同步。另一方面,代理模型意在表現(xiàn)的和所代理的模型一樣。它們總是與父模型保持一致,因為它們直接繼承其字段和管理器。
通用性規(guī)則:
Meta.managed=False?
?。這個選項在模型化未受 Django 控制的數(shù)據(jù)庫視圖和表格時很有用。Meta.proxy=True
??。這個配置使得代理模型在保存數(shù)據(jù)時,確保數(shù)據(jù)結(jié)構(gòu)和原模型的完全一樣。和 Python 中的繼承一樣,Django 模型也能繼承自多個父類模型。請記住,Python 的命名規(guī)則這里也有效。第一個出現(xiàn)的基類(比如 ??Meta
??)就是會被使用的那個;舉個例子,如果存在多個父類包含 ??Meta
??,只有第一個會被使用,其它的都會被忽略。一般來說,你并不會同時繼承多個父類。常見的應(yīng)用場景是 “混合” 類:為每個繼承此類的添加額外的字段或方法。試著保持你的繼承層級盡可能的簡單和直接,這樣未來你就不用為了確認某段信息是哪來的而拔你為數(shù)不多的頭發(fā)了。注意,繼承自多個包含 ?id?主鍵的字段會拋出錯誤。正確的使用多繼承,你可以在基類中顯示使用 ??AutoField
??:
class Article(models.Model):
article_id = models.AutoField(primary_key=True)
...
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...
class BookReview(Book, Article):
pass
或者在公共祖先中存儲 ??AutoField
??。這會要求為每個父類模型和公共祖先使用顯式的 ??OneToOneField
??,避免與子類自動生成或繼承的字段發(fā)生沖突:
class Piece(models.Model):
pass
class Article(Piece):
article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
class Book(Piece):
book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
class BookReview(Book, Article):
pass
在正常的 Python 類繼承中,允許子類覆蓋父類的任何屬性。在 Django 中,模型字段通常不允許這樣做。如果一個非抽象模型基類有一個名為 ??author
??的字段,你就不能在繼承自該基類的任何類中,創(chuàng)建另一個名為 ??author
??的模型字段或?qū)傩?。這個限制并不適用于從抽象模型繼承的模型字段。這些字段可以用另一個字段或值覆蓋,或者通過設(shè)置 ??field_name = None
?? 來刪除。
模型管理器是從抽象基類中繼承的。重寫一個被繼承的 ??Manager
??所引用的繼承字段,可能會導致微妙的錯誤。
某些字段在模型內(nèi)定義了額外的屬性,例如 ??ForeignKey
??定義了一個額外的屬性 ??_id
?? 附加在字段名上,類似的還有外鍵上的 ??related_name
??和 ??related_query_name
??。這些額外的屬性不能被覆蓋,除非定義它的字段被改變或刪除,使它不再定義額外的屬性。
重寫父模型中的字段會導致一些困難,比如初始化新實例(在 ??Model.__init__?
? 中指定哪個字段被初始化)和序列化。這些都是普通的 Python 類繼承所不需要處理的功能,所以 Django 模型繼承和 Python 類繼承之間的區(qū)別并不是任意的。這些限制只針對那些是 ??Field
??實例的屬性。普通的 Python 屬性可被隨便重寫。它還對 Python 能識別的屬性生效:如果你同時在子類和多表繼承的祖先類中指定了數(shù)據(jù)表的列名(它們是兩張不同的數(shù)據(jù)表中的列)。若你在祖先模型中重寫了任何模型字段,Django 會拋出一個 ??FieldError
??。
請注意,由于在類定義期間解析字段的方式,從多個抽象父模型繼承的模型字段以嚴格的深度優(yōu)先順序解析。這與標準 Python MRO 形成對比,后者在菱形繼承的情況下以廣度優(yōu)先解決。這種差異只影響復雜的模型層次結(jié)構(gòu),(根據(jù)上面的建議)你應(yīng)該盡量避免。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: