django官方文档——模型与数据库
2012-08-31 09:51
633 查看
模型和数据库¶
一个模型是你的数据的唯一的,精确的数据来源。它包含你所储存的数据的字段的本质和行为。通常每个模型对应一个数据库中的表。模型
构造查询
统计
管理器
执行原始 SQL 查询
管理数据库事务
使用多个数据库
数据库访问优化
模型¶
一个模型是你的数据的唯一的,精确的数据来源。它包含你所储存的数据的字段的本质和行为。通常每个模型对应一个数据库中的表。基本上:
每一个模型是一个由
django.db.models.Model 继承而来的子类。
模型的每一个属性对应一个数据库中的字段。
Django 会为你的模型自动生成数据库操作的 API ;详见
构造查询 。
See also
本文档有一篇名为
模型例子的官方文档 姐妹文档。(在 Django 的源代码中,这些例子在 tests/modeltests 目录下。)
快速例子¶
以下例子定义了一个关于人( 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" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL );
一些技术说明:
数据库表的名称 myapp_person 是自动根据模型的元数据创建的,创建的方法可以重载。详见
Table names 。
自动添加了一个 id 字段,这种行为也是可以重载的。参见
自动主键字段 。
上例中的 CREATE
TABLE SQL 使用的是 PostgreSQL 语法,但值得指出的是 Django 会根据你的
设置文件 中设置的数据库类型在后台生成相应的 SQL 语句。
使用模型¶
一旦定义好了你的模型,就得告诉 Django 你要 使用 这些模型。操作方法是编辑你的设置文件,在INSTALLED_APPS 设置中增加你的
models.py 。
例如,如果你的应用中模型放在 mysite.myapp.models 模块(由
manage.py
startapp 语句创建的包结构)中,那么
INSTALLED_APPS 应当包含如下内容:
INSTALLED_APPS = ( #... 'mysite.myapp', #... )
每次向
INSTALLED_APPS 增加新的应用后,必须执行
manage.py
syncdb 。
字段¶
一个模型最重要且不可或缺的部分就是数据库中定义的字段的列表。字段以类的属性的方式定义。例如:
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) name = models.CharField(max_length=100) release_date = models.DateField() num_stars = models.IntegerField()
字段类型¶
你的模型中的每一个字段都应当是相应的Field 类的实例。 Django 使用字段类来决定以下事项:
数据库中的列的类型(如 INTEGER 、
VARCHAR )。
在 Django 管理站点中使用的
控件 (如 <input
type="text"> 、 <select> )。
在 Django 管理站点和自动创建的表单中使用的数据验证方式。
Django 带有许多内置的字段类型,你可以在
模型字段参考 中找到完整的清单。如果内置的字段类型不够用,你可以方便地创建自定义的字段类型,参见
Writing custom model fields 。
字段参数¶
每种字段都有一些特定的参数,参见模型字段参考 。例如
CharField (包括其子类)用于数据中的
VARCHAR 字段,这个类需要一个定义长度的
max_length 参数。
还有一些通用于各种字段的参数,这些参数都是可选参数。详见
参考 ,以下是这些参数中最常用参数的概述:
null如果值为 True ,那么 Django 在数据库中储存空值,即
NULL 。缺省值为
False 。blank
如果值为 True ,那么 field 允许为空。缺省值为
False 。
注意这个参数与
null 是不同的。
null 是与数据库相关的,而
blank 是与验证相关的。如果一个字段设置为
blank=True ,那么在 Django 的管理站点中允许输入项为空;如果设置为
blank=False ,那么这个字段为必填项。
choices
一个用于该字段的选择项的可迭代对象(例如 list 或 tuple ),由二元 tuple 组成。如果有这个参数,那么 Django 管理站点中这个字段的输入会用一个选择框来代替文本框。
一个 choices 列表例子如下:
YEAR_IN_SCHOOL_CHOICES = ( (u'FR', u'Freshman'), (u'SO', u'Sophomore'), (u'JR', u'Junior'), (u'SR', u'Senior'), (u'GR', u'Graduate'), )
每个 tuple 第一个元素该选项是要储存的实际值,第二个元素管理站点或 ModelChoiceField 中要显示的内容。通过使用
get_FOO_display 可以得到一个模型对象要显示的值。例如:
from django.db import models class Person(models.Model): GENDER_CHOICES = ( (u'M', u'Male'), (u'F', u'Female'), ) name = models.CharField(max_length=60) gender = models.CharField(max_length=2, choices=GENDER_CHOICES)
>>> p = Person(name="Fred Flinstone", gender="M") >>> p.save() >>> p.gender u'M' >>> p.get_gender_display() u'Male'
default字段的缺省值。可以是一个值或一个可调用的对象。如果是一个可调用的对象,那么每次创建一个新对象时都会调用这个对象一次。help_text显示在对象的 admin 表单中的 field 下的额外的“帮助”文本。即使你不使用 admin 表单,这个对于程序说明也有很大帮助。primary_key
如果值为 True ,那么这个字段就是模型的主键。
如果你不在任何一个字段定义 primary_key=True ,那么 Django 自动添加一个
IntegerField 作为 primary key 。所以除非你想重载缺省的 primary-key ,你不必在任何一个 field 设置
primary_key=True 。更多内容参见
自动主键字段 。
unique如果值为 True ,那么这个 field 在表中只能有唯一值。
重复一下,以上只是最常用的字段参数的简要说明。完整的细节参见
常用模型字段参数参考 。
自动主键字段¶
缺省情况下, Django 给每一个模型添加下面的字段:id = models.AutoField(primary_key=True)
这是一个自增主键。
如果你想要自定义主键,那么只要在你想要的字段上定义
primary_key=True 就可以了。即如果 Django 发现你显式的设置了
Field.primary_key ,那么就不会自动增加
id 列了。
每个模型都需要一个有
primary_key=True 属性的字段。
详细字段名称¶
除了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, verbose_name="the related poll") sites = models.ManyToManyField(Site, verbose_name="list of sites") place = models.OneToOneField(Place, verbose_name="related place")
按照惯例,
verbose_name 开头第一个字母是不大写的。 Django 会在恰当的时候自动处理开头字母大写。
关系¶
显然,关系型数据库的关键是表与表之间的关系。 Django 提供了三种常用的的关系:多对一、多对多、一对一。多对一关系¶
要定义多对一关系,请使用django.db.models.ForeignKey 。使用方法与其他
Field 类型相同:把它作为你的模型的一个类属性。
ForeignKey 需要一个位置参数:相关联的类。
例如,一个 汽车(
Car ) 模型有一个 厂商(Manufacturer
) 字段。一个 厂商 可以对应多个汽车,但一个
汽车 只有一个
厂商 。定义如下:
class Manufacturer(models.Model): # ... class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer) # ...
你也可以创建一个
递归关系 (一个对象对于其本身有多对一关系)和
对应于未创建模型的关系 ,详见
模型字段关系参考 。
只是作为一个建议,但不是规定,一个
ForeignKey 字段(如上例中的
厂商 )的名称应当与相应模型的名称相同,并且小写。当然,你也可以定义为任何名称,例如:
class Car(models.Model): company_that_makes_it = models.ForeignKey(Manufacturer) # ...
See also
ForeignKey 也可以接受许多额外的参数,详见
模型字段参考 。这些参数定义了关系如何工作,且都是可选参数。
回溯关系对象参见
根据关系回溯举例.
更多例子参见
多对一关系模型测试 。
多对多关系¶
要定义多对多关系,请使用ManyToManyField 。使用方法与其他
Field 类型相同:把它作为你的模型的一个类属性。
ManyToManyField
需要一个位置参数:相关联的类。
例如,假设一个 比萨饼(
Pizza ) 有多个 调料(
Topping ) 对象,一个
调料 对应多个比萨饼并且每个 比萨饼 有多个调料。定义如下:
class Topping(models.Model): # ... class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
就象
ForeignKey 一样,你也可以创建
递归关系 (一个对象对于其本身有多对多关系)和
对应于未创建模型的关系 ,详见
模型字段关系参考 。
只是作为一个建议,但不是规定,一个
ManyToManyField (如上例中的
调料 )的名称应当是相关模型对象的复数。
不管在哪个模型中使用
ManyToManyField ,但是你只需要在一个模型中使用,不需要两个模型中都使用。
通常,如果你想使用管理站点,那么
ManyToManyField 实例应当在准备中管理站点中编辑的对象中使用。在上例中,
调料 在
比萨饼 中定义(比 调料 有一个关于
比萨饼 的
ManyToManyField 好)。因为一个比萨饼有多种调料比一个调料有多个比萨饼要自然一点。根据上例中的定义,在
比萨饼 管理表单中用户可以选择不同的调料。
See also
更多例子参见
多对多关系模型举例 。
ManyToManyField
也可以接受许多额外的参数,详见
模型字段参考 。这些参数定义了关系如何工作,且都是可选参数。
多对多关系上的扩展字段¶
当你处理如上例中的比萨饼和调料这种简单的多对多关系时,标准的ManyToManyField 就够用了。然而,有时你需要处理两个模型之间的相关联的数据,标准的类就不够用了。
例如,假设有一个管理音乐团体和音乐家的应用程序。这个程序中有一个人与其所属团体之间的关系,所以你可以使用一个
ManyToManyField 来表现这种关系。然而,你可能要搜集一个成员的许多信息,如成员加入团体的时间。
对于这种情况,在 Django 中你可以定义中介模型专门用于处理关系的模型。你可以把扩展的字段放在中介模型中。中介模型是通过使用
ManyToManyField 的
through 属性来定义的。对于上述的音乐家的例子,代码如下:
class Person(models.Model): name = models.CharField(max_length=128) def __unicode__(self): return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __unicode__(self): return self.name class Membership(models.Model): person = models.ForeignKey(Person) group = models.ForeignKey(Group) date_joined = models.DateField() invite_reason = models.CharField(max_length=64)
在你的中介模型中,你必须把多对多关系中的两个模型显式定义为外键,以明确相关模型是如何关联的。
中介模型有一些规定:
你的中模型中必须有且仅有一个目标模型外键(如上例中的 Person )。如果定义了多个,会产生验证错误。
你的中模型中必须有且仅有一个来源模型外键(如上例中的 Group )。如果定义了多个,会产生验证错误。
唯一的例外是,当一个模型通过中介模型来表现相对于自身的多对多关系时,两个外键可以指向同一个模型,但是这两个外键会被当作多对多关系的两面来对待。
当使用中介模型来表现相对于自身的多对多关系时,你 必须 使用
symmetrical=False (参见
模型字段参考 )。
现在你已经通过使用中介模型(本例中的 Membership )来表现
ManyToManyField ,下面通过创建中介模型的实例来创建一些多对多关系:
>>> 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() [<Person: Ringo Starr>] >>> ringo.group_set.all() [<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() [<Person: Ringo Starr>, <Person: Paul McCartney>]
和一般多对多关系字段不同,你 不能 使用 add 、
create 或赋值(例如:
beatles.members =
[...] )来创建关系:
# 这样是不对的 >>> beatles.members.add(john) # 这样也不对 >>> beatles.members.create(name="George Harrison") # 这样还是不对 >>> beatles.members = [john, paul, ringo, george]
为什么?因为你不能只创建 Person 和
Group 之间的关系,你还必须去定义
Membership 模型所需要的全部细节内容。而简单的
add 、 create 和赋值无法做到这点。所以使用中介模型不能这样创建关系,只能通过创建中介模型的实例来创建关系。
同理,
remove() 方法也不可以。然而
clear() 方法可以用于移除一个实例的所有多对多关系:
# Beatles 中所有关系都被清除 >>> beatles.members.clear()
一旦你通过创建中介模型实例的方式创建了关系,就可以进行查询了。和一般多对多关系一样,你可以使用多对多关系模型的属性来查询:
# 查找所有包含名字以 'Paul' 开头的组员的组 >>> Group.objects.filter(members__name__startswith='Paul') [<Group: The Beatles>]
当你使用中介模型时,你还可以通过相关模型的属性来查询:
# 查找所有 Beatles 的 1961 年 1 月 1 日以后加入的成员 >>> Person.objects.filter( ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1)) [<Person: Ringo Starr]
一对一关系¶
要定义一对一关系,请使用OneToOneField 。使用方法与其他
Field 类型相同:把它作为你的模型的一个类属性。
这种关系常用于通过主键来扩展相关的对象。
OneToOneField
需要一个位置参数:相关联的类。
例如,假设你要建立一个“场所”数据库,你会在数据库中创建一些常用的内容,如地址、电话号码等等。接着,假设你要创建一个基于场所的餐厅数据库,那么你就不必重复创建场所数据库中已有的内容,而只要在
Restaurant 模型中建立一个与
Place 关联的
OneToOneField 就可以了。因为餐厅是场所的一种。实际上,要处理这种情况,最好使用
继承 。继承暗含了一对一关系。
就象
ForeignKey 一样,你也可以创建
递归关系 (一个对象对于其本身有多对一关系)和
对应于未创建模型的关系 ,详见
模型字段关系参考 。
See also
更多例子参见
一对一关系模型举例 。
New in Django 1.0:
Please, see the release notes
OneToOneField
字段还可以接受一个
模型字段参考 中说明的可选参数。
OneToOneField
类通常自动成为一个模型的主键。但是你也可以通过
primary_key 参数来手动设置。现在也可以在一个模型中使用多个
OneToOneField 。
使用不同文件中的模型¶
关联不同应用之中模型是非常方便的。只要在模型的开头导入要关联的模型,就可以引用在本模型中引用了。例如: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' 是一个保留字!
字段名不能包含一个以上的下划线,这是 Django 的查询搜索语法所决定的。例如:
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' 有两个下划线!
以上限制是可以绕过的,因为字段名称与数据库中的列名称不必相同。参见
db_column 选项。
SQL 保留字,如 join 、
where 或 select ,
允许 作为模型字段的名称。因为 Django 在背后生成的 SQL 查询语句中对所有数据库表名和列名都进行了转义,这些 SQL 语句针对特定的数据库使用引号语法。
自定义字段类型¶
如果现有的模型字段类型无法满足你的需要,或者你想要使用冷门的数据库列类型,那么你可以创建你自己的字段类。详见Writing custom model fields 。
元数据选项¶
通过内置的 classMeta 可以为你的模型加入元数据,就象下面的例子:
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 )。这些参数都是可选的,包括向模型中添加
class Meta 也是可选的。
所有可用的 Meta 选项列表可以在
模型选项参考 中找到。
模型方法¶
在模型中添加自定义方法可以为对象增加自定义的“行级别”的功能。鉴于Manager 方法尝试做“表级别”的工作,模型中的方法应当只能作用于特定的模型实例。
把事务逻辑集中在一个地方(模型)中是一个有用的技术。
例如以下模型包含一些自定义方法:
from django.contrib.localflavor.us.models import USStateField class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField() address = models.CharField(max_length=100) city = models.CharField(max_length=50) state = USStateField() # Yes, this is America-centric... def baby_boomer_status(self): "Returns the person's baby-boomer status." import datetime if datetime.date(1945, 8, 1) <= self.birth_date <= datetime.date(1964, 12, 31): return "Baby boomer" if self.birth_date < datetime.date(1945, 8, 1): return "Pre-boomer" return "Post-boomer" def is_midwestern(self): "Returns True if this person is from the Midwest." return self.state in ('IL', 'WI', 'MI', 'IN', 'OH', 'IA', 'MO') def _get_full_name(self): "Returns the person's full name." return '%s %s' % (self.first_name, self.last_name) full_name = property(_get_full_name)
上例中最后一个方法是一个
property 。
阅读更多关于属性的内容 。
模型实例参考 中有
每个模型内置的方法 的完整列表。你可以重载其中的大部分(参见
重载预定义的模型方法 ),以下方法是几乎总是要定义的:
__unicode__()
一个 Python 的“魔力方法”,返回一个对象的 unicode 格式的“描述”。当一个模型实例需要表达或显示为纯字符串时, Python 和 Django 就会调用这个方法。即当你要在终端或管理站点中显示一个对象时就会用到这个方法。
你将总是要定义这个方法,缺省的不怎么有用。
get_absolute_url()
这个方法告诉 Django 如何给出一个对象的 URL 。 Django 在管理站点中和任何需要给出一个对象的 URL 时会使用这个方法。
任何有 URL 的对象应当在这个方法中定义唯一标识。
重载预定义的模型方法¶
还有一组定义数据库行为的模型的方法 你会想要自定义。通常你会想要改变保存(
save() )和删除(
delete() )的工作方式。
你可以随心所欲地重载这些方法(包括其他模型方法)以改变数据库的行为。
一种典型的情况是当你在保存对象时你想做一些其他工作,这时就需要重载方法。例如(相关参数参见
save() ):
class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def save(self, *args, **kwargs): do_something() super(Blog, self).save(*args, **kwargs) # 调用“真正”的 save() 方法。 do_something_else()
你也可以通过重载来阻止保存:
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 不能有自己的博客! else: super(Blog, self).save(*args, **kwargs) # 调用“真正”的 save() 方法。
有一点非常重要:调用超类方法(即 super(Blog,
self).save(*args, **kwargs) )来保证对象被存入数据库。如果你忘了调用超类方法,缺省的行为不会发生且数据库也不会变化。
同样重要的是传递 *args,
**kwargs 参数。 Djaong 经常会扩展内建模型方法,增加新的参数,所以我们在方法定义中使用
*args, **kwargs 作为参数,这样就可以保证当参数增加时我们的方法可以自动适应。
重载删除
注意一个对象的
delete() 方法在
使用一个查询集成批删除对象 时是不调用的。为保证自定义的删除方法被执行,可以使用
pre_delete 和/或
post_delete 信号。
执行自定义 SQL¶
另一个常见的需求是要在模型方法和模块级别方法中使用自定义的 SQL 。更多使用原始 SQL 的内容参见使用原始 SQL 。
模型继承¶
Djaongo 中的模型继承的工作方式与 Python 中一般的类继承方式是基本一样的。你唯一要决定的是你想要父模型只有自身的权利(有自己的数据库表)还是只管理只能通过子模型可见的一般信息。Django 中可能出现的继承有以下三种:
通常,你想到父模型管理不同子模型共有的相同信息。这种父模型不会单独使用。这情况你需要要
抽象基类 。
如果你要继承一个已有的模型(也许是另一个应用的模型),且每个模型都有自己的数据库表,那么你需要
多表继承 。
最后,如果你只要修改模型的 Python 级别的行为,而不要改变模型的字段,你可以使用
代理模型 。
抽象基类¶
当许多不同模型要使用相同的信息进,抽象基类非常有用。抽象基类的写法是在元数据 中放入 abstract=True ,这样这个模型就不会用于创建数据库表了,取而代之的是当这个模型被用作其他模型的基类时,其字段会被添加到其子类之中。基类和子类的类名称不能相同,否则 Django 会报错(抛出例外)。
一个例子:
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
) 模型将会有三个字段: name 、
age 和
home_group 。``一般信息( CommonInfo )`` 不能作为一个一般 Django 模型使用,因为这是一个抽象基类。抽象基类不会生成数据库表,没有管理器,不能实例化,也不能直接存储。
在多数情况下,这种类型的继承就是最适用的。这种继承提供一种在 Python 级别提取相同信息,同时在数据库级别每个子模型只创建一个数据库表的方法。
Meta 继承¶
当一个抽象基类被创建后, Django 把任何该类中内置的元数据 都作为一个属性。如果该类的子类没有声明其自己的
元数据 ,那么子类将会继承父类的
元数据 。如果子类希望扩展父类的
元数据 ,可以继承父类的元数据类,然后扩展。例如:
class CommonInfo(models.Model): ... class Meta: abstract = True ordering = ['name'] class Student(CommonInfo): ... class Meta(CommonInfo.Meta): db_table = 'student_info'
在 Django 中,对于抽象基类的
元数据 有一个调整:在安装
元数据 属性前,设置 abstract=False 。这就意味着抽象基类的子类不会自动成为抽象基类。当然,你可以把抽象基类的子类也变成抽象基类,只要显式地声明
abstract=True 就行了。
一些属性不应当在抽象基类的
元数据 中使用。例如,如果使用 db_table ,那么其所有子类(没有在
元数据 中定义自己的数据库表的)都会使用同一个数据库表,多数情况下这不会是你要的结果。
小心 related_name¶
如果在一个 ForeignKey 或ManyToManyField 上使用
related_name 属性,那么你一定要给字段定义一个
唯一 的反向名称。否则,在抽象基类中常常会出问题,因为抽象基类中的字段会包含到其子类中,造成重名。
Changed in Django 1.2:
Please, see the release notes
如果要解决这个问题,那么当你在(仅在)抽象基类中使用
related_name 时,名称应当包含
'%(app_label)s' 和
'%(class)s' 。
'%(class)s' 会被所用字段的子类的小写名称所替换。
'%(app_label)s' 会被包含子类的应用的小写名称所替换。每个已安装的应用名称应当是唯一的,并且每个应用中的模型类名称也应当是唯一的,因此最后形成的名称也应当是唯一的。
例如,有一个应用 common/models.py:
class Base(models.Model): m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related") class Meta: abstract = True class ChildA(Base): pass class ChildB(Base): pass
与另一个应用 rare/models.py:
from common.models import Base class ChildB(Base): pass
common.ChildA.m2m 字段的反向名为
childa_related ,而
ChildB.m2m 字段的反向名为 common.childb_related 。最后,
rare.ChildB.m2m 的反向名为
rare_childb_related 。是否使用
'%(class)s' 和 '%(app_label)s 来构建关联名称由你决定,但是如果不使用,那么当你验证你的模型(或运行
syncd 时)会产生错误。
如果你没有定义抽象基类中的字段的
related_name 属性,并且如果你没有在子类中直接声明反向名称,那么缺省的反向名称将会是子类名加上
'_set' ,就像你直接在子类中直接声明字段一样。例如在上面的代码中,如果
related_name 属性被省略了,那么
ChildA 的
m2m 字段的反向名称将会是 childa_set ,而
ChildB 的是
childb_set 。
多表继承¶
多表继承是 Django 支持的第二种模型继承类型,这种继承类型中的模型是相对独立的。即每个模型都有自己对应的可以查询和分别创建的数据库表。这种继承会引进子模型与每一个父模型之间的链接(通过一个自动创建的OneToOneField )。例如:
class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(Place): serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField()
所有 地点(
Place ) 中的字段在 餐厅(
Restaurant ) 都能使用,但是两者的数据会储存在不同的数据库表中。因此以下代码都是可用的:
>>> Place.objects.filter(name="Bob's Cafe") >>> Restaurant.objects.filter(name="Bob's Cafe")
如果你有一个 地点 同时也是一个
餐厅 ,你可以通过小写的模型名称用 地点 对象引用
餐厅 对象:
>>> p = Place.objects.get(id=12) # 如果 p 是一个餐厅对象,以下代码将会给出子类: >>> p.restaurant <Restaurant: ...>
然而,如果上例中的 p 不是 一个
餐厅 (直接作为一个
地点 对象创建或是其他类的父类),那么引用
p.restaurant 将会抛出一个 Restaurant.DoesNotExist 例外。
Meta 和多表继承¶
在多表继承的情况下,子类继承父类的元数据 是没有意义的,只会导致矛盾行为。这与抽象基类是不同的,因为抽象基类没有自己的数据库表。
所以子模型不能引用其父模型的
元数据 类。然而,也有一些例外:如果子类没有定义一个
ordering 属性或一个 ~:attr:django.db.models.Options.get_latest_by 属性,那么子类会从父类继承这些属性。
如果父类有一个排序属性,而子类要取消这个属性,可以如下显式定义:
class ChildModel(ParentModel): ... class Meta: # Remove parent's ordering effect ordering = []
继承和回溯关系¶
因为多表继承使用一个隐藏的OneToOneField 来关联父类和子类,所以可以由父类移动到子类,就像上例中一样。然而,这样使用的是
django.db.models..ForeignKey 和
django.db.models.ManyToManyField 关系的缺省的
related_name 的值。如果你要把这些类型的关系放在一个其他模型的子类上,你
必须 在每一个字段上定义
related_name 属性。否则,当你运行
validate or
syncdb 时会产生一个错误。
例如,还是使用上面的 Place 类,让我们用
ManyToManyField 创建另一个子类:
class Supplier(Place): # Must specify related_name on all relations. customers = models.ManyToManyField(Restaurant, related_name='provider')
定义父链接字段¶
前文提到 Django 会自动创建一个OneToOneField 来链接你的子类和非抽象父类。如果你要控制这个链接类的名字,那么你可以创建你自己的
OneToOneField 同时设置
parent_link=True 以回联到父类。
代理模型¶
当使用多表继承 时,会为每一个模型的子类创建一个新的数据库表。通常这种方式是我们期望得到的,因为子类需要存储基类所没有的另加的数据字段。但是,有时,你只是想要改变一个模型的 Python 行为,比如改变缺省的管理器或增加一个新的方法。
这时我们就需要使用代理模型继承方法来为源模型创建一个 代理 。你可以创建、删除和更新代理模型的实例并且所有的数据都会被保存,就象没有使用代理一样。所不同的是你可以在代理模型中改变一些东西,如源模型的排序方式或缺省的管理器等等,而源模型则不会发生变化。
代理模型的声明方式与一般模型一样。把 元数据 的
proxy 属性设置为
True 就代表这个模型是一个代理模型。
例如,假设你想要给标准的
User 模型添加一个方法,以便于以后用于你自己的模板中,你可以用下面的代码:
from django.contrib.auth.models import User class MyUser(User): class Meta: proxy = True def do_something(self): ...
MyUser 类与
User 类操作的是同一个数据库。特别的是两个类都可以操作对方的实例:
>>> u = User.objects.create(username="foobar") >>> MyUser.objects.get(username="foobar") <MyUser: foobar>
你还可以使用代理模型来定义与源模型不同缺省排序方式。标准的
User 模型没有定义排序方式(这是有意为之的,因为排序是要耗费资源的,我们不希望每次读取用户信息时都进行排序)。当你使用代理模型时,你可能想要根据
username 来排序。通过以下代码可以轻易实现:
class OrderedUser(User): class Meta: ordering = ["username"] proxy = True
现在,一般
User 查询是无序的,而
OrderedUser 查询是按
username 排序的。
查询集只会返回被请求的模型¶
当你针对User 对象查询时,不会返回一个
MyUser 对象。一个针对
User 对象的查询集只会返回 User 对象。代理对象的要点是各个对象只会使用自己的代码而不会使用他方的代码。代理不是代替。
基类限制¶
一个代理模型应当继承自一个非抽象基类。而且不能继承自多个非抽象基类,因为代理模型不包含不同数据库表中的行之间的关联。只有当一个代理模型 不定义 任何字段时,才可以继承自一个或多个抽象基类。代理模型会从父类继承任何 元数据 ,只要这个元数据在代理模型中没有定义。
代理模型管理器¶
如果你没有为代理模型定义任何管理器,那么代理模型会从其父模型继承管理器。如果为代理模型定义了管理器,那么定义的管理器会成为缺省的管理器,但是父模型的管理器仍是可用的。还是使用上面的例子,当你查询 User 模型时你可以像如下代码一样改变缺省的管理器:
class NewManager(models.Manager): ... class MyUser(User): objects = NewManager() class Meta: proxy = True
如果你要为代理模型新增一个管理器,而不取代缺省的管理器,你可以使用
自定义管理器 文档中描述的方法:创建一个包含新管理器基类并且在继承主基类后继承它:
# 为新管理器创建一个绝对基类。 class ExtraManagers(models.Model): secondary = NewManager() class Meta: abstract = True class MyUser(User, ExtraManagers): class Meta: proxy = True
你可能不是经常要这样做,可是这种方法是可行的。
代理继承和无管理器模型的区别¶
代理模型继承看上去和创建一个无管理器的模型,并在该模型的 Meta 类中使用managed 属性差不多。但是两种方式并不完全一样,选择哪一种方式是值得我们考虑的。
一个区别是你可以(除了你要一个空模型外实际上是必须的)使用 Meta.managed=False 在模型中定义字段。你可以小心的设置
Meta.db_table 属性来镜像一个已存在的模型,并且在模型中添加 Python 方法。但是如果你要作一些改动,那么你必须十分小心地同步两个模型。
对于代理模型另一个更重要的区别是模型管理器是如何运作的。代理模型被设计用于像被代理对象一样精确地运作,因此它们会继承父模型的管理器,包括缺省管理器。在一般的多表继承情况下,子模型不继承父模型的管理器,因为子模型添加了额外的字段,而父模型的管理器通常是不适用于子模型的。更多细节详见
管理器文档 。
当两个功能都要执行时,我们应当尝试把它们压缩为单一选项。否则情况就复杂了,也比较难以理解。
所以,通常的规则是:
如果你要镜像一个已存在的模型或数据库表,并且不需要所有的原数据库中的列,那么就使用 Meta.managed=False 。这个选择通常适用于数据库表视图和不在 Django 管控下的数据库表。
如果你要改变模型的 Python 行为,但又要保持所有的原字段不变,那么就使用 Meta.proxy=True 。这个选择适用于代理模型的字段结构与父模型完全一致的情况。
多重继承¶
就像 Python 的继承一样, Django 模型也可以继承自多个父模型。要记住的是名称确定规则使用的是一般 Python 名称确定规则,子类使用第一个基类的特定名称(如元数据 )。例如,如果多个父模型包含一个
元数据 类,那么仅第一个父模型的才会被使用,其他则被忽略。
通常,不需要使用多重继承。多重继承一般适用于 "mix-in" 类,即为每一个 mix-in 的子类增加一个指定的额外的字段或方法。让我们尽量简化继承,以免以后难以分清数据的来源。
字段名“隐藏”是不允许的¶
在一般的 Python 类继承中,子类可以重载父类的任何属性。但在 Django 中,不允许重载(起码现在不行)Field 实例属性。假设一个基类有一个
author 字段,你就不能在其任何子类中创建名为
author 的字段。
重载字段会导致初始化新实例(如在 Model.__init__ 中指定哪个字段被初始化)和连续化方面的问题。这些功能在 Python 中处理的方法是不同的。 Django 模型继承和 Python 类继承之间的差异不是随意产生的。
这个限制只用于 Field 实例属性上。一般的 Python 属性是可以重载的。这个限制也只用于被 Python 所见的属性名称。例如如果你手工定义数据库列名称,那么在多表继承中你就可以在子模型和祖先模型中使用相同的列名(因为列不在同一个数据库表中)。
如果你重载任何祖先类的字段,那么 Django 会抛出一个
FieldError 异常。
相关文章推荐
- django官方文档——管理器(数据库操作接口)
- django官方文档——数据库访问优化
- django 1.8 官方文档翻译: 2-1-1 模型语法(初稿)
- django 1.8 官方文档翻译: 2-5-6 多数据库
- django官方文档——管理器(数据库操作接口)
- Django 2.0 之Models(模型) 官方文档翻译(一)
- django 1.8 官方文档翻译: 2-6-4 数据库访问优化
- django官方文档——模型字段关系参考
- django 1.8 官方文档翻译: 2-3-1 模型实例参考
- (2)django官方教程---数据库模型
- Django官方文档学习2——数据库及模板
- django 1.8 官方文档翻译: 2-6-2 遗留的数据库
- django 1.8 官方文档翻译:2-1-1 模型语法
- django官方文档——构造查询(数据库查询 查询数据库)
- django官方文档——管理数据库事务
- django官方文档——使用多个数据库
- 第一天学英语:django官方文档翻译——目录
- Django1.5的分页中,官方文档的一个小错误
- tensorflow学习笔记十六:tensorflow官方文档学习 如何训练Inception v3模型最后一层
- Django项目-数据库,模型创建