前言:
contenttypes
不是中间件,不是视图,也不是模板,而是一些"额外的数据表"!所以,在使用它们之前,你需要执行makemigrations
和migrate
操作,为contenttypes框架创建它需要的数据表,用于保存特定的数据。这张表
通常叫做django_content_type
。
django_content_type表字段说明:
- 该表一共有三个字段
id
:表的主键app_label
:模型所属的app的名字:即你在settings.py文件的INSTALLED_APPS
中注册的应用model
:具体对应的模型的名字
ContentType的实例方法
ContentType.model_class()
:获取模型类ContentType.get_object_for_this_type(**kwargs)
:根据模型类的字段获取具体实例对象
示例:
- 首先根据应用(app)名,应用下的模型名,获取一个ContentType对象
from django.contrib.contenttypes.models import ContentType
article_type = ContentType.objects.get(app_label='literature', model='article') # 获取到一条记录
- 使用它来查询 Article模型类,或者访问具体的Article实例对象
article_class = article_type.model_class() # 获取到Article模型类
# 从literature应用下的Article模型对象中获取到 title=谁的青春不迷茫 的模型对象实例
article_obj = article_type.get_object_for_this_type(title='谁的青春不迷茫')
小结:
上述示例中我们要查找Article
模型对象的实例,但我们并未导入该模型对象,所以,我们可以通过ContentType
组件直接获取到任何一个模型的实例对象,而不用通过导入该模型对象,来通过它查询出具体实例。当然这不是重点,正常情况下我们也没必要这么做,因为这样增加了我们的工作量,在此我只是为了说明ContentType组件可以做很多事情,接下来我们继续往下看。
ContentType配合GenericForeignKey的使用:
具体情况如下:我们现在有一个综合类网站,上面有视频
、文章
、美术
…,而每一个应用模型都要让用户可以对其评论,我们应该怎么做?
方式一:
可以通过每个应用模型都单独建一张评论表,与对应应用模型外键关联,但这样做数据库表会显得很冗余,如果只有一两个对象需要评论功能还好,但要评论的对象有上百个,我们依然建对应数量的评论表吗?显然这种方法不合适。
方式二:
建一张评论表,然后将所有带有评论的应用模型都关联到该评论表,即该评论表存在很多的外键,为了确保互不影响,这些外键必须都默认允许为空,种方式不但要写重复的代码,而且效率低,安全差,也不利于后期维护。
方式三:
当你需要对某个对象或模型进行评论时,才创建comment与那个模型的关系。这时你就需要使用django contenttypes了。
app_label
:应用名称- 根据
content_type_id
字段可以在contenttype表中确定模型对象 - 根据
object_id
可以确定该模型对象的具体实例
示例讲解:
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Article(models.Model):
title = models.CharField(max_length=36)
content = models.TextField(blank=True)
def __str__(self):
return 'article: {0}'.format(self.title)
class Video(models.Model):
name = models.CharField(max_length=36)
url = models.URLField()
def __str__(self):
return 'article: {0}'.format(self.name)
class Comment(models.Model):
object_ct = models.ForeignKey(ContentType, on_delete=models.CASCADE) # 记录关联对象的类型
object_id = models.PositiveIntegerField() # 记录关联对象的主键
# 具体实例对象:比如:id为2的文章对象
object = GenericForeignKey('content_type', 'object_id')
text = models.TextField() # 评论内容
def __str__(self):
return 'comment: {0}'.format(self.text)
给id=2的文章插入一条评论
from django.shortcuts import get_object_or_404
article = get_object_or_404(Article, pk=2)
comment = Comment(object=article, text='行云流水,妙趣横生')
comment.save()
ContentType还有一个自定义管理器,也就是ContentTypeManager。它有下面的方法:
clear_cache()
:用于清除内部缓存 。一般不需要手动调用它,Django会在需要时自动调用它get_for_id(id)
:通过id值查询一个ContentType实例。比ContentType.objects.get(pk=id)的方式更优get_for_model(model,for_concrete_model = True)
:获取模型类或模型的实例,并返回表示该模型的ContentType 实例。设置参数for_concrete_model=False允许获取代理模型的ContentTypeget_for_models(*model,for_concrete_model = True)
: 获取可变数量的模型类,并返回模型类映射ContentType实例的字典get_by_natural_key(app_label, model)
:给定app标签和模型名称,返回唯一匹配的ContentType实例
GenericForeignKey:通用关系
普通的ForeignKey
字段只能指向另外唯一一个模型,这意味着如果Comment
模型使用了ForeignKey,则必须选择一个且只有一个模型来存储标签。contenttypes框架提供了一个特殊的字段类型(GenericForeignKey
),它可以解决这个问题,并允许关联任何模型。
正向查找 :
需求:根据评论找对象,即这条评论是对哪个对象的评论
conmmnet = Comment.objects.first()
obj = conmmnet.object
GenericRelation反向通用关系
既然前面使用GenericForeignKey字段可以帮我们正向查询关联的对象,那么就必然有一个对应的反向关联类型,也就是GenericRelation字段类型。使用它可以帮助我们从关联的对象反向查询对象本身,也就是ORM中的反向关联。
反向查找:
需求一:根据具体文章对象,找出该的所有评论
方式一:直接查找
- 无法直接从其他对象找到Comment对应的评论集
- 必须通过ContentType表作为中间跳板
article = Article.objects.first()
article_object_ct = ContentType.objects.get_for_model(Article) # 找到ContentType文章模型记录
# 通过Comment自身的筛选查询得到
comments = Comment.objects.filter(content_type=article_content_type, object_id=article.id)
方式二:通过反向关系进行查找
- 可以直接从其他对象找到Comment对应的评论集
- 拿文章Article模型做演示
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
class Article(models.Model):
title = models.CharField(max_length=36)
content = models.TextField(blank=True)
comments = GenericRelation('Comment')
def __str__(self):
return 'article: {0}'.format(self.title)
article = Article.objects.first()
comments = article.comments.all()
- 上述示例反向关系只能通过单个对象反向查询
- 如果文章对象是一个查询集时则不适用
- 此时就需要反向引用关系的出现了:
related_query_name
需求二:找出文章名包含青春
的所有文章的评论
方式一:直接查找
- 无法直接从其他对象找到Comment对应的评论集
- 必须通过ContentType表作为中间跳板
# 获取符合查询条件的文章id列表
article_ids = Article.objects.filter(title__contains='青春').values_list('id', flat=True)
# 找到ContentType文章模型记录
article_object_ct = ContentType.objects.get_for_model(Article)
# 通过Comment自身的筛选查询得到
comments = Comment.objects.filter(content_type=article_content_type, object_id__in=article_ids)
方式二:通过反向引用进行查找
- 可以直接从其他对象找到Comment对应的评论集
- 拿文章Article模型做演示
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
class Article(models.Model):
title = models.CharField(max_length=36)
content = models.TextField(blank=True)
comments = GenericRelation('Comment', related_query_name='article')
def __str__(self):
return 'article: {0}'.format(self.title)
comments = article.objects.filter(article__title__contains='青春')