Django关系类型字段
简述
本文一共三大类,分别是
- 多对一(外键ForeignKey)
- 多对多(ManyToManyField)
- 一对一(OneToOneField)
其中用的最多的一般是外键,也是本文重点,如果你正在学习Django或者准备学习Django,本文将对你至关重要,请仔细阅读,没有需要略过的地方
外键ForeignKey
class ForeignKey(to, on_delete, **options)
-
to
:关联的模型 -
on_delete
: 关联删除(2.0后必填)
常规使用
from django.db import models
# 如果要关联的对象在另外一个app中,可以显式的指出
# 如CreateCar在appone中,则appone.CreateCar
class Car(models.Model):
create_car = models.ForeignKey(
'CreateCar', # here
on_delete=models.CASCADE,
)
class CreateCar(models.Model):
pass
自身递归
# 自身递归外键可以用self (评论系统常用)
class Comment(models.Model):
title = models.CharField(max_length=128)
text = models.TextField()
parent_comment = models.ForeignKey('self', on_delete=models.CASCADE)
在实际的数据库后台,Django会为每一个外键添加_id后缀,并以此创建数据表里的一列。
重要参数
这块之所以要单独列一栏,是因为在关系类型的字段中,大多是一样的,这列主要是外键参数,在另外的多对多和一对一中,有很多相同的参数,所以在后续的两栏中只列举与此参数组不同的部分
on_delete
当外键关联对象被删除时,Django将按照on_delete
的约束执行相应操作。
"""
你有一个可为空的外键
你想让它在关联的对象被删除时,自动设为null
其实较常用的还是on_delete=models.CASCADE
"""
user = models.ForeignKey(
User,
models.SET_NULL,
blank=True,
null=True,
)
该参数常量可以在django.db.models.deletion.py
中看到所有常量
-
CASCADE
:模拟SQL语言中的ON DELETE CASCADE
约束,将定义有外键的模型对象同时删除!(默认操作,且最常用) -
PROTECT
:阻止删除操作,执行删除时,会抛出ProtectedError
异常 -
SET_NULL
:将外键字段设为null
,只有当字段设置了null=True
时,方可使用该值。 -
SET_DEFAULT
:将外键字段设为默认值。只有当字段设置了default
参数时,方可使用。 -
DO_NOTHING
:啥都不干 -
SET()
:设置为一个传递给SET()
的值或者一个回调函数的返回值。
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
def get_sentinel_user():
return get_user_model().objects.get_or_create(username='deleted')[0]
class MyModel(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET(get_sentinel_user),
)
limit_choices_to
该参数用于限制外键所能关联的对象,只能用于Django
的ModelForm
(表单模块)和admin
后台,对其它场合无限制功能。
其值可以是一个dict
、Q
对象(参见Q以及F)或者一个返回dict
或Q()
的函数调用,如下例所示:
# employee列中只显示is_employee=True的Users对象
# 这一功能对于admin后台非常有用。
employee = models.ForeignKey(
User,
on_delete=models.CASCADE,
limit_choices_to={'is_employee': True},
)
related_name
用于关联对象反向引用模型的名称。默认为None
用Car和CreateCar的例子解释,就是从CreateCar反向关联Car的关系名称。
这个参数可以不设置,通常情况下,Django会默认以模型的小写作为反向关联名
如果你不想为外键设置一个反向关联名称,可以将这个参数设置以+
结尾
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='+',
)
related_query_name
反向关联查询。
用于从目标模型反向过滤模型对象的名称。
class Tag(models.Model):
article = models.ForeignKey(
Article,
on_delete=models.CASCADE,
related_name="tags",
related_query_name="tag",
)
name = models.CharField(max_length=255)
# 现在可以使用‘tag’作为查询名了
Article.objects.filter(tag_name="important")
to_field
默认情况下,外键都是关联到被关联对象的主键上。
如果指定to_field
参数,可以关联到指定的字段上,但是该字段必须具有唯一性(unique=True
属性)
db_constraint
遵从数据库约束,默认值为True
如果设为False
,那么将无法保证数据的完整性和合法性。
只有在特殊情况下,才可能需要将它设置为False
:
- 有历史遗留的不合法数据
- 你正在分割数据表
当它为False
,而且试图访问一个不存在的关系对象时,会抛出DoesNotExist
异常。
多对多ManyToManyField
多对多关系在数据库中也是非常常见的关系类型。
比如一本书可以有好几个作者,一个作者也可以写好几本书。
多对多的字段可以定义在任何的一方,请尽量定义在符合人们思维习惯的一方,但不要同时都定义。
多对多关系需要一个位置参数,用来关联的对象模型。它的用法和外键差不多
class ManyToManyField(to, **options)
在数据库后台,Django实际上会额外创建一张用于体现多对多关系的中间表。
- 默认情况下,该表的名称是
字段名 + 关联对象模型 + hashCode
,如author_books_9cdf4
, - 可以通过
db_table
选项,自定义表名。
多对多参数说明
参考外键参数部分。
related_name
related_query_name
db_constraint
特别说明
-
ManyToManyField
不支持Django内置的validators验证功能。 -
null
参数对ManyToManyField
无效!设置null=True
毫无意义 -
swappable
如果当前外键指向一个可交换的模型,可以控制迁移框架的动作。默认为True
,几乎用不到 -
limit_choices_to
对于使用through
参数自定义中间表的多对多字段无效, 其他参考外键参数
symmetrical
默认情况下,Django中的多对多关系是对称的,从这个参数的名称就能看出来,意为“对称”。
from django.db import models
class Person(models.Model):
friends = models.ManyToManyField("self")
Django认为,如果我是你的朋友,那么你也是我的朋友,这是一种对称关系
Django不会为Person模型添加person_set
属性用于反向关联。如果你不想使用这种对称关系,可以将symmetrical
设置为False
(强制Django为反向关联添加描述符)。
through
定义中间表,特别说明中的limit_choices_to
参数,除了这个,其他都参照外键参数,就说明through
参数是多对多特有的,而且在多对多关系中至关重要。
如果你想自定义多对多关系中额外的关联表,可以使用这个参数!参数值为中间模型。
# 该表在数据库中的结构
中间表的id列...模型对象的id列...被关联对象的id列
# 中间表添加字段的结构
中间表的id列...模型对象的id列...被关联对象的id列...定义的字段对象列
demo
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership', # 自定义中间表
through_fields=('group', 'person'), # 参数说明见下方
)
# 定义中间表模型
# 用来保存Person和Group模型的多对多关系
# 同时增加了‘邀请人’和‘邀请原因’的字段。
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey( # 添加邀请人字段
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
# 添加邀请原因字段
invite_reason = models.CharField(max_length=64)
through_fields
Membership
模型中包含两个关联Person
的外键,Django无法确定到底使用哪个作为和Group关联的对象。所以,在这个例子中,必须显式的指定through_fields
参数,用于定义关系。
through_fields
参数接收一个元组('field1', 'field2')
-
field1
:指向定义有多对多关系的模型的外键字段的名称,这里是Membership
中的group
字段, -
field2
指向目标模型的外键字段的名称,这里是Membership
中的person
,而不是inviter
。
再通俗的说,就是through_fields
参数指定从中间表模型Membership
中选择哪两个字段,作为关系连接字段。
db_table
设置中间表的名称。不指定的话,则使用默认值。
一对一OneToOneField
class OneToOneField(to, on_delete, parent_link=False, **options)[source]
从概念上讲,一对一关系非常类似具有unique=True
属性的外键关系,但是反向关联对象只有一个。
这种关系类型多数用于当一个模型需要从别的模型扩展而来的情况。比如,Django自带auth模块的User用户表,如果你想在自己的项目里创建用户模型,又想方便的使用Django的认证功能,那么一个比较好的方案就是在你的用户模型里,使用一对一关系,添加一个与auth模块User模型的关联字段。
该关系的to
参数为关联的模型,其用法和前面的多对一外键一样。
如果你没有给一对一关系设置related_name
参数,Django将使用当前模型的小写名作为默认值。
OneToOneField
一对一关系拥有和多对一外键关系一样的额外可选参数,只是多了一个parent_link
参数。
from django.conf import settings
from django.db import models
# 两个字段都使用一对一关联到了Django内置的auth模块中的User模型
class MySpecialUser(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
supervisor = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='supervisor_of',
)
这样下来,你的User模型将拥有下面的属性
>>> user = User.objects.get(pk=1)
>>> hasattr(user, 'myspecialuser')
True
>>> hasattr(user, 'supervisor_of')
True
跨模块的模型
对于跨模块的模型,导入即可使用