Django1.10主题指南—模型

2023-05-16,,

 

模型是你的数据的唯一的、权威的信息源。它包含你所储存数据的必要字段和操作行为。通常,每个模型都对应着数据库中的唯一一张表。

基础认识:

每个model都是一个继承django.db.models.Model的子类;
model中的每个属性(attribute)都代表数据库中的一个字段;
Django 提供一套自动生成的用于数据库访问的API;详见Making queries。

简短例子

例子中的模型定义了一个带有first_namelast_namePersonmodel:

from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)

其中first_namelast_name都是模型的字段 ,每个字段(fields)都是类的一个属性,每个属性映射到数据库中的列。

上面的Personmodel将会像下面sql语句类似的创建一张表:

CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);

特别注意:

表的名字myapp_person_name是根据模型中的元数据自动生成的(app_name + class_name),也可以自定义表的名称,参考这里Table names;
id字段是自动添加的,你可以重新改写这一行为,参见自增主键;
本示例中的CREATETABLESQL使用的是PostgreSQL语法,但是在应用中,Django会根据settings 文件中指定的数据库类型来使用相应的SQL语句。

使用模型

定义好模型之后,接下来你需要告诉Django使用这些模型,你要做的就是修改配置文件中的INSTALLED_APPS设置, 在其中添加models.py所在应用的名称。

例如,如果你的应用的模型位于myapp.models(文件结构是由manage.pystartapp命令自动创建的),INSTALLED_APPS部分看上去应该是:

INSTALLED_APPS = [
#...
'myapp',
#...
]

当你在INSTALLED_APPS中添加新的应用名时,一定要运行命令manage.pymigrate, 你可以事先使用manage.pymakemigrations给应用生成迁移脚本。

字段

对于一个模型来说,最重要的是列出该模型在数据库中定义的字段。字段由models类属性指定。 要注意选择的字段名称不要和models API冲突,比如cleansave或者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根据字段类的类型确定以下信息:

数据库中字段的类型(e.g.INTEGER,VARCHAR,TEXT);
渲染表单时使用的默认HTMLwidget(e.g.<inputtype="text">,<select>);
在Django的admin和自动生成的表单中使用的最低验证要求;

Django有几十种内置字段类型;你可以在模型字段参考找到所有的字段, 如果Django内置字段类型无法满足要求,你也可以自定义字段,参见Writing custom model fields。

字段选项

每个字段都有一些特有的参数,详见模型字段参考。 例如,max_length参数来指定数据库字段VARCHAR的大小。

下面是所有字段类型的通用选项,它们都是可选的,reference有它们详细的介绍。下面只对最常用的一种选项快速总结:

null

如果为True,Django将会把数据库中的空值保存为NULL。默认值为False.

blank

如果为True,该字段允许为空值,默认为False.

要注意,blanknull不同。null纯粹是数据库范畴,指数据库中字段内容是否允许为空, 而blank是表单数据输入验证范畴的。如果一个字段的blank=True, 表单的验证将允许输入一个空值。如果字段的blank=False,该字段就是必填的。

choices

由二项元组构成的一个可迭代对象(列表或元组),用来给字段提供选择项。 如果设置了choices,默认的表单将是一个选择框而不是文本框,而且这个选择框的选项就是choices中的选项。 下面是一个关于choices列表的例子:

YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)

每个元组中的第一个元素是存储在数据库中的值。第二个元素是在管理界面或ModelChoiceField中用作显示的内容。给定一个模型实例, 可以使用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'

default

字段的默认值。这可以是一个值,也可以是可调用对象。如果可调用,那么每次创建新对象时都会被调用。

help_text

表单部件额外显示的帮助内容。即使字段不在表单中使用,它对生成文档也很有用。

primary_key

如果为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']

unique

如果该值设置为True, 这个数据字段在整张表中必须是唯一的。

最后重申,这些只是对最常见的字段选项的简短描述,更加详细内容参见公共字段选项。

自增主键

默认情况下Django会给所有的model添加类似下面的字段:

id = models.AutoField(primary_key=True)

这就是自增主键。

如果你想将某个字段设置为主键,在那个字段设置primary_key=True即可, Django发现已经有字段设置了primary_key=True时, 将不会再自动添加id字段。

