# 第五节:外键和表关系
# 外键和表关系
## 外键:
在`MySQL`中,表有两种引擎,一种是`InnoDB`,另外一种是`myisam`。如果使用的是`InnoDB`引擎,是支持外键约束的。外键的存在使得`ORM`框架在处理表关系的时候异常的强大。因此这里我们首先来介绍下外键在`Django`中的使用。
类定义为`class ForeignKey(to,on_delete,**options)`。第一个参数是引用的是哪个模型,第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理,比如有`CASCADE`、`SET_NULL`等。这里以一个实际案例来说明。比如有一个`User`和一个`Article`两个模型。一个`User`可以发表多篇文章,一个`Article`只能有一个`Author`,并且通过外键进行引用。那么相关的示例代码如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span><span class="hljs-params">(models.Model)</span>:</span>
username = models.CharField(max_length=<span class="hljs-params">20</span>)
password = models.CharField(max_length=<span class="hljs-params">100</span>)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.CASCADE)
```
```
以上使用`ForeignKey`来定义模型之间的关系。即在`article`的实例中可以通过`author`属性来操作对应的`User`模型。这样使用起来非常的方便。示例代码如下:
```
<pre class="calibre12">```
article = Article(title=<span class="hljs-string">'abc'</span>,content=<span class="hljs-string">'123'</span>)
author = User(username=<span class="hljs-string">'张三'</span>,password=<span class="hljs-string">'111111'</span>)
article.author = author
article.save()
<span class="hljs-title"># 修改article.author上的值</span>
article.author.username = <span class="hljs-string">'李四'</span>
article.save()
```
```
为什么使用了`ForeignKey`后,就能通过`author`访问到对应的`user`对象呢。因此在底层,`Django`为`Article`表添加了一个`属性名_id`的字段(比如author的字段名称是author\_id),这个字段是一个外键,记录着对应的作者的主键。以后通过`article.author`访问的时候,实际上是先通过`author_id`找到对应的数据,然后再提取`User`表中的这条数据,形成一个模型。
如果想要引用另外一个`app`的模型,那么应该在传递`to`参数的时候,使用`app.model_name`进行指定。以上例为例,如果`User`和`Article`不是在同一个`app`中,那么在引用的时候的示例代码如下:
```
<pre class="calibre12">```
<span class="hljs-title"># User模型在user这个app中</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span><span class="hljs-params">(models.Model)</span>:</span>
username = models.CharField(max_length=<span class="hljs-params">20</span>)
password = models.CharField(max_length=<span class="hljs-params">100</span>)
<span class="hljs-title"># Article模型在article这个app中</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
author = models.ForeignKey(<span class="hljs-string">"user.User"</span>,on_delete=models.CASCADE)
```
```
如果模型的外键引用的是本身自己这个模型,那么`to`参数可以为`'self'`,或者是这个模型的名字。在论坛开发中,一般评论都可以进行二级评论,即可以针对另外一个评论进行评论,那么在定义模型的时候就需要使用外键来引用自身。示例代码如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Comment</span><span class="hljs-params">(models.Model)</span>:</span>
content = models.TextField()
origin_comment = models.ForeignKey(<span class="hljs-string">'self'</span>,on_delete=models.CASCADE,null=<span class="hljs-keyword">True</span>)
<span class="hljs-title"># 或者</span>
<span class="hljs-title"># origin_comment = models.ForeignKey('Comment',on_delete=models.CASCADE,null=True)</span>
```
```
### 外键删除操作:
如果一个模型使用了外键。那么在对方那个模型被删掉后,该进行什么样的操作。可以通过`on_delete`来指定。可以指定的类型如下:
1. `CASCADE`:级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。
2. `PROTECT`:受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。
3. `SET_NULL`:设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。
4. `SET_DEFAULT`:设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。
5. `SET()`:如果外键的那条数据被删除了。那么将会获取`SET`函数中的值来作为这个外键的值。`SET`函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。
6. `DO_NOTHING`:不采取任何行为。一切全看数据库级别的约束。
**以上这些选项只是Django级别的,数据级别依旧是RESTRICT!**
- - - - - -
## 表关系:
表之间的关系都是通过外键来进行关联的。而表之间的关系,无非就是三种关系:一对一、一对多(多对一)、多对多等。以下将讨论一下三种关系的应用场景及其实现方式。
### 一对多:
1. 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
2. 实现方式:一对多或者多对一,都是通过`ForeignKey`来实现的。还是以文章和作者的案例进行讲解。
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span><span class="hljs-params">(models.Model)</span>:</span>
username = models.CharField(max_length=<span class="hljs-params">20</span>)
password = models.CharField(max_length=<span class="hljs-params">100</span>)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.CASCADE)
```
```
那么以后在给`Article`对象指定`author`,就可以使用以下代码来完成:
```
<pre class="calibre12">```
article = Article(title=<span class="hljs-string">'abc'</span>,content=<span class="hljs-string">'123'</span>)
author = User(username=<span class="hljs-string">'zhiliao'</span>,password=<span class="hljs-string">'111111'</span>)
<span class="hljs-title"># 要先保存到数据库中</span>
author.save()
article.author = author
article.save()
```
```
并且以后如果想要获取某个用户下所有的文章,可以通过`article_set`来实现。示例代码如下:
```
<pre class="calibre12">```
user = User.objects.first()
<span class="hljs-title"># 获取第一个用户写的所有文章</span>
articles = user.article_set.all()
<span class="hljs-keyword">for</span> article <span class="hljs-keyword">in</span> articles:
print(article)
```
```
### 一对一:
1. 应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不经常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另外一张表中我们叫做`UserExtension`。但是用户表`User`和用户信息表`UserExtension`就是典型的一对一了。
2. 实现方式:`Django`为一对一提供了一个专门的`Field`叫做`OneToOneField`来实现一对一操作。示例代码如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span><span class="hljs-params">(models.Model)</span>:</span>
username = models.CharField(max_length=<span class="hljs-params">20</span>)
password = models.CharField(max_length=<span class="hljs-params">100</span>)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserExtension</span><span class="hljs-params">(models.Model)</span>:</span>
birthday = models.DateTimeField(null=<span class="hljs-keyword">True</span>)
school = models.CharField(blank=<span class="hljs-keyword">True</span>,max_length=<span class="hljs-params">50</span>)
user = models.OneToOneField(<span class="hljs-string">"User"</span>, on_delete=models.CASCADE)
```
```
在`UserExtension`模型上增加了一个一对一的关系映射。其实底层是在`UserExtension`这个表上增加了一个`user_id`,来和`user`表进行关联,并且这个外键数据在表中必须是唯一的,来保证一对一。
### 多对多:
1. 应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。
2. 实现方式:`Django`为这种多对多的实现提供了专门的`Field`。叫做`ManyToManyField`。还是拿文章和标签为例进行讲解。示例代码如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
tags = models.ManyToManyField(<span class="hljs-string">"Tag"</span>,related_name=<span class="hljs-string">"articles"</span>)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Tag</span><span class="hljs-params">(models.Model)</span>:</span>
name = models.CharField(max_length=<span class="hljs-params">50</span>)
```
```
在数据库层面,实际上`Django`是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到`article`和`tag`两张表的主键。
- - - - - -
### related\_name和related\_query\_name:
#### related\_name:
还是以`User`和`Article`为例来进行说明。如果一个`article`想要访问对应的作者,那么可以通过`author`来进行访问。但是如果有一个`user`对象,想要通过这个`user`对象获取所有的文章,该如何做呢?这时候可以通过`user.article_set`来访问,这个名字的规律是`模型名字小写_set`。示例代码如下:
```
<pre class="calibre12">```
user = User.objects.get(name=<span class="hljs-string">'张三'</span>)
user.article_set.all()
```
```
如果不想使用`模型名字小写_set`的方式,想要使用其他的名字,那么可以在定义模型的时候指定`related_name`。示例代码如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
<span class="hljs-title"># 传递related_name参数,以后在方向引用的时候使用articles进行访问</span>
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.SET_NULL,null=<span class="hljs-keyword">True</span>,related_name=<span class="hljs-string">'articles'</span>)
```
```
以后在方向引用的时候。使用`articles`可以访问到这个作者的文章模型。示例代码如下:
```
<pre class="calibre12">```
user = User.objects.get(name=<span class="hljs-string">'张三'</span>)
user.articles.all()
```
```
如果不想使用反向引用,那么可以指定`related_name='+'`。示例代码如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
<span class="hljs-title"># 传递related_name参数,以后在方向引用的时候使用articles进行访问</span>
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.SET_NULL,null=<span class="hljs-keyword">True</span>,related_name=<span class="hljs-string">'+'</span>)
```
```
以后将不能通过`user.article_set`来访问文章模型了。
#### related\_query\_name:
在查找数据的时候,可以使用`filter`进行过滤。使用`filter`过滤的时候,不仅仅可以指定本模型上的某个属性要满足什么条件,还可以指定相关联的模型满足什么属性。比如现在想要获取写过标题为`abc`的所有用户,那么可以这样写:
```
<pre class="calibre12">```
users = User.objects.filter(article__title=<span class="hljs-string">'abc'</span>)
```
```
如果你设置了`related_name`为`articles`,因为反转的过滤器的名字将使用`related_name`的名字,那么上例代码将改成如下:
```
<pre class="calibre12">```
users = User.objects.filter(articles__title=<span class="hljs-string">'abc'</span>)
```
```
可以通过`related_query_name`将查询的反转名字修改成其他的名字。比如`article`。示例代码如下:
```
<pre class="calibre12">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Article</span><span class="hljs-params">(models.Model)</span>:</span>
title = models.CharField(max_length=<span class="hljs-params">100</span>)
content = models.TextField()
<span class="hljs-title"># 传递related_name参数,以后在方向引用的时候使用articles进行访问</span>
author = models.ForeignKey(<span class="hljs-string">"User"</span>,on_delete=models.SET_NULL,null=<span class="hljs-keyword">True</span>,related_name=<span class="hljs-string">'articles'</span>,related_query_name=<span class="hljs-string">'article'</span>)
```
```
那么在做反向过滤查找的时候就可以使用以下代码:
```
<pre class="calibre12">```
users = User.objects.filter(article__title=<span class="hljs-string">'abc'</span>)
```
```
- Introduction
- 第一章:学前准备
- 第一节:虚拟环境
- 第二节:准备工作
- 第三节:Django介绍
- 第四节:URL组成部分
- 第二章:URL与视图
- 第一节:第一个Django项目
- 第二节:视图与URL分发器
- 第三章:模板
- 第一节:模板介绍
- 第二节:模板变量
- 第三节:常用标签
- 第四节:常用过滤器
- 第五节:自定义过滤器
- 第七节:模版结构优化
- 第八节:加载静态文件
- 第四章:数据库
- 第一节:MySQL相关软件
- 第二节:数据库操作
- 第三节:ORM模型
- 第四节:模型常用字段
- 第五节:外键和表关系
- 第六节:增删改查操作
- 第七节:查询操作
- 第八节:QuerySet API
- 第九节:ORM模型迁移
- 第十节:ORM作业
- 第十一节:ORM作业参考答案
- 第十二节:Pycharm连接数据库
- 第五章:视图高级
- 第一节:限制请求method
- 第二节:页面重定向
- 第三节:HttpRequest对象
- 第四节:HttpResponse对象
- 第五节:生成CSV文件
- 第六节:类视图
- 第七节:错误处理
- 第六章:表单
- 第一节:表单概述
- 第二节:用表单验证数据
- 第三节:ModelForm
- 第四节:文件上传
- 第七章:cookie和session
- 第八章:上下文处理器和中间件
- 第一节:上下文处理器
- 第二节:中间件
- 第九章:安全
- 第一节:CSRF攻击
- 第二节:XSS攻击
- 第三节:点击劫持攻击
- 第四节:SQL注入
- 第十章:信号
- 第一节:什么是信号
- 第十一章:验证和授权
- 第一节:概述
- 第二节:用户对象
- 第三节:权限和分组
- 第十二章:Admin系统
- 第十三章:Django的缓存
- 第十四章:memcached
- 第十五章:Redis