表之间的关系都是通过外键来进行关联的。而表之间的关系,无非就是三种关系:一对一、一对多(多对一)、多对多等。
一对多:
- 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。作者和文章的关系就是一对多。
- 实现方式:一对多或者多对一,都是通过ForeignKey来实现的。还是以文章和作者的案例进行讲解。
创建一个user的app,models下编写
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField(max_length=100)
创建一个article的app,使用外键引用user中的User模型,和使用外键引用当前app中的Category。
models编写:
from django.db import models
# Create your models here.
class Category(models.Model):
name = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# 是由Category影响Article
category = models.ForeignKey('Category',on_delete=models.CASCADE)
# 将user中的User这个模型作为外键
author = models.ForeignKey('user.User',on_delete=models.CASCADE,null=True)
在article中的views中编写
def one_to_many(request):
# 插入文章
# article = models.Article(title='lala',content=333)
# category = models.Category.objects.get(pk=1)
# author = User.objects.get(pk=1)
# article.category = category
# article.author = author
# article.save()
# 获取某个分类下的所有文章
category = models.Category.objects.get(pk=1)
# article_set这个属性在Category这张表被外键引入时就会自动生成
# 属性名为<引用模型的模型名转化为小写_set>
# 可以执行.all(), .first(), .filter()等操作
articles = category.article_set.all()
for article in articles:
print(article.title,article.content,article.category.name,article.author)
return HttpResponse('succes')
修改上面代码的相应注释实现不同效果。要想实现上面的代码,我们首先需要手动在数据库中新建信息。
在User表中添加一条信息,
在Category中也添加一条信息,
然后在Article中新建两至三条信息,外键category_id和user_id都指向User表和Category表中的id。自行配置url等,就能进行测试了。
在我们获取某个分类下的所有文章时,我们使用的是<引用模型的模型名转化为小写_set>
(即上面的article_set
)来获取,那么如果我们不想使用这个属性来获取,而是自定义一个属性来获取呢?
这个时候就需要用到related_name
这个参数了,
在models中的Article中设置外键的属性中添加这个参数就行了,示例,修改Article下的category
属性的参数
category = models.ForeignKey('Category', on_delete=models.CASCADE,related_name='articles')
这样,我们以后获取种类下的Artilces
时,就可以用下面这种方式了。
# 没有使用related_name时获取分类下的所有文章的使用方法
# articles = category.article_set.all()
# 使用related后获取分类下的所有文章的使用方法
articles = category.articles.all()
注意: 我们使用related_name
后,就没有<引用模型的模型名转化为小写_set>
(即上面的article_set
)这个属性名字了,而是改为我们自己设置的属性名字,所以也就不能使用原来的名字,否则会报错。
在上面views中注释的代码中,我们使用了一种最常见的方式添加数据到数句库中去,那种方式也是Django所推介的方式,但是还有一些不常用的添加方式我们也可以了解一下
修改views中的代码
def one_to_many(request):
# 插入文章,Django推介的方式,也是最常用的
# article = models.Article(title='lala',content=333)
# category = models.Category.objects.get(pk=1)
# author = User.objects.get(pk=1)
# article.category = category
# article.author = author
# article.save()
# 获取某个分类下的所有文章
category = models.Category.objects.get(pk=1)
# article_set这个属性在Category这张表被外键引入时就会自动生成
# 属性名为<引用模型的模型名转化为小写_set>
# 可以执行.all(),.first(),.filter()等操作
# 没有使用related_name时获取分类下的所有文章的使用方法
# articles = category.article_set.all()
# 使用related后获取分类下的所有文章的使用方法
# articles = category.articles.all()
# for article in articles:
# print(article.title,article.content,article.category.name,article.author)
# 不常用的插入数据方式
article = models.Article(title='111',content='222')
article.author = User.objects.first()
#article.save()
category.articles.add(article)
category.save()
return HttpResponse('succes')
当我们运行上面代码的时候,就会报错,错误的大概意思就是artilce
这个这个实例还没有被保存,不能被category
添加,这是我们将上面代码中的article.save()
取消注释,先保存一下这个article
的实例。这里又会分为两种情况了,我们对article
这个实例只传入了title
和content
这两个属性的值,而没有传入category
这个外键的值:
- 如果我们设置了
category
这个值可以为空的话,那么将不会报错,能够正常的添加数据进去。 - 如果设置了
category
这个属性的值不能为空的话,那么运行时就会报错,说我们没有传入这个参数,但是我们如果不先保存这个article
实例对象,下面我们也不能使用category.save()
所以这就陷入了死循环中。所以在我们使用这种方式的时候需要确保category
这个属性可以为空才行
那么我想使用这种方式,又不想设置category
这个属性为空,又应该怎样做呢?
这个时候我们就可以使用bulk
这个参数了
示例代码
def one_to_many(request):
# 插入文章,Django推介的方式,也是最常用的
# article = models.Article(title='lala',content=333)
# category = models.Category.objects.get(pk=1)
# author = User.objects.get(pk=1)
# article.category = category
# article.author = author
# article.save()
# 获取某个分类下的所有文章
category = models.Category.objects.get(pk=1)
# article_set这个属性在Category这张表被外键引入时就会自动生成
# 属性名为<引用模型的模型名转化为小写_set>
# 可以执行.all(),.first(),.filter()等操作
# 没有使用related_name时获取分类下的所有文章的使用方法
# articles = category.article_set.all()
# 使用related后获取分类下的所有文章的使用方法
# articles = category.articles.all()
# for article in articles:
# print(article.title,article.content,article.category.name,article.author)
# 不常用的插入数据方式
article = models.Article(title='333',content='444')
article.author = User.objects.first()
#article.save()
category.articles.add(article,bulk=False)
#category.save()
return HttpResponse('succes')
这个时候我们也不需要使用.save()
来保存信息到数据库了,使用这个参数后就会自动给我们保存这些所有实例化的对象。
所以Django也是推介我们使用第一种方式进行存储信息到数据库。
一对一:
-
应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不经常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另外一张表中我们叫做UserExtension。但是用户表User和用户信息表UserExtension就是典型的一对一了。
-
实现方式:Django为一对一提供了一个专门的Field叫做OneToOneField来实现一对一操作。
在user
app中的models中加入两个模型
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField(max_length=200)
class UserExtension(models.Model):
school = models.CharField(max_length=100)
user = models.OneToOneField('User',on_delete=models.CASCADE)
然后makemigrations后再migrate进行数据迁移
然后在user中的views中写入代码
from django.shortcuts import render
from django.http import HttpResponse
from . import models
# Create your views here.
def one_to_one(request):
user = models.User.objects.first()
extension = models.UserExtension(school='城北中学')
extension.user = user
extension.save()
return HttpResponse('success')
然后配置url等,运行项目,访问网址。
这样,我们就对一对一的数据表添加了数据,使用的也还四Django官方所推介的方式,也是比较好用的一种方式。也就是和使用普通的外键添加数据是没有差别的。
注意: 当我们不做任何修改再次刷新网页时,就会报错了,因为我们刷新页面时就会执行views中one_to_one
函数中的代码,而我们的user还是获取的第一个user这个实例,这样就相当与对user这个实例又添加了一条信息,user这个实例就拥有了两条信息了,但是我们使用的是一对一模式,所以这个地方就是会报错的,Django是不会允许我们这样操作的。所以以后使用一对一要注意到这一点细节。
那么当我们拿到user的拓展信息后,我们也能拿到user,示例:
def one_to_one(request):
# user = models.User.objects.first()
#
# extension = models.UserExtension(school='城北中学')
# extension.user = user
# extension.save()
# 通过用户拓展信息获取用户
extension = models.UserExtension.objects.first()
print(extension.user.username)
return HttpResponse('success')
当我们拿到user时,也能拿到user的拓展信息
def one_to_one(request):
# user = models.User.objects.first()
#
# extension = models.UserExtension(school='城北中学')
# extension.user = user
# extension.save()
# 通过用户拓展信息获取用户
# extension = models.UserExtension.objects.first()
# print(extension.user.username)
# 通过用户获取用户的拓展信息
user = models.User.objects.first()
print(user.userextension.school)
return HttpResponse('success')
那么我们不想通过user.userextension
来获取这个信息,而是自定义一个user.extension
来获取信息,那么这个时候我们就可以使用上面用过的related_name
这个参数来实现了
首先修改models中的UserExtension
的代码
class UserExtension(models.Model):
school = models.CharField(max_length=100)
user = models.OneToOneField('User',on_delete=models.CASCADE,related_name='extension')
views中修改代码
def one_to_one(request):
# user = models.User.objects.first()
# extension = models.UserExtension(school='城北中学')
# extension.user = user
# extension.save()
# 通过用户拓展信息获取用户
# extension = models.UserExtension.objects.first()
# print(extension.user.username)
# 通过用户获取用户的拓展信息
user = models.User.objects.first()
# 没有使用related_name前的方法
# print(user.userextension)
# 使用related_name之后的方法
print(user.extension.school)
return HttpResponse('success')
同上面的一样,修改代码之后如果我们在使用user.userextension
这个属性时就会报错。
这样,就实现了我们的需求了
多对多:
-
应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。
-
实现方式:Django为这种多对多的实现提供了专门的Field。叫做ManyToManyField。还是拿文章和标签为例进行讲解。
我们在Article中的models中添加一个模型:
class Tag(models.Model):
name = models.CharField(max_length=100)
articles = models.ManyToManyField('Article')
这样,我们就给Tag模型添加了一个多对多的外键。因为是多对多的关系,我们也可以不用在Tag中添加这个外键,而是在Article模型中添加外键:例如
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# 是由Category影响Article
# category = models.ForeignKey('Category',on_delete=models.CASCADE)
# 设置related_name参数修改category中Article的默认名字
category = models.ForeignKey('Category', on_delete=models.CASCADE,related_name='articles')
# 将user中的User这个模型作为外键
author = models.ForeignKey('user.User',on_delete=models.CASCADE,null=True)
# 也可以在此处添加标签,因为在Tag中添加了,所以就不用在此处添加了
# tags = models.ManyToManyField('Tag')
我们只需要多对多的两个模型中随便一个模型添加外键就行了,至于在哪个模型中添加,就看个人心情把。
添加好了之后就makemigrations
和migrate
一下,使模型映射到我们数据库中去。
然后我们打开navicat查看映射进去的数据表,因为我们只创建了一个模型Tag
,那么数据库中因该也只会多一张表article_tag
.但是,我们发现还生成了一张表article_tag_articles
,然后我们右键点击这场表–>设计表
再点击外键
我们发现这张表只有三个值,一个是id主键,另外两个都是外键,一个参考article_article
这个表,另外一个参考article_tag
这个表。
看到这里可能你就会明白了,在数据库层面,Django为了实现这种多对多的方式,就在两个表之间新建了一个中间表,这个中间表里面分别定义了两个外键,引用到article
和tag
这两张表,从而实现多对多的关系。
然后在views中添加一个函数many_to_many
def many_to_many(request):
# 给第一篇文章添加一个标签
article = models.Article.objects.first()
tag = models.Tag(name='热门文章')
article.tag_set.add(tag)
return HttpResponse('success')
然后添加映射,运行项目并进行访问。
然后我们会得到一个报错
大概意思就是说我们没有办法对Article添加这个标签,因为Tag标签里面还没有信息,也就是我们还没有对Tag中的信息进行保存。
这个时候我们是不是可以使用一下bluk
参数了呢
修改代码
def many_to_many(request):
# 给第一篇文章添加一个标签
article = models.Article.objects.first()
tag = models.Tag(name='热门文章')
article.tag_set.add(tag,bluk=False)
return HttpResponse('success')
然后继续运行,然后它又给我们报出了另外一个错误
这个错误的大概意思就是add()这个函数得到一个意外的参数bulk
,也就是add()没有这个参数。这就很奇怪了,为什么我们在前面一对多的时候又可以使用bluk
呢,在这里就没有这个参数了。
原因是因为我们只要使用的ManyToManyField
这个多对多的字段,在我们使用add这个函数的时候就没有bluk
这个参数了。这个时候我们就应该先将tag的信息保存至数据库中了,所以修改代码如下:
def many_to_many(request):
# 给第一篇文章添加一个标签
article = models.Article.objects.first()
tag = models.Tag(name='热门文章')
# 先保存tag
tag.save()
# 再添加tag
article.tag_set.add(tag)
return HttpResponse('success')
然后在运行就没有错误了
然后我们在navicat中查看数据表,信息也被我们添加进去了。
然后我们也可以使用得到的tag
,然后向tag里面添加articles
def many_to_many(request):
# 给第一篇文章添加一个标签
# article = models.Article.objects.first()
# tag = models.Tag(name='热门文章')
# # 先保存tag
# tag.save()
# # 再添加tag
# article.tag_set.add(tag)
# 通过标签添加文章
tag = models.Tag.objects.get(pk=1)
article = models.Article.objects.get(pk=2)
tag.articles.add(article)
return HttpResponse('success')
这样我们也成功的对多对多的表进行了操作了,而且,在我们操作的时候,我们是不是根本没有感觉到第三章表(article_tag_articles
)的存在,这就是Django的神奇之处了,我们也不用管它,Django已将给我们封装好了,我们只管使用就行了。
我们在给文章添加一个标签的时候使用到了tag_set
这个属性,我们也可以像一对多那样,对在设置外键的时候添加一个related_name
这个参数来进行设置。