Django中表关系详解

表之间的关系都是通过外键来进行关联的。而表之间的关系,无非就是三种关系:一对一、一对多(多对一)、多对多等。

一对多:

  1. 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。作者和文章的关系就是一对多。
  2. 实现方式:一对多或者多对一,都是通过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这个实例只传入了titlecontent这两个属性的值,而没有传入category这个外键的值:

  1. 如果我们设置了category这个值可以为空的话,那么将不会报错,能够正常的添加数据进去。
  2. 如果设置了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也是推介我们使用第一种方式进行存储信息到数据库。

一对一:

  1. 应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不经常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另外一张表中我们叫做UserExtension。但是用户表User和用户信息表UserExtension就是典型的一对一了。

  2. 实现方式:Django为一对一提供了一个专门的Field叫做OneToOneField来实现一对一操作。

userapp中的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这个属性时就会报错。
这样,就实现了我们的需求了

多对多:

  1. 应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。

  2. 实现方式: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')

我们只需要多对多的两个模型中随便一个模型添加外键就行了,至于在哪个模型中添加,就看个人心情把。

添加好了之后就makemigrationsmigrate一下,使模型映射到我们数据库中去。

然后我们打开navicat查看映射进去的数据表,因为我们只创建了一个模型Tag,那么数据库中因该也只会多一张表article_tag.但是,我们发现还生成了一张表article_tag_articles,然后我们右键点击这场表–>设计表在这里插入图片描述
再点击外键
在这里插入图片描述
我们发现这张表只有三个值,一个是id主键,另外两个都是外键,一个参考article_article这个表,另外一个参考article_tag这个表。
看到这里可能你就会明白了,在数据库层面,Django为了实现这种多对多的方式,就在两个表之间新建了一个中间表,这个中间表里面分别定义了两个外键,引用到articletag这两张表,从而实现多对多的关系。

然后在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这个参数来进行设置。

想深入学习django的可以看一下这个视频:超详细讲解Django打造大型企业官网

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值