Models¶
原文:Models
模型是关于数据的单一,明确的信息来源。它包含了你存储的数据的主要字段和行为。一般情况下,每个模型映射到单个数据库表。
基本点:
- 每一个模型都是一个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
是该模型的字段(fields)。每个字段被指定为类的一个属性,并且每个属性映射到数据库的一个列。
上面的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
,是自动从一些模型元数据中导出的,但可以被覆盖。见表名以获得更多信息。 id
字段是被自动添加的,但此行为可以被覆盖。见自动主键字段.- 该例子中的
CREATE TABLE
SQL使用PostgreSQL语法进行格式化,但值得注意的是,Django会对SQL进行调整以适应你的设置文件中指定的数据库后端。
使用模型¶
一旦你定义了模型,那么需要告诉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
, 或者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
)。 - 当渲染一个表单域(例如
<input type="text">
,<select>
)时,使用的默认的HTML 控件。 - 最小的验证要求,用于Django管理和自动生成的表单中。
Django自带几十个内置的字段类型;你可以在Django模型字段参考中找到完整的列表。如果Django内置的类型达不到你的目的,你可以轻松的编写自己的字段类型;见编写自定义模型字段.
字段选项¶
每个字段接收特定字段参数的一个特定集合(见模型字段参考)。例如,CharField
(及其子类)需要一个max_length
参数,它指定了用来存储数据的VARCHAR
类型的数据库字段的长度。
还有一个适用于所有字段类型的常见的参数的集合。该集合中的所有参数都是可选的。他们在参考中有充分的解释,但这里有最常用的那些的一个简单总结:
若为True
,Django将在数据库中将空值存储为NULL
。
默认是False
。
若为True
,则允许该字段为空。默认是False
。
注意,这不同于null
。null
纯粹是数据库相关的,而blank
则是验证相关的。如果一个字段被设置为blank=True
,那么表单验证将允许项值为空。如果一个字段被设置为 blank=False
,那么该字段是必传的。
一个二元组的可迭代对象(例如,一个列表或元组),它被用作此字段的选项。若给定此参数,那么默认的表单空间将是一个选择框,而不是标准的文本字段,并且将会限制选择为给定的选项。
一个选项列表如下:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每一个元组的第一个元素是将要存储在数据库中的值,第二个元素显示在默认的表单控件中,或在ModelChoiceField中显示。给定一个模型对象的一个实例,可以使用get_FOO_display
方法访问一个选项(choices)字段的展示值。例如:
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'
该字段的默认值。它可以是一个值,或是一个可调用对象。若是一个可调用对象,那么它将在每次创建一个新对象时被调用。
额外的“帮助”文本,它将于表单控件一起显示。即使你的字段不用于表单,但是它对于文档也是很有用的。
若为True
,那么该字段是该模型的主键。
若你不为模型中的任何字段指定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)
['Apple', 'Pear']
若为True
,那么该字段在整个表中必须是唯一的。
再次说明,这些只是最常用字段选项的简短说明。详情可见常用模型字段选项参考。
自动主键字段¶
默认地,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,
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还提供方法来定义三种最常见的类型的数据库关系:多对一,多对多和一对一。
多对一(Many-to-one)关系¶
要定义一个多对一关系,使用django.db.models.ForeignKey
。就像任何其他Field
类型一样使用它:通过将其作为模型的一个类属性包含它。
ForeignKey
要求一个位置参数:该模型相关的类。
例如,如果一个Car
模型有一个Manufacturer
,也就是说,一个Manufacturer
制造多辆车,但每辆Car
只有一个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
字段接收许多额外的参数,它们在模型字段参考中有描述。这些选项帮助定义关系应该如何工作;它们所有都是可选的。
有关访问向后相关对象的详细信息,见下面的关系向后例子.
有关示例代码,见多对一关系模型。
多对多(Many-to-many)关系¶
要定义一个多一对多的关系,使用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拥有toppings,而不是一个topping在多个pizza上。根据上面设置的方式,Pizza
表单将让用户选择toppings。
另见
见多到多关系模型例子以获得一个完整的例子。
ManyToManyField
字段也接收一些额外的参数,在模型字段参考中对它们进行了解释。这些选项帮助定义关系应该如何工作;它们所有都是可选的。
多对多关系的额外字段¶
当你只处理简单的多对多关系,如混合和匹配pizzas和toppings,一个标准的 ManyToManyField
是你所需要的。但是,有时你可能需要将数据与两个模型之间的关系联系起来。
例如,考虑一个应用程序跟踪音乐家属于的音乐团体的情况。一个人与其所在团队之间有一种多对多的关系,所以你可以使用一个ManyToManyField
来表示这种关系。然而,你可能要收集很多有关此关系的细节有关,如在此人加入此团体的日期。
对于这些情况,Django允许你指定将用于管理多对多关系的模型。然后,您可以把额外的字段放在中间模型中。中间模型通过使用through
参数指向作为中间媒介的模型与ManyToManyField
相关联。对于我们的音乐家例子,代码会是这个样子:
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
明确指定Django应该对此关系使用的外键。如果你有一个以上的外键,并且未指定through_fields
,将引发一个验证错误。类似的限制适用于到目标模型的外键(将是我们的例子中的Person
)。 - 对于通过中介模型拥有对其自身多对多关系的模型,允许到相同模型的两个外键,但他们将被视为多对多关系的两个(不同的)方面。如果有超过两个外键,你还必须如上指定
through_fields
,或将引发一个验证错误。 - 当定义一个模型到其自身的一个多对多关系时,使用一个中介模型,你必须使用
symmetrical=False
(见 模型字段参考)。
现在,你已经设置了你的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()
[<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 have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
[]
一旦你通过创建中介模型的实例来建立多对多关系,你就可以发出查询。正如正常的多对多关系,您可以使用多对多相关模型的属性进行查询:
# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
[<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))
[<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.'
Another way to access the same information is by querying the
另一种访问相同信息的方式是从一个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.'
一对一(One-to-one)关系¶
要定义一个一对一关系,使用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只有两个限制:
- 字段名不能是一个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查询中转义所有的数据库表名和列名。它使用不同的数据库引擎的引用语法。
自定义字段类型¶
如果现有模型字段不能使用以满足你的需要,或者如果你想利用一些不常见的数据库列类型,你可以创建你自己的字段类。创建自己的字段的全部内容,见编写自定义模型字段。
Meta选项¶
通过使用一个内部的class Meta
来提供模型元数据,像这样:
from django.db import models
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
选项的完整列表可见模型选项参考。
模型属性¶
objects
一个模型最重要的属性是 Manager
。它是提供给Django的数据库查询操作,并被用于从数据库中检索实例的接口。如果没有定义自定义的Manager
,那么默认名为 objects
。管理器(Manager)只能通过模型类进行访问,而不是模型实例。
模型方法¶
定义一个模型的自定义方法以添加自定义的“行级”功能到你的对象。而Manager
方法的目的在于“表范围”,模型方法应该作用于特定的模型实例。
这对于在一个位置(模型)保持业务逻辑来说,是一个很有价值的技术。
例如,这个模型有一些自定义方法:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
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)。
该模型实例参考有一个完整的自动提供给每个模型的方法的列表。 你可以覆盖它们大部分方法 – 见下面的重写预定义的模型方法,但也有一对你几乎总是想定义的方法:
__str__()
(Python 3)
一个Python“魔术方法”,它返回任何对象的unicode的“代表”。每当一个模型实例需要被强制转换,并显示为纯字符串,Python和Django就会使用它。最值得注意的是,当你在一个交互式控制台或管理(admin)中显示一个对象时也会出现这种情况。
你总是会想要定义这个方法的; 默认并不非常有用。
__unicode__()
(Python 2)
Python 2相当于__str__()
的方法。
这告诉Django如何为一个对象计算URL。Django在其管理界面使用这一点,任何时候它需要为一个对象找出一个URL。
具有唯一标识它的URL的任何一个对象应该定义该方法。
重写预定义的模型方法¶
还有另一组模型方法,它们封装了一对你想要自定义的数据库行为。特别是,你将常想修改save()
和delete()
工作的方式。
你可以自由地重写这些方法(和任何其他模型方法)以改变其行为。
一个典型的重写内置方法的用例是无论何时保存一个对象时,你想要做些什么。例如(见save()
接受的参数的文档):
from django.db import models
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) # Call the "real" save() method.
do_something_else()
你还可以阻止保存:
from django.db import models
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 shall never have her own blog!
else:
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
重要的是要记住调用父类的方法 - 这就是super(Blog, self).save(*args, **kwargs)
的工作 - 以确保对象仍然会保存到数据库中。如果你忘记调用父类的方法,那么默认的行为不会发生,并且该数据库将不会被涉及。
同样重要的是,你传递一个可以传递到模型方法的参数-这是*args, **kwargs
所做的事。Django会不时地扩展内置模型方法的能力,增加新的参数。如果你在你的方法定义中使用*args, **kwargs
,可以保证在添加新参数时,你的代码会自动支持这些参数。
不在批量操作上调用重写的模型方法
注意,当使用查询集批量删除对象或者作为
cascading delete
的结果时,一个对象的delete()
方法不一定被调用。为了确保执行自定义的删除逻辑,你可以使用pre_delete
和/或者post_delete
信号。
不幸的是,当批量
creating
或者updating
对象时,并没有一种解决方法,因为不会调用save()
,pre_save
, 和post_save
。
执行自定义SQL¶
另一种常见的模式是在模型方法和模块级方法编写定制的SQL语句。有关使用原始的SQL的详细信息,请参阅文档使用原始的SQL。
模型继承¶
Django模型继承的工作方式几乎与Python中正常类继承的方式相同,但仍然应该遵循页面开始的基本点。这意味着基类应该继承django.db.models.Model
子类。
你必须做出的唯一决定是你是否想父模型对自身来说是模型(有自己的数据库表),或者父模型只是普通信息的持有者,这些只能通过子模型可见。
Django中有三种可能的继承风格:
- 通常情况下,你只需要使用父类来保存你不希望为每个子模型都打一遍的信息。这个类是不会孤立地被使用的,所以抽象基类是你的选择。
- 如果您继承现有模型(可能是完全另一个应用程序中的模型),并希望每个模型都有自己的数据库表,那么多表继承是你的选择。
- 最后,如果你只是想要修改模型Python级别的行为,而不以任何方式修改模型的字段,那么你可以使用 代理模型。
抽象基类¶
当你想将一些常用的信息放到其他一些模型中时,抽象基类是有用的。编写你的基类,并把abstract=True
放在Meta类中。这个模型将不会再被用于创建任何数据库表。相反,当它被用作其它模型的基类时,其字段将被添加到那些子类的中。在子类中具有与抽象基类相同名称的字段是错误的(Django将引发一个异常)。
一个例子:
from django.db import models
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使任何你在基类中声明的Meta内部类作为一个属性。如果一个子类没有声明自己的Meta类,它将继承父类的Meta类。如果该子类想扩展父类的Meta类,它可以继承它。例如:
from django.db import models
class CommonInfo(models.Model):
# ...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
# ...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
Django确实对抽象基类的Meta类做了一次调整:在安装Meta属性前,它设置abstract=False
。这意味着抽象基类的孩子不会自动成为抽象类本身。当然,你可以创建一个从另一个抽象基类继承的抽象基类。你只需要记住每次都明确设置abstract=True
。
将某些属性包含在抽象基类的Meta类中并无任何意义。例如,包含db_table
意味着所有的子类(那些没有指定自己的Meta的类)将使用同一个数据库表,这是几乎可以肯定不是你想要的。
小心related_name
¶
如果你在一个ForeignKey
或ManyToManyField
上使用related_name
属性,那么你必须总是为该字段指定一个唯一的反向名称。这通常会导致抽象基类的一个问题,因为该类的每一个子类每次都会包含这些字段,并且字段值完全相同(包括related_name
)。
要解决此问题,当你(只)在一个抽象基类中使用related_name
时,名称的一部分应该包含'%(app_label)s'
和'%(class)s'
。
'%(class)s'
会被使用此字段的子类的小写名所替代。'%(app_label)s'
会被包含该子类的应用的小写名所替代。每一个安装的应用名必须唯一,且每一个应用中的模型类名也必须唯一,因此,所得到的名最终将会不同。
例如,给定一个应用的common/models.py
:
from django.db import models
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
字段的反向名将是common_childa_related
, 而common.ChildB.m2m
字段的反向名将是common_childb_related
,最后,rare.ChildB.m2m
字段的反向名将是rare_childb_related
。
这取决于你如何使用'%(class)s'
和'%(app_label)s
部分来构建你的相关名称,但是如果你忘记使用它,当你执行系统检查(或运行migrate
)时,Django将会引发错误。
若没有为一个抽象类的一个字段指定related_name
属性,那么默认的反向名将是``子类名__set,如果你直接在子类中声明该字段,那么它通常会是这样的。例如,在上面的代码中,如果省略[
related_name`](https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name")属性,那么`m2m`字段的反向名在`ChildA`中将是`childa_set`,而在`ChildB`中将是`childb_set`。
多表继承¶
Django支持的模型继承的第二种类型是,在继承层次中,每个模型是自身都是一个模型。每个模型对应于它自己的数据库表,可以查询并独立创建。继承关系引入了子模型和它的每一个父母之间的连接(通过一个自动创建的OneToOneField
)。
例如:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
虽然数据将存储在不同的数据库表格中,但是Place
中的所有字段也将可用于Restaurant
。所以,这些都是可能的:
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
如果你有一个Place
,它也是一个Restaurant
的话,那么你可以通过使用模型名称的小写版本来从Place
对象中获得Restaurant
对象:
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
然而,如果上面例子中的p
并不是一个Restaurant
(它直接由Place
对象创建,或者是其他类的父类),那么p.restaurant
将会引发一个Restaurant.DoesNotExist
异常。
Meta
和多表继承¶
在多表继承的情况下,子类继承其父类的Meta 类是没有道理的。所有的Meta选项已被应用到父类,而再应用它们通常只会导致矛盾的行为(这与抽象基类的情况相反,基类并不存在自己的行为)。
因此,一个子模型并没有访问其父类的Meta类的权利。但是,在少数有限的情况下,子类从父类中继承行为:如果子类不指定ordering
属性或get_latest_by
属性,它会从其父继承这些。
若果父类有一个ordering
,而你不想让子类有任何自然的顺序,你可以显式禁用它:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
继承和反向关系¶
由于多表继承使用隐式的OneToOneField
来链接父类和子类,因此有可能从父类下移到子类,正如上面的例子那样。然而,这将占用用于ForeignKey
和ManyToManyField
关系的默认值。如果你将这些类型的关系放到该父类模型的子类中,那么你必须为每个这样的字段指定related_name
属性。假如你忘记了,Django将会抛出一个验证错误。
例如,再次使用上面的Place
类,让我们创建一个带有ManyToManyField
的另一个类:
class Supplier(Place):
customers = models.ManyToManyField(Place)
这会导致这样的错误:
Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.
HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.
像下面这样将related_name
添加到customers
字段中将解决这个错误:models.ManyToManyField(Place, related_name='provider')
.
指定父链接字段¶
As mentioned, Django will automatically create a
如前所述,Django将自动创建一个OneToOneField
用于链接你的子类到任何非抽象父模型。如果你想要控制链接到父类的属性名,那么你可以创建自己的OneToOneField
,然后设置parent_link=True
,以表明你的字段是链接到父类的。
代理模型¶
当使用多表继承时,会为模型中的每个子模型创建一个新的数据库表。这通常是所期望的行为,因为子类需要一个地方来存储任何在基类中不存在的附加数据字段。但是有时候,你只想要更改模型的Python行为 - 也许是更改默认的管理员,或添加新的方法。
这就代理模型继承的作用:为原始模型创建一个代理。您可以创建,删除和更新代理模型的实例,并且所有的数据将像使用原始(非代理)模型一样被保存。所不同的是,你可以修改东西,例如默认模型排序或代理中的默认管理器,而无需改变原有的。
代理模型的声明就像正常模型一样。你通过将Meta
类的proxy
属性值设置为True
来告诉Django它是一个代理模型。
例如,假设你想添加一个方法到Person
模型。你可以这样做:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class MyPerson(Person):
class Meta:
proxy = True
def do_something(self):
# ...
pass
该MyPerson
类与它的父类Person
操作同样的数据库表。特别是,任何新的Person
类的实例也将可以通过MyPerson
访问,反之亦然:
>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>
你也可以使用代理模型来定义模型上不同的默认排序。你或许不会总是要排序Person
模型,而是当你使用代理时,你想定期的按照last_name
属性进行排序。这很简单:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
现在,正常的Person
查询将是无序的,而OrderedPerson
查询将按照last_name
排序。
查询集仍然会返回所请求的模型¶
没有办法让Django返回,比如说,无论何时你查询Person
对象,Django都没法返回一个MyPerson
对象。一个Person
对象的查询集将返回这些类型的对象。代理对象的全部要点是依托原始的Person
的代码将使用这些而你自己的代码可以使用你包括的扩展(不管怎样,没有其他代码所依托的)。这不是一个用你自己创造的东西到处取代Person
(或其他任何)模型的方法。
基类限制¶
代理模型必须只有一个非抽象模型类继承。你不能继承多个非抽象模型,因为代理模型不提供任何不同数据库表中的行之间的连接。代理模型可以继承自任意数量的抽象模型类,但前提是它们没有定义任何模型字段。
代理模型管理¶
如果没有在一个代理模型上指定任何模型管理者,那么它会继承其父模型的管理者。如果你在该代理模型上定义了一个管理者,它将成为默认的管理者,虽然在父类中定义的任何管理者仍然可用。
继续我们上面的例子,当你像这样查询Person
模型时,你可以改变使用的默认管理者:
from django.db import models
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
如果你想要给该代理添加新的管理者而不替换现有默认的,那么你可以使用在自定义管理者文档中提到的技术:创建一个包含新管理者的基类,然后在主基类后继承它:
# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
secondary = NewManager()
class Meta:
abstract = True
class MyPerson(Person, ExtraManagers):
class Meta:
proxy = True
你可能不需要经常这样做,但是,当你这样做时,就有可能。
代理继承和非托管模型之间的差异¶
代理模型继承可能看起来非常类似于使用模型的Meta
类中的managed
属性来创建一个非托管模式。这两个方案也不尽相同,而且值得考虑你应该使用哪一个。
一个区别是,你可以(而且,事实上,除非你想要一个空的模型,否则必须)使用Meta.managed=False
指定模型上的模型字段。你可以使用精心设置的Meta.db_table
来创建一个掩盖(shadow)现有模型的非托管模型,并为它添加Python方法。然而,当你需要保持同步两个副本时,如果你做任何更改,这将是非常重复及脆弱的。
对代理模型更重的另一个不同之处是,如何处理模型管理者。代理模型是用于表现得完全像他们进行代理的模型。因此,它们继承父模型的管理者,包括默认管理者。在正常的多表模型继承的情况下,子类不继承父类的管理者,因为当涉及额外的字段时,自定义的管理者并不总是适合的。管理者文档有更多关于后一种情况的细节。
当实现这两个特征时,会尝试将它们放进一个单选项。结果是,一般情况下与继承的互动,特殊情况下与管理的互动,会让API非常复杂,并且可能很难理解和使用。最终,在任何情况下都需要这两种选择,所以目前出现了选项的分离。
所以,一般的规则是:
-
如果你正对现有的模型或数据库表进行镜像,而且不想要所有的原始数据库表列,使用
Meta.managed=False
。该选项通常对不再Django控制下的数据库视图和表建模有用。 -
如果你只想改变模型Python行为,但要保持与原始一样的所有相同的字段,使用
Meta.proxy=True
。它设置一些东西,以使得当保持数据时,代理模型是与原始模型的存储结构完全相同的副本。
多重继承¶
正如Python的子类,Django模型继承多个父模型是可能的。请记住,常规的Python名称解析规则的应用。一个特定的名称(例如Meta)出现的第一个基类将是被使用的那个; 例如,它意味着,如果多个父类包含Meta类,那么将只使用第一个,而其他所有将被忽略。
通常情况下,你不需要继承多个父类。有用的主要用例是“混合式”类:为每个继承混合式类增加一个额外的特殊字段或方法。尽可能的保持你的继承层次越简单明了越好,这样你就不必努力去找某个特定信息的来源。
注意,继承有一个共同的主键字段id
的多个模型将引发一个错误。要正确的使用多类继承,可以在基类中使用一个显式的AutoField
:
class Article(models.Model):
article_id = models.AutoField(primary_key=True)
...
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...
class BookReview(Book, Article):
pass
或者使用一个共同的祖先来保存AutoField
:
class Piece(models.Model):
pass
class Article(Piece):
...
class Book(Piece):
...
class BookReview(Book, Article):
pass
不允许字段名“hiding”¶
在普通的Python类继承中,允许一个子类覆盖父类中的任何属性。在Django中,对于那些是Field
实例的属性是不允许的(至少,不是现在)。如果一个基类有一个名为author
的字段,那么在继承该基类的任何类中,你都不能创建另一个名为author
的模型字段。
在父模型中覆盖字段会带来某些领域的困难,例如初始化新实例(指明哪些字段正在Model.__init__
中被初始化)和系列化。这些都是普通的Python类继承没有以同样的方式来处理的特点,所以Django模型继承和Python类继承之间的差别并不是随意的。
这种限制仅适用于是 Field
实例的属性。如果你想,普通的Python属性可以被覆盖。它也只适用于Python认为的属性名:如果你手动指定数据库列名,那么对于多表继承,你可以让相同的列名都出现在子模型和父模型中(它们是在两个不同的数据库表的列)。
如果你覆盖任何祖先模型中的任何模型字段,那么Django会引发一个FieldError
错误。
另见
模型参考涵盖了所有的模型相关的API,包括模型字段,相关对象和
QuerySet
。