Django中的Model繼承

Django 中的 model 繼承和 Python 中的類繼承很是類似,只不過你要選擇具體的實現方式:讓父 model 擁有獨立的數據庫;仍是讓父 model 只包含基本的公共信息,而這些信息只能由子 model 呈現。python

Django中有三種繼承關係:sql

1.一般,你只是想用父 model 來保存那些你不想在子 model 中重複錄入的信息。父類是不使用的也就是不生成單獨的數據表,這種狀況下使用抽象基類繼承 Abstract base classes。數據庫

2.若是你想從現有的Model繼承並讓每一個Model都有本身的數據表,那麼使用多重表繼承Multi-table inheritance。django

3.最後,若是你只想在 model 中修改 Python-level 級的行爲,而不涉及字段改變。 代理 model (Proxy models) 適用於這種場合。app

 Abstract base classes

若是你想把某些公共信息添加到不少 model 中,抽象基類就顯得很是有用。你編寫完基類以後,在 Meta 內嵌類中設置 abstract=True ,該類就不能建立任何數據表。然而若是將它作爲其餘 model 的基類,那麼該類的字段就會被添加到子類中。抽象基類和子類若是含有同名字段,就會致使錯誤(Django 將拋出異常)。ide

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)

sqlall結果:this

CREATE TABLE "myapp_student" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(100) NOT NULL,
    "age" integer unsigned NOT NULL,
    "home_group" varchar(5) NOT NULL
)

只爲Student model 生成了數據表,而CommonInfo不能作爲普通的 Django model 使用,由於它是一個抽象基類。他即不生成數據表,也沒有 manager ,更不能直接被實例化和保存。設計

對不少應用來講,這種繼承方式正是你想要的。它提供一種在 Python 語言層級上提取公共信息的方式,但在數據庫層級上,每一個子類仍然只建立一個數據表,在JPA中稱做TABLE_PER_CLASS。這種方式下,每張表都包含具體類和繼承樹上全部父類的字段。由於多個表中有重複字段,從整個繼承樹上來講,字段是冗餘的。代理

Meta繼承rest

建立抽象基類的時候,Django 會將你在基類中所聲明的有效的 Meta 內嵌類作爲一個屬性。若是子類沒有聲明它本身的 Meta 內嵌類,它就會繼承父類的 Meta 。子類的 Meta 也能夠直接繼承父類的 Meta 內嵌類,對其進行擴展。例如:

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

sqlall結果:

CREATE TABLE "student_info" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(100) NOT NULL,
    "age" integer unsigned NOT NULL,
    "home_group" varchar(5) NOT NULL
)

按照咱們指定的名稱student_info生成了table。

繼承時,Django 會對基類的 Meta 內嵌類作一個調整:在安裝 Meta 屬性以前,Django 會設置 abstract=False。 這意味着抽象基類的子類不會自動變成抽象類。固然,你可讓一個抽象類繼承另外一個抽象基類,不過每次都要顯式地設置 abstract=True 。

對於抽象基類而言,有些屬性放在 Meta 內嵌類裏面是沒有意義的。例如,包含 db_table 將意味着全部的子類(是指那些沒有指定本身的 Meta 內嵌類的子類)都使用同一張數據表,通常來講,這並非咱們想要的。

當心使用 related_name (Be careful with related_name)

若是你在 ForeignKey 或 ManyToManyField 字段上使用 related_name 屬性,你必須老是爲該字段指定一個惟一的反向名稱。但在抽象基類上這樣作就會引起一個很嚴重的問題。由於 Django 會將基類字段添加到每一個子類當中,而每一個子類的字段屬性值都徹底相同 (這裏面就包括 related_name)。注:這樣使用 ForeignKey 或 ManyToManyField 反向指定時就沒法肯定是指向哪一個子類了。

當你在(且僅在)抽象基類中使用 related_name 時,若是想繞過這個問題,就要在屬性值中包含  '%(app_label)s' 和 '%(class)s'字符串。

1.'%(class)s'會被子類的名字取代。

2.'%(app_label)s'會被子類所在的app的名字所取代。

