很多时候,我们都不是从‘一穷二白’开始编写模型的,有时候可以从第三方库中继承,有时候可以从以前的代码中继承,甚至现写一个模型用于被其它模型继承。这样做的好处,我就不赘述了,每个学习Django的人都非常清楚。
类同于Python的类继承,Django也有完善的继承机制。
Django中所有的模型都必须继承django.db.models.Model
模型,不管是直接继承也好,还是间接继承也罢。
你唯一需要决定的是,父模型是否是一个独立自主的,同样在数据库中创建数据表的模型,还是一个只用来保存子模型共有内容,并不实际创建数据表的抽象模型。
Django有三种继承的方式:
- 抽象基类:被用来继承的模型被称为
Abstract base classes
,将子类共同的数据抽离出来,供子类继承重用,它不会创建实际的数据表; - 多表继承:
Multi-table inheritance
,每一个模型都有自己的数据库表; - 代理模型:如果你只想修改模型的Python层面的行为,并不想改动模型的字段,可以使用代理模型。
注意!同Python的继承一样,Django也是可以同时继承两个以上父类的!
一、 抽象基类:
只需要在模型的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类。下面的例子则扩展了基类的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'
这里有几点要特别说明:
- 抽象基类中有的元数据,子模型没有的话,直接继承;
- 抽象基类中有的元数据,子模型也有的话,直接覆盖;
- 子模型可以额外添加元数据;
- 抽象基类中的
abstract=True
这个元数据不会被继承。也就是说如果想让一个抽象基类的子模型,同样成为一个抽象基类,那你必须显式的在该子模型的Meta中同样声明一个abstract = True
; - 有一些元数据对抽象基类无效,比如
db_table
,首先是抽象基类本身不会创建数据表,其次它的所有子类也不会按照这个元数据来设置表名。
警惕related_name和related_query_name参数
如果在你的抽象基类中存在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' >>>