前面写了一篇有关QuerySet查询的基础知识文章:QuerySet查询基础。
其中,没怎么提及有外键的情况。本篇文章详细讲解如何处理该类型。
先给出示例模型,如下代码:
#coding:utf-8
from django.db import models
from django.contrib.auth.models import User
#博客模型
class Blog(models.Model):
#标题
caption = models.CharField(max_length=50)
#作者(外键关联User模型)
author = models.ForeignKey(User)
#内容
content = models.TextField()
#分类(和Tag模型关联,多对多)
tags = models.ManyToManyField(Tag)
#发表时间
publish_time = models.DateTimeField(auto_now_add=True)
#博客分类标签
class Tag(models.Model):
#分类名
tag_name = models.CharField(max_length=20,blank=True)
两个模型:Blog和Tag,其中Blog有个tags多对多字段关联。
先思考一下,如何得到如下数据集:
1)获取包含tag的id为1的全部Blog
2)获取Blog标题包含django的全部Tag先看第1个问题:获取包含tag的id为1的全部Blog。
Blog模型下有个多对多字段(ManyToManyField) tags。我们可以根据该字段使用Tag模型的字段。QuerySet查询如下:
qs = Blog.objects.filter(tags__id=1)
#输出SQL语句
print(qs.query)
用法和字段条件修饰差不多,两个下划线加Tag模型的字段。
得到SQL语句如下:
SELECT
"blog_blog"."id", "blog_blog"."caption", "blog_blog"."author_id",
"blog_blog"."content", "blog_blog"."publish_time"
FROM "blog_blog"
INNER JOIN "blog_blog_tags" ON ("blog_blog"."id" = "blog_blog_tags"."blog_id")
WHERE "blog_blog_tags"."tag_id" = 1
ORDER BY "blog_blog"."publish_time" DESC
其where条件为tag_id等于1。同样,我可以是哟个Tag其他字段作为条件。
当然,还有一种解法是通过Tag模型用set反向获取Blog:
tag = Tag.objects.get(id=1)
blogs = tag.blog_set.all()
这个在QuerySet查询基础文中也有提到。但为什么是blog_set,而不是blogs_set, blog_tags_set。这点没提到,待会看第2个问题详细讲解。
第2个问题:获取Blog标题包含django的全部Tag。
这个需要通过判断Blog模型返回Tag模型。问题多对多字段在Blog模型上。Tag不能使用Blog模型的tags字段。也许你可能会采取先判断Blog,再获取Tag,如下代码:
#获取标题包含django的Blog
blogs = Blog.objects.filter(caption__icontains='django')
#创建集合(集合的元素是不重复的)
tags = set()
#遍历blogs,获取tag
for blog in blogs:
for tag in blog.tags.all():
tags.add(tag)
这种代码过于低效且繁琐。QuerySet查询其实可以直接实现我们的需求。
qs = Tag.objects.filter(blog__caption__icontains='django')
#输出SQL语句
print(qs.query)
输出的SQL语句有点复杂,还是给大家看看:
SELECT "blog_tag"."id", "blog_tag"."tag_name"
FROM "blog_tag"
INNER JOIN "blog_blog_tags" ON ("blog_tag"."id" = "blog_blog_tags"."tag_id")
INNER JOIN "blog_blog" ON ("blog_blog_tags"."blog_id" = "blog_blog"."id")
WHERE "blog_blog"."caption" LIKE %django% ESCAPE '\'
看inner join和where部分即可,从中可看出确实实现了我们的需求。
“blogcaptionicontains”中blog对应Blog模型;caption是Blog模型的caption字段;icontains是包含字符条件(可参考QuerySet查询基础)。
那为什么可以使用blog?为什么是blog而不是别的名称呢?
我在研究测试时发现一个方法:
print(Tag._meta.get_fields()) #输出Tag模型可用字段
得到如下结果:
<ManyToManyRel: blog.blog>,
<django.db.models.fields.AutoField: id>,
<django.db.models.fields.CharField: tag_name>
原本Tag模型设计只写了tag_name字段。id字段是自动追加的主键。而blog.blog是多对多字段的引用(注意类型后面3字母“Rel”),我们可以通过blog引用字段直接访问Blog模型。就和上面我们Blog模型的tags字段一样使用方法。
blog.blog这个名称有点意思。第1个blog是我这个app应用名,第2个blog是Blog模型的小写。所以上面的blog_set和这里的blogcaptionicontains为什么使用“blog”的原因。
那我们可否修改该引用名称?
可以,这个需要修改Blog模型的外键字段。tags字段修改如下:
tags = models.ManyToManyField(Tag, related_query_name='blogs')
修改完成之后,我们可以使用blogscaptionicontains作为条件。
本文章来自 杨仕航的博客!本文链接:yshblog.com/blog/158)