舉例,在app common中,common/models.py:

class Base(models.Model):
    m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass

在另一個app中,rare/models.py:

class ChildB(Base):
    pass

那麼common.ChildA.m2m字段的反向名稱爲common_childa_related, common.ChildB.m2m字段的反向名稱爲common_childb_related, rare app中rare.ChildB.m2m字段的反向名稱爲rare_childb_related.

若是你沒有在抽象基類中爲某個關聯字段定義 related_name 屬性,那麼默認的反向名稱就是子類名稱加上 '_set',它可否正常工做取決於你是否在子類中定義了同名字段。例如,在上面的代碼中,若是去掉 related_name 屬性,在 ChildA 中,m2m 字段的反向名稱就是 childa_set;而 ChildB 的 m2m 字段的反向名稱就是 childb_set 。

多表繼承(Multi-table inheritance)

這是 Django 支持的第二種繼承方式。使用這種繼承方式時,同一層級下的每一個子 model 都是一個真正意義上完整的 model 。每一個子 model 都有專屬的數據表,均可以查詢和建立數據表。繼承關係在子 model 和它的每一個父類之間都添加一個連接 (經過一個自動建立的 OneToOneField 來實現)。 例如:

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField()
    serves_pizza = models.BooleanField()

sqlall:

BEGIN;
CREATE TABLE "myapp_place" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(50) NOT NULL,
    "address" varchar(80) NOT NULL
)
;
CREATE TABLE "myapp_restaurant" (
    "place_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "myapp_place" ("id"),

    "serves_hot_dogs" bool NOT NULL,
    "serves_pizza" bool NOT NULL
)
;

COMMIT;

父類和子類都生成了單獨的數據表,Restaurant中存儲了Place的id,也就是經過OneToOneField連接在一塊兒。繼承關係經過表的JOIN操做來表示。在JPA中稱做JOINED。這種方式下,每一個表只包含類中定義的字段,不存在字段冗餘,可是要同時操做子類和全部父類所對應的表。

Place 裏面的全部字段在 Restaurant 中也是有效的,只不過數據保存在另一張數據表當中。因此下面兩個語句都是能夠運行的:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

若是你有一個 Place,那麼它同時也是一個 Restaurant, 那麼你可使用子 model 的小寫形式從 Place 對象中得到與其對應的 Restaurant 對象:

>>> p = Place.objects.filter(name="Bob's Cafe")
# If Bob's Cafe is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

可是,若是上例中的 p 並非 Restaurant (好比它僅僅只是 Place 對象,或者它是其餘類的父類),那麼在引用 p.restaurant 就會拋開Restaurant.DoesNotExist 異常:

>>> from myapp.models import Place,Restaurant
>>> p=Place.objects.create(name='Place',address='Place')
>>> p.restaurant
DoesNotExist: Place has no restaurant.

也就是說,建立Place實例的同時不會建立Restaurant,可是建立Restaurant實例的同時會建立Place實例:

>>>Restaurant.objects.create(name='M',address='M',serves_hot_dogs=True,serves_pizza=True)
<Restaurant: Restaurant object>
>>> Place.objects.get(name='M')
<Place: Place object>

多表繼承中的Meta (Meta and multi-table inheritance)

在多表繼承中,子類繼承父類的 Meta 內嵌類是沒什麼意見的。全部的 Meta 選項已經對父類起了做用,再次使用只會起副作用。(這與使用抽象基類的狀況正好相反,由於抽象基類並無屬於它本身的內容)

因此子 model 並不能訪問它父類的 Meta 內嵌類。可是在某些受限的狀況下,子類能夠從父類繼承某些 Meta :若是子類沒有指定 django.db.models.Options.ordering 屬性或 django.db.models.Options.get_latest_by 屬性,它就會從父類中繼承這些屬性。

若是父類有了排序設置,而你並不想讓子類有任何排序設置,你就能夠顯式地禁用排序:

class ChildModel(ParentModel):
    # ...
    class Meta:
        # Remove parent's ordering effect
        ordering = []

繼承與反向關聯(Inheritance and reverse relations)

