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