模型¶
模型是关于您数据的唯一、明确的信息来源。它包含您正在存储的数据的基本字段和行为。通常,每个模型都映射到单个数据库表。
基础知识
每个模型都是一个继承自
django.db.models.Model
的 Python 类。模型的每个属性都代表一个数据库字段。
有了所有这些,Django 为您提供了一个自动生成的数据库访问 API;参见执行查询。
快速示例¶
此示例模型定义了一个Person
,它具有first_name
和last_name
。
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
first_name
和last_name
是模型的字段。每个字段都指定为类属性,并且每个属性都映射到一个数据库列。
上面的Person
模型将创建如下所示的数据库表:
CREATE TABLE myapp_person (
"id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
一些技术说明
使用模型¶
定义模型后,您需要告诉 Django 您将要*使用*这些模型。为此,请编辑您的设置文件并将INSTALLED_APPS
设置更改为添加包含您的models.py
的模块的名称。
例如,如果应用程序的模型位于模块myapp.models
中(这是manage.py startapp
脚本为应用程序创建的包结构),INSTALLED_APPS
应部分读取为:
INSTALLED_APPS = [
# ...
"myapp",
# ...
]
当您向INSTALLED_APPS
添加新的应用程序时,请务必运行manage.py migrate
,可以选择先使用manage.py makemigrations
为其创建迁移。
字段¶
模型最重要的部分——也是模型中唯一必需的部分——是它定义的数据库字段列表。字段由类属性指定。注意不要选择与模型 API(如clean
、save
或delete
)冲突的字段名。
示例
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
字段类型¶
模型中的每个字段都应该是相应Field
类的实例。Django 使用字段类类型来确定以下几点:
列类型,它告诉数据库存储哪种类型的数据(例如
INTEGER
、VARCHAR
、TEXT
)。渲染表单字段时使用的默认 HTML 小部件(例如
<input type="text">
、<select>
)。在 Django 的管理员和自动生成的表单中使用的最小验证要求。
Django 提供了数十种内置字段类型;您可以在模型字段参考中找到完整列表。如果 Django 的内置字段无法满足您的需求,您可以轻松编写自己的字段;参见如何创建自定义模型字段。
字段选项¶
每个字段都采用一组特定于字段的参数(在模型字段参考中进行了说明)。例如,CharField
(及其子类)需要一个max_length
参数,该参数指定用于存储数据的VARCHAR
数据库字段的大小。
所有字段类型还可以使用一组通用参数。所有参数都是可选的。它们在参考中进行了全面解释,但以下是最常用参数的快速摘要:
null
如果为
True
,Django 将空值存储为数据库中的NULL
。默认为False
。blank
如果为
True
,则允许该字段为空。默认为False
。请注意,这与
null
不同。null
纯粹与数据库相关,而blank
与验证相关。如果字段具有blank=True
,则表单验证将允许输入空值。如果字段具有blank=False
,则该字段将是必需的。choices
一个序列的2值元组,一个映射,一个枚举类型或一个可调用对象(它不需要参数并返回任何以前的格式),用作此字段的选择。如果给出此选项,则默认表单小部件将是选择框而不是标准文本字段,并且会将选择限制为给定的选择。
选择列表如下所示:
YEAR_IN_SCHOOL_CHOICES = [ ("FR", "Freshman"), ("SO", "Sophomore"), ("JR", "Junior"), ("SR", "Senior"), ("GR", "Graduate"), ]
注意
每次
choices
的顺序发生更改时,都会创建一个新的迁移。每个元组中的第一个元素是将存储在数据库中的值。第二个元素由字段的表单小部件显示。
给定一个模型实例,可以使用
get_FOO_display()
方法访问具有choices
的字段的显示值。例如:from django.db import models class Person(models.Model): SHIRT_SIZES = { "S": "Small", "M": "Medium", "L": "Large", } name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'
您也可以使用枚举类以简洁的方式定义
choices
from django.db import models class Runner(models.Model): MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE") name = models.CharField(max_length=60) medal = models.CharField(blank=True, choices=MedalType, max_length=10)
更多示例可在模型字段参考中找到。
Django 5.0 中的更改添加了对映射和可调用对象的支持。
default
字段的默认值。可以是值或可调用对象。如果可调用,则每次创建新对象时都会调用它。
db_default
字段的数据库计算默认值。可以是字面值或数据库函数。
如果同时设置了
db_default
和Field.default
,则在 Python 代码中创建实例时,default
将优先。db_default
仍然会在数据库级别设置,并在 ORM 外部插入行或在迁移中添加新字段时使用。help_text
要与表单小部件一起显示的额外“帮助”文本。即使您的字段不用于表单,它也对文档很有用。
primary_key
如果为
True
,则此字段是模型的主键。如果您没有为模型中的任何字段指定
primary_key=True
,Django 将自动添加一个IntegerField
来保存主键,因此您不需要在任何字段上设置primary_key=True
,除非您想覆盖默认主键行为。更多信息,请参见自动主键字段。主键字段是只读的。如果您更改现有对象的主键值然后保存它,则会与旧对象一起创建一个新对象。例如
from django.db import models class Fruit(models.Model): name = models.CharField(max_length=100, primary_key=True)
>>> fruit = Fruit.objects.create(name="Apple") >>> fruit.name = "Pear" >>> fruit.save() >>> Fruit.objects.values_list("name", flat=True) <QuerySet ['Apple', 'Pear']>
unique
如果为
True
,则此字段在整个表中必须唯一。
同样,这些只是对最常见字段选项的简短描述。完整的详细信息可以在常用模型字段选项参考中找到。
自动主键字段¶
默认情况下,Django 为每个模型提供一个自动递增的主键,其类型在每个应用中由AppConfig.default_auto_field
指定,或在DEFAULT_AUTO_FIELD
设置中全局指定。例如
id = models.BigAutoField(primary_key=True)
如果您想指定自定义主键,请在一个字段上指定primary_key=True
。如果 Django 看到您已显式设置Field.primary_key
,它不会添加自动id
列。
每个模型都要求恰好有一个字段具有primary_key=True
(显式声明或自动添加)。
详细字段名称¶
除ForeignKey
、ManyToManyField
和OneToOneField
之外,每个字段类型都接受一个可选的第一个位置参数——详细名称。如果没有给出详细名称,Django 将使用字段的属性名称自动创建它,并将下划线转换为空格。
在此示例中,详细名称为"person's first name"
first_name = models.CharField("person's first name", max_length=30)
在此示例中,详细名称为"first name"
first_name = models.CharField(max_length=30)
ForeignKey
、ManyToManyField
和OneToOneField
要求第一个参数是模型类,因此请使用verbose_name
关键字参数
poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
verbose_name="related place",
)
约定是不要大写verbose_name
的首字母。Django 会在需要的地方自动大写首字母。
关系¶
显然,关系数据库的强大功能在于将表彼此关联。Django 提供了定义三种最常见的数据库关系类型的方法:多对一、多对多和一对一。
多对一关系¶
要定义多对一关系,请使用django.db.models.ForeignKey
。您可以像使用任何其他Field
类型一样使用它:将其作为模型的类属性包含在内。
ForeignKey
需要一个位置参数:模型相关的类。
例如,如果Car
模型有一个Manufacturer
——也就是说,一个Manufacturer
制造多辆汽车,但每辆Car
只有一辆Manufacturer
——请使用以下定义
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
您还可以创建递归关系(具有与其自身多对一关系的对象)和尚未定义的模型的关系;有关详细信息,请参见模型字段参考。
建议(但不是必需)ForeignKey
字段(上述示例中的manufacturer
)的名称是小写的模型名称。您可以随意命名该字段。例如
class Car(models.Model):
company_that_makes_it = models.ForeignKey(
Manufacturer,
on_delete=models.CASCADE,
)
# ...
另请参见
ForeignKey
字段接受许多额外参数,这些参数在模型字段参考中进行了说明。这些选项有助于定义关系的工作方式;所有选项都是可选的。
有关访问反向关联对象的详细信息,请参见反向关联对象示例。
有关示例代码,请参见多对一关系模型示例。
多对多关系¶
要定义多对多关系,请使用ManyToManyField
。您可以像使用任何其他Field
类型一样使用它:将其作为模型的类属性包含在内。
ManyToManyField
需要一个位置参数:模型相关的类。
例如,如果一个Pizza
有多个Topping
对象——也就是说,一个Topping
可以在多个比萨饼上,而每个Pizza
有多个配料——您可以这样表示
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
与 ForeignKey
一样,您也可以创建 递归关系(对象与自身的多对多关系)和 尚未定义模型的关联关系。
建议(但不是必需)ManyToManyField
的名称(上例中的 toppings
)用复数形式来描述相关的模型对象集合。
哪个模型具有 ManyToManyField
没有关系,但您应该只在一个模型中添加它,而不是两个模型都添加。
通常,ManyToManyField
实例应该放在将在表单上进行编辑的对象中。在上例中,toppings
在 Pizza
中(而不是 Topping
具有 pizzas
ManyToManyField
),因为考虑披萨拥有配料比配料属于多个披萨更自然。按照上述设置方式,Pizza
表单将允许用户选择配料。
另请参见
有关完整示例,请参见 多对多关系模型示例。
ManyToManyField
字段还接受许多额外的参数,这些参数在 模型字段参考 中进行了说明。这些选项有助于定义关系的工作方式;所有选项都是可选的。
多对多关系中的额外字段¶
当您只处理多对多关系(例如混合搭配披萨和配料)时,标准的 ManyToManyField
就足够了。但是,有时您可能需要将数据与两个模型之间的关系关联起来。
例如,考虑一个跟踪音乐家所属乐队的应用程序。一个人和他们所属的乐队之间存在多对多关系,因此您可以使用 ManyToManyField
来表示这种关系。但是,您可能需要收集有关成员资格的大量详细信息,例如该人加入乐队的日期。
对于这些情况,Django 允许您指定将用于管理多对多关系的模型。然后,您可以将额外字段添加到中间模型。中间模型使用 ManyToManyField
的 through
参数与之关联,以指向将充当中介的模型。对于我们的音乐家示例,代码如下所示
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through="Membership")
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
设置中间模型时,您需要显式指定与多对多关系相关的模型的外键。此显式声明定义了两个模型之间的关联方式。
中间模型有一些限制
您的中间模型必须包含一个且只有一个指向源模型的外键(在本例中为
Group
),或者您必须使用ManyToManyField.through_fields
显式指定 Django 应用于该关系的外键。如果您有多个外键并且未指定through_fields
,则会引发验证错误。目标模型(在本例中为Person
)的外键也存在类似的限制。对于通过中间模型与自身具有多对多关系的模型,允许有两个指向同一模型的外键,但它们将被视为多对多关系的两个(不同)方面。但是,如果有多于两个外键,则也必须像上面一样指定
through_fields
,否则会引发验证错误。
现在您已将 ManyToManyField
设置为使用中间模型(在本例中为 Membership
),您可以开始创建一些多对多关系了。您可以通过创建中间模型的实例来实现此目的
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(
... person=ringo,
... group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.",
... )
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(
... person=paul,
... group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.",
... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
只要您为任何必需字段指定 through_defaults
,您也可以使用 add()
、create()
或 set()
来创建关系
>>> beatles.members.add(john, through_defaults={"date_joined": date(1960, 8, 1)})
>>> beatles.members.create(
... name="George Harrison", through_defaults={"date_joined": date(1960, 8, 1)}
... )
>>> beatles.members.set(
... [john, paul, ringo, george], through_defaults={"date_joined": date(1960, 8, 1)}
... )
您可能更倾向于直接创建中间模型的实例。
如果中间模型定义的自定义 through 表未对 (model1, model2)
对强制唯一性,允许多个值,则 remove()
调用将删除所有中间模型实例
>>> Membership.objects.create(
... person=ringo,
... group=beatles,
... date_joined=date(1968, 9, 4),
... invite_reason="You've been gone for a month and we miss you.",
... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>
clear()
方法可用于删除实例的所有多对多关系
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>
建立多对多关系后,您可以发出查询。与普通多对多关系一样,您可以使用多对多相关模型的属性进行查询
# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith="Paul")
<QuerySet [<Group: The Beatles>]>
由于您使用的是中间模型,因此您也可以查询其属性
# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
... group__name="The Beatles", membership__date_joined__gt=date(1961, 1, 1)
... )
<QuerySet [<Person: Ringo Starr]>
如果您需要访问成员资格信息,您可以通过直接查询 Membership
模型来实现
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
另一种访问相同信息的方法是从 Person
对象查询 多对多反向关系
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
一对一关系¶
要定义一对一关系,请使用 OneToOneField
。您可以像使用任何其他 Field
类型一样使用它:将其作为模型的类属性包含在内。
当某个对象以某种方式“扩展”另一个对象时,这在对象的 primary key 上最有用。
OneToOneField
需要一个位置参数:模型相关的类。
例如,如果您正在构建“地点”数据库,您将构建相当标准的内容,例如地址、电话号码等。然后,如果您想在“地点”数据库之上构建餐厅数据库,而不是重复自身并在 Restaurant
模型中复制这些字段,您可以使 Restaurant
与 Place
具有 OneToOneField
关系(因为餐厅“是”一个地方;事实上,为了处理这个问题,您通常会使用 继承,这涉及到隐式的一对一关系)。
与 ForeignKey
一样,可以定义 递归关系,并且可以对 尚未定义的模型 进行引用。
另请参见
有关完整示例,请参见 一对一关系模型示例。
OneToOneField
字段还接受可选的 parent_link
参数。
过去,OneToOneField
类会自动成为模型的主键。现在不再如此(尽管您可以手动传入 primary_key
参数)。因此,现在可以在单个模型中拥有多个 OneToOneField
类型的字段。
跨文件的模型¶
将模型与另一个应用中的模型关联起来完全没有问题。为此,请在定义模型的文件顶部导入相关的模型。然后,在需要的地方引用另一个模型类。例如
from django.db import models
from geography.models import ZipCode
class Restaurant(models.Model):
# ...
zip_code = models.ForeignKey(
ZipCode,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
字段名称限制¶
Django 对模型字段名称有一些限制。
字段名称不能是 Python 保留字,因为这会导致 Python 语法错误。例如
class Example(models.Model): pass = models.IntegerField() # 'pass' is a reserved word!
字段名称不能包含多个连续的下划线,因为 Django 的查询查找语法就是这样工作的。例如
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
字段名称不能以下划线结尾,原因类似。
但是,这些限制是可以解决的,因为您的字段名称不必与您的数据库列名称匹配。请参阅 db_column
选项。
SQL 保留字,例如 join
、where
或 select
,是可以用作模型字段名称的,因为 Django 会转义所有底层 SQL 查询中的所有数据库表名和列名。它使用您特定数据库引擎的引用语法。
自定义字段类型¶
如果现有的模型字段无法满足您的需求,或者您希望利用一些不太常见的数据库列类型,您可以创建自己的字段类。在 如何创建自定义模型字段 中提供了创建您自己的字段的完整内容。
Meta
选项¶
使用内部 class Meta
为您的模型提供元数据,如下所示
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
模型元数据是“任何不是字段的内容”,例如排序选项 (ordering
)、数据库表名 (db_table
) 或人类可读的单数和复数名称 (verbose_name
和 verbose_name_plural
)。没有一个是必需的,向模型添加 class Meta
是完全可选的。
所有可能的 Meta
选项的完整列表可以在 模型选项参考 中找到。
模型属性¶
模型方法¶
在模型上定义自定义方法,为您的对象添加自定义“行级”功能。虽然 Manager
方法旨在执行“表范围”操作,但模型方法应该作用于特定的模型实例。
这是一种将业务逻辑保存在一个地方(模型)中的宝贵技术。
例如,此模型有一些自定义方法
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
@property
def full_name(self):
"Returns the person's full name."
return f"{self.first_name} {self.last_name}"
此示例中的最后一个方法是一个 属性。
模型实例参考 包含 自动赋予每个模型的方法 的完整列表。您可以覆盖大多数这些方法——请参见下面的 覆盖预定义模型方法——但是有一些方法您几乎总是希望定义。
__str__()
返回任何对象的字符串表示形式的 Python“魔术方法”。每当需要将模型实例强制转换为普通字符串并显示时,Python 和 Django 都会使用它。最值得注意的是,当您在交互式控制台或管理员中显示对象时,就会发生这种情况。
您总是需要定义此方法;默认值并没有什么帮助。
get_absolute_url()
这告诉 Django 如何计算对象的 URL。Django 在其管理员界面中使用它,以及任何需要为对象确定 URL 的时候。
任何具有唯一标识其 URL 的对象都应定义此方法。
覆盖预定义模型方法¶
还有一组 模型方法 封装了许多您可能想要自定义的数据库行为。特别是,您通常希望更改 save()
和 delete()
的工作方式。
您可以随意覆盖这些方法(以及任何其他模型方法)以更改行为。
覆盖内置方法的一个经典用例是,如果您希望在保存对象时发生某些事情。例如(有关它接受的参数的文档,请参阅 save()
)
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, **kwargs):
do_something()
super().save(**kwargs) # Call the "real" save() method.
do_something_else()
您还可以阻止保存
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, **kwargs):
if self.name == "Yoko Ono's blog":
return # Yoko shall never have her own blog!
else:
super().save(**kwargs) # Call the "real" save() method.
重要的是要记住调用超类方法——那就是 super().save(**kwargs)
部分——以确保对象仍然保存到数据库中。如果您忘记调用超类方法,则默认行为将不会发生,并且数据库将不会被修改。
同样重要的是,您需要传递可以传递给模型方法的参数——这就是 **kwargs
的作用。Django 将不时扩展内置模型方法的功能,添加新的关键字参数。如果您在方法定义中使用 **kwargs
,则可以保证您的代码在添加这些参数时会自动支持它们。
如果您希望在 save()
方法中更新字段值,您可能还想将此字段添加到 update_fields
关键字参数中。这将确保在指定 update_fields
时保存该字段。例如
from django.db import models
from django.utils.text import slugify
class Blog(models.Model):
name = models.CharField(max_length=100)
slug = models.TextField()
def save(self, **kwargs):
self.slug = slugify(self.name)
if (
update_fields := kwargs.get("update_fields")
) is not None and "name" in update_fields:
kwargs["update_fields"] = {"slug"}.union(update_fields)
super().save(**kwargs)
有关详细信息,请参阅 指定要保存的字段。
覆盖的模型方法不会在批量操作中调用
请注意,当 使用 QuerySet 批量删除对象 或作为 级联删除
的结果时,对象的 delete()
方法不一定被调用。为了确保自定义删除逻辑得到执行,您可以使用 pre_delete
和/或 post_delete
信号。
不幸的是,当 创建
或 更新
对象时,没有解决方法,因为没有调用 save()
、pre_save
和 post_save
。
执行自定义 SQL¶
另一种常见模式是在模型方法和模块级方法中编写自定义 SQL 语句。有关使用原始 SQL 的更多详细信息,请参阅有关 使用原始 SQL 的文档。
模型继承¶
Django 中的模型继承的工作方式与 Python 中的普通类继承的工作方式几乎相同,但仍应遵循页面开头介绍的基本知识。这意味着基类应该继承自 django.db.models.Model
。
您唯一需要做的决定是,您是否希望父模型成为其自身的模型(具有其自己的数据库表),或者父模型只是持有公共信息的容器,只能通过子模型查看。
Django 中有三种可能的继承方式。
通常,你只需要使用父类来保存一些不想为每个子模型重复键入的信息。这个类永远不会被单独使用,因此你需要使用抽象基类。
如果你正在对现有模型(可能完全来自另一个应用程序)进行子类化,并且希望每个模型都有自己的数据库表,那么多表继承是最佳选择。
最后,如果你只想修改模型的 Python 级行为,而无需以任何方式更改模型字段,则可以使用代理模型。
抽象基类¶
当你想将一些公共信息放入其他多个模型时,抽象基类非常有用。你可以编写你的基类并在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
模型将有三个字段:name
、age
和 home_group
。CommonInfo
模型不能作为普通的 Django 模型使用,因为它是一个抽象基类。它不会生成数据库表或拥有管理器,也不能直接实例化或保存。
从抽象基类继承的字段可以用另一个字段或值覆盖,或者用None
删除。
对于许多用途来说,这种类型的模型继承正是你想要的。它提供了一种在 Python 层面提取公共信息的方法,同时在数据库层面上每个子模型仍然只创建一个数据库表。
Meta
继承¶
创建抽象基类时,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
多表继承¶
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选项都已应用于父类,再次应用它们通常只会导致矛盾的行为(这与抽象基类的情况相反,在抽象基类的情况下,基类本身并不存在)。
因此,子模型无法访问其父类的Meta类。但是,在少数情况下,子类会继承父类的行为:如果子类没有指定ordering
属性或get_latest_by
属性,它将继承自其父类的这些属性。
如果父类具有排序,而你又不希望子类具有任何自然排序,则可以显式地禁用它。
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
继承和反向关系¶
因为多表继承使用隐式的OneToOneField
来链接子类和父类,所以可以从父类向下移动到子类,如上例所示。但是,这会占用作为ForeignKey
和ManyToManyField
关系的默认related_name
值的名称。如果将这些类型的关系放在父模型的子类上,则**必须**在每个这样的字段上指定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 行为——也许是为了更改默认管理器或添加新方法。
这就是代理模型继承的目的:为原始模型创建代理。你可以创建、删除和更新代理模型的实例,所有数据都将像使用原始(非代理)模型一样保存。不同之处在于,你可以更改默认模型排序或代理中的默认管理器等内容,而无需更改原始模型。
代理模型的声明方式与普通模型相同。通过将Meta
类的proxy
属性设置为True
,来告诉 Django 它是一个代理模型。
例如,假设你想向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>
你还可以使用代理模型在模型上定义不同的默认排序。你可能并不总是希望对Person
模型进行排序,但在使用代理时,通常会按last_name
属性排序。
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
现在,普通的Person
查询将无序,而OrderedPerson
查询将按last_name
排序。
代理模型继承Meta
属性的方式与普通模型相同。
QuerySet
仍然返回请求的模型¶
无法让 Django 返回,比如说,每当你查询Person
对象时都返回一个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 控制的数据库视图和表。如果要更改模型的仅 Python 行为,但保留与原始模型相同的字段,请使用
Meta.proxy=True
。这将设置代理模型,使其在保存数据时成为原始模型存储结构的精确副本。
多重继承¶
就像 Python 的子类化一样,Django 模型也可以从多个父模型继承。请记住,普通的 Python 名称解析规则适用。使用第一个出现特定名称(例如 Meta)的基类;例如,这意味着如果多个父类包含 Meta 类,则只使用第一个,其他所有类都将被忽略。
通常,不需要从多个父类继承。这有用的主要用例是“mixin”类:向继承 mixin 的每个类添加特定的额外字段或方法。尽量使继承层次结构保持简单明了,这样就不必费力地找出特定信息来自哪里。
请注意,从具有公共 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 形成对比,后者在菱形继承的情况下以广度优先顺序解析。此差异仅影响复杂的模型层次结构,根据上述建议,应尽量避免这种情况。
在包中组织模型¶
manage.py startapp
命令创建一个应用程序结构,其中包含 models.py
文件。如果有很多模型,则将它们组织到单独的文件中可能很有用。
为此,请创建一个 models
包。删除 models.py
并创建一个 myapp/models/
目录,其中包含 __init__.py
文件以及用于存储模型的文件。必须在 __init__.py
文件中导入模型。
例如,如果在 models
目录中包含 organic.py
和 synthetic.py
from .organic import Person
from .synthetic import Robot
显式导入每个模型而不是使用 from .models import *
的优点是不使命名空间混乱,使代码更易于阅读,并保持代码分析工具的有用性。
另请参见
- 模型参考
涵盖所有与模型相关的 API,包括模型字段、相关对象和
QuerySet
。