Django开发之ContentType的使用
做网站时有些功能用到了Django的ContentType,百度了一番后并没有发现什么适合的参考资料,所以自己简单总结一下。以下内容默认读者已经对Django和数据库基础知识有一定的了解。
ContentType是Django框架默认的功能之一,用于追踪项目里的每个app与model的对应关系。如果我们新建一个Django项目,在settings.py里面的INSTALLED_APPS可以找到它。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
也就是说这玩意儿拿来就能用,基本不需要做其他的配置。那么它具体能拿来干嘛呢?
举个例子,比如说我们的网站需要开发一个评论功能。根据数据库的基础知识呢我们可以建一张表Comment,然后将其中一个字段外键关联至我们评论对象的表,比如说文章Article。看起来不难,不过,如果我们后续又对其他类别的对象有了评论需求该怎么办呢?在大多数的网站中,用户不止能对文章进行评论,还能对视频、图片、微博、动态等各种对象进行评论,这种时候应该怎么办呢?
如果不考虑Django,仅从数据库角度分析,我们可以有以下两种比较好的策略:
1.在Comment表中新建一个字段用以标识评论对象的类型。
2.新建一个表,用于关联需要评论的对象,再用Comment关联该表。
使用ContentType的思路本质上来说和以上两种策略的思想差不多。ContentType在Django里维护一张django_content_type的表格,每个app的每个model对应一条记录,而我们可以通过查询model_id以及object_id的方式,来查询任意模型的对象。废话不多说,直接上代码:
class Comment(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id') #评论对象
上面这段代码是在模型里的配置方式,content_type字段外键关联ContentType,object_id字段记录评论对象在它自己的model里面的id值,然后用一个content_object字段同时关联以上两个字段,就完成了在模型里的配置。这里的content_type、object_id、content_object只是字段名,叫别的也可以。
同时,ContentType还提供了一些内置函数供我们方便地使用。比如,上面的模型并不能直接地写入数据,我们不仅需要从前端传入评论对象的id,还要传入评论对象模型的id,在表单的数据清洗中可以这样写:
def clean(self):
content_type = self.cleaned_data['content_type']
object_id = self.cleaned_data['object_id']
try:
model_class = ContentType.objects.get(model=content_type).model_class()
model_obj = model_class.objects.get(pk=object_id)
self.cleaned_data['content_object'] = model_obj
except ObjectDoesNotExist:
raise forms.ValidationError('评论对象不存在')
return self.cleaned_data
ContentType中的model_class()方法可以将django_content_type表格中记录的模型转化为真实的模型。换言之,如果直接使用objects.get我们得到的只是django_content_type表格中的模型记录,并不能拿到模型本身。通过上面这种写法得到的model_obj即为评论的对象。
再比如说,我们需要查询一篇文章或者一张图片的所有评论,代码就可以这样写:
def get_comment_list(obj):
content_type = ContentType.objects.get_for_model(obj)
comments = Comment.objects.filter(content_type=content_type, object_id=obj.pk)
ContentType中的get_for_model()方法可以将模型转化为ContentType中的模型记录。对于使用ContentType的字段,我们不能直接通过filter对数据集进行过滤,必须带上对象的模型记录。通过上面这种写法即可搜索被评论对象对应的所有评论。
ContentType内置的方法还有很多,不过我用到的就这俩种。如果有兴趣的话可以在Django源码里查看。
如果使用pycharm的话看源码的具体操作如下:
from django.contrib.contenttypes.models import ContentType
然后ctrl+鼠标左键点一下ContentType就可以查看源码了。