面向对象语言的三大特性:多态、封装、继承。Django中模型类也拥有这些特性,本章说明下模型类的继承
概述
Django中,不管是直接继承也好,间接继承也罢,所有的模型类的基类是django.db.models.Model
- 模型类的分类
非抽象类:是一个独立自主的,会在数据库中创建数据表的模型类,在Meta
元数据中定义abstract = True
抽象类:只是用于保存子类模型共有内容,并不会在数据库中创建数据表的模型类 - 模型类的继承方式
1.继承抽象基类:被用来继承的的抽象基类模型被称为Absstract base classes
2.多表继承:Multi-table inheritance
,子父类都是独立自主,在数据库中创建数据表,功能完整的模型类
3.代理模型:当我们只想修改模型的Python层面的行为,而并不想改动模型的字段时使用
一、继承抽象基类
-
抽象模型类
在Meta
元数据中定义abstract = True
的模型类。Django不会为这种模型类创建数据库表,并且也不会有管理器,不能被实例化和直接保存。其用处就是用来保存子模型类共有的内容,被子模型类继承使用。当抽象模型类被子模型类继承时,它的字段会全部复制到子模型类中。 -
注意点
1.子模型会继承抽象基类的所有字段
2.子模型可以额外添加元数据,如果子模型没有声明自己的Meta类,那么会直接继承抽象基类的Meta类
3.抽象基类拥有的元数据,如果子模型没有,则子模型直接继承
4.抽象基类拥有的元数据,如果子模型也有,则子模型的进行覆盖
5.抽象基类元数据中的abstract = True
不会被继承。如果子模型也想成为抽象类,则需要在子模型中的元数据定义该字段
6.有一些元数据对于抽象基类来说是无效的,比如说db_table
,因为抽象类不会生成实际数据表,并且它的子模型并不会按照这个元数据来设置表名
- 下面例子中,Cat模型拥有named、age、skill这三个字段,Animal抽象基类不会生成数据表,不能当成一个正常的模型来使用。
class Animal(models.Model):
named = models.CharField(max_length=64)
age = models.PositiveIntegerField()
class Meta:
abstract = True
ordering = ['age']
class Cat(Animal):
skill = models.CharField(max_length=32)
class Meta:
db_table = 'cat'
-
related_name
和related_query_name
参数
如果抽象基类中,存在使用了related_name
或related_query_name
参数的ForeignKey
或ManyToMany
字段,那么要注意:
默认情况下,每个子类都会继承抽象基类的所有字段,所以每个子类都会拥有同样的related_name
和related_query_name
字段值,这样会导致错误。所以,在抽象基类中使用了related_name
和related_query_name
参数时,它们的值应该包含%(app_label)s
和%(class)s
部分
1.%(app_label)s
:使用子类的小写名字替换
2.%(class)s
:使用子类所属App的小写名替换 - 例子:
1.modelsinherit.Cat.key
字段的reverse name
/反向关系名是modelsinherit_cat_related
,reverse query name
/反向查询名是modelsinherit_cat
2.otherApp.Bird.key
的反向关系名是otherapp_bird_related
,反向查询名是otherapp_bird
class Animal(models.Model):
key = models.ManyToManyField(
xxxModel,
related_name='%(app_label)s_%(class)s_related',
related_query_name='%(app_label)s_%(class)s'
)
class Meta:
abstract = True
ordering = ['age']
### 同个应用的模块类
class Cat(Animal):
skill = models.CharField(max_length=32)
class Dog(Animal):
skill = models.CharField(max_length=32)
### 其它应用的模块类(otherApp)
from modelsinherit.models import Animal
class Bird(Animal):
skill = models.CharField(max_length=32)
二、多表继承/继承正常模型类
这种继承方式,父类和子类都是子父类都是独立自主,在数据库中创建数据表,功能完整的模型类。并且内部隐含了一对一的关系
- 例子
1.Cat
包含Animal
的所有字段,并且各自拥有自己的数据表
2.如果一个Animal
对象同时也是一个Cat
对象,那么它可以从父类获取子类:a = Animal.object.get(id=1); a.cat #获取一个cat对象
class Animal(models.Model):
named = models.CharField(max_length=64)
age = models.PositiveIntegerField()
class Cat(Animal):
skill = models.CharField(max_length=32)
- 多表继承的Meta
多表继承中,子类和父类都是一个功能完整的模型类,所以父类的Meta类会对子类造成影响。所以,Django在多表继承时,子类是不会继承父类的Meta类中大多数的元数据。
这两个元数据会被继承:ordering
和get_latest_by
,如果子类不想继承父类这两个元数据,可以在子类的Meta类中重写清空:
class Cat(Animal):
ordering = []
- 多表继承的反向关联
上面说过,当一个对象即是父类对象也是子类对象时,可以从父类访问子类。其原因是因为多表继承使用了一个隐含的OneToOneField
链接了子类和父类。
这里会有个隐患,因为这个OneToOneField
字段默认的related_name
的值与ForeignKey
和ManyToManyField
默认的related_name
的值相同。所以,如果一个子类和父类或其它子类是多对一、多对多关系时,Django会在运行或验证时抛出异常。
解决方法:在每个多对多、多对一字段上,指定related_name
的值
class Cat(Animal):
skill = models.ManyToManyField(
Animal,
related_name = 'related_skill' # 指定related_name
)
三、代理模型
有时候,我们只想更改模型类在python层面的行为,比如说:添加一个新的方法,更改默认的manager管理器。着时候就可以使用代理模型
使用代理模型时,可以创建、更新、删除代理模型的实例,并且所有的数据都可以和使用原始模型(非代理模型)一样,可以被保存。不同的地方是,在代理模型中进行的操作不会对原始模型产生影响
-
声明代理模型
在Meta
类中,设置proxy = True
-
例子
1.Cat
类和Animal
类使用同一个数据表,并且新的Animal
实例可以通过Cat
类进行访问,反过来也可以
2.并且Cat
类通过代理进行排序,而父类Animal
不进行排序
from django.db import models
calss Animal(models.Model):
named = models.CharField(max_length=64)
age = models.PositiveIntegerField()
class Cat(Animal):
skill = models.CharField(max_length=32)
class Meta:
ordering = ['age'] # 普通的Animal类是无序的,而Cat类会按照age进行排序
proxy = True # 代理模型
-
使用代理模型的约束
1.代理模型必须继承自一个非抽象的基类,并且不能同时继承多个非抽象基类
2.代理模型可以同时继承任意多个抽象基类,前提是这些抽象基类没有定义任何模型字段。
3.代理模型可以同时继承多个别的代理模型,前提是这些代理模型继承同一个非抽象基类。(早期Django版本不支持这一条) -
代理模型的管理器
如果代理模型中不指定管理器,那么就会继承父类的管理器。而如果定义了管理器,那么它就会称为默认管理器,但是父类的管理器依旧有效,比如:
from django.db import models
calss Animal(models.Model):
named = models.CharField(max_length=64)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Cat(Animal):
object = Animal()
class Meta:
proxy = True
如果想要在代理类中添加新的管理器,而不是替换现有的默认管理器。那么可以创建一个含有新的管理器的基类,然后继承时,把该基类放在主基类的后面:
## 创建一个含有新的管理器的基类
class ExtraManagers(models.Model):
secondary = Animal()
class Meta:
abstract = True
class Cat(Animal, ExtraManagers):
class Meta:
prox = True
四、多重继承
多重继承并非是多表继承
Django中模型的多重继承和python的一样。如果模型类的多个父类都含有Meta类,则该子类只会继承第一个父类的Meta类
不推荐多重继承,因为很容易混乱
- 多重继承时,如果存在含有相同id主键字段的父类,则会抛出异常(常会出现在使用默认主键的情况)。所以,可以在这些父类中,声明
AutoField
去显式地去指定主键字段,比如:
class Oldanimal(models.Model):
old_id = models.AutoField(primary_key=True) # 指定主键
class Newanimal(models.Model):
new_id = models.AutoField(primary_key=True) # 指定主键
class Cat(Newanimal, Oldanimal):
pass
- Python中当子类拥有和父类相同的属性名时,子类会覆盖父类的。但是在Django中,如果继承的父类不是一个抽象基类,那么子类不允许拥有和父类相同的字段。在执行
python manage.py makemigrations
时会报错:
django.core.exceptions.FieldError: Local field 'name' in class 'yy' clashes with field of the same name from base class 'xx'.
当然,如果父类是一个抽象模型类就没问题了
五、用包来管理模型类
当我们生成一个app时,该app的目录下会自动生成一个models.py
文件,我们就会在这个文件中创建这个app所需要的各种模型类。
但是,当模型类很多的时候,管理起来就不大方便,所以这个时候,我们可以把这些模型类分成各个py文件,使用包来管理这些模型类。
- 步骤:
1.删除自动生成的models.py
文件
2.在该app目录下创建一个models
文件夹
3.创建一个__init__.py
文件
4.和正常py文件一样,创建模型类的py文件并和之前编写model类一样进行编写,比如animal.py
、cat.py
5.在__init__.py
文件中导入这些模型类(显示明确地导入每个模型,而不是使用from .model import *
的方式,这样命名空间容易混淆,不容易被分析工具所分析使用)
from .animal import Animal
from .cat import Cat
网友评论