假若有一个书城系统,须要给做者和书籍加上评论功能。若是给每一个表单独建一个评论表,那么咱们之后要扩展其它模块评论功能的时候,还须要随之新建一张评论表,会显得很冗余。对于这种状况,Django 给咱们提供了解决方案,那就是 contenttypes 模块。数据库
from django.db import models from django.contrib.contenttypes.models import ContentType class Book(models.Model): ''' 书籍表 ''' title = models.CharField(max_length=32) # 书籍标题 class Author(models.Model): name = models.CharField(max_length=32) # 做者名称 class Comment(models.Model): ''' 评论表 ''' content_type = models.ForeignKey(ContentType, on_delete=None) # 被评论表(对哪张表进行评论) object_id = models.PositiveIntegerField() # 被评论表中数据的id content = models.CharField(max_length=200) # 评论内容
上述的表结构很简单,可是注意 Comment 表中的 content_type 字段,它关联的是 django.contrib.contenttypes.models.ContentType 这张表,而这张表就是 Django 为咱们提供的,初始化 DB 时就会自动生成。看一下它的数据:django
能够看到,它其实就是一张描述咱们所建的模型和 Django 内置模型的信息表。而咱们经过 Comment 表中的 content_type 字段与之关联,就能够标识出某条评论属于哪一个模型(在这里描述的就是某条评论,是对做者,仍是对书籍)。再经过 object_id 字段,关联到模型的主键,就能够标识出某条评论属于哪一个模型的哪条数据(在这里描述的就是某条评论,是对某个做者,仍是对某本书籍)。api
初始化测试数据:测试
import os if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_test.settings') import django django.setup() from api import models from django.contrib.contenttypes.models import ContentType models.Author.objects.create(name='鲁迅') models.Author.objects.create(name='郭德纲') models.Book.objects.create(title='傻子是这样炼成的') models.Book.objects.create(title='永夜') models.Book.objects.create(title='武则天传奇') models.Comment.objects.create(object_id=1, content_type=ContentType.objects.get(id=7), content='鲁大哥,666666') # 鲁迅 models.Comment.objects.create(object_id=1, content_type=ContentType.objects.get(id=7), content='鲁大哥 我占了二楼') # 鲁迅 models.Comment.objects.create(object_id=2, content_type=ContentType.objects.get(id=7), content='老郭没得话说。。') # 郭德纲 models.Comment.objects.create(object_id=1, content_type=ContentType.objects.get(id=8), content='这本书好神奇') # 傻子是这样炼成的 models.Comment.objects.create(object_id=2, content_type=ContentType.objects.get(id=8), content='永夜是什么夜') # 永夜 models.Comment.objects.create(object_id=3, content_type=ContentType.objects.get(id=8), content='武媚娘 武则天') # 武则天传奇
在新增评论表数据的时候咱们会发现会有很繁琐的操做,例如我要给郭德纲评论,我须要先在 django_content_type 表中找到做者表,让 content_type 与之关联,再在做者表中找到郭德纲的主键,让其和 object_id 关联,最后才是评论内容。为了简化这种操做,contenttypes 给咱们提供了 django.contrib.contenttypes.fields.GenericForeignKey 字段。修改 Comment 模型以下:spa
from django.contrib.contenttypes.fields import GenericForeignKey from django.db import models from django.contrib.contenttypes.models import ContentType class Comment(models.Model): ''' 评论表 ''' content_type = models.ForeignKey(ContentType, on_delete=None) # 被评论表(对哪张表 进行评论) object_id = models.PositiveIntegerField() # 被评论表中数据的id content = models.CharField(max_length=200) # 评论内容 comment_to = GenericForeignKey('content_type', 'object_id') # 被评论对象
能够看到 Comment 模型中新增了一个 comment_to 字段,此时咱们再来进行新增操做:code
models.Comment.objects.create(comment_to=models.Author.objects.get(id=2), content='老郭 我又来了~!') models.Comment.objects.create(comment_to=models.Book.objects.get(id=2), content='永远的夜晚?')
效果:orm
能够看到,相对于以前的新增操做,这里直接把要评论的对象赋值给 comment_to 字段,而后 contenttypes 会自动根据该对象的映射关系给 object_id 和 content_type 赋上值,不只简化了操做,还让操做更加直观明了。对象
注意:这里添加的 comment_to 列仅仅用于操做,并不会在数据库生成对应列。blog
假如要查询出每条评论对应的评论对象,很显然咱们须要先根据评论的 content_type 找到评论的对象类型(做者或书籍),而后从该对象类型对应的表中找到 pk 值为 object_id 值对应的那条数据。 django.contrib.contenttypes.fields.GenericForeignKey 字段除了给咱们提供新增数据时的便利,查询数据的时候也是,以下:get
for comment in models.Comment.objects.all(): print('{}=>{}'.format(comment.content,comment.comment_to))
效果:
能够看到每一个评论的 comment_to 属性直接对应了评论的对象,是否是很给力~
假如要查询出郭德纲(做者)和永夜(书籍)全部的评论,对于这种一对多的查询操做, contenttypes 给咱们提供了另一个字段—— django.contrib.contenttypes.fields.GenericRelation 。修改模型以下:
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey from django.db import models from django.contrib.contenttypes.models import ContentType class Book(models.Model): title = models.CharField(max_length=32) # 书籍标题 comments = GenericRelation("Comment") # 全部评论 class Author(models.Model): name = models.CharField(max_length=32) # 做者名称 comments = GenericRelation("Comment") # 全部评论 class Comment(models.Model): content_type = models.ForeignKey(ContentType, on_delete=None) # 被评论表(对哪张表 进行评论) object_id = models.PositiveIntegerField() # 被评论表中数据的id comment_to = GenericForeignKey('content_type', 'object_id') # 被评论对象 content = models.CharField(max_length=200) # 评论内容
如上,给模型 Book 和 Author 个各添加了一个 comments 字段,都对应 Comment 模型,此时咱们完成上述要求的查询就很简单了,以下:
print('做者【郭德纲】的评论:') [print(' '+comment.content) for comment in models.Author.objects.get(name='郭德纲').comments.all()] print('书籍【永夜】的评论:') [print(' '+comment.content) for comment in models.Book.objects.get(title='永夜').comments.all()]
效果:
注意:这里添加的 comments 列与上面加的 comment_to 列同样,都不会在数据库中生成对应列。