模型¶
模型是有关数据的信息的单一权威来源。它包含所存储数据的必要字段和行为。通常,每个模型映射到一个数据库表。
基础知识
- 每个模型都是一个 Python 类,其子类为
django.db.models.Model
。 - 模型的每个属性都表示一个数据库字段。
- 有了这一切,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
- 字段的默认值。这可以是一个值或一个可调用对象。如果可调用,它将在每次创建新对象时被调用。
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 允许您指定用于管理多对多关系的模型。然后,您可以在中间模型上放置额外的字段。中间模型使用 through
参数与 ManyToManyField
关联,以指向将充当中介的模型。对于我们的音乐家示例,代码看起来像这样
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>]>
您还可以使用 add()
、create()
或 set()
来创建关系,只要您为任何必需字段指定 through_defaults
即可
>>> 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)}
... )
您可能更愿意直接创建中间模型的实例。
如果中间模型定义的自定义通过表不强制 (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
类型一样使用它:通过将其包含为模型的类属性。
当对象以某种方式“扩展”另一个对象时,这在对象的某个主键上最有用。
OneToOneField
需要一个位置参数:模型所关联的类。
例如,如果你正在构建一个“地点”数据库,你将在数据库中构建诸如地址、电话号码等相当标准的内容。然后,如果你想在地点之上构建一个餐厅数据库,而不是重复自己并在 Restaurant
模型中复制那些字段,你可以让 Restaurant
拥有一个 OneToOneField
到 Place
(因为餐厅“是一个”地点;事实上,为了处理这种情况,你通常会使用 继承,它涉及一个隐式的单一对一关系)。
与 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 查询中转义所有数据库表名和列名。它使用特定数据库引擎的引用语法。
自定义字段类型¶
如果现有的模型字段之一不能用于满足你的目的,或者如果你希望利用一些不太常见的数据库列类型,你可以创建自己的字段类。在 如何创建自定义模型字段 中提供了创建自己字段的完整说明。
元数据
选项¶
使用内部 class 元数据
为模型提供元数据,如下所示
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
模型元数据是“任何不是字段的内容”,例如排序选项 (排序
)、数据库表名称 (db_table
) 或人类可读的单数和复数名称 (verbose_name
和 verbose_name_plural
)。没有必需项,将 class 元数据
添加到模型是完全可选的。
可以在 模型选项参考 中找到所有可能的 元数据
选项的完整列表。
模型属性¶
模型方法¶
在模型上定义自定义方法,为对象添加自定义“行级”功能。虽然 管理器
方法旨在执行“表级”操作,但模型方法应作用于特定模型实例。
这是一种将业务逻辑保存在一个位置(模型)中的宝贵技术。
例如,此模型有一些自定义方法
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, *args, **kwargs):
do_something()
super().save(*args, **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, *args, **kwargs):
if self.name == "Yoko Ono's blog":
return # Yoko shall never have her own blog!
else:
super().save(*args, **kwargs) # Call the "real" save() method.
重要的是要记住调用超类方法,即super().save(*args, **kwargs)
业务,以确保对象仍保存到数据库中。如果您忘记调用超类方法,则默认行为不会发生,并且数据库不会受到影响。
同样重要的是,您传递可以传递给模型方法的参数,即*args, **kwargs
位的作用。Django 会时不时地扩展内置模型方法的功能,添加新参数。如果您在方法定义中使用*args, **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, force_insert=False, force_update=False, using=None, update_fields=None
):
self.slug = slugify(self.name)
if update_fields is not None and "name" in update_fields:
update_fields = {"slug"}.union(update_fields)
super().save(
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields,
)
有关更多详细信息,请参见 指定要保存的字段。
不会在批量操作中调用重写的模型方法
请注意,当 使用 QuerySet 批量删除对象 或作为 cascading delete
的结果时,对象 delete()
方法不一定被调用。要确保执行自定义删除逻辑,您可以使用 pre_delete
和/或 post_delete
信号。
遗憾的是,在 creating
或 updating
对象时没有解决方法,因为 save()
、pre_save
和 post_save
都不被调用。
模型继承¶
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")
如果您有一个同时也是 Restaurant
的 Place
,您可以使用模型名称的小写版本从 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
上自动创建的 OneToOneField
将其链接到 Place
,如下所示
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
链接子类和父类,因此可以从父类向下移动到子类,如上例所示。但是,这会用掉 related_name
的默认 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'.
向 customers
字段添加 related_name
如下所示将解决错误: models.ManyToManyField(Place, related_name='provider')
。
指定父链接字段¶
如上所述,Django 将自动创建一个 OneToOneField
,将子类链接回任何非抽象父模型。如果您想控制链接回父类的属性的名称,则可以创建自己的 OneToOneField
,并设置 parent_link=True
以指示您的字段是链接回父类的字段。
代理模型¶
使用 多表继承 时,将为模型的每个子类创建一个新的数据库表。这通常是期望的行为,因为子类需要一个地方来存储基本类中不存在的任何附加数据字段。但是,有时您只想更改模型的 Python 行为 - 可能更改默认管理器或添加新方法。
代理模型继承用于:为原始模型创建代理。您可以创建、删除和更新代理模型的实例,所有数据都将保存,就像您使用原始(非代理)模型一样。不同之处在于,您可以在代理中更改默认模型排序或默认管理器等内容,而无需更改原始内容。
代理模型声明与普通模型类似。通过将 proxy
特性 Meta
类的属性设置为 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
您可能不需要经常这样做,但是,当您需要时,这是可能的。
代理继承和非托管模型之间的差异¶
代理模型继承可能看起来与使用 managed
属性在模型的 Meta
类上创建非托管模型非常相似。
通过仔细设置 Meta.db_table
,您可以创建一个非托管模型,该模型跟踪现有模型并向其添加 Python 方法。但是,如果您进行任何更改,则需要保持这两个副本同步,因此这将非常重复且脆弱。
另一方面,代理模型旨在表现得与它们代理的模型完全相同。它们始终与父模型同步,因为它们直接继承了它的字段和管理器。
一般规则是
- 如果您要镜像现有模型或数据库表,并且不需要所有原始数据库表列,请使用
Meta.managed=False
。该选项通常适用于对数据库视图和不受 Django 控制的表的建模。 - 如果您想更改模型的仅 Python 行为,但保留与原始模型中相同的字段,请使用
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 形成对比,后者在菱形继承的情况下按广度优先解析。此差异仅影响复杂的模型层次结构,(根据上述建议)您应尽量避免这种情况。
在包中组织模型¶
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
。