由於多表繼承使用了一個隱含的 OneToOneField 來連接子類與父類,因此象上例那樣,你能夠用父類來指代子類。可是這個 OnetoOneField 字段默認的 related_name 值與 django.db.models.fields.ForeignKey 和 django.db.models.fields.ManyToManyField 默認的反向名稱相同。若是你與其餘 model 的子類作多對一或是多對多關係,你就必須在每一個多對一和多對多字段上強制指定 related_name 。若是你沒這麼作,Django 就會在你運行 驗證(validate) 或 同步數據庫(syncdb) 時拋出異常。

例如,仍以上面 Place 類爲例,咱們建立一個帶有 ManyToManyField 字段的子類:

class Supplier(Place):
    # Must specify related_name on all relations.
    customers = models.ManyToManyField(Restaurant, related_name='provider')

指定連接父類的字段(Specifying the parent link field)

以前咱們提到,Django 會自動建立一個 OneToOneField 字段將子類連接至非抽象的父 model 。若是你想指定連接父類的屬性名稱,你能夠建立你本身的 OneToOneField 字段並設置 parent_link=True ,從而使用該字段連接父類。

代理model (Proxy models)

使用 多表繼承(multi-table inheritance) 時,model 的每一個子類都會建立一張新數據表,一般狀況下,這正是咱們想要的操做。這是由於子類須要一個空間來存儲不包含在基類中的字段數據。但有時,你可能只想更改 model 在 Python 層的行爲實現。好比:更改默認的 manager ,或是添加一個新方法。

而這,正是代理 model 繼承方式要作的:爲原始 model 建立一個代理(proxy)。你能夠建立,刪除,更新代理 model 的實例,並且全部的數據均可以象使用原始 model 同樣被保存。不一樣之處在於:你能夠在代理 model 中改變默認的排序設置和默認的 manager ,更不會對原始 model 產生影響。

聲明代理 model 和聲明普通 model 沒有什麼不一樣。設置Meta 內置類中 proxy 的值爲 True,就完成了對代理 model 的聲明。

舉個例子,假設你想給 Django 自帶的標準 User model (它被用在你的模板中)添加一個方法:

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

sqlall:

CREATE TABLE "myapp_person" (
    "id" integer NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
)
;

MyPerson 類和它的父類 Person操做同一個數據表。特別的是,Person 的任何實例也能夠經過 MyPerson 訪問,反之亦然:

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

你也可使用代理 model 給 model 定義不一樣的默認排序設置。Django 自帶的 User model 沒有定義排序設置(這是故意爲之,是由於排序開銷極大,咱們不想在獲取用戶時浪費額外資源)。你能夠利用代理對 username 屬性進行排序,這很簡單:

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

普通的 User 查詢,其結果是無序的;而 OrderedUser 查詢的結果是按 username 排序。

查詢集只返回請求時所使用的 model (Querysets still return the model that was requested)

不管你什麼時候查詢Person 對象,Django 都不會返回 MyPerson 對象。針對 Person 對象的查詢集只返回 Person 對象。代理對象的精要就在於依賴原始 User 的代碼僅對它本身有效,而你本身的代碼就使用你擴展的內容。無論你怎麼改動,都不會在查詢 Person 時獲得 MyPerson。

基類的限制(Base class restrictions)

代理 model 必須繼承自一個非抽象基類。你不能繼承自多個非抽象基類,這是由於一個代理 model 不能鏈接不一樣的數據表。代理 model 也能夠繼承任意多個抽象基類,但前提是它們沒有定義任何 model 字段。

代理 model 從非抽象基類中繼承那些未在代理 model 定義的 Meta 選項。

代理 model 的 manager (Proxy model managers)

若是你沒有在代理 model 中定義任何 manager ,代理 model 就會從父類中繼承 manager 。若是你在代理 model 中定義了一個 manager ,它就會變成默認的 manager ,不過定義在父類中的 manager 還是有效的。

繼續上面的例子,你能夠改變默認 manager,例如:

class NewManager(models.Manager):
    # ...
    pass