每个model都必须要有一个字段是primary_key=True。 如果你不添加,Django将自动添加。

字段描述名

ForeignKeyManyToManyFieldOneToOneField外,每个字段类型都接受一个可选的位置参数(第一个位置)——字段的描述名。 如果没有给定描述名,Django将根据字段的属性名称自动创建描述名——将属性名称的下划线替换成空格。

比如这个例子中描述名是person'sfirstname:

first_name = models.CharField("person's first name", max_length=30)

而没有主动设置时,则是firstname:

first_name = models.CharField(max_length=30)

ForeignKey,ManyToManyFieldOneToOneField也是有描述名的,只是他们的第一个参数必须是模型类, 所以描述名需要通过关键字参数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提供了定义三种最常见的数据库关系类型方法:多对一、多对多和一对一。

多对一

Django使用django.db.models.ForeignKey来定义一个多对一的关系,和Field类型一样, 在模型当中把它做为一个类属性包含进来。django.db.models.ForeignKey接收一个位置参数——与model关联的类。

例如:一辆汽车(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字段的其他参数。

这些选项帮助定义关联关系应该如何工作;它们都是可选的参数。 访问反向关联对象的细节,请见反向关联示例.

示例代码,请见多对一关系模型示例.

多对多

使用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里面设置pizzasManyToManyField字段), 因为设想一个Pizza有多种Topping比一个Topping位于多个Pizza上要更加自然。 按照上面的方式,在Pizza的表单中将允许用户选择不同的Toppings

参见

完整的示例参见多对多关系模型示例。

ManyToManyField还接受其他参数,你可以在模型字段参考中查看。 这些选项帮助定义关系应该如何工作;它们都是可选的

多对多关系中的其他字段

处理类似搭配pizzatopping这样简单的多对多关系时,使用标准的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, 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()
<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])

为什么不能这样做? 这是因为你不能只创建PersonGroup之间的关联关系, 你还要指定Membership模型中所需要的所有信息;而简单的addcreate和赋值语句是做不到这一点的。 所以它们不能在使用中介模型的多对多关系中使用。此时,唯一的办法就是创建中介模型的实例。

remove()方法被禁用也是出于同样的原因。

如果中间模型定义的自定义表在(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 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]>

如果你需要访问一个成员的信息,你可以直接获取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。 不过现在已经不这么做了(不过要是你愿意的话,你仍可以传递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 的保留字例如joinwhereselect,可以用作模型的字段名, 因为Django 会对底层的SQL 查询语句中的数据库表名和列名进行转义。 它根据你的数据库引擎使用不同的引用语法。

自定义字段类型

如果已有的模型字段都不合适,或者你想用到一些很少见的数据库列类型的优点, 你可以创建你自己的字段类型。创建你自己的字段在Writing custom model fields中有完整讲述。

Meta选项

使用内部的classMeta定义模型的元数据,例如:

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_nameverbose_name_plural)。在模型中添加classMeta是可选的,所有选项都不是必须的。

Meta选项的完整列表可以在模型选项参考中找到。

模型属性

objects
模型最重要的属性是Manager。它是Django 模型进行数据库查询操作的接口, 并用于从数据库获取实例。 如果没有定义自定义Manager, 则默认名称为objects。Managers只能通过模型类访问,而不是模型实例。

模型方法

通过在模型中添加自定义方法来给对象添加“row级别”的功能。而Manager方法的目的是做“table范围”的事情, 模型方法应该着眼于一个特定的模型实例。

这是一个非常有价值的技术,让业务逻辑位于同一个地方 —— 模型中。

例如,下面的模型具有一些自定义的方法:

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在模型实例需要被显示和强制转为为普通字符串时将使用的内容。 最明显是在交互式控制台或者管理站点显示一个对象的时候。

你会经常需要重新定义这个方法;默认方法返回的内容几乎没有帮助。

__unicode__()(Python 2)

Python 2 中使用,相当于__str__()

get_absolute_url()

它告诉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将保证你的代码自动支持这些新的参数。

批量操作中被重载的模型方法不会被调用

