Django 知识库:模型的抽象基类和多表继承
4154 views, 2020/05/18 updated Go to Comments
Django 中模型的继承大体上与 Python 原生的继承差不多,但又有些区别。
主要有下面三种形式。
抽象基类
抽象基类通常就是你想要的继承形式,像这样:
class Animal(models.Model): name = models.CharField(max_length=100) age = models.IntegerField(default=5) finger_count = models.IntegerField(default=10) class Meta: # 告诉 Django 这是个抽象基类 abstract = True class Bird(Animal): flying_height = models.IntegerField(default=2000) # 覆写父类字段 age = models.TextField(default='5 years old') # 删除父类字段 finger_count = None
上面代码中的 Animal
就是抽象基类,在迁移时它不会真的在数据库里生成一张 Animal
的表。它的作用就是把字段继承到 Bird
子类中,并且这些字段可以被覆写,也可以被删除。
多表继承
与抽象基类不同,多表继承的父类和子类都会生成在数据库中:
class Human(models.Model): name = models.CharField(max_length=100) class Baby(Human): age = models.IntegerField(default=0)
现在数据库同时存在 Human
和 Baby
两张表了。更重要的是这两张表并不是分离的,而是用外键链接起来的。具体来说,当你保存了一个 Baby
表时,同时也创建了一个 Human
表,而且 name
字段是存储在 Human
表中的,像这样:
>>> human = Human.objects.get(id=1) >>> human.baby <Baby: ...> # 如果 .baby 不存在,则会报错
也就是说,多表继承其实有一个隐藏的 OneToOneField
外键:
human_ptr = models.OneToOneField( Human, on_delete=models.CASCADE, parent_link=True, primary_key=True, )
在某些情况下,你可以声明 parent_link
来显示的覆写它。
注意,多表继承不允许你覆写父类中已有的字段,这和抽象基类也是不同的。
代理模式
有时候你可能并不想改变父类的字段内容,而仅仅是想改变模型的某些行为模式。这时候代理模式就是你的好选择:
class Car(models.Model): name = models.CharField(max_length=30) class MyCar(Car): class Meta: ordering = ["name"] proxy = True def do_something(self): # ... pass
MyCar
并不会在数据库中生成一张表,而仅仅是给父类 Car
添加了方法、改变了排序诸如此类的东西,并且你可以像真的有这张表一样去操作它:
>>> my_car = MyCar.objects.get(name='BiYaDi') >>> my_car <MyCar: ...> >>> my_car.name 'BiYaDi'
所有对 MyCar
的修改都会体现在 Car
的数据表中。垂帘听政,这就是 Django 界的慈禧太后啊。
多重继承
Django 模型可以具有多个父类,这跟 Python 是一样的,并且解析规则也是相同的。也就是说,如果多个父类都包含一个 Meta
类,那么只有第一个会被使用,而其他的将被忽略。
多个父类同时拥有 id
字段将引发错误,你必须显式指定它们:
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
:
class Piece(models.Model): pass class Article(Piece): article_piece = models.OneToOneField( Piece, ... parent_link=True ) class Book(Piece): book_piece = models.OneToOneField( Piece, ... parent_link=True ) class BookReview(Book, Article): pass
记住,除了抽象基类外,Django 的模型是不允许覆写父类的字段的。