八、Django之Models(译)
作者:@skyflask
转载本文请注明出处:https://www.cnblogs.com/skyflask/p/9349706.html
目录
模型(Models)
简短示例
使用模型
字段Meta
选项
模型属性
模型方法
模型继承
在包中组织模型
模型(Models)
模型是你的数据的唯一的、确定的信息源。 它包含你所储存数据的必要字段和行为。 通常,每个模型对应数据库中唯一的一张表。
基础:
- 每个模型都是一个Python类,它们都是
django.db.models.Model
的子类。 - 每一个模型属性都代表数据库中的一个字段。
- 通过所有这一切,Django为你提供一个自动生成的数据库访问API;请参阅执行查询。
简短示例
这个例子定义一个Person
模型,它有first_name
和last_name
两个属性:
1 2 3 4 5 | 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
模型会在数据库中创建这样一张表:
1 2 3 4 5 | 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 会根据settings文件 中指定的数据库类型来使用相应的SQL 语句。
使用模型
定义好模型之后,接下来你需要告诉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 冲突,比如save
、clean
或者delete
。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 | 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根据Field的类型确定以下信息:
- 列类型,它告知数据库要存储哪种数据(例如,
INTEGER
,VARCHAR
,TEXT
)。 - 渲染表单时使用的默认HTML widget(例如,
<input type="text">
,<select>
)。 - 最低限度的验证需求,它被用在 Django 管理站点和自动生成的表单中。
Django拥有数十种内置的Field类型;你可以在模型字段参考中找到完整列表。 如果Django内置的字段不能胜任你的要求,你可以轻松地编写自己的字段;请参阅编写自定义的模型字段。
字段选项
每个字段都接受一组与字段有关的参数(文档在模型字段参考中)。 例如,CharField
(和它的派生类)需要max_length
参数来指定VARCHAR
数据库字段的大小。
还有一些适用于所有字段的通用参数。 这些参数在参考中有详细定义,这里我们只简单介绍一些最常用的:
null
- 如果为
True
,Django将在数据库中把空值存储为NULL
。 默认为False
。 blank
-
如果为
True
,该字段允许为空值, 默认为False
。要注意,这与
null
不同。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()
方法来访问选项字段的显示值。 例如:12345678910111213141516from
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
。 详见字段生成主键字段。主键字段是只读的。 如果你在一个已存在的对象上面更改主键的值并且保存,一个新的对象将会在原有对象之外创建出来。 例如:
123456789from
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
, 则这个字段在整张表中必须是唯一的。
再说一次,这些仅仅是常用字段的简短介绍, 要了解详细内容,请查看通用模型字段选项参考。
自动主键字段
默认情况下,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
关键字参数才能指定自述名:
1 2 3 4 5 6 7 8 9 10 11 | 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),多对多(many-to-many),一对一(one-to-one)。
多对一关系
Django 使用 django.db.models.ForeignKey
定义多对一关系。 和使用其它Field
类型一样:在模型当中把它做为一个类属性包含进来。
ForeignKey
需要一个位置参数:与该模型关联的类。
比如,一辆Car
有一个Manufacturer
— 但是一个Manufacturer
生产很多汽车(Car),每一辆Car
只能有一个Manufacturer
— 使用下面的定义:
1 2 3 4 5 6 7 8 9 | from django.db import models class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete = models.CASCADE) # ... |
你还可以创建递归关联关系(与自身具有多对一关系的对象)和与尚未定义的模型的关系;有关详细信息,请参见模型字段参考。
建议你用被关联的模型的小写名称做为ForeignKey
字段的名字(例如,上面manufacturer
)。 当然,你也可以起别的名字。 例如:
1 2 3 4 5 6 | class Car(models.Model): company_that_makes_it = models.ForeignKey( Manufacturer, on_delete = models.CASCADE, ) # ... |
另见
ForeignKey
字段还接受许多别的参数,在模型字段参考有详细介绍。 这些选项有助于确定关系如何工作;都是可选的。
访问反向关联对象的细节,请见反向查询关联关系的示例。
示例代码,请见多对一关联关系示例。
多对多关系
ManyToManyField
用来定义多对多关系, 和使用其它Field
类型一样:在模型当中把它做为一个类属性包含进来。
ManyToManyField
需要一个位置参数:和该模型关联的类。
例如,一个Pizza
可以有多种Topping
即一种Topping
也可以位于多个Pizza上,而且每个Pizza
有多个topping,下面是如何表示这个关系:
1 2 3 4 5 6 7 8 9 | from django.db import models class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping) |
与ForeignKey
一样,你还可以创建递归关联关系(与其自身具有多对多关系的对象)和与尚未定义的模型的关联关系。
建议你以被关联模型名称的复数形式做为ManyToManyField
的名字(例如上例中的toppings
)。
在哪个模型中设置 ManyToManyField
并不重要,在两个模型中任选一个即可 —— 不要两个模型都设置。
一般来说,ManyToManyField
实例应该放在要在表单中被编辑的对象中。 在上面的例子中,Topping
位于Pizza
中(而不是在 toppings
里面设置pizzas
的 ManyToManyField
字段),因为设想一个Pizza 有多种Topping 比一个Topping 位于多个Pizza 上要更加自然。 按照上面的方式,在Pizza
的表单中将允许用户选择不同的Toppings。
另见
完整的示例参见多对多关联关系模型示例。
ManyToManyField
字段还接受别的参数,在模型字段参考中有详细介绍。 这些选项有助于确定关系如何工作;都是可选的。
多对多关系的额外字段
处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的ManyToManyField
就可以了。 但是,有时你可能需要关联数据到两个模型之间的关系上。
例如,有这样一个应用,它记录音乐家所属的音乐小组。 我们可以用一个ManyToManyField
表示小组和成员之间的多对多关系。 但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。
对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。 源模型的ManyToManyField
字段将使用through
参数指向中介模型。 对于上面的音乐小组的例子,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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
),接下来你要开始创建多对多关系。 你要做的就是创建中介模型的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | >>> 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()
创建关系:
1 2 3 4 | >>> # 下列语句都是无法工作的 >>> beatles.members.add(john) >>> beatles.members.create(name = "George Harrison" ) >>> beatles.members. set ([john, paul, ringo, george]) |
为什么不能这样做? 这是因为你不能只创建 Person
和 Group
之间的关联关系,你还要指定 Membership
模型中所需要的所有信息; 而简单的add
、create
和赋值语句是做不到这一点的。 所以它们不能在使用中介模型的多对多关系中使用。 此时,唯一的办法就是创建中介模型的实例。
remove()
方法被禁用也是出于同样的原因。 例如,如果通过中介模型定义的表没有在(model1, model2)
对上强制执行唯一性,则remove()
调用将不能提供足够的信息,说明应该删除哪个中介模型实例:
1 2 3 4 5 6 7 | >>> 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()
方法却是可用的。它可以清空某个实例所有的多对多关系:
1 2 3 4 | >>> # Beatles have broken up >>> beatles.members.clear() >>> # Note that this deletes the intermediate model instances >>> Membership.objects. all () |
通过创建中介模型的实例来建立对多对多关系后,你就可以执行查询了。 和普通的多对多字段一样,你可以直接使用被关联模型的属性进行查询:
1 2 3 | # Find all the groups with a member whose name starts with 'Paul' >>> Group.objects. filter (members__name__startswith = 'Paul' ) <QuerySet [<Group: The Beatles>]> |
如果你使用了中介模型,你也可以利用中介模型的属性进行查询:
1 2 3 4 5 | # 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
模型:
1 2 3 4 5 | >>> 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
对象上查询多对多反向关系:
1 2 3 4 5 | >>> 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
用来定义一对一关系。 和使用其它Field
类型一样:在模型当中把它做为一个类属性包含进来。
当某个对象想扩展自另一个对象时,最常用的方式就是在这个对象的主键上添加一对一关系。
OneToOneField
要一个位置参数:与模型关联的类。
例如,如果你正在建立一个“places”的数据库,那么你将建立一个非常标准的地址、电话号码等 在数据库中。 接下来,如果你想在place数据库的基础上建立一个restaurant数据库,而不想将已有的字段复制到Restaurant
模型,那你可以在 Restaurant
添加一个OneToOneField
字段,这个字段指向Place
(因为Restaurant 本身就是一个Place;事实上,在处理这个问题的时候,你应该使用一个典型的 inheritance,它隐含一个一对一关系)。
与ForeignKey
一样,可以定义递归关系,并可以引用尚未定义的模型。
另见
在一对一关联关系模型示例中有一套完整的例子。
OneToOneField
字段也接受一个可选的parent_link
参数。
在以前的版本中,OneToOneField
字段会自动变成模型 的主键。 不过现在已经不这么做了(不过要是你愿意的话,你仍可以传递 primary_key
参数来创建主键字段)。 所以一个 模型 中可以有多个OneToOneField
字段。
跨文件的模型
访问其他应用的模型是非常容易的。 在文件顶部你定义模型的地方,导入相关的模型来实现它。 然后,无论在哪里需要的话,都可以引用它。 例如:
1 2 3 4 5 6 7 8 9 10 11 | 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 的保留字例如select
、where
和join
,可以用作模型的字段名,因为Django 会对底层的SQL 查询语句中的数据库表名和列名进行转义。 它根据你的数据库引擎使用不同的引用语法。
自定义字段类型
如果已有的模型字段都不合适,或者你想用到一些很少见的数据库列类型的优点,你可以创建你自己的字段类型。 创建你自己的字段在编写自定义模型字段中有完整讲述。
Meta
选项
使用内部的class Meta
定义模型的元数据,例如:
1 2 3 4 5 6 7 8 | 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
选项的完整列表可以在模型选项参考中找到。
模型方法
可以在模型上定义自定义的方法来给你的对象添加自定义的“底层”功能。 Manager
方法用于“表范围”的事务,模型的方法应该着眼于特定的模型实例。
这是一个非常有价值的技术,让业务逻辑位于同一个地方 — 模型中。
例如,下面的模型具有一些自定义的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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" @property def full_name( self ): "Returns the person's full name." return '%s %s' % ( self .first_name, self .last_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()
中关于它接受的参数的文档):
1 2 3 4 5 6 7 8 9 10 | 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() |
你还可以阻止保存:
1 2 3 4 5 6 7 8 9 10 11 | 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
,将保证你的代码自动支持这些新的参数。
批量操作中被覆盖的模型方法不会被调用
请注意,当使用QuerySet批量删除对象或由于级联删除
时,对象的delete()
方法不一定被调用。 为确保自定义的删除逻辑得到执行,你可以使用pre_delete
和/或post_delete
信号。
不幸的是,当批量creating
或updating
对象时没有变通方法,因为不会调用save()
、pre_save
和 post_save
。
执行自定义SQL
另外一个常见的需求是在模型方法和模块级别的方法中编写自定义的SQL 语句。 关于使用原始SQL语句的更多细节,参见使用原生SQL的文档。
模型继承
Django 中的模型继承与 Python 中普通类继承方式几乎完全相同,但是本页头部列出的模型基本的要求还是要遵守。 这表示自定义的模型类应该继承django.db.models.Model
。
你唯一需要作出的决定就是你是想让父模型具有它们自己的数据库表,还是让父模型只持有一些共同的信息而这些信息只有在子模型中才能看到。
在Django 中有3种风格的继承。
- 通常,你只想使用父类来持有一些信息,你不想在每个子模型中都敲一遍。 这个类永远不会单独使用,所以你要使用抽象的基类。
- 如果你继承一个已经存在的模型且想让每个模型具有它自己的数据库表,那么应该使用多表继承。
- 最后,如果你只是想改变一个模块Python 级别的行为,而不用修改模型的字段,你可以使用代理模型。
抽象基类
当你想将一些共有信息放进其它一些model的时候,抽象化类是十分有用的。 你编写完基类之后,在 Meta类中设置 abstract=True
, 这个模型就不会被用来创建任何数据表。 取而代之的是,当它被用来作为一个其他model的基类时,它的字段将被加入那些子类中。 如果抽象基类和它的子类有相同的字段名,那么将会出现error(并且Django将抛出一个exception)。
一个例子
1 2 3 4 5 6 7 8 9 10 11 | 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类,它可以子类化它。 例如:
1 2 3 4 5 6 7 8 9 10 11 12 | 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
来实现)。 例如:
1 2 3 4 5 6 7 8 9 | 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
表中。 所以下面两个语句都是可以运行的:
1 2 | >>> Place.objects. filter (name = "Bob's Cafe" ) >>> Restaurant.objects. filter (name = "Bob's Cafe" ) |
如果你有一个 Place
,它同时也是一个 Restaurant
, 那么你可以使用 model 的小写形式从 Place
对象中获得与其对应的 Restaurant
对象:
1 2 3 | >>> p = Place.objects.get( id = 12 ) # If p is a Restaurant object, this will give the child class: >>> p.restaurant |
但是,如果上例中的p
并不是一个Restaurant
(比如它仅仅只是Place
一个对象,或者它是其它类的父类),那么在引用 p.restaurant
时就会抛出Restaurant.DoesNotExist
异常。
Restaurant
上自动创建的将它链接到Place
的OneToOneField
如下所示:
1 2 3 4 | place_ptr = models.OneToOneField( Place, on_delete = models.CASCADE, parent_link = True , ) |
你可以通过在Restaurant
上使用parent_link=True
声明自己的OneToOneField
来覆盖该字段。
Meta
和多表继承
在多表继承中,子类继承父类的 Meta类是没什么意义的。 所有的 Meta 选项已经对父类起了作用,再次使用只会起反作用(这与使用抽象基类的情况正好相反,因为抽象基类并没有属于它自己的内容)。
所以子 model 并不能访问它父类的 Meta 类。 但是在某些受限的情况下,子类可以从父类继承某些 Meta :如果子类没有指定 ordering
属性或 get_latest_by
属性,它就会从父类中继承这些属性。
如果父类有了排序设置,而你并不想让子类有任何排序设置,你就可以显式地禁用排序:
1 2 3 4 5 | class ChildModel(ParentModel): # ... class Meta: # Remove parent's ordering effect ordering = [] |
继承和反向关系
因为多表继承使用了一个隐含的 OneToOneField
来链接子类与父类,所以象上例那样,你可以用父类来指代子类。 但是这个 OnetoOneField 字段默认的 related_name
值与 ForeignKey
和 ManyToManyField
默认的反向名称相同。 如果你与该父类的另一个子类做多对一或是多对多关系,你就必须在每个多对一和多对多字段上强制指定 related_name
。 如果你没这么做,Django 就会在你运行 验证(validation) 时抛出异常。
例如,仍以上面 Place
类为例,我们创建一个带有 ManyToManyField
字段的子类:
1 2 | class Supplier(Place): customers = models.ManyToManyField(Place) |
这会产生一个错误:
1 2 3 4 5 | 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')
。
指定父链接字段
如前所述,Django将自动创建一个将您的子类的链接返回到任何非抽象父模型的OneToOneField
。 如果你想指定链接父类的属性名称,你可以创建你自己的 OneToOneField
字段并设置 parent_link=True
,从而使用该字段链接父类。
代理模型
使用 多表继承时,model 的每个子类都会创建一张新数据表, 通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包含在基类中的字段数据。 但有时,你可能只想更改 model 在 Python 层的行为实现。比如:更改默认的 manager ,或是添加一个新方法。
而这,正是代理继承要做的:为原始模型创建一个代理 。 你可以创建,删除,更新代理 model 的实例,而且所有的数据都可以像使用原始 model 一样被保存。 不同之处在于:你可以在代理 model 中改变默认的排序设置和默认的 manager ,更不会对原始 model 产生影响。
声明代理 model 和声明普通 model 没有什么不同。 设置Meta
类中 proxy
的值为 True
,就完成了对代理 model 的声明。
举个例子,假设你想给 Person
模型添加一个方法。 你可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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")
你也可以使用代理 model 给 model 定义不同的默认排序设置。 你可能并不想每次都给Person
模型排序,但是使用代理的时候总是按照last_name
属性排序。 这非常容易:
class OrderedPerson(Person):
class Meta:
ordering = ["last_name"]
proxy = True
现在,普通的last_name
查询是无序的,而 OrderedPerson
查询会按照Person
排序。
代理模型以与常规模型相同的方式继承Meta
属性。
QuerySet
仍然返回请求的模型
也就是说,没有办法让django在查询MyPerson
对象时返回Person
对象。 Person
对象的查询集会返回相同类型的对象。 代理对象的要点是:它会使用依赖于原生Person
的代码,而你可以使用你添加进来的扩展对象(它不会依赖其它任何代码)。 而并不是将Person
模型(或者其它)在所有地方替换为其它你自己创建的模型。
基类限制
代理模型必须继承自一个非抽象基类。 你不能继承自多个非抽象基类,这是因为一个代理模型不能连接不同的数据表。 代理模型可以继承任意多个抽象基类,但前提是它们没有 定义任何模型 字段。 代理模型也可以继承任意数量的代理模型,只有这些代理模型共享一个非抽象父类。
在早期版本中,代理模型无法继承多个共享同一父类的代理模型。
代理模型管理器
如果你没有在代理 模型中定义任何管理器 ,代理模型就会从父类中继承管理器 。 如果你在代理模型中定义了一个管理器 ,它就会变成默认的管理器 ,不过定义在父类中的管理器仍然有效。
继续上面的例子,当你查询Person
模型的时候,你可以改变默认 管理器,例如:
1 2 3 4 5 6 7 8 9 10 11 | from django.db import models class NewManager(models.Manager): # ... pass class MyPerson(Person): objects = NewManager() class Meta: proxy = True |
如果你想要向代理中添加新的管理器,而不是替换现有的默认管理器,你可以使用custom manager管理器文档中描述的技巧:创建一个含有新的管理器的基类,并继承时把他放在主基类的后面:
1 2 3 4 5 6 7 8 9 10 | # 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
属性的非托管 model 非常相似。
通过仔细设置Meta.db_table
,您可以创建一个非托管模型来影响现有模型,并向其添加Python方法。 因此,如果你要保证这两个 model 同步并对程序进行改动,那么就会变得繁冗而脆弱。
另一方面,代理模型的目的是与其代理的模型完全相似。 它们始终与父模型同步,因为它们直接继承其字段和管理器。
一般规则是:
- 如果你要借鉴一个已有的 模型或数据表,且不想涉及所有的原始数据表的列,那就令
Meta.managed=False
。 通常情况下,对模型数据库创建视图和表格不需要由 Django 控制时,就使用这个选项。 - 如果你想对 model 做 Python 层级的改动,又想保留字段不变,那就令
Meta.proxy=True
。 因此在数据保存时,代理 model 相当于完全复制了原始 模型的存储结构。
多重继承
就像Python的子类那样,django的模型可以继承自多个父类模型。 切记一般的Python名称解析规则也会适用。 特定名称(例如Meta)出现的第一个基类将是使用的基类;例如,这意味着如果多个父节点包含一个Meta类,则只会使用第一个,而所有其他父类将被忽略。
一般来说,你并不需要继承多个父类。 主要的用例是“混合”类:为继承混合的每个类添加一个特殊的额外的字段或方法。 尽量让您的继承层次结构尽可能简单直观,这样您就不用努力找出特定信息来源。
请注意,从具有常用id
主键字段的多个模型继承将引发错误。 你可以在模型基类中使用显式的AutoField
来合理使用多重继承:
1 2 3 4 5 6 7 8 9 10 | 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
。 这需要使用从每个父模型到共同祖先的显式OneToOneFiel
”来避免由子自动生成和继承的字段之间的冲突:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Piece(models.Model): pass class Article(Piece): article_piece = models.OneToOneField(Piece, on_delete = models.CASCADE, parent_link = True ) ... class Book(Piece): book_piece = models.OneToOneField(Piece, on_delete = models.CASCADE, parent_link = True ) ... class BookReview(Book, Article): pass |
字段名称“隐藏”不允许
普通的 Python 类继承允许子类覆盖父类的任何属性。 在Django中,模型字段通常不允许这样做。 如果非抽象模型基类有一个名为author
的字段,则不能在任何继承自该基类的类中创建另一个模型字段或定义一个属性叫做author
。
此限制不适用于从抽象模型继承的模型字段。 这些字段可能会被另一个字段或值覆盖,或者通过设置field_name = None
来删除。
添加覆盖抽象字段的能力。
警告
抽象基类继承了模型管理器。 覆盖由继承的Manager
引用的继承字段可能会导致微妙的错误。 请参阅自定义模型管理器和模型继承。
注意
一些字段在模型上定义了额外的属性,例如一个ForeignKey
定义了一个额外的属性,其中附加了字段名称的_id
,以及外部模型的related_name
和related_query_name
。
除非定义该属性的字段被更改或删除,否则这些额外的属性不能被覆盖,以便它不再定义额外的属性。
重写父类的字段会导致很多麻烦,比如:初始化实例(指定在 Model.__init__
中被实例化的字段) 和序列化。 而普通的 Python 类继承机制并不能处理好这些特性。所以 Django 的继承机制被设计成与 Python 有所不同,这样做并不是随意而为的。
这些限制仅仅针对做为属性使用的 Field
实例, 并不是针对 Python 属性,Python 属性仍是可以被重写的。 它也只适用于Python所看到的属性名称:如果手动指定数据库列名称,则可以在子表和祖先模型中出现相同的列名以实现多表继承(它们是列在两个不同的数据库表中)。
如果你在任何一个祖先类中重写了某个 model 字段,Django 都会抛出 FieldError
异常。
在包中组织模型
manage.py startapp
命令创建的应用目录结构包括一个models.py
文件。 如果你有很多模型,在单独的文件中组织它们可能更有用。
为此,请创建一个models
包。 删除models.py
并创建一个myapp/models/
目录,下面有一个__init__.py
文件以及存储你的模型的文件。 你必须在__init__.py
文件中导入这些模型。
例如,如果models
目录中有organic.py
和synthetic.py
from .organic import Person
from .synthetic import Robot
明确导入每个模型而不是使用from .models import *
,这样的好处是不会混淆命名空间、使代码更易读并保持代码分析工具能派上用场。
另见
- 模型参考
- 涵盖模型相关的API,包括模型字段、关联对象和
QuerySet
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」