注意,当使用查询集批量删除对象或者使用cascadingdelete时,将不会为每个对象调用delete()方法。为确保自定义的删除逻辑得到执行,你可以使用pre_deleteand/orpost_delete信号。

不幸的是,当批量creatingorupdating对象时没有变通方法, 因为不会调用save()pre_save, 和post_save

执行自定义SQL

另一种常见的需求是在模型方法和模块级方法中编写自定义SQL语句。有关使用原始SQL的更多细节参见使用原始 SQL。

模型继承

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,agehome_groupCommonInfo模型无法像一般的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属性之前, Django 会设置abstract=False。 这意味着抽象基类的子类本身不会自动变成抽象类。 当然,你可以让一个抽象基类继承自另一个抽象基类, 你只要记得每次都要显式地设置abstract=True

一些属性被包含在抽象基类的Meta里面是没有意义的。例如,包含db_table属性将意味着所有的子类(是指那些没有指定自己的Meta类的子类)都使用同一张数据表, 这总归不会是你想要的。

小心使用related_namerelated_query_name

如果你在ForeignKeyManyToManyField字段是使用related_namerelated_query_name属性, 您必须始终为该字段指定惟一的反向名称和查询名称。 但在抽象基类上这样做就会引发一个很严重的问题, 应为Django会将基类字段添加到每个子类中,因此每个子类都具有相同的属性值(包括related_name和related_query_name)。

当您在抽象基类中使用related_namerelated_query_name) 时,要解决这个问题,名称中就要包含“%(app_label)s”“%(class)s”

'%(class)s'会替换为子类的小写加下划线格式的名称,字段在子类中使用。
'%(app_label)s'会替换为应用的小写加下划线格式的名称,应用包含子类。每个安装的应用名称都应该是唯一的,而且应用里每个模型类的名称也应该是唯一的,所以产生的名称应该彼此不同。

例如,假设有一个app叫做common/models.py:

from django.db import models
class Base(models.Model):
m2m = models.ManyToManyField(
OtherModel,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",
)
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_childascommon.ChildB.m2m字段的反向名称是common_childb_related,反向查询名称是common_childbs。最后,rare.ChildB.m2m字段的反向名称是rare_childb_related,反向查询名称是rare_childbs。这取决于你如何使用'%(class)s''%(app_label)s'来构造。 如果你没有这样做,Django 就会在验证 model (或运行migrate) 时抛出错误。

如果你没有在抽象基类中为某个关联字段定义related_name属性, 那么默认的反向名称就是子类名称加上'_set', 它能否正常工作取决于你是否在子类中定义了同名字段。 例如,在上面的代码中,如果去掉related_name属性,ChildA中,m2m字段的反向名称是childa_set;而ChildB中的m2m字段的反向名称就是childb_set

related_query_name可以添加'%(app_label)s''%(class)s'是在1.10中加入。

多表继承

这是 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类是没什么意义的。 所有的Meta选项已经对父类起了作用,再次使用只会起反作用。 (这与使用抽象基类的情况正好相反,因为抽象基类并没有属于它自己的内容)

所以子 model 并不能访问它父类的Meta类。但是在某些受限的情况下,子类可以从父类继承某些行为:如果子类没有指定ordering属性或get_latest_by属性,它就会从父类中继承这些属性。

如果父类有了排序设置,而你并不想让子类有任何排序设置,你就可以显式地禁用排序:

class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []

继承与反向关联

因为多表继承使用了一个隐含的OneToOneField来链接子类与父类, 所以象上例那样,你可以用父类来指代子类。但是这个字段默认的related_name的值与ForeignKeyManyToManyField默认的反向名称相同。 如果你与该父类的另一个子类做多对一或是多对多关系,你就必须在每个多对一和多对多字段上强制指定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 ,或是添加一个新方法。

这就是代理模型继承的好处:为原始模型创建一个代理。您可以创建、删除和更新代理模型的实例, 并且所有的数据将被保存,就像您使用原始的(非代理的)模型一样。 不同之处在于,您可以在代理中更改默认的模型排序或manager,而不必更改原始数据。

声明代理 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排序。

代理模型的Meta属性继承和普通模型相同。

QuerySet始终返回请求的模型

也就是说,没有办法让django在查询Person对象时返回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

