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
若是你想把某些公共信息添加到不少 model 中,抽象基类就显得很是有用。你编写完基类以后,在 Meta 内嵌类中设置 abstract=True ,该类就不能建立任何数据表。然而若是将它作为其余 model 的基类,那么该类的字段就会被添加到子类中。抽象基类和子类若是含有同名字段,就会致使错误(Django 将抛出异常)。ide
1
2
3
4
5
6
7
8
9
|
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
1
2
3
4
5
6
|
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 ,更不能直接被实例化和保存。spa
对不少应用来讲,这种继承方式正是你想要的。它提供一种在 Python 语言层级上提取公共信息的方式,但在数据库层级上,每一个子类仍然只建立一个数据表,在JPA中称做TABLE_PER_CLASS。这种方式下,每张表都包含具体类和继承树上全部父类的字段。由于多个表中有重复字段,从整个继承树上来讲,字段是冗余的。设计
Meta继承代理
建立抽象基类的时候,Django 会将你在基类中所声明的有效的 Meta 内嵌类作为一个属性。若是子类没有声明它本身的 Meta 内嵌类,它就会继承父类的 Meta 。子类的 Meta 也能够直接继承父类的 Meta 内嵌类,对其进行扩展。例如:
1
2
3
4
5
6
7
8
9
10
11
|
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结果:
1
2
3
4
5
6
|
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:
1
2
3
4
5
6
7
8
9
10
11
|
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:
1
2
|
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 。
这是 Django 支持的第二种继承方式。使用这种继承方式时,同一层级下的每一个子 model 都是一个真正意义上完整的 model 。每一个子 model 都有专属的数据表,均可以查询和建立数据表。继承关系在子 model 和它的每一个父类之间都添加一个连接 (经过一个自动建立的 OneToOneField 来实现)。 例如:
1
2
3
4
5
6
7
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 中也是有效的,只不过数据保存在另一张数据表当中。因此下面两个语句都是能够运行的:
1
2
|
>>> Place.objects.
filter
(name
=
"Bob's Cafe"
)
>>> Restaurant.objects.
filter
(name
=
"Bob's Cafe"
)
|
若是你有一个 Place,那么它同时也是一个 Restaurant, 那么你可使用子 model 的小写形式从 Place 对象中得到与其对应的 Restaurant 对象:
1
2
3
4
|
>>> 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 异常:
1
2
3
4
|
>>>
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实例:
1
2
3
4
|
>>>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 属性,它就会从父类中继承这些属性。
若是父类有了排序设置,而你并不想让子类有任何排序设置,你就能够显式地禁用排序:
1
2
3
4
5
|
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 字段的子类:
1
2
3
|
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 ,从而使用该字段连接父类。
使用 多表继承(multi-table inheritance) 时,model 的每一个子类都会建立一张新数据表,一般状况下,这正是咱们想要的操做。这是由于子类须要一个空间来存储不包含在基类中的字段数据。但有时,你可能只想更改 model 在 Python 层的行为实现。好比:更改默认的 manager ,或是添加一个新方法。
而这,正是代理 model 继承方式要作的:为原始 model 建立一个代理(proxy)。你能够建立,删除,更新代理 model 的实例,并且全部的数据均可以象使用原始 model 同样被保存。不一样之处在于:你能够在代理 model 中改变默认的排序设置和默认的 manager ,更不会对原始 model 产生影响。
声明代理 model 和声明普通 model 没有什么不一样。设置Meta 内置类中 proxy 的值为 True,就完成了对代理 model 的声明。
举个例子,假设你想给 Django 自带的标准 User model (它被用在你的模板中)添加一个方法:
1
2
3
4
5
6
7
8
9
10
11
|
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:
1
2
3
4
5
6
|
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 访问,反之亦然:
1
2
3
|
>>> p
=
Person.objects.create(first_name
=
"foobar"
)
>>> MyPerson.objects.get(first_name
=
"foobar"
)
<MyPerson: foobar>
|
你也可使用代理 model 给 model 定义不一样的默认排序设置。Django 自带的 User model 没有定义排序设置(这是故意为之,是由于排序开销极大,咱们不想在获取用户时浪费额外资源)。你能够利用代理对 username 属性进行排序,这很简单:
1
2
3
4
|
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,例如:
1
2
3
4
5
6
7
8
9
|
class
NewManager(models.Manager):
# ...
pass
class
MyPerson(Person):
objects
=
NewManager()
class
Meta:
proxy
=
True
|
若是你想给代理添加一个新的 manager ,却不想替换已有的默认 manager ,那么你能够参考 自定义 manager (custom manager) 中提到的方法:建立一个包含新 manager 的基类,而后放在主基类后面继承:
1
2
3
4
5
6
7
8
9
|
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 异常。