Django4 中文入门教程 Django4.0 模型-字段

2024-02-25 开发教程 Django4 中文入门教程 匿名 8

模型中最重要且唯一必要的是数据库的字段定义。字段在类属性中定义。定义字段名时应小心避免使用与 模型 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​​:一系列二元组,用作此字段的选项。如果提供了二元组,默认表单小部件是一个选择框,而不是标准文本字段,并将限制给出的选项。

​​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.choices, max_length=10)
  • 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 自动设置)。

在旧版本中,自动创建的主键字段总是 ​​AutoField​​。

字段备注名

除了 ​​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​​和​​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​​的首字母大写,必要时 Djanog 会自动把首字母转换为大写。

关联关系

显然,关系型数据库的强大之处在于各表之间的关联关系。 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​​字段。

跨文件模型

关联另一个应用中的模型是当然可以的。为了实现这一点,在定义模型的文件开头导入需要被关联的模型。接着就可以在其他有需要的模型类当中关联它了。比如:

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 对模型的字段名有一些限制:

1、一个字段的名称不能是 Python 保留字,因为这会导致 Python 语法错误。比如:

class Example(models.Model):
pass = models.IntegerField() # 'pass' is a reserved word!

2、一个字段名称不能包含连续的多个下划线,原因在于 Django 查询语法的工作方式。比如:

class Example(models.Model):
foo__bar = models.IntegerField() # 'foo__bar' has two underscores!

3、字段名不能以下划线结尾,原因同上。

但是,这些限制是可以被解决的,因为字段名没要求和数据库列名一样。

SQL保留字,例如 ​​join​​, ​​where​​或 ​​select​​, 是 可以被用在模型字段名当中的,因为 Django 在对底层的 SQL 查询当中清洗了所有的数据库表名和字段名,通过使用特定数据库引擎的引用语法。

自定义的字段类型

如果已经存在的模型字段不能满足你的需求,或者你希望支持一些不太常见的数据库列类型,你可以创建自己的字段类。