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

该参数用于限制外键所能关联的对象,只能用于DjangoModelForm(表单模块)和admin后台,对其它场合无限制功能。

其值可以是一个dictQ对象(参见Q以及F)或者一个返回dictQ()的函数调用,如下例所示:

# 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

跨模块的模型

对于跨模块的模型,导入即可使用

你可能感兴趣的:(Django关系类型字段)