你可能不需要经常这样做,但这样做是可行的。

代理模型与非托管模型之间的差异

代理 model 继承看上去和使用Meta类中的managed属性的非托管 model 非常相似。但两者并不相同,使用时选用哪种方案是一个值得考虑的问题。

通过设置Meta.db_table可以创建一个非托管的模型,它可以将现有模型隐藏, 并向它添加Python方法。但是,这非常繁冗和脆弱,因为不论您做任何更改,都需要同时保持两个副本同步。

另一方面,代理模型的行为与代理模型完全相同。它们总是与父模型保持同步,因为它们直接继承了它的字段和管理器。

所以,一般规则是:

    如果你要借鉴一个已有的 模型或数据表,并不想要所有原始的数据库表列,那就令Meta.managed=False。通常情况下,对模型数据库创建视图和表格不需要由 Django 控制时,就使用这个选项。
    如果你想对 model 做 Python 层级的改动,又想保留字段不变,那就令Meta.proxy=True。 因此在数据保存时,代理 model 相当于完全复制了原始 模型的存储结构。

多重继承

和Python的普通类一样,django的模型可以继承自多个父类模型。 切记一般的Python名称解析规则也会适用。 一个特定名称(例如Meta)出现的第一个基类将将是真正使用基类。这意味着如果多个父类含有Meta类,只有第一个会被使用,剩下的会忽略掉。

一般来说,你并不需要继承多个父类。多重继承主要对“mix-in”类有用: 向每个继承mix-in的类添加一个特定的、额外的字段或者方法。 你应该尝试将你的继承关系保持得尽可能简洁和直接,这样你就不必费很大力气来弄清楚某段特定的信息来自哪里。

注意,从具有普通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

不“重写”字段

在普通的Python类继承中,子类可以覆盖父类的任何属性。在Django中,模型字段通常不允许这样做。 如果一个非抽象模型基类有一个名为author的字段,那么您就不能创建另一个模型字段, 也不能在任何继承自基类的类中定义一个名为author的属性。

这个限制并不适用于从抽象模型继承的模型字段。这种情况字段可以被另一个字段或值覆盖, 或者被设置成field_name=None删除掉。

在1.10版本中新增了重写抽象字段的功能。

警告

模型管理器是从抽象基类继承而来的。重写继承Manager引用的继承字段 可能会导致一些的错误。参见自定义管理器和模型继承。

注解

一些字段在模型上定义了额外的属性,例如,一个ForeignKey定义了一个额外的属性, 并将_id附加到字段名,以及在外键模型上的related_namerelated_query_name

除非更改或删除了定义它的字段,否则不能重写这些额外的属性。

重写父类的字段会导致很多麻烦,比如:初始化实例(指定在 Model.__init__ 中被实例化的字段) 和序列化。 而普通的 Python 类继承机制并不能处理好这些特性。 所以 Django 的继承机制被设计成与 Python 有所不同,这样做并不是随意而为的。

这些限制仅仅针对做为属性使用的Field实例, 并不是针对 Python 属性,Python 属性仍是可以被重写的。 在 Python 看来,上面的限制仅仅针对字段实例的名称: 如果你手动指定了数据库的列名称,那么在多重继承中,你就可以在子类和某个祖先类当中使用同一个列名称。 (因为它们使用的是两个不同数据表的字段)。

如果你在祖先类中重写了某个 model 字段,Django 都会抛出FieldError异常

将模型放到包中

manage.pystartapp命令创建一个包含models.py的应用文件结构。 如果您有许多模型,把他们放在单独的文件夹中非常有用。

为此,创建一个models包。删除models.py。 新建一个包含__init__.py文件的路径myapp/models/来放置模型。 必须在__init__.py中导入模型。

myapp/models/__init__.py

from .organic import Person
from .synthetic import Robot

显式导入每个模型而不是使用from.modelsimport*。这样具有命名空间明确,代码更具可读性等优点。

参见

模型参考
涵盖模型相关的API,包括模型字段、关联对象和查询集

全部文档: https://github.com/jhao104/django-chinese-doc

Django1.10主题指南—模型的相关教程结束。

《Django1.10主题指南—模型.doc》

下载本文的Word格式文档,以方便收藏与打印。