模型是你的数据的唯一的、权威的信息源。它包含你所储存数据的必要字段和行为。通常,每个模型对应数据库中唯一的一张表。
基础:
这个例子定义一个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
);
一些技术上的注意事项:
定义好模型之后,接下来你需要告诉Django 使用这些模型。你要做的就是修改配置文件中的INSTALLED_APPS 设置,在其中添加models.py所在应用的名称。
例如,如果你的应用的模型位于myapp.models(由manage.py startapp 命令自动创建的结构),INSTALLED_APPS部分看上去应该是:
INSTALLED_APPS = (
#...
'myapp',
#...
)
当你在INSTALLED_APPS 中添加新的应用名时,请确保运行命令manage.py migrate,可以事先使用manage.py makemigrations 给应用生成迁移脚本。
对于一个模型来说,最重要的和不可或缺的是列出该模型在数据库中定义的字段。字段由models类属性指定。要注意选择的字段名称不要和模型 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)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
模型中的每个字段都是 Field 子类的某个实例。Django 根据字段类的类型确定以下信息:
Django 自带数十种内置的字段类型;完整字段类型列表可以在模型字段参考 中找到。如果内置类型仍不能满足你的要求,你可以自由地编写符合你要求的字段类型; 详见编写自定义的模型字段。
每个字段有一些特有的参数,详见模型字段参考。例如,CharField(和它的派生类)需要max_length 参数来指定VARCHAR 数据库字段的大小。
还有一些适用于所有字段的通用参数。 这些参数在参考中有详细定义,这里我们只简单介绍一些最常用的:
如果为True,该字段允许为空值,默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴,指数据库中字段内容是否允许为空,而 blank 是表单数据输入验证范畴的。如果一个字段的blank=True,表单的验证将允许输入一个空值。如果字段的blank=False,该字段就是必填的。
由二项元组构成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。
这是一个关于 choices 列表的例子:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或 ModelChoiceField 中用作显示的内容。 在一个给定的 model 类的实例中,想得到某个 choices 字段的显示值,就调用 get_FOO_display 方法(这里的 FOO 就是 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']
再说一次,这些仅仅是常用字段的简短介绍, 要了解详细内容,请查看 通用 model 字段选项参考(通用模型字段选项参考).
默认情况下,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 提供了三种最常见的数据库关系:多对一(many-to-one),多对多(many-to-many),一对一(one-to-one)。
Django 使用 django.db.models.ForeignKey 定义多对一关系。和使用其它字段类型一样:在模型当中把它做为一个类属性包含进来。
ForeignKey 需要一个位置参数:与该模型关联的类。
比如,一辆汽车(Car)有一个制造商(Manufacturer) —— 但是一个制造商(Manufacturer) 生产很多汽车(Car),每一辆汽车(Car) 只能有一个制造商(Manufacturer) —— 使用下面的定义:
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
# ...
你还可以创建递归的关联关系(对象和自己进行多对一关联)和 与尚未定义的模型的关联关系;详见模型字段参考。
建议你用被关联的模型的小写名称做为ForeignKey 字段的名字(例如,上面manufacturer)。当然,你也可以起别的名字。例如:
class Car(models.Model):
company_that_makes_it = models.ForeignKey(Manufacturer)
# ...
另见
ForeignKey 字段还接受许多别的参数,在模型字段参考有详细介绍。这些选项帮助定义关联关系应该如何工作;它们都是可选的参数。
访问反向关联对象的细节,请见反向关联示例。
示例代码,请见多对一关系模型示例)。
ManyToManyField 用来定义多对多关系,用法和其他Field 字段类型一样:在模型中做为一个类属性包含进来。
ManyToManyField 需要一个位置参数:和该模型关联的类。
例如,一个披萨可以有多种馅料 ,一种馅料 也可以位于多个披萨上。 如下展示:
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 有多种Topping 比一个Topping 位于多个Pizza 上要更加自然。按照上面的方式,在Pizza 的表单中将允许用户选择不同的Toppings。
另见
完整的示例参见多对多关系模型示例。
ManyToManyField 字段还接受别的参数,在模型字段参考中有详细介绍。这些选项帮助定义关系应该如何工作;它们都是可选的。
处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的ManyToManyField 就可以了。但是,有时你可能需要关联数据到两个模型之间的关系上。
例如,有这样一个应用,它记录音乐家所属的音乐小组。我们可以用一个ManyToManyField 表示小组和成员之间的多对多关系。但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。
对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。源模型的ManyToManyField 字段将使用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)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
在设置中介模型时,要显式地指定外键并关联到多对多关系涉及的模型。这个显式声明定义两个模型之间是如何关联的。
中介模型有一些限制:
在Django 1.6 及之前的版本中,中介模型禁止包含多于一个的外键。
既然你已经设置好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 = [...])来创建关系:
# THIS WILL NOT WORK
>>> beatles.members.add(john)
# NEITHER WILL THIS
>>> beatles.members.create(name="George Harrison")
# AND NEITHER WILL THIS
>>> 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.'
另一种获取相同信息的方法是,在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用来定义一对一关系。 用法和其他字段类型一样:在模型里面做为类属性包含进来。
当某个对象想扩展自另一个对象时,最常用的方式就是在这个对象的主键上添加一对一关系。
OneToOneField要一个位置参数:与模型关联的类。
例如,你想建一个“places” 数据库,里面有一些常用的字段,比如address、 phone number 等等。 接下来,如果你想在Place 数据库的基础上建立一个Restaurant 数据库,而不想将已有的字段复制到Restaurant模型,那你可以在 Restaurant 添加一个OneToOneField 字段,这个字段指向Place(因为Restaurant 本身就是一个Place;事实上,在处理这个问题的时候,你应该使用一个典型的 继承,它隐含一个一对一关系)。
和使用 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)
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 查询语句中的数据库表名和列名进行转义。 它根据你的数据库引擎使用不同的引用语法。
如果已有的模型字段都不合适,或者你想用到一些很少见的数据库列类型的优点,你可以创建你自己的字段类型。创建你自己的字段在编写自定义的模型字段中有完整讲述。
使用内部的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是完全可选的,所有选项都不是必须的。
所有元选项的完整列表可以在模型选项参考找到。
可以在模型上定义自定义的方法来给你的对象添加自定义的“底层”功能。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)
这个例子中的最后一个方法是一个属性。
模型实例参考 具有一个完整的为模型自动生成的方法 列表。你可以覆盖它们 —— 参见下文覆盖模型预定义的方法 —— 但是有些方法你会始终想要重新定义:
一个Python的特殊方法(magic method),返回对象的字符串表达式(unicode格式)当模型实例需要强制转换并显示为普通的字符串时,Python 和Django 将使用这个方法。最明显是在交互式控制台或者管理站点显示一个对象的时候。
你会经常需要定义这个方法;默认方法返回的内容几乎没有帮助。
它告诉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,将保证你的代码自动支持这些新的参数。
批量操作中被覆盖的模型方法不会被调用
注意,当使用查询集批量删除对象时,将不会为每个对象调用delete() 方法。为确保自定义的删除逻辑得到执行,你可以使用pre_delete 和/或post_delete 信号。
不幸的是,当批量creating 或updating 对象时没有变通方法,因为不会调用save()、pre_save和 post_save。
Django 中的模型继承与 Python 中普通类继承方式几乎完全相同,但是本页头部列出的模型基本的要求还是要遵守。这表示自定义的模型类应该继承django.db.models.Model。
你唯一需要作出的决定就是你是想让父模型具有它们自己的数据库表,还是让父模型只持有一些共同的信息而这些信息只有在子模型中才能看到。
在Django 中有3种风格的继承。
当你想将一些共有信息放进其它一些model的时候,抽象化类是十分有用的。你编写完基类之后,在 Meta类中设置 abstract=True ,这个模型就不会被用来创建任何数据表。取而代之的是,当它被用来作为一个其他model的基类时,它的字段将被加入那些子类中。如果抽象基类和它的子类有相同的字段名,那么将会出现error(并且Django将抛出一个exception)。
一个例子
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 语言层级上提取公共信息的方式,同时在数据库层级上,每个子类各自仍然只创建一个数据库表。
当一个抽象基类被创建的时候, 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属性之前,Django 会设置 abstract=False。这意味着抽象基类的子类本身不会自动变成抽象类。 当然,你可以让一个抽象基类继承自另一个抽象基类,你只要记得每次都要显式地设置 abstract=True。
一些属性被包含在抽象基类的Meta类里面是没有意义的。例如,包含 db_table属性将意味着所有的子类(是指那些没有指定自己的 Meta 类的子类)都使用同一张数据表,这总归不会是你想要的。
这是 Django 支持的第二种继承方式。使用这种继承方式时,每一个层级下的每个 model 都是一个真正意义上完整的 model 。 每个 model 都有专属的数据表,都可以查询和创建数据表。 继承关系在子 model 和它的每个父类之间都添加一个链接 (通过一个自动创建的 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中也是有效的,只不过没有保存在数据库中的Restaurant表中。所以下面两个语句都是可以运行的:
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
如果你有一个 Place ,它同时也是一个 Restaurant, 那么你可以使用 model 的小写形式从 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 选项已经对父类起了作用,再次使用只会起反作用。(这与使用抽象基类的情况正好相反,因为抽象基类并没有属于它自己的内容)
所以子 model 并不能访问它父类的 Meta 类。但是在某些受限的情况下,子类可以从父类继承某些 Meta :如果子类没有指定 ordering属性或 get_latest_by 属性,它就会从父类中继承这些属性。
如果父类有了排序设置,而你并不想让子类有任何排序设置,你就可以显式地禁用排序:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
因为多表继承使用了一个隐含的 OneToOneField来链接子类与父类,所以象上例那样,你可以用父类来指代子类。但是这个 OnetoOneField 字段默认的 related_name 值与 ForeignKey 和 ManyToManyField 默认的反向名称相同。如果你与该父类的另一个子类做多对一或是多对多关系,你就必须在每个多对一和多对多字段上强制指定 related_name。如果你没这么做,Django 就会在你运行 验证(validation) 时抛出异常。
例如,仍以上面 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'.
像下面那样,向customers字段中添加related_name可以解决这个错误:models.ManyToManyField(Place, related_name='provider')。
之前我们提到,Django 会自动创建一个 OneToOneField字段将子类链接至非抽象的父 model 。如果你想指定链接父类的属性名称,你可以创建你自己的 OneToOneField字段并设置 parent_link=True ,从而使用该字段链接父类。
使用 多表继承时,model 的每个子类都会创建一张新数据表,通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包含在基类中的字段数据。 但有时,你可能只想更改 model 在 Python 层的行为实现。比如:更改默认的 manager ,或是添加一个新方法。
而这,正是代理继承要做的:为原始模型创建一个代理 。你可以创建,删除,更新代理 model 的实例,而且所有的数据都可以像使用原始 model 一样被保存。 不同之处在于:你可以在代理 model 中改变默认的排序设置和默认的 manager ,更不会对原始 model 产生影响。
声明代理 model 和声明普通 model 没有什么不同。 设置Meta类中 proxy 的值为 True,就完成了对代理 model 的声明。
举个例子,假设你想给 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>
你也可以使用代理 model 给 model 定义不同的默认排序设置。 你可能并不想每次都给Person模型排序,但是使用代理的时候总是按照last_name属性排序。这非常容易:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
现在,普通的Person查询是无序的,而 OrderedPerson查询会按照last_name排序。
也就是说,没有办法让django在查询Person对象时返回MyPerson对象。Person 对象的查询集会返回相同类型的对象。代理对象的要点是:它会使用依赖于原生Person的代码,而你可以使用你添加进来的扩展对象(它不会依赖其它任何代码)。而并不是将Person模型(或者其它)在所有地方替换为其它你自己创建的模型。
代理模型必须继承自一个非抽象基类。 你不能继承自多个非抽象基类,这是因为一个代理 model 不能连接不同的数据表。 代理 model 也可以继承任意多个抽象基类,但前提是它们没有 定义任何 model 字段。
如果你没有在代理 模型中定义任何管理器 ,代理模型就会从父类中继承管理器 。 如果你在代理模型中定义了一个管理器 ,它就会变成默认的管理器 ,不过定义在父类中的管理器仍然有效。
继续上面的例子,当你查询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
你可能不需要经常这样做,但这样做是可行的。
代理 model 继承看上去和使用Meta类中的 managed 属性的非托管 model 非常相似。但两者并不相同,使用时选用哪种方案是一个值得考虑的问题。
一个不同之处是你可以在Meta.managed=False的 model 中定义字段(事实上,是必须指定,除非你真的想得到一个空 model )。在创建非托管 model 时要谨慎设置Meta.db_table ,这是因为创建的非托管 model 映射某个已存在的 model ,并且有自己的方法。因此,如果你要保证这两个 model 同步并对程序进行改动,那么就会变得繁冗而脆弱。
另一个不同之处是两者对 管理器的处理方式不同。 代理 model 要与它所代理的 model 行为相似,所以代理 model 要继承父 model 的 managers ,包括它的默认 manager 。 但在普通的多表继承中,子类不能继承父类的 manager ,这是因为在处理非基类字段时,父类的 manager 未必适用。 后一种情况在 管理器文档有详细介绍。
我们实现了这两种特性之后,曾尝试把两者结合到一起。 结果证明,宏观的继承关系和微观的 管理器揉在一起,不仅导致 API 复杂难用,而且还难以理解。 由于任何场合下都可能需要这两个选项,所以目前二者仍是各自独立使用的。
所以,一般规则是:
就像Python的子类那样,django的模型可以继承自多个父类模型。切记一般的Python名称解析规则也会适用。出现特定名称的第一个基类(比如Meta)是所使用的那个。例如,这意味着如果多个父类含有 Meta类,只有第一个会被使用,剩下的会忽略掉。
一般来说,你并不需要继承多个父类。多重继承主要对“mix-in”类有用:向每个继承mix-in的类添加一个特定的、额外的字段或者方法。你应该尝试将你的继承关系保持得尽可能简洁和直接,这样你就不必费很大力气来弄清楚某段特定的信息来自哪里。
Django 1.7之前,继承多个含有id主键字段的模型不会抛出异常,但是会导致数据丢失。例如,考虑这些模型(由于id字段的冲突,它们不再有效):
class Article(models.Model):
headline = models.CharField(max_length=50)
body = models.TextField()
class Book(models.Model):
title = models.CharField(max_length=50)
class BookReview(Book, Article):
pass
这段代码展示了如何创建子类的对象,并覆写之前创建的父类对象中的值。
>>> article = Article.objects.create(headline='Some piece of news.')
>>> review = BookReview.objects.create(
... headline='Review of Little Red Riding Hood.',
... title='Little Red Riding Hood')
>>>
>>> assert Article.objects.get(pk=article.pk).headline == article.headline
Traceback (most recent call last):
File "<console>", line 1, in <module>
AssertionError
>>> # the "Some piece of news." headline has been overwritten.
>>> Article.objects.get(pk=article.pk).headline
'Review of Little Red Riding Hood.'
你可以在模型基类中使用显式的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
普通的 Python 类继承允许子类覆盖父类的任何属性。 但在 Django 中,重写 Field实例是不允许的(至少现在还不行)。如果基类中有一个 author字段,你就不能在子类中创建任何名为 author的字段。
重写父类的字段会导致很多麻烦,比如:初始化实例(指定在 Model.__init__ 中被实例化的字段) 和序列化。而普通的 Python 类继承机制并不能处理好这些特性。所以 Django 的继承机制被设计成与 Python 有所不同,这样做并不是随意而为的。
这些限制仅仅针对做为属性使用的 Field实例,并不是针对 Python 属性,Python 属性仍是可以被重写的。 在 Python 看来,上面的限制仅仅针对字段实例的名称:如果你手动指定了数据库的列名称,那么在多重继承中,你就可以在子类和某个祖先类当中使用同一个列名称。(因为它们使用的是两个不同数据表的字段)。
如果你在任何一个祖先类中重写了某个 model 字段,Django 都会抛出 FieldError异常。
另见
2015年5月13日