关于本站
1、基于Django+Bootstrap开发
2、主要发表本人的技术原创博客
3、本站于 2015-12-01 开始建站
有关Django的QuerySet查询,前面已经写了两篇文章:
本篇博文讲解一下较为特殊的ContentTypes。
ContentTypes是一种表结构。我将从数据库层面直接讲解该类型,直接了解本质。
举个栗子,我有个两个模型博客(Blog)和专题(Subject)。为了方便讲解,简化模型如下:
#coding:utf-8 from django.db import models class Blog(models.Model): caption = models.CharField(max_length=50) class Subject(models.Model): caption = models.CharField(max_length=50)
另外,这两个模型的数据我都需要记录阅读次数或者记录其他东西。
为了保证这两个模型干净,不直接添加过多的附加字段在模型中。这种情况一般新建其他模型作为拓展表。那么我们可创建新的模型并加入外键关联到对应的表。如下:
#coding:utf-8 from django.db import models class BlogView(models.Model): blog = models.OneToOneField(Blog) #关联Blog,一对一外键 view_num = models.IntegerField(default=0) class SubjectView(models.Model): subject = models.OneToOneField(Blog) #关联Subject,一对一外键 view_num = models.IntegerField(default=0)
有没发现,代码重复了!若还有其他模型需要加入阅读次数记录,岂不是还要继续添加一样的代码。
现在该代码形成的表结构如下:
很明显这种结构冗余了,可以修改为如下结构:
为了区分View表的记录是对应那个模型。需要加入一个字段记录该条数据对应的模型。
如上图的结构可以记录什么模型(object_type),该模型哪条数据(object_id)和其他对应该模型的数据。通过这两个字段,我们可以很清楚知道分别对应那个模型的数据。只用了两个字段解决上面冗余的表结构。这个object_type和object_id就是我们这里所说的ContentTypes。
创建带有ContentTypes字段的模型如下(目前我使用的版本是django 1.9。1.6之前的版本可能引用的类不同):
#coding:utf-8 from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey #阅读数量记录模型 class ViewNum(models.Model): #Content Type字段 content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = GenericForeignKey( ct_field = "content_type", fk_field = "object_id" ) #其他字段 view_num = models.IntegerField(default=0)
此处,真正被创建的字段有content_type、object_id和view_num。而content_object是content_type和object_id的组合,方便我们使用。
ps:创建新模型记得同步数据库。
创建模型是为了保存和读取数据。上面我们创建了有ContentTypes类型字段的模型。写入数据方法如下:
#获取对应的博客 blog = Blog.objects.get(id=1) #创建1条记录 view = ViewNum(content_object=blog, view_num=1) view.save()
还有另外一种写入方式:
from django.contrib.contenttypes.models import ContentType #获取对应的博客 blog = Blog.objects.get(id=1) #获取Blog的ContentType类型 blog_content_type = ContentType.objects.get_for_model(Blog) #使用具体模型对象也行 #blog_content_type = ContentType.objects.get_for_model(blog) #创建1条记录 view = ViewNum(content_type=blog_content_type, object_id=blog.id, view_num=1) view.save()
该方式稍微麻烦点,通常用于读取数据。因为读取数据无法直接使用content_object作为条件。因为content_object字段是GenericForeignKey。该字段没有真正被创建,不是普通意义的字段。无法被解析成SQL语句。所以我们读取对应的数据方法如下:
from django.contrib.contenttypes.models import ContentType #获取对应的博客 blog = Blog.objects.get(id=1) #获取Blog的ContentType类型 blog_content_type = ContentType.objects.get_for_model(Blog) #读取数据 view_nums = ViewNum.objects.filter(content_type=blog_content_type, object_id=blog.id)
如果是简单的读取数据,这种方法倒是无伤大雅。若要查询稍微复杂一点的数据,例如:
1)获取阅读次数小于等于10的Blog
2)获取Blog标题包含django的阅读记录
3)Blog按照阅读次数倒序排序(由大到小)
这些都是实际可能会碰到的问题,方法见下一节。
为了能够顺畅的关联Blog和ViewNum两个模型,可以在Blog模型加入一个特殊的外键。修改Blog模型如下:
#coding:utf-8 from django.db import models from django.contrib.contenttypes.fields import GenericRelation class Blog(models.Model): caption = models.CharField(max_length=50) #关联ViewNum view_num = GenericRelation(ViewNum, related_query_name='blogs')
该操作无需同步数据库。我们可以使用外键的QuerySet查询提到的获取全部字段的方法查看有ViewNum多了哪些字段。
Print(ViewNum._meta.get_fields())
得到如下结果:
<GenericRel: blog.blog>, <django.db.models.fields.AutoField: id>, <django.db.models.fields.related.ForeignKey: content_type>, <django.db.models.fields.PositiveIntegerField: object_id>, <django.db.models.fields.IntegerField: view_num>, <django.contrib.contenttypes.fields.GenericForeignKey object at 0x03BE2BF0>
多了第1条GenericRel的引用。我们在Blog模型可以通过加入的view_num获取ViewNum模型的数据。也可以在ViewNum模型通过blogs(related_query_name设定的名称)获取Blog模型的相关数据。
例如,获取Blog id为1的阅读数据:
blog = Blog.objects.get(id=1) blog.view_num.all() #获取全部的阅读数据
讲到这里,回头看看上一节抛出的3个问题。
1)获取阅读次数小于等于10的Blog
我们已经在Blog模型加入了外键view_num,参考外键的QuerySet查询一文,实现代码如下:
qs = Blog.objects.filter(view_num__view_num__lte=10) #输出SQL语句 print(qs.query)
第1个view_num是Blog模型的view_num字段,第2个view_num是ViewNum模型的view_num。
通过Blog模型的view_num外键访问ViewNum模型。
__lte是条件修饰,小于等于的意思。具体知识可看QuerySet查询基础。
得到SQL语句如下:
SELECT "blog_blog"."id", "blog_blog"."caption" FROM "blog_blog" INNER JOIN "view_record_viewnum" ON ( "blog_blog"."id" = "view_record_viewnum"."object_id" AND "view_record_viewnum"."content_type_id" = 9 ) WHERE "view_record_viewnum"."view_num" <= 10
SQL语句看不懂可以略过,其中着重看inner join 和 where条件部分。inner join 将ViewNum模型和Blog模型关联。content_type_id = 9 是对应Blog模型的ContentTypes id值。
2)获取Blog标题包含django的阅读记录
第1小问,通过Blog访问ViewNum。第2小问是演示如何通过ViewNum访问Blog。
上面模型中已经加入ContentTypes引用字段,引用名称为blogs。则使用代码如下:
qs = ViewNum.objects.filter(blogs__caption__icontains='django')
filter中的blogs__caption__icontains意思是判断Blog模型的caption字段,条件为是否包含django字样。
可自行输入SQL语句参考,或看具体的返回结果。
3)Blog按照阅读次数倒序排序(由大到小)
这个相对上面两个问题还要复杂些。
我们从SQL语句角度思考。该问题需要Blog表关联ViewNum表,再对ViewNum分组求和统计。
分组统计在SQL语句中是统计函数加Group py。在QuerySet中需要使用annotate。annotate的知识可参考我前面写的文章:Django用annotate实现联合统计查询。
既然可以通过Blog关联到ViewNum模型。分组统计和排序如下:
from django.db import models qs = Blog.objects.annotate(view_nums=models.Sum('view_num')).order_by('-view_nums')
总结,ContentTypes可检查特殊的组合外键。在对应模型加上引用字段即可和普通外键一样访问和查询。
相关专题: Django QuerySet查询
HwiLu
好难啊😂
2019-08-30 15:20 回复