模型 — Django 文档
型号
模型是有关您的数据的唯一、明确的信息来源。 它包含您存储的数据的基本字段和行为。 通常,每个模型都映射到单个数据库表。
基础知识:
- 每个模型都是一个 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" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
一些技术说明:
- 表的名称
myapp_person
自动派生自某些模型元数据,但可以覆盖。 有关更多详细信息,请参阅 表名称 。 id
字段是自动添加的,但可以覆盖此行为。 请参阅 自动主键字段 。- 本示例中的
CREATE TABLE
SQL 使用 PostgreSQL 语法进行格式化,但值得注意的是,Django 使用为 设置文件 中指定的数据库后端量身定制的 SQL。
使用模型
定义模型后,您需要告诉 Django 您将 使用 这些模型。 为此,请编辑您的设置文件并更改 :setting:`INSTALLED_APPS` 设置以添加包含您的 models.py
的模块的名称。
例如,如果您的应用程序的模型存在于模块中myapp.models
(由应用程序为应用程序创建的包结构 :djadmin:`manage.py startapp ` 脚本), :设置:`INSTALLED_APPS` 部分应阅读:
INSTALLED_APPS = [
#...
'myapp',
#...
]
当您将新应用添加到 :设置:`INSTALLED_APPS` ,一定要运行 :djadmin:`manage.py 迁移 ` ,可以选择首先为他们进行迁移 :djadmin:`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 widget(例如
<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.choices, max_length=10)
模型字段参考 中提供了更多示例。
default
字段的默认值。 这可以是值或可调用对象。 如果可调用,则每次创建新对象时都会调用它。
help_text
与表单小部件一起显示的额外“帮助”文本。 即使您的字段未在表单上使用,它也可用于文档。
primary_key
如果是
True
,则该字段是模型的主键。如果你没有为模型中的任何字段指定 primary_key=True,Django 会自动添加一个 IntegerField 来保存主键,所以你不需要设置 [X182X ]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 为每个模型提供以下字段:
id = models.AutoField(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 相关联,使用 到 参数指向将充当中介的模型。 对于我们的音乐家示例,代码如下所示:
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)
设置中间模型时,您明确指定多对多关系中涉及的模型的外键。 这个显式声明定义了两个模型之间的关系。
中间模型有一些限制:
- 您的中间模型必须包含一个 - 并且 only 一个 - 源模型的外键(在我们的示例中为
Group
),或者您必须明确指定 Django 应该使用的外键使用 ManyToManyField.through_fields 的关系。 如果您有多个外键且未指定through_fields
,则会引发验证错误。 类似的限制适用于目标模型的外键(在我们的示例中为Person
)。 - 对于通过中间模型与自身具有多对多关系的模型,允许同一模型的两个外键,但它们将被视为多对多关系的两个(不同)方面。 如果 more 多于两个外键,您还必须指定
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 查询中。 它使用您的特定数据库引擎的引用语法。
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
选项的完整列表可以在 型号选项参考 中找到。
模型属性
objects
- 模型最重要的属性是 Manager。 它是向 Django 模型提供数据库查询操作的接口,用于 从数据库中检索实例 。 如果未定义自定义
Manager
,则默认名称为 objects。 管理器只能通过模型类访问,不能通过模型实例访问。
模型方法
在模型上定义自定义方法以向对象添加自定义“行级”功能。 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 '%s %s' % (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
,则可以保证您的代码在添加这些参数时会自动支持这些参数。
批量操作不会调用覆盖的模型方法
请注意,当 使用 QuerySet 批量删除对象或作为 级联删除 的结果时,不一定会调用对象的 delete() 方法。 为确保执行自定义删除逻辑,您可以使用 pre_delete 和/或 post_delete 信号。
不幸的是,在 批量创建 或 更新 对象时没有解决方法,因为 save()、pre_save 和post_save 被调用。
模型继承
Django 中模型继承的工作方式与 Python 中普通类继承的工作方式几乎相同,但仍应遵循页面开头的基础知识。 这意味着基类应该子类化 django.db.models.Model。
您必须做出的唯一决定是,您是否希望父模型本身成为模型(具有自己的数据库表),或者父模型是否只是仅通过子模型可见的公共信息的持有者。
在 Django 中有三种可能的继承风格。
- 通常,您只想使用父类来保存您不想为每个子模型输入的信息。 这个类永远不会被单独使用,所以 抽象基类 是你所追求的。
- 如果您要继承现有模型(可能完全来自另一个应用程序)并希望每个模型都有自己的数据库表,则 多表继承 是您要走的路。
- 最后,如果您只想修改模型的 Python 级行为,而不以任何方式更改模型字段,您可以使用 代理模型 。
抽象基类
当您想将一些公共信息放入许多其他模型时,抽象基类很有用。 您编写基类并将 abstract=True
放在 Meta 类中。 此模型将不会用于创建任何数据库表。 相反,当它用作其他模型的基类时,其字段将添加到子类的字段中。
一个例子:
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
上自动创建的 OneToOneField 将其链接到 Place
如下所示:
place_ptr = models.OneToOneField(
Place, on_delete=models.CASCADE,
parent_link=True,
primary_key=True,
)
您可以通过在 Restaurant
上使用 parent_link=True 声明您自己的 OneToOneField 来覆盖该字段。
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
属性 与常规模型 相同。
QuerySets 仍然返回请求的模型
每当您查询 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 控制的数据库视图和表。 - 如果您想更改模型的仅 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
来删除。
笔记
一些字段在模型上定义了额外的属性,例如 a ForeignKey 定义了一个额外的属性,在字段名称后附加 _id
,以及外国模型上的 related_name
和 related_query_name
。
这些额外的属性不能被覆盖,除非定义它的字段被更改或删除,使其不再定义额外的属性。
覆盖父模型中的字段会导致初始化新实例(指定在 Model.__init__
中初始化哪个字段)和序列化等方面的困难。 这些是普通 Python 类继承不必以完全相同的方式处理的功能,因此 Django 模型继承和 Python 类继承之间的区别不是任意的。
此限制仅适用于 Field 实例的属性。 如果您愿意,可以覆盖普通的 Python 属性。 它也仅适用于 Python 看到的属性名称:如果您手动指定数据库列名,则可以在子模型和祖先模型中出现相同的列名以进行多表继承(它们是列在两个不同的数据库表中)。
如果覆盖任何祖先模型中的任何模型字段,Django 将引发 FieldError。
在包中组织模型
这 :djadmin:`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 *
的优点是不会使命名空间混乱,使代码更具可读性,并保持代码分析工具的有用性。