模型继承在 Django 中与普通类继承在 Python 中的工作方式几乎完全相同,但也仍应遵循本页开头的内容。这意味着其基类应该继承自 django.db.models.Model
。你只需要决定父类模型是否需要拥有它们的权利(拥有它们的数据表),或者父类仅作为承载仅子类中可见的公共信息的载体。Django 有三种可用的继承风格。
抽象基类在你要将公共信息放入很多模型时会很有用。编写你的基类,并在 Meta
类中填入 abstract=True
。该模型将不会创建任何数据表。当其用作其它模型类的基类时,它的字段会自动添加至子类。
一个例子:
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
模型拥有3个字段: name
, age
和 home_group
。 CommonInfo
模型不能用作普通的 Django 模型,因为它是一个抽象基类。它不会生成数据表,也没有管理器,也不能被实例化和保存。从抽象基类继承来的字段可被其它字段或值重写,或用 None
删除。对很多用户来说,这种继承可能就是你想要的。它提供了一种在 Python 级抽出公共信息的方法,但仍会在子类模型中创建数据表。
当一个抽象基类被建立,Django 将所有你在基类中申明的 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"
Django 在安装 Meta
属性前,对抽象基类的 Meta
做了一个调整——设置 abstract=False
。这意味着抽象基类的子类不会自动地变成抽象类。为了继承一个抽象基类创建另一个抽象基类,你需要在子类上显式地设置 abstract=True
。抽象基类的某些 Meta
属性对子类是没用的。比如,包含 db_table
意味着所有的子类(你并未在子类中指定它们的 Meta
)会使用同一张数据表,这肯定不是你想要的。由于Python继承的工作方式,如果子类从多个抽象基类继承,则默认情况下仅继承第一个列出的类的 Meta
选项。为了从多个抽象类中继承 Meta
选项,必须显式地声明 Meta
继承。例如:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
ordering = ["name"]
class Unmanaged(models.Model):
class Meta:
abstract = True
managed = False
class Student(CommonInfo, Unmanaged):
home_group = models.CharField(max_length=5)
class Meta(CommonInfo.Meta, Unmanaged.Meta):
pass
若你在 外键 或 多对多字段 使用了 related_name
或 related_query_name
,你必须为该字段提供一个 独一无二 的反向名字和查询名字。这在抽象基类中一般会引发问题,因为基类中的字段都被子类继承,且保持了同样的值(包括 related_name
和 related_query_name
)。为了解决此问题,当你在抽象基类中(也只能是在抽象基类中)使用 related_name
和 related_query_name
,部分值需要包含 "%(app_label)s"
和 "%(class)s"
。
"%(class)s"
用使用了该字段的子类的小写类名替换。"%(app_label)s"
用小写的包含子类的应用名替换。每个安装的应用名必须是唯一的,应用内的每个模型类名也必须是唯一的。因此,替换后的名字也是唯一的。举个例子,有个应用 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
字段的反转名是 common_childa_related
,反转查询名是 common_childas
。 common.ChildB.m2m
字段的反转名是 common_childb_related
, 反转查询名是 common_childbs
。 rare.ChildB.m2m
字段的反转名是 rare_childb_related
,反转查询名是 rare_childbs
。这决定于你如何使用"%(class)s"
和"%(app_label)s"
构建关联名字和关联查询名。但是,若你忘了使用它们,Django 会在你执行系统检查(或运行 migrate
)时抛出错误。如果你未指定抽象基类中的 related_name
属性,默认的反转名会是子类名,后接 "_set"
。这名字看起来就像你在子类中定义的一样。比如,在上述代码中,若省略了 related_name
属性, ChildA
的 m2m
字段的反转名会是 childa_set
, ChildB
的是 childb_set
。
Django 支持的第二种模型继承方式是层次结构中的每个模型都是一个单独的模型。每个模型都指向分离的数据表,且可被独立查询和创建。继承关系介绍了子类和父类之间的连接(通过一个自动创建的 OneToOneField
)。比如:
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)
Place
的所有字段均在 Restaurant
中可用,虽然数据分别存在不同的表中。所有,以下操作均可:
>>> Place.objects.filter(name="Bob"s Cafe")
>>> Restaurant.objects.filter(name="Bob"s Cafe")
若有一个 Place
同时也是 Restaurant
,你可以通过小写的模型名将 Place
对象转为 Restaurant
对象。
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
然而,若上述例子中的 p
不是 一个 Restaurant
(它仅是个 Place
对象或是其它类的父类),指向 p.restaurant
会抛出一个 Restaurant.DoesNotExist
异常。Restaurant
中自动创建的连接至 Place
的 OneToOneField
看起来像这样:
place_ptr = models.OneToOneField(
Place, on_delete=models.CASCADE,
parent_link=True,
primary_key=True,
)
你可以在 Restaurant
中重写该字段,通过申明你自己的 OneToOneField
,并设置 parent_link=True
。
多表继承情况下,子类不会继承父类的 Meta
。所以的 Meta
类选项已被应用至父类,在子类中再次应用会导致行为冲突(与抽象基类中应用场景对比,这种情况下,基类并不存在)。故子类模型无法访问父类的 Meta
类。不过,有限的几种情况下:若子类未指定 ordering
属性或 get_latest_by
属性,子类会从父类继承这些。如果父类有排序,而你并不期望子类有排序,你可以显示的禁止它:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent"s ordering effect
ordering = []
由于多表继承使用隐式的 OneToOneField
连接子类和父类,所以直接从父类访问子类是可能的,就像上述例子展示的那样。然而,使用的名字是 ForeignKey
和 ManyToManyField
关系的默认值。如果你在继承父类模型的子类中添加了这些关联,你 必须 指定 related_name
属性。假如你忘了,Django 会抛出一个合法性错误。比如,让我们用上面的 Place
类创建另一个子类,包含一个 ManyToManyField
:
class Supplier(Place):
customers = models.ManyToManyField(Place)
这会导致以下错误:
Reverse query name for "Supplier.customers" clashes with reverse query
name for "Supplier.place_ptr".
HINT: Add or change a related_name argument to the definition for
"Supplier.customers" or "Supplier.place_ptr".
将 related_name
像下面这样加至 customers
字段能解决此错误:models.ManyToManyField(Place, related_name="provider")
。
如上所述,Django 会自动创建一个 OneToOneField
,将子类连接回非抽象的父类。如果你想修改连接回父类的属性名,你可以自己创建 OneToOneField
,并设置 parent_link=True
,表明该属性用于连接回父类。
使用 多表继承 时,每个子类模型都会创建一张新表。这一般是期望的行为,因为子类需要一个地方存储基类中不存在的额外数据字段。不过,有时候你只想修改模型的 Python 级行为——可能是修改默认管理器,或添加一个方法。这是代理模型继承的目的:为原模型创建一个 代理。你可以创建,删除和更新代理模型的实例,所以的数据都会存储的像你使用原模型(未代理的)一样。不同点是你可以修改代理默认的模型排序和默认管理器,而不需要修改原模型。代理模型就像普通模型一样申明。你需要告诉 Django 这是一个代理模型,通过将 Meta
类的 proxy
属性设置为 True
。例如,假设你想为 Person
模型添加一个方法。你可以这么做:
from django.db import models
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
MyPerson
类与父类 Person
操作同一张数据表。特别提醒, Person
的实例能通过 MyPerson
访问,反之亦然。
>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>
你也可以用代理模型定义模型的另一种不同的默认排序方法。你也许不期望总对 “Persion”
进行排序,但是在使用代理时,总是依据 “last_name”
属性进行排序:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
现在,普通的 Person
查询结果不会被排序,但 OrderdPerson
查询结果会按 last_name
排序。代理模型继承“Meta”
属性 和普通模型一样。
当你用 Person
对象查询时,Django 永远不会返回 MyPerson
对象。Person
对象的查询结果集总是返回对应类型。代理对象存在的全部意义是帮你复用原 Person
提供的代码和自定义的功能代码(并未依赖其它代码)。不存在什么方法能在你创建完代理后,帮你替换所有 Person
或其它模型。
一个代理模型必须继承自一个非抽象模型类。你不能继承多个非抽象模型类,因为代理模型无法在不同数据表之间提供任何行间连接。一个代理模型可以继承任意数量的抽象模型类,假如他们 没有 定义任何的模型字段。一个代理模型也可以继承任意数量的代理模型,只需他们共享同一个非抽象父类。
若你未在代理模型中指定模型管理器,它会从父类模型中继承。如果你在代理模型中指定了管理器,它会成为默认管理器,但父类中定义的管理器仍是可用的。随着上面的例子一路走下来,你可以在查询 Person
模型时这样修改默认管理器:
from django.db import models
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
若你在不替换已存在的默认管理器的情况下,为代理添加新管理器,你可以创建一个包含新管理器的基类,在继承列表中,主类后追加这个基类:
# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
secondary = NewManager()
class Meta:
abstract = True
class MyPerson(Person, ExtraManagers):
class Meta:
proxy = True
代理模型继承可能看起来和创建未托管的模型很类似,通过在模型的 Meta
类中定义 managed
属性。通过小心地配置 Meta.db_table
,你将创建一个未托管的模型,该模型将对现有模型进行阴影处理,并添加一些 Python 方法。然而,这会是个经常重复的且容易出错的过程,因为你要在做任何修改时保持两个副本的同步。另一方面,代理模型意在表现的和所代理的模型一样。它们总是与父模型保持一致,因为它们直接继承其字段和管理器。
通用性规则:
Meta.managed=False
。这个选项在模型化未受 Django 控制的数据库视图和表格时很有用。Meta.proxy=True
。这个配置使得代理模型在保存数据时,确保数据结构和原模型的完全一样。和 Python 中的继承一样,Django 模型也能继承自多个父类模型。请记住,Python 的命名规则这里也有效。第一个出现的基类(比如 Meta
)就是会被使用的那个;举个例子,如果存在多个父类包含 Meta
,只有第一个会被使用,其它的都会被忽略。一般来说,你并不会同时继承多个父类。常见的应用场景是 “混合” 类:为每个继承此类的添加额外的字段或方法。试着保持你的继承层级尽可能的简单和直接,这样未来你就不用为了确认某段信息是哪来的而拔你为数不多的头发了。注意,继承自多个包含 id主键的字段会抛出错误。正确的使用多继承,你可以在基类中显示使用 AutoField
:
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
。这会要求为每个父类模型和公共祖先使用显式的 OneToOneField
,避免与子类自动生成或继承的字段发生冲突:
class Piece(models.Model):
pass
class Article(Piece):
article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
class Book(Piece):
book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
...
class BookReview(Book, Article):
pass
在正常的 Python 类继承中,允许子类覆盖父类的任何属性。在 Django 中,模型字段通常不允许这样做。如果一个非抽象模型基类有一个名为 author
的字段,你就不能在继承自该基类的任何类中,创建另一个名为 author
的模型字段或属性。这个限制并不适用于从抽象模型继承的模型字段。这些字段可以用另一个字段或值覆盖,或者通过设置 field_name = None
来删除。
模型管理器是从抽象基类中继承的。重写一个被继承的 Manager
所引用的继承字段,可能会导致微妙的错误。
某些字段在模型内定义了额外的属性,例如 ForeignKey
定义了一个额外的属性 _id
附加在字段名上,类似的还有外键上的 related_name
和 related_query_name
。这些额外的属性不能被覆盖,除非定义它的字段被改变或删除,使它不再定义额外的属性。
重写父模型中的字段会导致一些困难,比如初始化新实例(在 Model.__init__
中指定哪个字段被初始化)和序列化。这些都是普通的 Python 类继承所不需要处理的功能,所以 Django 模型继承和 Python 类继承之间的区别并不是任意的。这些限制只针对那些是 Field
实例的属性。普通的 Python 属性可被随便重写。它还对 Python 能识别的属性生效:如果你同时在子类和多表继承的祖先类中指定了数据表的列名(它们是两张不同的数据表中的列)。若你在祖先模型中重写了任何模型字段,Django 会抛出一个 FieldError
。
请注意,由于在类定义期间解析字段的方式,从多个抽象父模型继承的模型字段以严格的深度优先顺序解析。这与标准 Python MRO 形成对比,后者在菱形继承的情况下以广度优先解决。这种差异只影响复杂的模型层次结构,(根据上面的建议)你应该尽量避免。
JMenu类表示从菜单栏部署的下拉菜单组件。类声明以下是javax.swing.JMenu类的声明public class JMenu extends JMenuItemimplemen...
本教程阐明了 JUnit 中的方法执行过程,即哪一个方法首先被调用,哪一个方法在一个方法之后调用。以下为 JUnit 测试方法的 API,...
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!“看看星空,会觉得自己很渺小,可能我们在宇宙中...
MyBatis-Plus 条件构造器-AbstractWrapperexists(String existsSql)exists(boolean condition, String existsSql)拼接EXISTS(sql...
该功能为了保护数据库配置及数据安全,在一定的程度上控制开发人员流动导致敏感信息泄露。3.3.2开始支持配置安全YML配置:// 加...
mybatis-mate为mp企业级模块,旨在更敏捷优雅处理数据。mybatis-mate示例:传送门数据审计(对账)mybatis-mate-audit对比两对...