不少時候,咱們都不是從‘一貧如洗’開始編寫模型的,有時候能夠從第三方庫中繼承,有時候能夠從之前的代碼中繼承,甚至現寫一個模型用於被其它模型繼承。這樣作的好處,我就不贅述了,每一個學習Django的人都很是清楚。python
類同於Python的類繼承,Django也有完善的繼承機制。shell
Django中全部的模型都必須繼承django.db.models.Model
模型,不論是直接繼承也好,仍是間接繼承也罷。數據庫
你惟一須要決定的是,父模型是不是一個獨立自主的,一樣在數據庫中建立數據表的模型,仍是一個只用來保存子模型共有內容,並不實際建立數據表的抽象模型。django
Django有三種繼承的方式:app
Abstract base classes
,將子類共同的數據抽離出來,供子類繼承重用,它不會建立實際的數據表;Multi-table inheritance
,每個模型都有本身的數據庫表;注意!同Python的繼承同樣,Django也是能夠同時繼承兩個以上父類的!ide
只須要在模型的Meta類裏添加abstract=True
元數據項,就能夠將一個模型轉換爲抽象基類。Django不會爲這種類建立實際的數據庫表,它們也沒有管理器,不能被實例化也沒法直接保存,它們就是用來被繼承的。抽象基類徹底就是用來保存子模型們共有的內容部分,達到重用的目的。當它們被繼承時,它們的字段會所有複製到子模型中。看下面的例子:學習
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模型不能當作一個正常的模型使用。代理
若是子類沒有聲明本身的Meta類,那麼它將繼承抽象基類的Meta類。下面的例子則擴展了基類的Meta:rest
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'
這裏有幾點要特別說明:code
abstract=True
這個元數據不會被繼承。也就是說若是想讓一個抽象基類的子模型,一樣成爲一個抽象基類,那你必須顯式的在該子模型的Meta中一樣聲明一個abstract = True
;db_table
,首先是抽象基類自己不會建立數據表,其次它的全部子類也不會按照這個元數據來設置表名。若是在你的抽象基類中存在ForeignKey或者ManyToManyField字段,而且使用了related_name
或者related_query_name
參數,那麼必定要當心了。由於按照默認規則,每個子類都將擁有一樣的字段,這顯然會致使錯誤。爲了解決這個問題,當你在抽象基類中使用related_name
或者related_query_name
參數時,它們二者的值中應該包含%(app_label)s
和%(class)s
部分:
%(class)s
用字段所屬子類的小寫名替換%(app_label)s
用子類所屬app的小寫名替換例如,對於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
對於另一個應用中的rare/models.py
:
from common.models import Base class ChildB(Base): pass
對於上面的繼承關係:
common.ChildA.m2m
字段的reverse name
(反向關係名)應該是common_childa_related
;reverse query name
(反向查詢名)應該是common_childas
。common.ChildB.m2m
字段的反向關係名應該是common_childb_related
;反向查詢名應該是common_childbs
。rare.ChildB.m2m
字段的反向關係名應該是rare_childb_related
;反向查詢名應該是rare_childbs
。固然,若是你不設置related_name
或者related_query_name
參數,這些問題就不存在了。
這種繼承方式下,父類和子類都是獨立自主、功能完整、可正常使用的模型,都有本身的數據庫表,內部隱含了一個一對一的關係。例如:
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)
Restaurant將包含Place的全部字段,而且各有各的數據庫表和字段,好比:
>>> Place.objects.filter(name="Bob's Cafe") >>> Restaurant.objects.filter(name="Bob's Cafe")
若是一個Place對象同時也是一個Restaurant對象,你可使用小寫的子類名,在父類中訪問它,例如:
>>> p = Place.objects.get(id=12) # 若是p也是一個Restaurant對象,那麼下面的調用能夠得到該Restaurant對象。 >>> p.restaurant <Restaurant: ...>
可是,若是這個Place是個純粹的Place對象,並非一個Restaurant對象,那麼上面的調用方式會彈出Restaurant.DoesNotExist
異常。
讓咱們看一組更具體的展現,注意裏面的註釋內容。
>>> from app1.models import Place, Restaurant # 導入兩個模型到shell裏 >>> p1 = Place.objects.create(name='coff',address='address1') >>> p1 # p1是個純Place對象 <Place: Place object> >>> p1.restaurant # p1沒有餐館屬性 Traceback (most recent call last): File "<console>", line 1, in <module> File "C:\Python36\lib\site-packages\django\db\models\fields\related_descriptors.py", line 407, in __get__ self.related.get_accessor_name() django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist: Place has no restaurant. >>> r1 = Restaurant.objects.create(serves_hot_dogs=True,serves_pizza=False) >>> r1 # r1在建立的時候,只賦予了2個字段的值 <Restaurant: Restaurant object> >>> r1.place # 不能這麼調用 Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: 'Restaurant' object has no attribute 'place' >>> r2 = Restaurant.objects.create(serves_hot_dogs=True,serves_pizza=False, name='pizza', address='address2') >>> r2 # r2在建立時,提供了包括Place的字段在內的4個字段 <Restaurant: Restaurant object> >>> r2.place # 能夠看出這麼調用都是非法的,異想天開的 Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: 'Restaurant' object has no attribute 'place' >>> p2 = Place.objects.get(name='pizza') # 經過name,咱們獲取到了一個Place對象 >>> p2.restaurant # 這個P2其實就是前面的r2 <Restaurant: Restaurant object> >>> p2.restaurant.address 'address2' >>> p2.restaurant.serves_hot_dogs True >>> lis = Place.objects.all() >>> lis <QuerySet [<Place: Place object>, <Place: Place object>, <Place: Place object>]> >>> lis.values() <QuerySet [{'id': 1, 'name': 'coff', 'address': 'address1'}, {'id': 2, 'name': '', 'address': ''}, {'id': 3, 'name': 'pizza', 'address': 'address2'}]> >>> lis[2] <Place: Place object> >>> lis[2].serves_hot_dogs Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: 'Place' object has no attribute 'serves_hot_dogs' >>> lis2 = Restaurant.objects.all() >>> lis2 <QuerySet [<Restaurant: Restaurant object>, <Restaurant: Restaurant object>]> >>> lis2.values() <QuerySet [{'id': 2, 'name': '', 'address': '', 'place_ptr_id': 2, 'serves_hot_dogs': True, 'serves_pizza': False}, {'id': 3, 'name': 'pizza', 'address ': 'address2', 'place_ptr_id': 3, 'serves_hot_dogs': True, 'serves_pizza': False}]>
其機制內部隱含的OneToOne字段,形同下面所示:
place_ptr = models.OneToOneField( Place, on_delete=models.CASCADE, parent_link=True, )
能夠經過建立一個OneToOneField字段並設置 parent_link=True
,自定義這個一對一字段。
在多表繼承的狀況下,因爲父類和子類都在數據庫內有物理存在的表,父類的Meta類會對子類形成不肯定的影響,所以,Django在這種狀況下關閉了子類繼承父類的Meta功能。這一點和抽象基類的繼承方式有所不一樣。
可是,還有兩個Meta元數據特殊一點,那就是ordering
和get_latest_by
,這兩個參數是會被繼承的。所以,若是在多表繼承中,你不想讓你的子類繼承父類的上面兩種參數,就必須在子類中顯示的指出或重寫。以下:
class ChildModel(ParentModel): # ... class Meta: # 移除父類對子類的排序影響 ordering = []
由於多表繼承使用了一個隱含的OneToOneField來連接子類與父類,因此象上例那樣,你能夠從父類訪問子類。可是這個OnetoOneField字段默認的related_name
值與ForeignKey和 ManyToManyField默認的反向名稱相同。若是你與父類或另外一個子類作多對一或是多對多關係,你就必須在每一個多對一和多對多字段上強制指定related_name
。若是你沒這麼作,Django就會在你運行或驗證(validation)時拋出異常。
仍以上面Place類爲例,咱們建立一個帶有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'.
解決方法是:向customers字段中添加related_name
參數.
customers = models.ManyToManyField(Place, related_name='provider')。
使用多表繼承時,父類的每一個子類都會建立一張新數據表,一般狀況下,這是咱們想要的操做,由於子類須要一個空間來存儲不包含在父類中的數據。但有時,你可能只想更改模型在Python層面的行爲,好比更改默認的manager管理器,或者添加一個新方法。
代理模型就是爲此而生的。你能夠建立、刪除、更新代理模型的實例,而且全部的數據均可以像使用原始模型(非代理類模型)同樣被保存。不一樣之處在於你能夠在代理模型中改變默認的排序方式和默認的manager管理器等等,而不會對原始模型產生影響。
聲明一個代理模型只須要將Meta中proxy的值設爲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>
下面的例子經過代理進行排序,但父類卻不排序:
class OrderedPerson(Person): class Meta: # 如今,普通的Person查詢是無序的,而OrderedPerson查詢會按照`last_name`排序。 ordering = ["last_name"] proxy = True
一些約束:
代理模型的管理器
如不指定,則繼承父類的管理器。若是你本身定義了管理器,那它就會成爲默認管理器,可是父類的管理器依然有效。以下例子:
from django.db import models class NewManager(models.Manager): # ... pass class MyPerson(Person): objects = NewManager() class Meta: proxy = True
若是你想要向代理中添加新的管理器,而不是替換現有的默認管理器,你能夠建立一個含有新的管理器的基類,並在繼承時把他放在主基類的後面:
# 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
注意,多重繼承和多表繼承是兩碼事,兩個概念。
Django的模型體系支持多重繼承,就像Python同樣。若是多個父類都含有Meta類,則只有第一個父類的會被使用,剩下的會忽略掉。
通常狀況,能不要多重繼承就不要,儘可能讓繼承關係簡單和直接,避免沒必要要的混亂和複雜。
請注意,繼承同時含有相同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字段,並在直接的父類裏經過一個OneToOne字段保持與祖先的關係,以下所示:
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,若是繼承的是一個非抽象基類,那麼子類與父類之間不能夠有相同的字段名!
好比下面是不行的!
class A(models.Model): name = models.CharField(max_length=30) class B(A): name = models.CharField(max_length=30)
若是你執行python manage.py makemigrations
會彈出下面的錯誤:
django.core.exceptions.FieldError: Local field 'name' in class 'B' clashes with field of the same name from base class 'A'.
可是!若是父類是個抽象基類就沒有問題了(1.10版新增特性),以下:
class A(models.Model): name = models.CharField(max_length=30) class Meta: abstract = True class B(A): name = models.CharField(max_length=30)