模型
模型准确且唯一的描述了数据。它包含您储存的数据的重要字段和行为。一般来说,每一个模型都映射一张数据库表。
基础:
- 每个模型都是一个 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
);
一些技术上的说明:
- 该表的名称
myapp_person
是自动从某些模型元数据中派生出来,但可以被改写。参阅 表名称 获取更多信息。 - 一个
id
字段会被自动添加,但是这种行为可以被改写。请参阅 自动设置主键。 - 本例子中
创建数据表
的语法是 PostgreSQL 格式的。值得注意的是,Django 依据你在 配置文件 中指定的数据库后端生成对应的 SQL 语句。
使用模型
一旦你定义了你的模型,你需要告诉 Django 你准备 使用 这些模型。你需要修改设置文件中的 INSTALLED——APPS ,在这个设置中添加包含 models.py
文件的模块名称。
例如,若模型位于项目中的 myapp.models
模块( 此包结构由 manages.py startapp 命令创建), INSTALLED_APPS 应设置如下:
INSTALLED_APPS = [
# ...
"myapp",
# ...
]
当你向 INSTALLED_APPS 添加新的应用的时候,请务必运行 manage.pymigrate,此外你也可以先使用以下命令进行迁移 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
)。 - 在渲染表单字段时默认使用的 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,则字段为必填项。
一个 2 值元组的 sequence,一个 mapping,一个 枚举类型,或者一个可调用对象(不接受参数并返回前述任意格式之一),用作此字段的选择项。如果提供了此参数,则默认的表单小部件将是选择框,而不是标准文本字段,并且将限制选择范围为给定的选择项。
一个选项列表:
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 中定义了更多的示例。
Changed in Django 5.0:
添加了对映射和可调用对象的支持。
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 字段也接受一些 模型字段参考 中介绍的参数。这些可选的参数可以更深入地规定关联关系的具体实现。
在多对多(many-to-many)关系中添加添加额外的属性字段
如果你只是想要一个类似于记录披萨和配料之间混合和搭配的多对多关系,标准的 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
参数,要不然会出现验证错误。
现在你已经设置好了你的 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 模型,你也可以将一个指向 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,
)