模型准确且唯一的描述了数据。它包含您储存的数据的重要字段和行为。一般来说,每一个模型都映射一张数据库表。
基础:
django.db.models.Model
这个样例定义了一个 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
, or 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
)。<input type="text">
, <select>
)。Django 内置了数十种字段类型;你可以在 模型字段参考 中看到完整列表。如果 Django 内置类型不能满足你的需求,你可以很轻松地编写自定义的字段类型;参见 编写自定义模型字段。
每一种字段都需要指定一些特定的参数(参考 模型字段 )。 例如, CharField
(以及它的子类)需要接收一个 max_length
参数,用以指定数据库存储 VARCHAR
数据时用的字节数。
一些可选的参数是通用的,可以用于任何字段类型,详情请见 参考 ,下面介绍一部分经常用到的通用参数:
null
True
,当该字段为空时,Django 会将数据库中该字段设置为 NULL
。默认为 False
。blank
如果设置为 True
,该字段允许为空。默认为 False
。
注意该选项与 null
不同, null
选项仅仅是数据库层面的设置,而 blank
是涉及表单验证方面。如果一个字段设置为 blank=True
,在进行表单验证时,接收的数据该字段值允许为空,而设置为 blank=False
时,不允许为空。
choices
一系列二元组,用作此字段的选项。如果提供了二元组,默认表单小部件是一个选择框,而不是标准文本字段,并将限制给出的选项。
一个选项列表:
YEAR_IN_SCHOOL_CHOICES = [
("FR", "Freshman"),
("SO", "Sophomore"),
("JR", "Junior"),
("SR", "Senior"),
("GR", "Graduate"),
]
备注
每当 choices
的顺序变动时将会创建新的迁移。
每个二元组的第一个值会储存在数据库中,而第二个值将只会用于在表单中显示。
对于一个模型实例,要获取该字段二元组中相对应的第二个值,使用 get_FOO_display()
方法。例如:
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)
model field reference 中定义了更多的示例。
default
help_text
primary_key
如果设置为 True
,将该字段设置为该模型的主键。
在一个模型中,如果你没有对任何一个字段设置 primary_key=True
选项。 Django 会自动添加一个 IntegerField
字段,并设置为主键,因此除非你想重写 Django 默认的主键设置行为,你可以不手动设置主键。详情请见 自动设置主键 。
主键字段是只可读的,如果你修改一个模型实例的主键并保存,这等同于创建了一个新的模型实例。例如:
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
的字段(无论是显式的设置还是 Django 自动设置)。
除了 ForeignKey
, ManyToManyField
和 OneToOneField
,任何字段类型都接收一个可选的位置参数 verbose_name
,如果未指定该参数值, 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
and 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
制造许多辆车,但是每辆车都仅有一个制造者-- 那么使用下面的方法定义这个关系:
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
字段还可以接收一些其他的参数,详见 模型字段参考 ,这些可选的参数可以更深入的规定关联关系的具体实现。
关于反向关联对象的细节,参见 反向关联例子。
如要查看相关示例代码,详见 模型多对一关联实例 。
定义一个多对多的关联关系,使用 django.db.models.ManyToManyField
类。就和其他 Field
字段类型一样,只需要在你模型中添加一个值为该类的属性。
ManyToManyField
类需要添加一个位置参数,即你想要关联的模型类名。
例如:如果 Pizza
含有多种 Topping
(配料) -- 也就是一种 Topping
可能存在于多个 Pizza
中,并且每个 Pizza
含有多种 Topping
--那么可以这样表示这种关系:
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
参数在多个外键当中手动选择一个外键,如果有多个外健且没有用 through_fields
参数选择一个的话,会出现验证错误。对于指向目标模型(我们例子当中的 Person
)的外键也有同样的限制。through_fields
参数,要不然会出现验证错误。Now that you have set up your ManyToManyField
to use
your intermediary model (Membership
, in this case), you're ready to start
creating some many-to-many relationships. You do this by creating instances of
the intermediate model:
>>> 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>]>
You can also use add()
,
create()
, or
set()
to create
relationships, as long as you specify through_defaults
for any required
fields:
>>> 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)}
... )
你可能更倾向直接创建中间模型。
If the custom through table defined by the intermediate model does not enforce
uniqueness on the (model1, model2)
pair, allowing multiple values, the
remove()
call will
remove all intermediate model instances:
>>> 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>]>
The clear()
method can be used to remove all many-to-many relationships for an instance:
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>
Once you have established the many-to-many relationships, you can issue queries. Just as with normal many-to-many relationships, you can query using the attributes of the many-to-many-related model:
# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith="Paul")
<QuerySet [<Group: The Beatles>]>
As you are using an intermediate model, you can also query on its attributes:
# 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]>
If you need to access a membership's information you may do so by directly
querying the Membership
model:
>>> 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.'
Another way to access the same information is by querying the
many-to-many reverse relationship from a
Person
object:
>>> 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
模型,你也可以将一个指向 Place
OneToOneField
放到 Restaurant
当中(因为餐厅“是一个”地点);事实上,在处理这样的情况时最好使用 模型继承 ,它隐含的包括了一个一对一关系。
和 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 对模型的字段名有一些限制:
A field name cannot be a Python reserved word, because that would result in a Python syntax error. For example:
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
选项¶使用内部 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
)。这些都不是必须的,并且在模型当中添加 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}"
例子中最后一个方法是 property。
文档 模型实例参考 包含了 自动提供给每个模型的方法 的完整列表。你可以复写大部分的方法,参考下面的 overriding predefined model methods ——下面介绍两个你最可能期望复写的:
__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
,这将确保你的方法能接受这些新加的参数。
If you wish to update a field value in the save()
method, you may
also want to have this field added to the update_fields
keyword argument.
This will ensure the field is saved when update_fields
is specified. For
example:
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,
)
See 指定要保存的字段 for more details.
重写的模型方法不会在批量操作中调用
注意,删除一个模型对象不总是要调用 delete()
方法。例如, ref:使用 QuerySet 批量删除对象 <topics-db-queries-delete> 和 级联删除
。为了确保自定义的删除逻辑被执行,你可以使用 pre_delete
和 post_delete
信号。
不幸的是,批量 creating
和 updating
操作不支持上述操作,因为这两种操作未调用 save()
,pre_save
和 post_save
。
模型继承在 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 级抽出公共信息的方法,但仍会在子类模型中创建数据表。
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)
All of the fields of Place
will also be available in Restaurant
,
although the data will reside in a different database table. So these are both
possible:
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
If you have a Place
that is also a Restaurant
, you can get from the
Place
object to the Restaurant
object by using the lowercase version of
the model name:
>>> 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
属性。假如你忘了,Django 会抛出一个合法性错误。
比如,让我们用上面的 Place
类创建另一个子类,包含一个 ManyToManyField
:
class Supplier(Place):
customers = models.ManyToManyField(Place)
This results in the error:
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
The MyPerson
class operates on the same database table as its parent
Person
class. In particular, any new instances of Person
will also be
accessible through MyPerson
, and vice-versa:
>>> 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
查询结果不会被排序,但 OrderdPerson
查询接轨会按 last_name
排序。
代理模型继承“Meta”属性 和普通模型一样。
QuerySet
仍会返回请求的模型¶当你用 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
。
Note that because of the way fields are resolved during class definition, model fields inherited from multiple abstract parent models are resolved in a strict depth-first order. This contrasts with standard Python MRO, which is resolved breadth-first in cases of diamond shaped inheritance. This difference only affects complex model hierarchies, which (as per the advice above) you should try to avoid.
manage.py startapp
命令创建了一个应用结构,包含一个 models.py
文件。若你有很多 models.py
文件,用独立的文件管理它们会很实用。
为了达到此目的,创建一个 models
包。删除 models.py
,创建一个 myapp/models
目录,包含一个 __init__.py
文件和存储模型的文件。你必须在 __init__.py
文件中导入这些模块。
比如,若你在 models
目录下有 organic.py
和 synthetic.py
:
myapp/models/__init__.py
¶from .organic import Person
from .synthetic import Robot
显式导入每个模块,而不是使用 from .models import *
有助于不打乱命名空间,使代码更具可读性,让代码分析工具更有用。
参见
QuerySet
。12月 05, 2023