class MyPerson(Person):
    objects = NewManager()

    class Meta:
        proxy = True

若是你想給代理添加一個新的 manager ,卻不想替換已有的默認 manager ,那麼你能夠參考 自定義 manager (custom manager) 中提到的方法:建立一個包含新 manager 的基類,而後放在主基類後面繼承:

class ExtraManagers(models.Model):
    secondary = NewManager()

    class Meta:
        abstract = True

class MyPerson(Person, ExtraManagers):
    class Meta:
        proxy = True

你可能不須要常常這樣作,但這樣作是可行的。

代理 model 與非託管 model 之間的差別(Differences between proxy inheritance and unmanaged models)

代理 model 繼承看上去和使用 Meta 內嵌類中的 managed 屬性的非託管 model 很是類似。但二者並不相同,你應當考慮選用哪一種方案。

一個不一樣之處是你能夠在 Meta.managed=False 的 model 中定義字段(事實上,是必須指定,除非你真的想獲得一個空 model )。在建立非託管 model 時要謹慎設置 Meta.db_table ,這是由於建立的非託管 model 映射某個已存在的 model ,而且有本身的方法。所以,若是你要保證這兩個 model 同步並對程序進行改動,那麼就會變得繁冗而脆弱。

另外一個不一樣之處是二者對 manager 的處理方式不一樣。這對於代理 model 很是重要。代理 model 要與它所代理的 model 行爲類似,因此代理 model 要繼承父 model 的 managers ,包括它的默認 manager 。但在普通的多表繼承中,子類不能繼承父類的 manager ,這是由於在處理非基類字段時,父類的 manager 未必適用。

咱們實現了這兩種特性(Meta.proxy和Meta.unmanaged)以後,曾嘗試把二者結合到一塊兒。結果證實,宏觀的繼承關係和微觀的 manager 揉在一塊兒,不只致使 API 複雜難用,並且還難以理解。因爲任何場合下均可能須要這兩個選項,因此目前兩者還是各自獨立使用的。

因此,通常規則是:

1.若是你要鏡像一個已有的 model 或數據表,且不想涉及全部的原始數據表的列,那就令 Meta.managed=False。一般狀況下,對數據庫視圖建立 model 或是數據表不須要由 Django 控制時,就使用這個選項。
2.若是你想對 model 作 Python 層級的改動,又想保留字段不變,那就令 Meta.proxy=True。所以在數據保存時,代理 model 至關於徹底複製了原始 model 的存儲結構。

多重繼承(Multiple inheritance)

和 Python 同樣,Django 的 model 也能夠作多重繼承。這裏要記住 Python 的名稱解析規則。若是某個特定名稱 (例如,Meta) 出如今第一個基類當中,那麼子類就會使用第一個基類的該特定名稱。例如,若是多重父類都包含 Meta 內嵌類,只有第一個基類的 Meta 纔會被使用,其餘的都被會忽略。

通常來講,你不必使用多重繼承。

不容許"隱藏"字段(Field name "hiding" is not permitted)

普通的 Python 類繼承容許子類覆蓋父類的任何屬性。但在 Django 中,重寫 Field 實例是不容許的(至少如今還不行)。若是基類中有一個 author 字段,你就不能在子類中建立任何名爲 author 的字段。

重寫父類的字段會致使不少麻煩,好比:初始化實例(指定在 Model.__init__ 中被實例化的字段) 和序列化。而普通的 Python 類繼承機制並不能處理好這些特性。因此 Django 的繼承機制被設計成與 Python 有所不一樣,這樣作並非隨意而爲的。

這些限制僅僅針對作爲屬性使用的 Field 實例,並非針對 Python 屬性,Python 屬性還是能夠被重寫的。在 Python 看來,上面的限制僅僅針對字段實例的名稱:若是你手動指定了數據庫的列名稱,那麼在多重繼承中,你就能夠在子類和某個父類當中使用同一個列名稱。(由於它們使用的是兩個不一樣數據表的字段)。

若是你在任何一個父類中重寫了某個 model 字段,Django 都會拋出 FieldError 異常。

相關文章
相關標籤/搜索