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)

现在数据库同时存在 HumanBaby 两张表了。更重要的是这两张表并不是分离的,而是用外键链接起来的。具体来说,当你保存了一个 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 的模型是不允许覆写父类的字段的。




本文作者: 杜赛
发布时间: 2020年05月18日 - 10:03
最后更新: 2020年05月18日 - 10:03
转载请保留原文链接及作者