文章目录
模型
模型准确且唯一的描述了数据。它包含您储存的数据的重要字段和行为。一般来说,每一个模型都是映射一张数据库表。
基础:
- 每个模型都是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,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
一些技术上的说明:
- 该表的名称
myapp_person
是自动从某些模型元数据中派生出来,但可以被改写。 - 一个
id
字段会被自动添加,但是这种行为可以被改写。
使用指定表名称
用于模型的数据库表的名称:
db_table = 'music_album'
主键定义
默认情况下,Django 给每个模型一个自动递增的主键,其类型在 AppConfig.default_auto_field
(对应apps中) 中指定,或者在 DEFAULT_AUTO_FIELD
(setting中) 配置中全局指定。例如:
id = models.BigAutoField(primary_key=True)
如果你想自己指定主键, 在你想要设置为主键的字段上设置参数 primary_key=True
。如果 Django 看到你显式地设置了 Field.primary_key
,将不会自动在表(模型)中添加 id 列。
每个模型都需要拥有一个设置了 primary_key=True
的字段(无论是显式的设置还是 Django 自动设置)。
使用模型
一旦你定义了你的模型,你需要告诉 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()
字段参数
每一种字段都需要指定一些特定的参数(参考 模型字段 )。 例如, CharField
(以及它的子类)需要接收一个 max_length
参数,用以指定数据库存储 VARCHAR
数据时用的字节数。
null
如果设置为 True,当该字段为空时,Django 会将数据库中该字段设置为 NULL。默认为 False 。
blank
如果设置为 True,该字段允许为空。默认为 False。
注意该选项与 null 不同, null 选项仅仅是数据库层面的设置,而 blank 是涉及表单验证方面。如果一个字段设置为 blank=True ,在进行表单验证时,接收的数据该字段值允许为空,而设置为 blank=False 时,不允许为空。
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.choices, max_length=10)
default
该字段的默认值。可以是一个值或者是个可调用的对象,如果是个可调用对象,每次实例化模型时都会调用该对象。
help_text
额外的“帮助”文本,随表单控件一同显示。即便你的字段未用于表单,它对于生成文档也是很有用的
primary_key
如果设置为 True ,将该字段设置为该模型的主键。
在一个模型中,如果你没有对任何一个字段设置 primary_key=True 选项。 Django 会自动添加一个 IntegerField 字段,并设置为主键,因此除非你想重写 Django 默认的主键设置行为,你可以不手动设置主键。详情请见 自动设置主键 。
主键字段是只可读的,如果你修改一个模型实例的主键并保存,这等同于创建了一个新的模型实例。例如:
unique
如果设置为 True,这个字段的值必须在整个表中保持唯一。
字段备注名
除了 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",
)
关联关系
显然,关系型数据库的强大之处在于各表之间的关联关系。 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,
)
# ...
多对多关联
定义一个多对多的关联关系,使用 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 的表单时用户可以选择多种配料。
在多对多(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 字段。
在本例中,一个 Place 可是一个 Restaurant:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return "%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
primary_key=True,
)
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
下面是可以使用PythonAPI工具执行的操作示例。
创建几个 Place:
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()
创建一个 Restaurant。传递 “父” 对象作为该对象的主键:
>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()
餐厅可以获取所在地:
>>> r.place
<Place: Demon Dogs the place>
地点可以访问关联的餐厅(如果有的话):
>>> p1.restaurant
<Restaurant: Demon Dogs the restaurant>
p2 没有关联餐厅:
>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>> p2.restaurant
>>> except ObjectDoesNotExist:
>>> print("There is no restaurant here.")
There is no restaurant here.
您还可以使用 hasattr 来免除异常捕获:
>>> hasattr(p2, 'restaurant')
False
使用赋值符号来设置地方。因为地方是餐厅的主键,保存将创建一个新的餐馆:
>>> r.place = p2
>>> r.save()
>>> p2.restaurant
<Restaurant: Ace Hardware the restaurant>
>>> r.place
<Place: Ace Hardware the place>
再次设置地方,使用相反方向的赋值:
>>> p1.restaurant = r
>>> p1.restaurant
<Restaurant: Demon Dogs the restaurant>
跨文件模型
关联另一个应用中的模型是当然可以的。为了实现这一点,在定义模型的文件开头导入需要被关联的模型。接着就可以在其他有需要的模型类当中关联它了。比如:
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,
)