模型是数据信息的唯一并明确的来源。它包含了我们储存的数据的基本字段和行为。通常,每个模型映射到一张数据库表。
基本概念:
- 每个模型都是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是模型(model)的字段(fields).每个字段指定为类的一个属性,每个属性映射到数据库的一列(column)。
上面的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",是从模型的元数据(metadata)自动导入的,但是可以覆写(overridden)。具体参见文档的Table names章节。
- 自动添加了一个id字段,该行为也可以被覆写。具体参见文档的Automatic primary_key fields章节
- 这是一段运用PostgreSQL语法建立数据表的SQL语句,但我们不用为此操心,针对后台settings file中设定好的数据库,Django都有量身定做的SQL。
运用模型
一旦定义好模型之后,需要告诉Django我们将使用这些模型。方法是通过编辑setting.py文件,在INSTALLED_APPS设定中添加包含了我们models.py的模块的名称。
例如,如果我们应用程序的模型存放在myapp.models模块中(该包结构在通过manage.py startapp命令创建应用程序时形成的),INSTALLED_APP应该一部分看起来如下:
INSTALLED_APPS = [ #... \'myapp\', #... ]
当添加apps到INSTALLED_APPS以后,确保要运行mangae.py migrate指令,有时候还需要先用manage.py makemigrations进行迁移。
字段(Field)
一个模型最重要也是唯一要求的部分,就是定义数据库的字段。字段是由类的属性指定的。注意不要选择与模型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 types)
Each field in your model should be an instance of the appropriate Field
class. Django uses the field class types to determine a few things:
模型的每一个字段都是相应字段类的一个实例。Django用字段类的类型来决定一些东西:
- 列类型(column type),告诉数据库储存什么样的数据(比如INTERGER, VARCHAR, TEXT 等)。
- 渲染表单字段时用默认的HTML部件(比如<input type="text">, <select>等)
- Django的管理系统(admin)和自动生成的表单中,运用最低的验证要求。
Django拥有许多内置的字段类型;完整的清单参见model field reference。如果内置的字段满足不了要求,我们也可以方便的编写自己的字段,具体参见Writing custom model fields。
字段选项(Field options)
每个字段都有一些特定的参数(参见 model field reference),例如,CharField(及其子类)要求一个最大长度参数来规定数据库VARCHAR字段的大小。
也有一些每种字段都通用的参数,都是可选的。在reference中有完整的解释,这里对最常用的一些做个快速的概览:
null
如果值为True,在数据库中Django将把空值储存为Null。默认值为False。
blank
如果值为True,字段允许为空。默认值为False。
注意它与null是不同的。null是纯粹数据库相关的,而blank是验证相关的。如果一个字段设置了blank=True, 表单验证将允许输入空值。如果设置了blank=False,该字段则是必需的。
choices
一个包含了二维元组的可迭代对象(比如,列表或者元组)作为字段的选项,默认的表单部件将从标准的文本换成选择框,选项限定为choice参数。
chiices列表看起来像这样:
YEAR_IN_SCHOOL_CHOICES = ( (\'FR\', \'Freshman\'), (\'SO\', \'Sophomore\'), (\'JR\', \'Junior\'), (\'SR\', \'Senior\'), (\'GR\', \'Graduate\'), )
每个元组中的第一个元素是将储存在数据库中的值。第二个元素将通过默认的表单部件显示,或者放在ModelChoiceField中。给出一个模型实例,可以通过get_FOO_display()方法来访问choices field正在显示的值。
示例:
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\'
- default
- 字段的默认值。可以是一个值或者一个可调用的对象。如果是后者,每次新对象创建时都会调用。
- help text
- 表单部件显示附加的帮助信息。如果字段不是用在表单上,该参数对文档还是很有用的。
- primary_key
- 如果值为True,该字段设为模型的主键。
- 如果没有指定任何字段为主键,Django会自动添加一个IntegerField做为主键。所以如果不想覆写默认的主键,可以不设定任何字段的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,该字段在表中必须是唯一的。
- 再说一次,这些只是常用字段选项的简短描述,完整的信息请查看
- common model field option reference。
自动主键字段(Automatic primary_key fields)
- 默认情况下,Django中每个模型都有以下字段:
id = models.AutoField(primary_key=True)
这是一个自动增长的主键。
如果你想指定一个自定义的主键,只要将某个字段的选项设置primary_key=True。如果Django发现你已经明确了字段的主键(Field.primary_key),它就不会再添加自动的id列。
每个模型要求必须有一个字段设定为primary_key=True(明确指定的或者自动添加的都可以)。
详细字段名称(Verbose field names)
除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会再需要的时候自动将其首字母大写。
关系(Relationships)
很明显,关系型数据库的能力来源于相互关联的表。Django提供了方法定义三种最常见的数据库关系:many-to-one, many-to-many and one-to-one。
一对多关系(Many-to-one relationships)
通过django.db.models.ForeignKey来定义一对多关系。我们可以像使用其他字段类型一样:将其作为类属性包含在我们的模型中。
外键(ForeignKey)要求一个位置参数:该模型关联到哪个类。
比如,一个厂家制造了很多汽车,但是每辆汽车只有一个厂家,可以如下定义:
from django.db import models class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) # ...
你也可以创建一个递归关系recursive relationships(一个对象有一个指向自身的外键)以及与尚未定义的模型的关系relationships to models not yet defined,具体参见he model field reference。
建议,但没规定,将关联模型的小写名称作为外键字段的名称(上一个示例中的manufacturer)。当然,你可以叫外键字段任何名字,比如{
class Car(models.Model): company_that_makes_it = models.ForeignKey( Manufacturer, on_delete=models.CASCADE, ) # ...
参考信息
外键字段接收多种其他参数,具体参见the model field reference。这些选项帮助定义关系怎么工作,都是可选的。
关于访问向后相关(backwards-related)对象的细节,参见Following relationships backward example。
关于示例代码,参见Many-to-one relationship model example。
多对多关系(Many-to-many relationships)
要定义一个多对多关系,用MangToManyField。
我们可以像使用其他字段类型一样:将其作为类属性包含在我们的模型中。
MangToManyField要求一个位置参数:该模型关联到哪个类。
比如,一个披萨有多种配料,一种配料也可以用在多个披萨上。可以这样描述:
from django.db import models class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
多对多关系中的额外字段
当你只需要处理简单的多对多关系时,比如混合搭配披萨和配料,标准的多对多字段就够了。然而,有时候你需要两个模型之间关系的辅助数据。
例如,设想有一种程序用来追踪音乐家属于哪个乐队。人和乐队之间是一种多对多的关系,所以我们可以用一个多对多字段来描述该关系。然而,还有很多其他相关信息我们也想收集,比如某人加入某个乐队的日期。
针对这种情况,Django允许我们指定模型,用来管理该多对多关系。这样我们可以在中间模型中设置额外的字段。通过设置through参数来指出哪个模型作为媒介,将中间模型与多对多字段联合起来。我们的乐队示例,代码应该差不多像这样:
from django.db import models class Person(models.Model): name = models.CharField(max_length=128) def __str__(self): # __unicode__ on Python 2 return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through=\'Membership\') def __str__(self): # __unicode__ on Python 2 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,否则会引发验证错误。
- 当使用中间模型定义模型到自身的多对多关系时,必须使用
symmetrical=False
(参照 the model field reference)。
现在我们已经设置好ManyToMany字段来使用我们的中间模型(示例中的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() 去创建关系:
>>> # The following statements will not work >>> beatles.members.add(john) >>> beatles.members.create(name="George Harrison") >>> beatles.members.set([john, paul, ringo, george])
为什么?我们不能只创建人和乐队之间的关系,我们还需要指定Membership模型要求的关系的所有信息。简单的add, create无法指定额外的信息。所以,运用中间模型的多对多关系是禁用它们的。创建这种类型关系的唯一方法是创建中间模型的实例。
出于类似的原因, remove()方法也被禁用了。因为有时候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 will not work because it cannot tell which membership to remove >>> beatles.members.remove(ringo)
然而,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]>
如果需要访问中间模型的信息,我们可以直接对中间模型进行查询:
>>> 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对象查询many-to-many reverse relationship:
>>> 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.\'
一对一关系(One-to-one relationships)
用OneToOneField定义一对一关系。像任何其他字段类型一样使用:作为属性包含在模型中。
如果一个对象继承自其他对象,一对一关系最适合用于它的主键。
OneToOneField要求一个位置参数:哪个模型是相关的。
例如,我们要建立一个关于“处所”的数据库,可能要在数据库中建立很多标准的东西,如地址、电话号码等。这时,如果你还想在这些处所的基础上再建立一个餐厅的数据库,不必在餐厅模型中再重复指定这些字段,只需要在餐厅模型中建立一个指向处所模型的一对一字段。(因为一间餐厅也是一个处所。实际上,这种情况我们通常使用继承inheritance,它是毫无疑问的一对一关系。)
跟外键一样,你也可以创建递归关系以及与尚未定义的模型的关系。
参考信息
完整实例请参见One-to-one relationship model example 。
一对一字段也接收一个可选的 parent_link
参数。
一对一字段类以前会自动成为模型的主键,现在不是这样了(尽管可以手动设置primary_key参数,如果我们想的话)。因此,现在同一个模型中可以有多个一对一字段。
跨文件的模型
访问其他应用的模型是非常容易的。 在文件顶部我们定义模型的地方,导入相关的模型就可以了。然后,无论在哪里需要的话,都可以引用它。例如:
from django.db import models from geography.models import ZipCode class Restaurant(models.Model): # ... zip_code = models.ForeignKey(ZipCode)
字段命名的限制
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 查询语句中的数据库表名和列名进行转义。 它根据你的数据库引擎使用不同的引用语法。